tcp mss clamp while pppoe-凯发app官方网站

凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 752978
  • 博文数量: 144
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1150
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-17 14:32
个人简介

小公司研发总监,既当司令也当兵!

文章分类

全部博文(144)

相关博文
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·

分类: linux

2016-12-26 23:25:19

背景

网络程序员对于“最大传输单元”--mtu应该都不陌生。对于网络传输而已,一条链路上的负载大小通常都是有限制的。比如,对于以太网,mtu通常被设置为1500字节(ip报文最大长度)。对于网络传输中最常用tcp协议而言,其架设与ip协议之上:tcp是基于流,它的数据需要通过划分成一个一个的块,然后组装成一个一个tcp报文,再交由ip协议封装、并发送。通常,为了更好的使用效率,tcp最好确认其数据块的大小,使得一个tcp报文能够顺利的装入一个ip报文中,而且不超过mtu的限制(相反,如果tcp报文过大,那么在ip层发送时,需要将报文分割为更小的多个报文发送,在接收端重组,效率很低)。tcp为了很好的完成这个任务,其通过扩展选项,通过服务端和客户端进行友好协商,选择一个合适的分片大小(mss),保证双方在数据传输过程中,不需要进行ip分片。

经过上面tcp协商处理后,通常是没有问题的。但后来运营商在网络接入时引入了pppoe后,情况就不同了!

pppoe协议是架设在以太网的ppp协议,它会在ip与ethernet之间添加一个pppoe头部(包含pppoe头部和ppp头部,共8字节),这样其实变形减小了链路的mtu;但问题在于,对于tcp/ip层面而已,pppoe是不可见,即tcp在协商mss时,所看到的mtu依然是ethernet的mtu,并没有排除pppoe头部长度。
见下面一个典型拓扑,client 通过pppoe接入intenet,并试图访问站点server的资源:
(clinet)-------(pppoe cli) ------- (pppoe serv) ------(internet) ------(server)

step1:client向server端发起tcp连接请求,同时申明它支持的最大分片长度为1460字节(mtu - tcp_header_len - ip_header_len);
step2:server回复client,同时申明自己支持最大分片长度为1460字节;
step3:client发起读取资源请求;
step4-1:server把资源按最大1460字节切片,然封装为tcp报文,进一步封装ip报文,经以太网成帧后发往client。
step4-2:报文从server向client过程中,流经pppoe serv,pppoe serv需要向报文添加pppoe头部,此时发现添加pppoe头部后报文超过mtu限制了!(怎么办?怎么办?怎么办?)
step4-3: (i)server在发送报文时,明确这个报文不允许分片,那么pppoe serv只能丢弃该报文;(ii)pppoe serv检查报文已经超长,那么久默默丢弃,当什么事情也没发生;(iii)pppoe serv对报文进行分片,逐个发生到clinet。

从上面可以看到,当pppoe serv接受到一个“超长”报文时,其对待的态度是不一定的;同理,当client发生一个“超长”报文时,pppoe cli对待态度也是不一定的。很遗憾,绝大多数pppoe cli不会对报文进行分片,并且pppoe serv也不是总是会执行分片操作。由此引发的问题是,通常client可以和server建立连接,但进行大数据传输时却失败了!

凯发app官方网站的解决方案

问题原因知悉后,解决就不困难了。在上述拓扑中,pppoe cli (或者pppoe serv)监听连接过程,client与server进行mss协商时,主动参与,修正mss:
(clinet)-------(pppoe cli) ------- (pppoe serv) ------(internet) ------(server)

step1:client向server端发起tcp连接请求,同时申明它支持的最大分片长度为1460字节;
step1-1:pppoe cli 捕获tcp mss协商,修正为1412字节;
step2:server确认连接请求,知悉client最大支持1412字节;申明其支持1412字节没有问题;
step2-1:pppoe cli 捕获tcp mss 协商,判断其值不大于1412,ok没有问题
step2-2:client 了解到server最大支持1412
字节的分片长度;
step3:client请求资源;
step4:server按最大1412字节分片资源,组装发生给client;
step5:client 收到报文,与以ack确认;
......

经过上面分步说明,这个问题基本阐释清楚了。在路由器上,如果采用pppoe接入,通常需要执行tcp mss clamp,下面是内核pppoe模块添加tcp mss clamp的代码:

点击(此处)折叠或打开

  1. static uint16_t tcp_checksum(uint8_t *piphdr, uint8_t *ptcphdr)
  2. {
  3.     uint32_t sum = 0;
  4.     uint16_t count;
  5.     uint16_t tmp;

  6.     uint8_t *addr;
  7.     uint8_t pseudo_header[12];
  8.     int i;

  9.     /* count number of bytes in tcp header and data: ip total length - ip header length */
  10.     count = piphdr[2] * 256 piphdr[3];
  11.     count -= (piphdr[0] & 0x0f) * 4;

  12.     /*ip src addr, dest addr, protocl, payload length*/
  13.     memcpy(pseudo_header, piphdr12, 8);
  14.     pseudo_header[8] = 0;
  15.     pseudo_header[9] = piphdr[9];
  16.     pseudo_header[10] = (count >> 8) & 0xff;
  17.     pseudo_header[11] = (count & 0xff);

  18.     /* checksum the pseudo-header */
  19.     for (i = 0; i < 12; i = 2)
  20.     {
  21.         sum = *(uint16_t *)(pseudo_header i);
  22.     }

  23.     /* checksum the tcp header and data */
  24.     addr = ptcphdr;
  25.     while (count > 1)
  26.     {
  27.         memcpy(&tmp, addr, sizeof(tmp));
  28.         sum = (uint32_t) tmp;
  29.         addr = sizeof(tmp);
  30.         count -= sizeof(tmp);
  31.     }

  32.     if (count > 0)
  33.     {
  34.         sum = (uint8_t) *addr;
  35.     }

  36.     while (sum >> 16)
  37.     {
  38.         sum = (sum & 0xffff) (sum >> 16);
  39.     }
  40.     return (uint16_t) ((~sum) & 0xffff);
  41. }



  42. /**
  43. * detect syn of tcp, clamp msss
  44. */
  45. static void clamp_mss(struct sk_buff* skb, int clamp_mss)
  46. {
  47.     struct tcphdr* ptcphdr;
  48.     struct iphdr* piphdr;
  49.     uint8_t* pppphdr;
  50.     struct pppoe_hdr *ppppoehdr;
  51.     int len;
  52.     int minlen;
  53.     int optlen;

  54.     uint16_t csum;
  55.     uint16_t mss = 0;
  56.     uint8_t* opt;
  57.     uint8_t* mssopt;

  58.     ppppoehdr = pppoe_hdr(skb);

  59.     pppphdr = (uint8_t*)ppppoehdr sizeof(struct pppoe_hdr);

  60.     /* check ppp protocol type */
  61.     if (pppphdr[0] & 0x01)
  62.     {
  63.         /* may be 8 bit protocol type ? */
  64.         if (pppphdr[0] != 0x21)
  65.         {
  66.             return;
  67.         }

  68.         piphdr = (struct iphdr*)(pppphdr 1);
  69.         minlen = 41; // tcp header len ip header len ppp header len
  70.     }
  71.     else
  72.     {
  73.         /* 16 bit protocol type, upper layer is ip, and the protocol value is 0x0021*/
  74.         if (pppphdr[0] != 0x00 || pppphdr[1] != 0x21)
  75.         {
  76.             return;
  77.         }
  78.         piphdr = (struct iphdr*)(pppphdr 2);
  79.         minlen = 42;
  80.     }

  81.     /* is it too short? */
  82.     len = (int)ntohs(ppppoehdr->length);
  83.     if (len < minlen)
  84.     {
  85.         return;
  86.     }

  87.     /* verify once more that it's ipv4 */
  88.     if (piphdr->version != 4)
  89.     {
  90.         return;
  91.     }

  92.     /* is it a fragment that's not at the beginning of the packet? */
  93.     if ( ntohs(piphdr->frag_off) & 0x1fff)
  94.     {
  95.         return;
  96.     }

  97.     /* is it tcp? */
  98.     if (piphdr->protocol != 0x06)
  99.     {
  100.         return;
  101.     }

  102.     /* get start of tcp header */
  103.     ptcphdr = (struct tcphdr*)((uint8_t*)piphdr (piphdr->ihl) * 4);

  104.     /* is syn set? */
  105.     if (!ptcphdr->syn)
  106.     {
  107.         return;
  108.     }

  109.     /* compute and verify tcp checksum -- do not touch a packet with a bad checksum */
  110.     csum = tcp_checksum((uint8_t*)piphdr, (uint8_t*)ptcphdr);
  111.     if (csum)
  112.     {
  113.         return;
  114.     }

  115.     /* look for existing mss option */
  116.     optlen = ntohs(ptcphdr->doff) * 4 - 20;

  117.     if (optlen <= 0)
  118.     {
  119.         return;
  120.     }

  121.     opt = (uint8_t*)ptcphdr 20;

  122.     while (optlen > 0)
  123.     {
  124.         switch (*opt)
  125.         {
  126.         case 0:    // end of options
  127.         case 1:    // empty option, always use for pad
  128.             len = 1;
  129.             break;
  130.         case 2:    // mss option
  131.             if (opt[1] != 4)
  132.             {
  133.                 return;
  134.             }

  135.             len = 4;
  136.             mss = opt[2] * 256 opt[3];
  137.             mssopt = opt;
  138.             break;
  139.         case 3:
  140.         case 4:
  141.         case 5:
  142.         case 8:
  143.             len = (int)opt[1];
  144.             break;
  145.         default:
  146.             return;

  147.         }

  148.         if (mss > 0)
  149.         {
  150.             break;
  151.         }

  152.         optlen -= len;
  153.         opt = len;

  154.     }

  155.     /* if mss not exists or it's low enough, do nothing */
  156.     if (!mss || mss <= clamp_mss)
  157.     {
  158.         return;
  159.     }

  160.     mssopt[2] = (((unsigned) clamp_mss) >> 8) & 0xff;
  161.     mssopt[3] = ((unsigned) clamp_mss) & 0xff;

  162.     /* recompute tcp checksum */
  163.     ptcphdr->check = 0;

  164.     csum = tcp_checksum((uint8_t*)piphdr, (uint8_t*)ptcphdr);
  165.     ptcphdr->check = csum;
  166. }

阅读(3729) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
")); function link(t){ var href= $(t).attr('href'); href ="?url=" encodeuricomponent(location.href); $(t).attr('href',href); //setcookie("returnouturl", location.href, 60, "/"); }
网站地图