精华内容
下载资源
问答
  • 执行路径MTU发现,而不依赖于通常不传递的ICMP错误。 此程序执行所述的打包层路径MTU发现,这是在存在情况下检测MTU大小的更可靠的方法。 基本原理 尽管TCP连接会根据各种指标(网络性能,数据包丢失,ICMP错误消息...
  • RFC1191 路径MTU发现

    2019-08-22 19:56:48
    本文介绍了一种动态路径最大MTU发现的机制。介绍了路由器产生一种特殊icmp报文。 概述 1、主机先尝试发送一个576字节的报文,并把DF置位(dont fragment)。 这样如果路由器无法分片,会发送一个目的不可达icmp...

    本文介绍了一种动态路径最大MTU发现的机制。介绍了路由器产生一种特殊icmp报文。

    2. 概述

    1、主机先尝试发送一个576字节的报文,并把DF置位(dont fragment)。
    这样如果路由器无法分片,会发送一个目的不可达icmp报文。
    主机收到后,会减小MTU并继续尝试PMTU发现。
    2、一旦主机发现PMTU小到不需要分片即可达到对端,PMTU发现过程结束。
    3、路由器需要对报文太大的icmp中,需要报告MTU限制

    3. 主机规范

    1、当主机收到一个报文太大信息,主机必须减小其预估的对应路径上的PMTU。
    2、由于路径MTU不一定随时变化,所以主机不能频繁做路径MTU发现。间隔不能小于5分钟
    3、主机需要能灵活应对还不支持 next-hop MTU的报文太大消息。
    4、路径MTU不得小于68
    5、主机不能用增加估计的PMTU来响应报文太大消息。

    TCP MSS选项

    4. 路由规范

    当收到一个DF置位,且超过下一条MTU的报文时,路由器需要发送一个目的不可达消息给主机。
    并带上需要分片且DF置位
    为了支持PMTU发现,还需要带上低16bit下一跳MTU的字段。Next-Hop MTU字段不能小于68

     	   0                   1                   2                   3
           0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          |   Type = 3    |   Code = 4    |           Checksum            |
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          |           unused = 0          |         Next-Hop MTU          |
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          |      Internet Header + 64 bits of Original Datagram Data      |
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    5.主机处理老式报文信息

    本章介绍主机处理来自未修改的路由器发出的报文太长信息。
    1、最简单的办法是假设PMTU是PMTU和576的最小值,并停止DF置位。
    2、复杂的方式继续搜索,将包长乘以0.75,并继续发送PMTU发现报文。但不太建议这样做。
    3、更复杂的方法是二分法搜索,大概需要4-5次才能知道FDDI到MTU的网络。
    4、使用设定好的参考值。只搜索这些点。即使MTU没有出现在表格中,搜索出来的也不会低于这2个因数。
    5、每一次的搜索,都需要记录前一次的MTU。
    我们推荐的策略是使用小于返回总长字段的最大的参考值来作为下一个 PMTU 的估计值(如果必要,根据上面的注意事项进行修改)。

    主机实现

    6.1 分层

    IP层存储MTU信息,ICMP层用于处理报文太大消息。
    IP层也能控制报文是否需要将DF bit置位。

    6.2 存储PMTU信息

    IP层需要将获得的MTU信息和路径联系起来。路径信息可以是source,dest或IP类型。
    将MTU信息存储在路由表中。
    第一个报文创建的路由信息,MTU和上一条一致。如果PMTU估值比现在MTU高,则更新。

    6.3 清除无用PMTU信息

    需要支持老化机制,超过10分钟没有出现MTU下降,就恢复上一条MTU。
    需要提供设定时长无限的选项。
    上层对PMTU过程的报文不能重传。
    路由表中,添加时间戳栏位,当设定为保留时,说明MTU没有变化。一旦变化,更新时间戳。
    通过时间驱动的过程将立即处理路由表,对于时间戳不是“保留”并且比超时时间间隔老的条目:
    -PMTU估计值被设置为第一跳的MTU。
    -使用路由的打包层被通知这种增长。

    6.4 TCP层行为

    TCP层必须追踪目的地址的PMTU变化。TCP层直接从IP层获取MTU。
    TCP层还必须存储从对方发来的MSS值。
    当收到报文太大消息时,TCP层仅需要等待超时并重传该数据报文。如果PMTU发现过程开始,连接需要等待一段时间。
    同样,当收到报文太大消息后,可以立刻通知TCP层,仅改连接进行重传。
    注意:不能对每个报文太大消息做重传,因为多个分片会引起多个报文太大消息。如果新的PMTU还是过大。
    这个过程会成倍的增加分片的报文。TCP层需要能识别PMTU发现过程结束。
    现在TCP实现通过拥塞避免和慢启动来提升性能,报文太大消息不能改变拥塞窗口,而应该触发慢启动动作。
    PMTU发现不会影响TCP MSS选项。

    6.5 其他传输问题

    6.6 管理接口

    建议实现提供共用程序:

    • 指定路由不使用PMTU发现
    • 修改指定路由的PMTU
      第一条可以用过路由条目的一个flag,指定其报文不需要进行PMTU发现过程。IP层报文DF为0
      还需要可以修改PMTU老化时间。

    7 PMTU可用的值

    Plateau    MTU    Comments                      Reference
       ------     ---    --------                      ---------
                  65535  Official maximum MTU          RFC 791
                  65535  Hyperchannel                  RFC 1044
       65535
       32000             Just in case
                  17914  16Mb IBM Token Ring           ref. [6]
       17914
                  8166   IEEE 802.4                    RFC 1042
       8166
                  4464   IEEE 802.5 (4Mb max)          RFC 1042
                  4352   FDDI (Revised)                RFC 1188
       4352 (1%)
                  2048   Wideband Network              RFC 907
                  2002   IEEE 802.5 (4Mb recommended)  RFC 1042
       2002 (2%)
                  1536   Exp. Ethernet Nets            RFC 895
                  1500   Ethernet Networks             RFC 894
                  1500   Point-to-Point (default)      RFC 1134
                  1492   IEEE 802.3                    RFC 1042
       1492 (3%)
                  1006   SLIP                          RFC 1055
                  1006   ARPANET                       BBN 1822
       1006
                  576    X.25 Networks                 RFC 877
                  544    DEC IP Portal                 ref. [10]
                  512    NETBIOS                       RFC 1088
                  508    IEEE 802/Source-Rt Bridge     RFC 1042
                  508    ARCNET                        RFC 1051
       508 (13%)
                  296    Point-to-Point (low delay)    RFC 1144
       296
       68                Official minimum MTU          RFC 791
    

    7.1 检测PMTU变化的方法

    6.3提到的周期性增加PMTU方式,其实就是重新发现的过程。不能过于频繁。
    更好的方法是,周期性增加PMTU到一个更高值。
    如果PMTU增加了,建议使用更短的老化时间,加快增加的过程。
    如果PMTU变小了,建议使用更长的老化时间,避免周期性丢包的发生。

    8. 安全考虑

    展开全文
  • 4.5 路径MTU发现

    千次阅读 2015-03-25 22:33:15
    TCP报文需要封装成IP报文才会发送,报文在网络中按照一定路径传输后会抵达目的地。...而IP报文的传输路径是事先不知道的,而且在传输过程中也可能发送变化,所以TCP需要动态测路径MTU的大小,这就是路径MTU发现

      如果两台主机之间的通信要通过多个网络,那么每个网络的链路层就可能有不同的MTU。两台通信主机路径中的最小MTU。它被称作路径MTU(PMTU)。两台主机之间的路径MTU不一定是个常数,它取决于当时所选择的路由。而选路不一定是对称的(从A到B的路由可能与从B到A的路由不同),因此路径MTU在两个方向上不一定是一致的。

      本文研究路径MTU发现主要是要弄明白以下几个问题:

    1、路径MTU发现有什么用处?

    2、TCP什么时候执行路径MTU发现?

    3、TCP路径MTU发现的原理是什么?

    4、TCP路径MTU探测的结果是如何维护的?

    5、TCP如何使用路径MTU探测的结果?

      下面回答问题1:TCP报文需要封装成IP报文才会发送,报文在网络中按照一定路径传输后会抵达目的地。最理想的情况是IP报文的大小正好是这条路径所能容纳的最大尺寸,因为报文小了则数据传输效率不高,大了则会引起分片。分片会使得路由器的负担加重,增加延迟,而且会增加报文丢失的概率。而IP报文的传输路径是事先不知道的,而且在传输过程中也可能发送变化,所以TCP需要动态测路径MTU的大小,这就是TCP的路径MTU发现。

         接下来我们来寻找问题2的答案:PMTU探测包的发送是在tcp_write_xmit函数中进行:

    1811 static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
    1812                int push_one, gfp_t gfp)
    1813 {               
    1814     struct tcp_sock *tp = tcp_sk(sk);
    1815     struct sk_buff *skb;
    1816     unsigned int tso_segs, sent_pkts;
    1817     int cwnd_quota;
    1818     int result;
    1819             
    1820     sent_pkts = 0;
    1821         
    1822     if (!push_one) {
    1823         /* Do MTU probing. */
    1824         result = tcp_mtu_probe(sk);
    1825         if (!result) {
    1826             return false;
    ...

      可见只有tcp_write_xmit函数的参数push_one为0时TCP才会开启PMTU探测。直接调用tcp_write_xmit且push_one为0的函数有两个:tcp_tsq_handler和__tcp_push_pending_frames。前者是使用TSQ tasklet发送数据时调用的函数,而直接或间接调用后者的函数有:tcp_push_pending_frames、tcp_push、tcp_data_snd_check。下面一一列举开启PMTU探测的条件:

    (1)TSQ tasklet发送数据时:

     684 static void tcp_tsq_handler(struct sock *sk)
     685 {
     686     if ((1 << sk->sk_state) &
     687         (TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_CLOSING |
     688          TCPF_CLOSE_WAIT  | TCPF_LAST_ACK))
     689         tcp_write_xmit(sk, tcp_current_mss(sk), 0, 0, GFP_ATOMIC);
     690 }
    (2)通过发包系统调用或使用TCP Splice功能调用do_tcp_sendpages发送数据时:

    1016 int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
    1017         size_t size)
    1018 {
    ...
    1204             if (forced_push(tp)) {
    1205                 tcp_mark_push(tp, skb);
    1206                 __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
    1207             } else if (skb == tcp_send_head(sk))
    1208                 tcp_push_one(sk, mss_now);
    1209             continue;
    1210 
    1211 wait_for_sndbuf:
    1212             set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
    1213 wait_for_memory:
    1214             if (copied)
    1215                 tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
    1216 
    1217             if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
    1218                 goto do_error;
    1219 
    1220             mss_now = tcp_send_mss(sk, &size_goal, flags);
    1221         }
    1222     }
    1223 
    1224 out:
    1225     if (copied)
    1226         tcp_push(sk, flags, mss_now, tp->nonagle);
    1227     release_sock(sk);
    1228     return copied + copied_syn;
       TCP Splice
     827 static ssize_t do_tcp_sendpages(struct sock *sk, struct page *page, int offset,
     828                 size_t size, int flags)
     829 {
    ...
     914         if (forced_push(tp)) {
     915             tcp_mark_push(tp, skb);
     916             __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
     917         } else if (skb == tcp_send_head(sk))
     918             tcp_push_one(sk, mss_now);
     919         continue;
     920 
     921 wait_for_sndbuf:
     922         set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
     923 wait_for_memory:
     924         tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
     925 
     926         if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
     927             goto do_error;
     928 
     929         mss_now = tcp_send_mss(sk, &size_goal, flags);
     930     }
     931 
     932 out:
     933     if (copied && !(flags & MSG_SENDPAGE_NOTLAST))
     934         tcp_push(sk, flags, mss_now, tp->nonagle);
     935     return copied;
    ...
      这里只有一种情况是不开启PMTU探测的:当前已写出的字节数不大于对端通告的最大窗口的一半且发送队列中只有一个skb。其它情况下发送skb都会开启PMTU探测功能。
    (3)发现数据丢失并且使用了Forward RTO-Recovery (F-RTO)算法时:

    2685 static void tcp_process_loss(struct sock *sk, int flag, bool is_dupack)
    2686 {   
    2687     struct inet_connection_sock *icsk = inet_csk(sk);
    2688     struct tcp_sock *tp = tcp_sk(sk);
    2689     bool recovered = !before(tp->snd_una, tp->high_seq);
    2690     
    2691     if (tp->frto) { /* F-RTO RFC5682 sec 3.1 (sack enhanced version). */
    2692         if (flag & FLAG_ORIG_SACK_ACKED) {
    2693             /* Step 3.b. A timeout is spurious if not all data are
    2694              * lost, i.e., never-retransmitted data are (s)acked.
    2695              */
    2696             tcp_try_undo_loss(sk, true);
    2697             return;
    2698         }
    2699         if (after(tp->snd_nxt, tp->high_seq) &&
    2700             (flag & FLAG_DATA_SACKED || is_dupack)) {
    2701             tp->frto = 0; /* Loss was real: 2nd part of step 3.a */
    2702         } else if (flag & FLAG_SND_UNA_ADVANCED && !recovered) {
    2703             tp->high_seq = tp->snd_nxt;
    2704             __tcp_push_pending_frames(sk, tcp_current_mss(sk),
    2705                           TCP_NAGLE_OFF);
    ...
    (4)发送FIN关闭连接时:

    2545 void tcp_send_fin(struct sock *sk)
    2546 {
    ...
    2578     __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF);
    2579 }
    (5)使用setsockopt设置TCP_NODELAY功能时:
    2371 static int do_tcp_setsockopt(struct sock *sk, int level,
    2372         int optname, char __user *optval, unsigned int optlen)
    2373 {
    ...
    2423     case TCP_NODELAY:
    2424         if (val) {
    2425             /* TCP_NODELAY is weaker than TCP_CORK, so that
    2426              * this option on corked socket is remembered, but
    2427              * it is not activated until cork is cleared.
    2428              *
    2429              * However, when TCP_NODELAY is set we make
    2430              * an explicit push, which overrides even TCP_CORK
    2431              * for currently queued segments.
    2432              */
    2433             tp->nonagle |= TCP_NAGLE_OFF|TCP_NAGLE_PUSH;
    2434             tcp_push_pending_frames(sk);
    2435         } else {
    2436             tp->nonagle &= ~TCP_NAGLE_OFF;
    2437         }
    2438         break;
    ...
    
    (6) 使用setsockopt取消TCP_CORK功能时:
    2371 static int do_tcp_setsockopt(struct sock *sk, int level,
    2372         int optname, char __user *optval, unsigned int optlen)
    2373 {
    ...
    2503     case TCP_CORK:
    ...
    2515         if (val) {
    2516             tp->nonagle |= TCP_NAGLE_CORK;
    2517         } else {
    2518             tp->nonagle &= ~TCP_NAGLE_CORK;
    2519             if (tp->nonagle&TCP_NAGLE_OFF)
    2520                 tp->nonagle |= TCP_NAGLE_PUSH;
    2521             tcp_push_pending_frames(sk);
    2522         }
    2523         break;
    ...
    
    (7)收到对端发过来的ACK或数据包时:

    5076 int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
    5077             const struct tcphdr *th, unsigned int len)
    5078 {
    ...
    5109     if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
    5110         TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&
    5111         !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {
    5112         int tcp_header_len = tp->tcp_header_len;
    ...
    5136         if (len <= tcp_header_len) {
    5137             /* Bulk data transfer: sender */
    5138             if (len == tcp_header_len) {
    5139                 /* Predicted packet is in window by definition.
    5140                  * seq == rcv_nxt and rcv_wup <= rcv_nxt.
    5141                  * Hence, check seq<=rcv_wup reduces to:
    5142                  */
    5143                 if (tcp_header_len ==
    5144                     (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
    5145                     tp->rcv_nxt == tp->rcv_wup)
    5146                     tcp_store_ts_recent(tp);
    5147 
    5148                 /* We know that such packets are checksummed
    5149                  * on entry.
    5150                  */
    5151                 tcp_ack(sk, skb, 0);
    5152                 __kfree_skb(skb);
    5153                 tcp_data_snd_check(sk);
    5154                 return 0;
    ...
    5159         } else {
    ...
    5228             if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
    5229                 /* Well, only one small jumplet in fast path... */
    5230                 tcp_ack(sk, skb, FLAG_DATA);
    5231                 tcp_data_snd_check(sk);
    ...
    5274     /* step 7: process the segment text */
    5275     tcp_data_queue(sk, skb);
    5276 
    5277     tcp_data_snd_check(sk);
    5278     tcp_ack_snd_check(sk);
    5279     return 0;
    ...
    
    (8)非TCP_LISTEN和TCP_CLOSE状态下收到合法的包时:
    5600 int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
    5601               const struct tcphdr *th, unsigned int len)
    5602 {
    ...
    5649     case TCP_SYN_SENT:
    5650         queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
    5651         if (queued >= 0)
    5652             return queued;
    5653 
    5654         /* Do step6 onward by hand. */
    5655         tcp_urg(sk, skb, th);
    5656         __kfree_skb(skb);
    5657         tcp_data_snd_check(sk); //发送TFO的数据
    5658         return 0;
    5659     }
    ...
    5861     /* tcp_data could move socket to TIME-WAIT */
    5862     if (sk->sk_state != TCP_CLOSE) {
    5863         tcp_data_snd_check(sk);
    5864         tcp_ack_snd_check(sk);
    5865     }
    ...
    
      这样看来,TCP在发送数据的时候大多会执行路径MTU发现。

      对于问题3,TCP完成PMTU探测任务的基本方法是使用tcp_mtu_probe函数用于发送PMTU探测包:

    1675 static int tcp_mtu_probe(struct sock *sk)
    1676 {
    1677     struct tcp_sock *tp = tcp_sk(sk);
    1678     struct inet_connection_sock *icsk = inet_csk(sk);
    1679     struct sk_buff *skb, *nskb, *next;
    1680     int len;
    1681     int probe_size;      
    1682     int size_needed;     
    1683     int copy;
    1684     int mss_now;
    1685
    1686     /* Not currently probing/verifying,
    1687      * not in recovery,
    1688      * have enough cwnd, and
    1689      * not SACKing (the variable headers throw things off) */
    1690     if (!icsk->icsk_mtup.enabled ||    
    1691         icsk->icsk_mtup.probe_size ||   //正在进行PMTU探测
    1692         inet_csk(sk)->icsk_ca_state != TCP_CA_Open ||
    1693         tp->snd_cwnd < 11 ||
    1694         tp->rx_opt.num_sacks || tp->rx_opt.dsack)
    1695         return -1;
    1696
    1697     /* Very simple search strategy: just double the MSS. */
    1698     mss_now = tcp_current_mss(sk);
    1699     probe_size = 2 * tp->mss_cache; //设置探测包大小为当前MSS的两倍
    1700     size_needed = probe_size + (tp->reordering + 1) * tp->mss_cache;
    1701     if (probe_size > tcp_mtu_to_mss(sk, icsk->icsk_mtup.search_high)) {
    1702         /* TODO: set timer for probe_converge_event */
    1703         return -1;
    1704     }
    1705
    1706     /* Have enough data in the send queue to probe? */
    1707     if (tp->write_seq - tp->snd_nxt < size_needed)
    1708         return -1;
    1709
    1710     if (tp->snd_wnd < size_needed) //发送窗口太小
    1711         return -1;
    1712     if (after(tp->snd_nxt + size_needed, tcp_wnd_end(tp)))
    1713         return 0;
    1714
    1715     /* Do we need to wait to drain cwnd? With none in flight, don't stall */
    1716     if (tcp_packets_in_flight(tp) + 2 > tp->snd_cwnd) {
    1717         if (!tcp_packets_in_flight(tp))
    1718             return -1;
    1719         else
    1720             return 0;
    1721     }
    1722
    1723     /* We're allowed to probe.  Build it now. */
    1724     if ((nskb = sk_stream_alloc_skb(sk, probe_size, GFP_ATOMIC)) == NULL)
    1725         return -1;
    1726     sk->sk_wmem_queued += nskb->truesize;
    1727     sk_mem_charge(sk, nskb->truesize);
    1728
    1729     skb = tcp_send_head(sk);
    1730
    1731     TCP_SKB_CB(nskb)->seq = TCP_SKB_CB(skb)->seq;
    1732     TCP_SKB_CB(nskb)->end_seq = TCP_SKB_CB(skb)->seq + probe_size;
    1733     TCP_SKB_CB(nskb)->tcp_flags = TCPHDR_ACK;
    1734     TCP_SKB_CB(nskb)->sacked = 0;
    1735     nskb->csum = 0;
    1736     nskb->ip_summed = skb->ip_summed;
    1737
    1738     tcp_insert_write_queue_before(nskb, skb, sk);
    1739
    1740     len = 0;
    1741     tcp_for_write_queue_from_safe(skb, next, sk) { //将发送队列中的数据合并到大的探测包中
    1742         copy = min_t(int, skb->len, probe_size - len);
    1743         if (nskb->ip_summed)
    1744             skb_copy_bits(skb, 0, skb_put(nskb, copy), copy);
    1745         else
    1746             nskb->csum = skb_copy_and_csum_bits(skb, 0,
    1747                                 skb_put(nskb, copy),
    1748                                 copy, nskb->csum);
    1749
    1750         if (skb->len <= copy) {
    1751             /* We've eaten all the data from this skb.
    1752              * Throw it away. */
    1753             TCP_SKB_CB(nskb)->tcp_flags |= TCP_SKB_CB(skb)->tcp_flags;
    1754             tcp_unlink_write_queue(skb, sk);
    1755             sk_wmem_free_skb(sk, skb);
    1756         } else {
    1757             TCP_SKB_CB(nskb)->tcp_flags |= TCP_SKB_CB(skb)->tcp_flags &
    1758                            ~(TCPHDR_FIN|TCPHDR_PSH);
    1759             if (!skb_shinfo(skb)->nr_frags) {
    1760                 skb_pull(skb, copy);
    1761                 if (skb->ip_summed != CHECKSUM_PARTIAL)
    1762                     skb->csum = csum_partial(skb->data,
    1763                                  skb->len, 0);
    1764             } else {
    1765                 __pskb_trim_head(skb, copy);
    1766                 tcp_set_skb_tso_segs(sk, skb, mss_now);
    1767             }
    1768             TCP_SKB_CB(skb)->seq += copy;
    1769         }
    1770
    1771         len += copy;
    1772
    1773         if (len >= probe_size)
    1774             break;
    1775     }
    1776     tcp_init_tso_segs(sk, nskb, nskb->len);
    1777
    1778     /* We're ready to send.  If this fails, the probe will
    1779      * be resegmented into mss-sized pieces by tcp_write_xmit(). */
    1780     TCP_SKB_CB(nskb)->when = tcp_time_stamp;
    1781     if (!tcp_transmit_skb(sk, nskb, 1, GFP_ATOMIC)) { //发送探测包
    1782         /* Decrement cwnd here because we are sending
    1783          * effectively two packets. */
    1784         tp->snd_cwnd--;
    1785         tcp_event_new_data_sent(sk, nskb);
    1786
    1787         icsk->icsk_mtup.probe_size = tcp_mss_to_mtu(sk, nskb->len); //记录此次探测的PMTU值
    1788         tp->mtu_probe.probe_seq_start = TCP_SKB_CB(nskb)->seq;
    1789         tp->mtu_probe.probe_seq_end = TCP_SKB_CB(nskb)->end_seq;
    1790
    1791         return 1;
    1792     }
    1793
    1794     return -1;
    1795 }

      默认情况下TCP数据包的IP部分都会设置不分片位。在tcp_mtu_probe函数发送大的探测包后需要等待三种结果:(1)收到ACK确认了探测包;这意味着PMTU大于或等于当前探测包的MTU;(2)收到ICMP“需要分片”的报文;这时需要根据报文中通告的MTU来调整PMTU值;(3)数据包丢失导致重传。下面分别讨论这3种情况。

      (1)收到ACK确认了探测包。TCP在收到ACK后会调用tcp_clean_rtx_queue函数来清理发送缓存中的skb:

    3001 static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets,
    3002                    u32 prior_snd_una)
    3003 {
    ...
    3099         if (unlikely(icsk->icsk_mtup.probe_size &&  //正在PMTU探测中
    3100                  !after(tp->mtu_probe.probe_seq_end, tp->snd_una))) { //探测报文全部到达对端
    3101             tcp_mtup_probe_success(sk); //探测成功
    3102         }
    ...
    
      tcp_mtup_probe_success函数:
    2588 static void tcp_mtup_probe_success(struct sock *sk)
    2589 {
    2590     struct tcp_sock *tp = tcp_sk(sk);
    2591     struct inet_connection_sock *icsk = inet_csk(sk);
    2592 
    2593     /* FIXME: breaks with very large cwnd */
    2594     tp->prior_ssthresh = tcp_current_ssthresh(sk);
    2595     tp->snd_cwnd = tp->snd_cwnd *
    2596                tcp_mss_to_mtu(sk, tp->mss_cache) /
    2597                icsk->icsk_mtup.probe_size;
    2598     tp->snd_cwnd_cnt = 0;
    2599     tp->snd_cwnd_stamp = tcp_time_stamp;
    2600     tp->snd_ssthresh = tcp_current_ssthresh(sk);
    2601 
    2602     icsk->icsk_mtup.search_low = icsk->icsk_mtup.probe_size; //记录最小PMTU的值
    2603     icsk->icsk_mtup.probe_size = 0; //本次探测结束
    2604     tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); //保存探测结果
    2605 }
      tcp_sync_mss函数用于保存PMTU的值:
    1296 unsigned int tcp_sync_mss(struct sock *sk, u32 pmtu)
    1297 {
    1298     struct tcp_sock *tp = tcp_sk(sk);
    1299     struct inet_connection_sock *icsk = inet_csk(sk);
    1300     int mss_now;
    1301 
    1302     if (icsk->icsk_mtup.search_high > pmtu)
    1303         icsk->icsk_mtup.search_high = pmtu; //记录PMTU最大值
    1304     
    1305     mss_now = tcp_mtu_to_mss(sk, pmtu); //将PMTU的值转换为MSS
    1306     mss_now = tcp_bound_to_half_wnd(tp, mss_now);
    1307 
    1308     /* And store cached results */
    1309     icsk->icsk_pmtu_cookie = pmtu; //记录当前PMTU
    1310     if (icsk->icsk_mtup.enabled)
    1311         mss_now = min(mss_now, tcp_mtu_to_mss(sk, icsk->icsk_mtup.search_low));
    1312     tp->mss_cache = mss_now; //记录MSS,即TCP报文最大数据长度
    1313 
    1314     return mss_now;
    1315 }  
    
      在对端收到探测包的情况下TCP会把探测包的PMTU记录下来,当PMTU探测再次启动时发送的探测包的PMTU会更大,最终TCP会得到结果(2)或(3)。先看结果(2), TCPv4中处理ICMP报文的函数是 tcp_v4_err

     326 void tcp_v4_err(struct sk_buff *icmp_skb, u32 info)
     327 {
    ...
     389     switch (type) {
    ...
     399     case ICMP_DEST_UNREACH: //路由器丢弃探测包后发送的ICMP报文会由这个分支处理
    ...
     403         if (code == ICMP_FRAG_NEEDED) { /* PMTU discovery (RFC1191) */
     404             /* We are not interested in TCP_LISTEN and open_requests
     405              * (SYN-ACKs send out by Linux are always <576bytes so
     406              * they should go through unfragmented).
     407              */
     408             if (sk->sk_state == TCP_LISTEN)
     409                 goto out;
     410
     411             tp->mtu_info = info; //记录ICMP报文返回的MTU值
     412             if (!sock_owned_by_user(sk)) { //进程没有锁定socket
     413                 tcp_v4_mtu_reduced(sk); //修改MSS的值
     414             } else {
     415                 if (!test_and_set_bit(TCP_MTU_REDUCED_DEFERRED, &tp->tsq_flags)) //推迟到进程解除锁定socket时调用tcp_v4_mtu_reduced
     416                     sock_hold(sk);
     417             }
     418             goto out;
    ...
      tcp_v4_mtu_reduced函数:
     271 static void tcp_v4_mtu_reduced(struct sock *sk)
     272 {
     273     struct dst_entry *dst;
     274     struct inet_sock *inet = inet_sk(sk);
     275     u32 mtu = tcp_sk(sk)->mtu_info;
     276
     277     dst = inet_csk_update_pmtu(sk, mtu);  //更新路由表中mtu的信息
     278     if (!dst)
     279         return;
     280
     281     /* Something is about to be wrong... Remember soft error
     282      * for the case, if this connection will not able to recover.
     283      */
     284     if (mtu < dst_mtu(dst) && ip_dont_fragment(sk, dst))
     285         sk->sk_err_soft = EMSGSIZE;    
     286
     287     mtu = dst_mtu(dst);   //得到路由表中记录的MTU
     288
     289     if (inet->pmtudisc != IP_PMTUDISC_DONT && //确实能够发送不分片报文
     290         inet_csk(sk)->icsk_pmtu_cookie > mtu) { //socket中记录的PMTU大于路由表中的PMTU
     291         tcp_sync_mss(sk, mtu); //更新socket中记录的MTU的值
     292
     293         /* Resend the TCP packet because it's
     294          * clear that the old packet has been
     295          * dropped. This is the new "fast" path mtu
     296          * discovery.    
     297          */
     298         tcp_simple_retransmit(sk);     //重传数据,因为有数据丢失
     299     } /* else let the usual retransmit timer handle it */
     300  

      在路径MTU过大被路由器丢弃并收到ICMP报文的情况下,TCP会把ICMP中通告的PMTU作为结果保存下来。但出于安全等考虑,并不是所有的路由器在丢弃分片过大的报文时都会发送ICMP消息。如果探测包被这样的路由器丢弃,TCP不会收到任何响应,就好像探测包进入了“黑洞”一样,这就是TCP PMTU发现中的Black Hole Detection问题。即结果(3)。探测包丢失后TCP有两张方式处理:快速重传和超时重传。先来看快速重传,指向这个功能的是tcp_fastretrans_alert函数:

    2745 static void tcp_fastretrans_alert(struct sock *sk, int pkts_acked,
    2746                   int prior_sacked, int prior_packets,
    2747                   bool is_dupack, int flag)
    2748 {
    ...
    2833         /* MTU probe failure: don't reduce cwnd */
    2834         if (icsk->icsk_ca_state < TCP_CA_CWR &&
    2835             icsk->icsk_mtup.probe_size &&  //开启PMTU探测
    2836             tp->snd_una == tp->mtu_probe.probe_seq_start) { //探测包完全未收到
    2837             tcp_mtup_probe_failed(sk);
    2838             /* Restores the reduction we did in tcp_mtup_probe() */
    2839             tp->snd_cwnd++;
    2840             tcp_simple_retransmit(sk);
    2841             return;
    2842         }
    ...
      tcp_mtup_probe_failed函数处理探测失败的情况:
    2580 static void tcp_mtup_probe_failed(struct sock *sk)
    2581 {
    2582     struct inet_connection_sock *icsk = inet_csk(sk);
    2583 
    2584     icsk->icsk_mtup.search_high = icsk->icsk_mtup.probe_size - 1;
    2585     icsk->icsk_mtup.probe_size = 0; //结束本次探测
    2586 }
      在快速重传的情况下,TCP会更新一下PMTU探测的上限。超时重传时呢?重传定时器会调用tcp_write_timeout函数:
    156 static int tcp_write_timeout(struct sock *sk)
    157 {
    158     struct inet_connection_sock *icsk = inet_csk(sk);
    159     int retry_until;
    160     bool do_reset, syn_set = false;
    161 
    162     if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
    163         if (icsk->icsk_retransmits)    
    164             dst_negative_advice(sk);       
    165         retry_until = icsk->icsk_syn_retries ? : sysctl_tcp_syn_retries;
    166         syn_set = true;
    167     } else {
    168         if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0, 0)) {
    169             /* Black hole detection */     
    170             tcp_mtu_probing(icsk, sk);     
    ...
      确定是超时时,tcp_write_timeout函数会调用tcp_mtu_probing函数处理PMTU:
    102 static void tcp_mtu_probing(struct inet_connection_sock *icsk, struct sock *sk)
    103 {
    104     /* Black hole detection */
    105     if (sysctl_tcp_mtu_probing) {
    106         if (!icsk->icsk_mtup.enabled) { //如果未开启PMTU发现机制
    107             icsk->icsk_mtup.enabled = 1; //开启之
    108             tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); //初始化PMTU和MSS
    109         } else {
    110             struct tcp_sock *tp = tcp_sk(sk);
    111             int mss;
    112 
    113             mss = tcp_mtu_to_mss(sk, icsk->icsk_mtup.search_low) >> 1; //缩小MSS
    114             mss = min(sysctl_tcp_base_mss, mss);
    115             mss = max(mss, 68 - tp->tcp_header_len);
    116             icsk->icsk_mtup.search_low = tcp_mss_to_mtu(sk, mss);
    117             tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); //保存缩小后的MSS
    118         }
    119     }
    120 }
      这样看来,超时的情况下TCP会减小MSS,如此就使TCP在路由器不支持PMTU发现机制的情况下实现了PMTU的探测。

      下面来总结一下问题3的答案:TCP在发送数据时会将小段数据合并到大的探测包再发送,TCP发送的包的IP头设置会不分片的DF位。如果包顺利抵达目的地,则用这个包的MTU作为PMTU;如果包过大被丢弃,若路由器会发送ICMP“需要分片,但设置了DF位”的ICMP报文,则使用ICMP报文中的MTU值作为PMTU;若路由器不发送IMCP,则在超时重传时TCP会减小PMTU。在得到PMTU后,TCP会将其保存在socket中,并更新MSS信息,接下来用新的MSS继续发送探测包和普通数据包。

      根据对问题3的代码分析来回答第4个问题:TCP路径MTU探测得到的PMTU保存在inet_connection_sock的icsk_pmtu_cookie中,MSS保存在tcp_sock的mss_cache变量中。如果收到了ICMP报文,则用报文中的值更新路由表中保存的MTU信息。

      问题5:TCP会同时使用保存在路由表和socket中的MTU:

      client端连接建立时:

    2752 void tcp_connect_init(struct sock *sk)
    2753 {
    ...
    2773     tcp_mtup_init(sk);
    2774     tcp_sync_mss(sk, dst_mtu(dst));
    ...
      server端创建socket时:
    1642 struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
    1643                   struct request_sock *req,
    1644                   struct dst_entry *dst)
    1645 {
    ...
    1691     tcp_mtup_init(newsk);
    1692     tcp_sync_mss(newsk, dst_mtu(dst));
    ...
      发送数据时:
    1016 int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
    1017         size_t size)
    1018 {
    ...
    1067     mss_now = tcp_send_mss(sk, &size_goal, flags);
    ...
      tcp_send_mss函数会调用tcp_current_mss函数用于获取当前MSS的值:

    1321 unsigned int tcp_current_mss(struct sock *sk)
    1322 {
    1323     const struct tcp_sock *tp = tcp_sk(sk);
    1324     const struct dst_entry *dst = __sk_dst_get(sk);
    1325     u32 mss_now;
    1326     unsigned int header_len;
    1327     struct tcp_out_options opts;
    1328     struct tcp_md5sig_key *md5;
    1329 
    1330     mss_now = tp->mss_cache;
    1331 
    1332     if (dst) {
    1333         u32 mtu = dst_mtu(dst);
    1334         if (mtu != inet_csk(sk)->icsk_pmtu_cookie)
    1335             mss_now = tcp_sync_mss(sk, mtu);
    1336     }
    1337 
    1338     header_len = tcp_established_options(sk, NULL, &opts, &md5) +
    1339              sizeof(struct tcphdr);
    1340     /* The mss_cache is sized based on tp->tcp_header_len, which assumes
    1341      * some common options. If this is an odd packet (because we have SACK
    1342      * blocks etc) then our calculated header_len will be different, and
    1343      * we have to adjust mss_now correspondingly */
    1344     if (header_len != tp->tcp_header_len) {
    1345         int delta = (int) header_len - tp->tcp_header_len;
    1346         mss_now -= delta; 
    1347     }
    1348 
    1349     return mss_now;
    1350 }

      至此,关于TCP PMTU发现的问题全部回答完毕。在开启PMTU发现功能时,PMTU探测会不断的进行。网络中路径的情况在不停的变化,TCP也会不时地得到新的探测结果,并利用这些结果去影响所发送的报文段的大小。PMTU发现机制使得TCP能尽快获得数据传输路径的MTU大小,从而尽可能使用“不会因为报文过大而被路由器丢弃”的最大长度去发送没一个报文段,进而力求使得数据发送的效率最大化。

          补充:关于DF设置的说明

    一、首先我要说明一点:我认为所有TCP报文的IP报头都会设置DF位,不只是TCP MTU探测报文会设置。

    代码:

      if (ip_dont_fragment(sk, &rt->dst) && !skb->local_df)
            iph->frag_off = htons(IP_DF);
      else
            iph->frag_off = 0;

    只要满足了“ip_dont_fragment(sk, &rt->dst) == 1”且“skb->local_df == 0”,则一定会设置DF位。

    二、我们先来看看skb->local_df。

    TCP中发送数据包时申请SKB(包括MUT探测报文)的函数是sk_stream_alloc_skb,在这个函数中skb->local_df是0。skb->local_df只有在__ip_make_skb函数中才可能被设置为1::
    /* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow
     * to fragment the frame generated here. No matter, what transforms
     * how transforms change size of the packet, it will comeout.                                                                              
     */
       if (inet->pmtudisc < IP_PMTUDISC_DO)
           skb->local_df = 1;

    而__ip_make_skb只有UDP协议会调用,故对所有TCP报文满足“skb->local_df == 0”。

    三、ip_dont_fragment函数:

     static inline
     int ip_dont_fragment(struct sock *sk, struct dst_entry *dst)
     {   
         return  inet_sk(sk)->pmtudisc == IP_PMTUDISC_DO ||
             (inet_sk(sk)->pmtudisc == IP_PMTUDISC_WANT &&
              !(dst_metric_locked(dst, RTAX_MTU)));
     }

    在inet_create函数中inet_sk(sk)->pmtudisc会被设置为 IP_PMTUDISC_WANT:

      if (ipv4_config.no_pmtu_disc) //ipv4_config.no_pmtu_disc默认是0
           inet->pmtudisc = IP_PMTUDISC_DONT;
      else
           inet->pmtudisc = IP_PMTUDISC_WANT;

    dst_metric_locked(dst,RTAX_MTU)是查看dst_metric的MTU对应的位是否被锁定了。metric是用来保存与对端通信时的参数,与MTU对应的参数应该只有在修改时才会被锁定。故在通常情况下dst_metric_locked(dst, RTAX_MTU)的值应该是0。

    四、综上,对于所有TCP报文,其IP报头的DF位都会被设置。


    展开全文
  • 如何设置don't fragment (DF) flag 在socket上? (实际模拟路径 MTU 发现).pdf
  • 路径MTU发现

    千次阅读 2013-02-22 23:26:28
    1.简介 当一台IP主机有大量的数据要发送给另一台主机的时候,数据是作为一系列的IP数据报...当前因特网协议族的缺点就是对一台主机来说缺乏发现任意一条路径的PMTU的标准机制。 注意:路径MTU在[1]中被称作为“用于

    1.简介
    当一台IP主机有大量的数据要发送给另一台主机的时候,数据是作为一系列的IP数据报传输。数据报最好具有在从源点到目的点的路径上不需要分片的最大尺寸。(避免分片的情况,见[5]。)这种数据报的尺寸称作为路径MTU(PMTU),它等于路径上每一跳的MTU之中的最小值。当前因特网协议族的缺点就是对一台主机来说缺乏发现任意一条路径的PMTU的标准机制。

    注意:路径MTU在[1]中被称作为“用于发送的有效MTU"(EMTU_S).
    PMTU与一条路径相关,路径是IP的源地址、目的地址,也许还有
    服务类型(TOS)的特定组合。

    当前实际[1]采用的是576和第一跳MTU中的较小者作为任何不与源地址网络或者子网直接相连的目的地址的PMTU。在许多情况下,这导致了使用比必须要求小的数据报,因为许多路径的PMTU比576大。一台主机发送比路径MTU小的多的数据报是浪费因特网的资源,达不到最优的吞吐量。而且,当前的实现在所有的情况下不防止分片,因为一些路径的MTU比576小。

    期望未来的路由协议将能够在一个路径区域中提供准确的PMTU信息,尽管也许不能越过多级路由层次。还要多久这种未来的路由协议才能广泛应用现在还不清楚。所以在以后的几年中,因特网在所有主机和路由器被修改前为了不浪费资源需要一种简单的发现PMTU的机制。
    2.协议概览
    在此备忘录中,我们描述了一种技术,在IP首部使用不分片(DF)比特位动态发现一条路径的PMTU。基本思想就是源主机开始假定一条路径的PMTU是它的(已知的)第一跳的MTU,在这条路径上发送的数据报都设置DF比特位。如果有的数据报太大,不被路径中的某些路由器分片就不能转发,那么路由器将丢弃这些数据报,然后返回一个意思为“需要分片,设置了DF位[7]”的ICMP目的不可达报文。在收到这样一条报文后(以后称它为“数据报太大”报文),源主机减小它假定的这条路径的PMTU。

    当主机对PMTU的估计值小到它的数据报不需要分片也能转发的时候,PMTU发现过程结束。或者,主机可以选择停止在数据报首部中设置DF比特位来结束发现过程;它可能会这样做,例如主机想在某些情况下让数据报分片。通常,主机继续在所有的数据报中设置DF,这是为了如果路由改变并且新的PMTU减小的时候,将会被发现。

    不幸的是,当前指定的数据报太大报文不报告拒绝太大数据报的那一跳的MTU。所以源主机不能准确地判定把它假设的PMTU减小多少。为了弥补这个缺点,我们建议使用当前在数据报太大报文中没有使用的一个报头字段来报告减小的那一跳的MTU。这是支持PMTU发现的路由器唯一被指定的改变。

    路径的PMTU可能随着时间而改变,因为路由的拓扑结构可能改变。PMTU的减小通过数据报太大报文被检测到,除非主机停止设置沿此路径的数据报的DF比特位。为了检测路径的PMTU值的增加,主机周期地增加它假定的PMTU(如果它已经停止,再重新设置DF比特位)。这几乎总是导致数据报被丢弃,数据报太大报文产生,因为在大多数情况下,路径的PMTU不会改变,所以不应该频繁地做这种工作。

    因为这种机制本质上保证了主机接收不到来自另一台进行PMTU发现的对等者的分片,它可能对与某个不能重新装配分片的数据报的主机进行互操作有帮助。
    3,主机规范
    当主机收到一个数据报太大报文时,它必须基于此报文中的下一跳MTU字段中的值(见第四节),减少对相关路径的PMTU估计值。因为不同的应用程序有不同的需要,不同的实现体系倾向于不同的策略,所以我们不能在这种情况下指定确定的行为。

    我们要求在收到数据报太大报文后,主机必须尽量去避免在最近一段时间再引出这样的报文。主机可以减小沿着这条路径发送的数据报的尺寸,或者在这些数据报首部停止设置不分段比特位。显然,前一种策略在一段时间内可能继续导致产生数据报太大报文,但是因为这些报文中任何一个(和它们所响应的被丢弃的数据报)都消耗因特网的资源,主机必须强迫PMTU发现过程汇聚。

    使用PMTU发现的主机一定要尽可能快地检测到路径MTU的减少。主机可以检测到路径MTU的增加,但是,因为这样做需要发送比当前估计的PMTU大的数据报,而且PMTU很可能不增加,所以这种工作一定不要频繁地做。检测一个增加的尝试(通过发送一个比当前估计值大的数据报)一定不要在数据报太大报文收到后5分钟内做,或者在前一个成功的增加尝试之后1分钟内做。我们建议把计时器设置为它们最小值的两倍(分别为10分钟和2分钟)。

    主机必须能够处理不包括下一跳MTU的数据报太大报文,因为不可能在有限的时间内升级因特网中的所有路由器。来自一个没有修改的路由器的数据报太大报文中的下一跳MTU字段(新定义的)中的值为0(这对于ICMP规范[7]是必需的,ICMP规范要求“未使用”字段必须要是0)。在第五节中,我们讨论响应老式的数据报太大报文(由一个没有修改的路由器发出)的主机可能遵守的策略。

    主机一定不要使对路径MTU的估计值低于68字节。

    主机一定不能增加它对路径MTU的估计值来响应数据报太大报文的内容。一个通告路径MTU增加的报文可能是一个在因特网中飘移的过时的数据报,一个作为服务拒绝攻击一部分的虚假的数据报,或者是由于到目的地址有多条路径造成的结果。
    3.1TCPMSS选项
    作PMTU发现的主机一定要遵守不发送大于576字节的IP数据报的规则,除非它具有接收者的许可。对于TCP连接来说,这意味着主机一定不能发送大于40字节加上它的对等者发来的最大段尺寸(MSS)的数据报。

    注意:TCPMSS被定义为相关的IP数据报尺寸减40[9]。最大IP数据报尺寸的默认值576将导致TCPMSS的默认值是536字节。

    "RequirementsforInternetHosts--CommunicationLayers"[1]的4.2.2.6节中陈述了:

    一些TCP实现只有当目的主机在一个非直接连接网络上才发送MSS选项。但是,通常TCP层不可能有适当地信息来作出这种决定,所有它更愿意把决定因特网路径合适的MTU的工作留给IP层来完成。

    实际上,很多TCP实现总是发送MSS选项,但是,如果目的地不是本地的,把值设置为536。当因特网中充满了不遵守超过576字节的数据报不发给非本地的目的地址规则的主机的时候,这种行为是正确的。现在大多数主机遵守这个规则,所以对非本地的对等者来说也不必把TCPMSS选项的值限制为536。

    而且,这样做防止发现超过576的PMTU,所以,主机应该不再减少它们在MSS选项中发送的值。MSS选项应该比主机能够重组的最大数据报(MSS_R,在[1]中定义)的尺寸小40字节;在许多情况下,这将有65495(65535-40)字节结构上的限制。主机可能发送从它连接网络上的MTU(对一个多宿主主机来说,是在它所有连接网络中的最大MTU)得到的MSS值;这应该不会对PMTU发现造成问题,可以阻止一个损坏的对等者发送巨大的数据报。

    注意:这时,我们没有看到发送比连接的网络最大的MTU还要大的MSS的原因。我们建议主机不使用65495。因为一些IP实现可能有负比特位的错误,这种错误在在没有必要使用这么大的MSS的时候被触发。
    4.路由器规范
    路由器不能转发数据报,是因为数据报超过了下一跳网络的MTU并且它的不分段比特位被设置,路由器需要给数据报的源地址返回一个ICMP的目的不可达报文,此报文带有表示“需要分段,设置了DF位”的代码。为了支持在此备忘录中说明的路径MTU发现技术,路由器必须在ICMP首部字段中低序的16bit中包含下一跳网络的MTU,这个字段在ICMP规范[7]中被标记为“未使用”。高序的16bit保持未用,必须设置为0。因此,报文具有下面的格式:

    0123
    01234567890123456789012345678901
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |类型=3|代码=4|校验和|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |未使用=0|下一跳MTU|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |Internet首部+原始数据报中的前64bit|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    在下一跳MTU字段中的值是:

    沿着原始数据报的路径,在此路由器上不需分段能够转发的最大数据报的用字节表示的尺寸,这个尺寸包含IP首部和IP数据,不包含任何低层的首部。

    这个字段不会包含小于68字节的值,因为每一个路由器都“必须不分段转发68字节的数据报”[8]。

    5.主机对老式报文的处理
    在这一节中,我们概述几种主机接收来自没有修改的路由器所发出的数据报太大报文(即,下一跳的MTU字段为0的数据报太大报文)所遵守的策略。这一节不是协议规范的一部分。

    主机响应这种报文所作的最简单的事就是假定PMTU是当前假定的PMTU和576之中的最小值,和停止设置在这条路径上发送的数据报的DF比特位。这样,主机会得到和当前实现中选择的相同的PMTU(见"RequirementsforInternetHosts--CommunicationLayers"[1]的3.3.3节)。这种策略的优点就是它终止很快,不差于现存的其他实现。它的缺点就是在一些情况下避免分段失败,在另一些情况不能最有效利用因特网。

    更先进复杂的策略包含对一个精确PMTU估计值的“搜索”,当改变它们的尺寸时,继续发送带有DF比特位的数据报。一个好的搜索策略在执行过程中不必产生很多被丢弃的包就可以得到正确的路径MTU估计值。

    一些可能的策略采用前一次估计PMTU的算法函数来产生一个新的估计值。例如,可以用一个常数(比如说,0.75)来乘旧的估计值,得到一个新的估计值。我们不推荐使用这种方法;它要么汇聚的太慢,要么则过多地低估了真正的PMTU。

    一个更高级的方法是在包尺寸上作二进制搜索。这种方法汇聚得快了一些,尽管如此,它从FDDIMTU汇聚到以太网MTU仍然需要4至5步。一个严重的缺点就是当数据报到另一端的时候(指出当前的估计值太小)为了识别它需要一个复杂的实现。我们也不推荐使用这种方法。

    从观察中发现有一种策略工作的相当好,实际上,相对较少的值使用在因特网中。因此,与其盲目搜索任意选择的值,不如只搜索那些可能出现的值。而且,因为设计者倾向于用相似的方法选择MTU,所以,可能收集到成组的相似的MTU值,使用组中的最小值作为“参考点”。(显然,低估MTU的百分之几比高估MTU甚至一个字节也要好)。

    在第七节,我们描述了怎样使用在PMTU估计中有代表性的MTU参考点的表。使用这张表,在最坏情况下汇聚也与二进制搜索一样好,在普通的情况下则更好(例如,只花费两次往返的时间就从FDDIMTU到了以太网MTU)。因为参考点位于接近2的次幂的地方,所以如果一个MTU在表中没有描述,这个算法也不会低估它超过一个2的因数。

    为了选择下一个值,任何搜索策略都必须记住以前的估计值。一种方法就是使用当前缓冲区来保存路径MTU的估计值,但是,实际上在数据报太大报文本身也包含较好的可用信息。所有ICMP目的不可达报文,包括这一种报文,都包含着原始数据报的IP首部,此IP首部包含着这个太大的不能分片转发的数据报的长度。因为总长度可能比当前估计的PMTU小,但是比实际的PMTU大,它对于选择下一个PMTU估计值的方法来说可能是一个好的输入。

    注意:基于源自4.2BSDUnix实现的路由器对于原始IP数据报的总长度发送一个不正确的值。这些路由器发送的值是原始总长度与原始首部长度的总和(用字节表示)。因为收到数据报太大报文的主机不可能知道报文是否是由这种路由器中的一个发出的,所以主机必须保守的假定它是的。如果返回的总长字段不小于当前PMTU估计值,它必须减去返回的首部长度字段值的4倍

    我们推荐的策略是使用小于返回总长字段的最大的参考值来作为下一个PMTU的估计值(如果必要,根据上面的注意事项进行修改)。
    6.主机实现
    在这一节中,我们讨论PMTU发现怎样在主机软件中实现。这不是一个规范,而是一组建议。

    要点包括:
    -PMTU发现实现在哪一层或者哪几层?
    -PMTU信息缓存在哪里?
    -陈旧的PMTU信息怎样被删除?
    -传输层和更高层必须做什么?
    6.1分层
    在IP体系中,选择发送数据报的尺寸在IP层上层的协议执行。我们把这样一种协议称作“打包协议”。打包协议通常是传输层协议(例如TCP),但是也可能是更高层的协议(例如,建立在UDP上层的协议)。

    在打包层实现PMTU发现使层内部的一些问题简化,但是也有一些缺点:实现可能必须在每一个打包协议中再重做一遍,在不同的打包层之间很难共享PMTU信息,由一些打包层保持的面向连接的状态不容易扩展来长时间的保存PMTU信息。

    因此我们认为IP层应该存储PMTU信息,ICMP层应该处理收到的数据报太大报文。通过改变它们发送的数据报的尺寸,打包层必须仍然能够响应路径MTU的改变,也必须能确定设置了DF比特位的数据报被发送。我们不想IP层简单的在每一个包中都设置DF比特位,因为,打包层,也许是核心外部的UDP应用程序可能不能改变它的数据报的尺寸。包含有意分片的协议有时是成功的(NFS是最主要的例子),我们不想打破这种协议。

    为了支持分层,打包层需要定义在[1]中的IP服务接口的扩展:

    一种得知MMS_S值改变的方法是“最大发送传输层报文尺寸”,
    它通过路径MTU减去最小IP首部尺寸得到。
    6.2存储PMTU信息
    通常,IP层应该与它从一条特定的路径获得的每一个PMTU值联系起来。一条路径是由一个源地址,一个目的地址和一个IP服务类型共同确定的。(一些实现不记录路径的源地址;这对于单宿主主机是可接受的,这种主机仅有一个可能的源地址。)

    注意:一些路径可以通过不同的安全分类来进一步区分。
    这种分类的详情超过了本备忘录的范围。

    存储这些联合的明显的地方是在路由表的条目中作为一个字段。主机不会对每一个可能的目的地都有一个路由,但是对每一个活动的目的地都应该缓存一条主机路由。(必要的条件是需要处理ICMP重定向报文。)

    当给主机路由不存在的主机发送第一个数据报的时候,一条路由从一组网络路由中或者从一组默认路由中选出。在路由条目中的PMTU字段应该被初始化为关联的第一跳数据链路的MTU,而且在PMTU发现过程中不再被改变(PMTU发现仅仅创建或者改变主机路由条目)。关联于最初选择路由的PMTU被假定为正确的,直到接收到数据报太大报文。

    当收到一个数据报太大报文时,ICMP层为路径MTU决定一个新的估计值(要么来自包中的下一跳MTU中的非0值,或者使用第五节描述的方法)。如果这条路径的主机路由不存在,那么将创建一个(几乎就象主机ICMP重定向被处理一样;新的路由使用与当前路由一样的第一跳路由器)。如果与主机路由关联的PMTU估计值比新值高,那么此路由条目中的PMTU值将改变。

    打包层必须被通知PMTU减小。任意正在使用这条路径的打包层实例(例如,TCP连接)必须在PMTU估计值减小的时候被通知。

    注意:即使数据报太大报文包含一个引用UDP包的源数据报首部,如果有的TCP连接使用这条给定的路径,TCP层也必须被通知。

    同样,发送引起数据报太大报文的数据报的实例应该被通知它的数据报已经被丢弃了,即使PMTU估计值没有改变。这是为了它可以重传丢弃的数据报。

    注意:这种通知机制与ICMP源路由抑止提供的通知机制是类似的。在一些实现中(诸如源自4.2BSD的系统),现在存在的通知机制不能识别特别的相关连接,所以,一个附加的机制是必要的。

    作为选择,一种实现能够避免使用对于PMTU减小的异步通知机制,这种机制是通过延迟通知直到下一次尝试发送一个比PMTU估计值大的数据报。在这种方法中,当尝试发送一个带有DF比特位设置的数据报,并且这个数据报比PMTU估计值大,SEND函数会失败,返回一个适当的错误指示。这种方法可能更适合于非连接的打包层(例如使用UDP的打包层),它(在一些实现中)可能很难从ICMP层通报。在这种情况下,正常的基于超时的重传输机制被使用于从丢失的数据报中恢复。

    应该知道打包层实例使用的PMTU改变路径的通知与包被丢弃的特别通知有区别,了解这一点很重要。后者是比较实用的(即,从打包层实例的观点来看是异步的),而前者可能有延迟直到打包层实例想创建一个包。仅当已知包丢失,才应该重传,这由数据报太大报文指定。
    6.3清除过时的PMTU信息
    互联网网络拓扑结构是动态变化的;路由随着时间改变。如果新的路由开始被使用,对指定目的地已发现的PMTU可能是错误的。因此,在主机中缓存的PMTU信息可能变得过时。

    因为使用PMTU发现的主机总是设置DF比特位,如果过时的PMTU值太大,一旦一个数据报被发送给指定的目的地,就会立即发现这种情况。认为过时的值太小的机制不存在,所以一个实现应该使缓冲值“变老”。当一个PMTU值一段时间内没有减少(在预订的10分钟内),PMTU估计值应该被设置为第一跳数据链路MTU,打包层应该被通知这种改变。这将导致完全的PMTU发现过程再次发生。

    注意:实现应该提供改变超时持续时间的方法,包括设置它为“无限”。例如,连接在FDDI网络上的主机通过一条低速的串行线接入因特网将不会发现一个新的非本地的PMTU,所以它们不必忍受每十分钟丢弃数据报。

    在响应PMTU估计值增长的时候,上层不必重传数据报。因为在响应丢弃数据报的指示的时候,增长从不发生。

    一种实现PMTU老化的方法是在路由表条目中加入时间戳字段。这个字段初始化为一个“保留”值,表明PMTU从没改变过。当响应一个数据报太大报文,PMTU减少的时候,时间戳被设置为当前时间。

    通过时间驱动的过程将立即处理路由表,对于时间戳不是“保留”并且比超时时间间隔老的条目:

    -PMTU估计值被设置为第一跳的MTU。
    -使用路由的打包层被通知这种增长。

    如果主机路由被删除,PMTU估计值可能从路由表中消失;这可能发生在响应一个ICMP重定向报文的情况中,或者因为某些路由表守护程序在几分钟后删除了旧的路由。在一个多宿主主机上拓扑改变也可能导致使用不同的源接口。当这种情况发生,如果打包层没有被通知,那么它可能继续使用对现在来说太小的缓冲PMTU值。一种解决方法就是当重定向报文导致路由改变和路由从路由表中删除时通知打包层PMTU可能改变。

    注意:检测PMTU增长的更高级复杂的方法在7.1节中描述。
    6.4TCP层的行为
    TCP层必须追踪连接到目的地的PMTU;不应该发送比它还大的数据报。一个简单的实现可能在每次创建一个新的段的时候,向IP层请求这个值(使用在[1]中描述的GET_MAXSIZES接口),但是这种方法效率不高。而且遵守“慢启动”避免阻塞算法[4]的TCP实现计算和缓存从PMTU得到的一些其它的值。当PMTU改变的时候接收异步的通知较为简单,以至于这些变量可以更新。

    TCP实现也必须存储从它的对等者那里接收的MSS值(默认为536),不发送任何比MSS大的段,而不管PMTU的值是多少。在源自4.xBSD的实现中,这需要加入一个附加的字段给TCP状态记录。

    最后,当收到数据报太大报文的时候,这意味着一个数据报被发送这个ICMP报文的路由器丢弃。把它作为任意其它种类被丢弃的段,等待重传计时器期满导致这个段重传,这样的行为就足够了。如果PMTU发现过程需要一些步骤来估计正确的PMTU,这可能因为要往返许多次数据报而造成连接延迟。

    作为选择,重传可以在对路径MTU已改变的通知立即响应时发生,但是这仅仅对于由数据报太大报文指定的特定连接。使用在重传中的数据报尺寸当然应该没有新的PMTU大。

    注意:在响应每一个数据报太大报文的时候一定不要重传相同大小的段,因为特大型的段的突发将造成这样的报文,所以会重传同样的数据。如果新估计的PMTU值仍然错误,这个过程重复,送的多余段的数量将成几何级增长。

    这意味着当数据报太大通知实际上减少已经使用在给定的连接中发送数据报的PMTU时,TCP层必须能够识别,并且忽略任何其它的通知。

    现代的TCP实现把“避免阻塞”和“慢启动”算法结合起来提高性能[4]。不象由TCP重传超时导致的重传,由数据报太大报文导致的重传不应该改变拥塞窗口。然而,它应该触发慢启动机制(即,只有一个段将被重传直到确认开始到达)。

    如果发送者最大窗口的尺寸不是使用中段尺寸的准确的倍数(这不是拥塞窗口尺寸,它总是段尺寸的倍数),TCP性能可能降低。在许多系统中(诸如从4.2BSD中发展的系统),段尺寸总是设置为1024字节,最大窗口尺寸(“发送空间”)总是1024字节的倍数,所以,这种适当的关系保持为默认。然而,如果PMTU发现被使用,段尺寸可能不是发送空间的约数,而且它可能在连接中改变;这意味着当PMTU发现改变PMTU值时,TCP层可能需要改变传输窗口尺寸。最大窗口尺寸应该被设置为小于或等于发送者缓冲区空间尺寸的段尺寸(PMTU-40)的最大倍数。

    PMTU发现不影响在TCPMSS选项中发送的值,因为这个值用在连接的另一端,它可能使用一个不相关的PMTU值。
    6.5其它传输协议的问题
    一些传输层协议(例如ISOTP4[3])在重传的时候,不允许重新打包。也就是说,一旦试图传输某种尺寸的数据报,它的内容就不能分成较小的数据报重传。在这种情况下,原始数据报应该不设置DF比特位重传,允许它作必要的分段来到达它的目的地。当第一次传输的时候,后来的数据报应该没有路径MTU允许值大,并且应该设置DF比特位。

    在许多情况下,Sun网络文件系统(NFS)使用远程过程调用(RPC)协议[11]发送必须分段的数据报,甚至对第一跳链路也是如此。在某些情况下,这可能提高性能,但是众所周知它也导致可靠性和性能的问题,尤其是当客户端和服务器被路由器分开的时候。

    当涉及到路由器的时候,我们建议NFS实现使用PMTU发现。大多数NFS实现允许在安装的时候改变RPC数据报尺寸(间接的,通过改变有效文件系统块尺寸),但是可能需要一些修改来支持以后的改变。

    而且,因为一个单一的NFS操作不能分开成一些UDP数据报,某些操作(主要是在文件名和目录上的操作)需要可能比PMTU大的最小数据报的尺寸。NFS实现不应该减少数据报的尺寸小于这个极限值,即使PMTU发现建议了一个较小的值。(当然,在这种情况下数据报发送时不应该再设置DF比特位。)
    6.6管理接口
    我们建议实现提供一种适合于系统公用程序的方法:

    -确定在给定的路由上没有使用PMTU发现。
    -改变与给定路由相关的PMTU值。

    前者通过与路由条目关联一个标志来完成。当一个发送的包经过具有这个标志的路由的时候,IP层把DF比特位清除,而不管上层的请求如何。

    这些特性可以使用在不规则的情况中,或者用在能够得到路径MTU值的路由协议实现中。

    实现应该提供一种方法改变使PMTU信息变老的超时周期。
    7.路径MTU的可能值
    在第五节建议的“搜索”路径MTU空间的算法基于严格限制搜索空间的取值表。我们在这里描述的MTU取值表声明了在因特网中使用的所有主要的数据链路技术。

    在表7-1中,数据链路以减少的MTU顺序列出,并且为了每组相似的MTU与等于这组中最小MTU的“参考点”关联在一起而进行了分组。(这个表也包括一些不与当前数据链路关联的条目,并且给出在哪里可用的参考)。在那里一个参考点代表了不止一个MTU,此表显示了与参考点关联的最大误差,用一个百分数来表示。

    我们不希望表中的值,尤其对于较高级别的MTU值,永远是有效的。这里给出的值是对实现的建议,不是一个规范或者要求必备。实现者应该使用最新的参考来挑选一组参考点;这个表不应该包含太多的条目,否则检索PMTU的过程可能浪费因特网的资源。实现者应该使没有源代码的用户方便地更新他们系统中的表值(例如,在源自BSDUnix内核中的表可以使用"ioctl"命令来改变)。

    注意:加入值等于2的较小此幂加40(IP和TCP的首部)的新的表项,可能是个好主意。在那里,没有相似的值存在,因为这看起来是不能随意选择任意值的情况。

    这个表也可能包含值仅比的2的较大次幂小一点的条目,以防MTU被定义为接近这些值(这种情况下,表中的条目值低一点比高一点好,否则,下一个最小的参考值可能代替被选择)。
    7.1一种较好的检测PMTU增长的方法
    6.3节建议通过周期性地增长对第一跳MTU的估计值来检测PMTU值的增长。这个过程简化“重新发现”当前PMTU估计值,要以丢失一些数据报作为代价,所以这种工作不应该经常做。

    一种较好的方法是周期性地增长PMTU的估计值到参考点表中下一个最高值(如果它太小,就采用第一跳MTU)。如果增长的估计值是错误的,在正确值被重新发现之前,至多有一次往返时间浪费。如果增长的估计值仍然太低,一个较高的估计值将在以后的时间尝试。

    因为需要几个这样的周期来发现在PMTU中重要的增长,我们推荐在估计值增长后,使用较短的超时周期。

    PlateauMTUCommentsReference
    --------------------------
    65535OfficialmaximumMTURFC791
    65535HyperchannelRFC1044
    65535
    32000Justincase
    1791416MbIBMTokenRingref.[6]
    17914
    8166IEEE802.4RFC1042
    8166
    4464IEEE802.5(4Mbmax)RFC1042
    4352FDDI(Revised)RFC1188
    4352(1%)
    2048WidebandNetworkRFC907
    2002IEEE802.5(4Mbrecommended)RFC1042
    2002(2%)
    1536Exp.EthernetNetsRFC895
    1500EthernetNetworksRFC894
    1500Point-to-Point(default)RFC1134
    1492IEEE802.3RFC1042
    1492(3%)
    1006SLIPRFC1055
    1006ARPANETBBN1822
    1006
    576X.25NetworksRFC877
    544DECIPPortalref.[10]
    512NETBIOSRFC1088
    508IEEE802/Source-RtBridgeRFC1042
    508ARCNETRFC1051
    508(13%)
    296Point-to-Point(lowdelay)RFC1144
    296
    68OfficialminimumMTURFC791

    Table7-1:CommonMTUsintheInternet

    在PTMU估计值因为数据报太大报文而减小后,使用一个较长的超时。例如,在PMTU估计值减少后,超时应该被设置为10分钟;一旦计时器超期,一个较大的MTU值被尝试,这个超时可能被设置为一个较小的值(比如说,2分钟)。超时决不能比估计的往返时间短。
    8.安全性的考虑
    路径MTU发现机制可能造成两种服务拒绝攻击,两者都基于有恶意的一方发送伪造的数据报太大报文给一个因特网主机。

    在第一种攻击中,伪造的报文指明一个比实际PMTU小的多的PMTU。因为受害主机不会设置PMTU估计值低于真正的最小值,所以这不会完全停止数据流,但是,在每一个数据报中可能只有8字节IP数据,所以传送数据的进展将非常慢。

    在另一种攻击中,伪造报文指示一个比真实值大的PMTU。如果相信了这个值,当受害者发送将被路由器丢弃的数据报的时候,将可能导致临时阻塞。在一个往返时间中,主机应该能发现它的错误(从那个丢弃数据报的路由器中接收数据报太大报文而得知),但是这种攻击频繁重复可能导致许多数据报被丢弃。然而,主机不会基于数据报太大报文来提高它对PMTU的估计值,所以也不会容易受到这种攻击。

    如果恶意的一方阻止受害者接收合法的数据报太大报文,也会产生问题,但是在这种情况下,仅可用较简单的服务拒绝攻击。
    参考书目:
    [1]R.Braden,ed.RequirementsforInternetHosts--Communication
    Layers.RFC1122,SRINetworkInformationCenter,October,1989.

    [2]GeofCooper.IPDatagramSizes.Electronicdistributionofthe
    TCP-IPDiscussionGroup,Message-ID
    <8705240517.AA01407@apolling.imagen.uucp>.

    [3]ISO.ISOTransportProtocolSpecification:ISODP8073.RFC905,
    SRINetworkInformationCenter,April,1984.

    [4]VanJacobson.CongestionAvoidanceandControl.InProc.SIGCOMM
    '88SymposiumonCommunicationsArchitecturesandProtocols,pages
    314-329.Stanford,CA,August,1988.

    [5]C.KentandJ.Mogul.FragmentationConsideredHarmful.InProc.
    SIGCOMM'87WorkshoponFrontiersinComputerCommunications
    Technology.August,1987.

    [6]DrewDanielPerkins.PrivateCommunication.
    [7]J.Postel.InternetControlMessageProtocol.RFC792,SRI
    NetworkInformationCenter,September,1981.

    [8]J.Postel.InternetProtocol.RFC791,SRINetworkInformation
    Center,September,1981.

    [9]J.Postel.TheTCPMaximumSegmentSizeandRelatedTopics.RFC
    879,SRINetworkInformationCenter,November,1983.

    [10]MichaelReilly.PrivateCommunication.

    [11]SunMicrosystems,Inc.RPC:RemoteProcedureCallProtocol.RFC
    1057,SRINetworkInformationCenter,June,1988.
    作者地址:
    JeffreyMogul
    DigitalEquipmentCorporationWesternResearchLaboratory
    100HamiltonAvenue
    PaloAlto,CA94301

    Phone:(415)853-6643
    EMail:mogul@decwrl.dec.com

    SteveDeering
    XeroxPaloAltoResearchCenter
    3333CoyoteHillRoad
    PaloAlto,CA94304

    Phone:(415)494-4839
    EMail:deering@xerox.com

    本篇文章来源于 中国协议分析网|www.cnpaf.net 原文链接:http://www.cnpaf.net/Class/RFC/200408/977.html

    展开全文
  • 数据链路不同,MTU则相异 每个数据链路的最大传输单元(MTU)都不尽相同,是因为每个不同类型的数据链路的使用目的不同。使用目的不同,可承载的MTU也就不同。鉴于IP属于数据链路上一层,它必须不受限于不同数据链路的...

    数据链路不同,MTU则相异

    每个数据链路的最大传输单元(MTU)都不尽相同,是因为每个不同类型的数据链路的使用目的不同。使用目的不同,可承载的MTU也就不同。鉴于IP属于数据链路上一层,它必须不受限于不同数据链路的MTU大小。

    # 各种数据链路及其MTU
    IP的最大MTU | IP over ATM | FDDI | 以太网 | PPP(Default) | IEEE802.3 Ethernet
       65535         9180       4352   1500        1500              1492
    

    IP报文的分片与重组

    任何一台主机都有必要对IP分片(IP Fragmentation)进行相应的处理,经过分片之后的IP数据在被重组的时候只能由目标主机进行。路由器虽然做分片但不会进行重组。

    发送主机:设置一个唯一数字作为IP首部的标识码发送
    路由器:路由器负责进行分片
    接收主机:参考IP首部的识别码进行重组,传给上层
    
    IP首部中的"片偏移"字段表示分片之后在用户数据中的相对位置和该分片之后是否还有后续其他分片。
    根据这个字段可以判断一个IP数据报是否分片以及当前分片为整个数据报的起始、中段还是末尾。
    

    路径MTU发现

    分片机制也有它的不足:

    • 路由器的处理负荷加重

    • 随着人们对网络安全的要求提高路由器需要做的其他处理也越来越多

    • 在分片处理中一旦某个分片丢失则会造成整个IP数据报作废

    为了应对以上问题,产生了一种新的技术路径MTU发现(Path MTU Discovery)。所谓路径MTU是指从发送端主机到接收端主机之间不需要分片时最大MTU的大小,即路径中存在的所有数据链路中最小的MTU。而路径MTU发现从发送主机按照路径MTU的大小将数据报分片后进行发送。进行路径MTU发现就可以避免在中途的路由器上进行分片处理,也可以在TCP中发送更大的包。

    # 路径MTU发现的机制(UDP的情况下)
    发送时IP首部的分片标志位设置为不分片。路由器丢包。
    由ICMP通知下一次MTU的大小。
    UDP中没有重发处理。应用在发送下一个消息时会被分片。
    具体来说,就是指UDP层传过来的"UDP首部+UDP数据"在IP层被分片。对于IP,它并不区分UDP首部和应用的数据。
    所有的分片到达目标主机后被重组,再传给UDP层。
    

    路径MTU发现的工作原理如下:

    • 在发送端主机发送IP数据报时将其首部的分片禁止标志位设置为1。根据这个标志位,途中的路由器即使遇到需要分片才能处理的大包,也不会去分片,而是将包丢弃。

    • 通过一个ICMP的不可达消息将数据链路上的MTU的值给发送主机。

    • 下一次,从发送给同一个目标主机的IP数据报获得ICMP所通知的MTU值以后,将它设置为当前的MTU。发送主机根据这个MTU对数据报进行分片处理。如此反复,直到数据报被发送到目标主机为止没有再收到任何ICMP,就认为最后一次ICMP所通知的MTU即是一个合适的MTU值。那么,当MTU的值比较多时,最少可以缓存约10分钟。在这10分钟内使用刚刚求得的MTU,但过了这10分钟以后则重新根据链路上的MTU做一次路径MTU发现。

    # 路径MTU发现的机制(TCP的情况下)
    发送时IP首部的分片标志位设置为不分片。路由器丢包。
    由ICMP通知下一次MTU的大小。
    根据TCP的重发处理,数据报会被重新发送。TCP负责将数据分成IP层不会再被分片的粒度以后传给IP层。IP层不再做分片处理。
    不需要重组。数据被原样发送给接收端主机的TCP层。
    

    (最近更新:2019年09月18日)

    展开全文
  • 11.8 采用UDP的路径MTU发现 下面对使用U D P的应用程序与路径 M T U发现机制之间的交互作用进行研究。看一看如果应用程序写了一个对于一些中间链路来说太长的数据报时会发生什么情况。 例子 由于我们所使用的支持...
  • 1、MTU的概念 MTU即Maximum Transmission Unit 最大传输单元。它是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。 2、路径MTU 路径MTU是指一条因特网传输路径中,从源地址到目的地址所...
  • python网络编程——路径MTU发现

    千次阅读 2020-02-20 23:53:58
    手动构造一个指定大小的数据包,在IP首部设置强制不分片,根据返回的ICMP差错报文类型来判断数据包大小是否超过MTU 如果返回数据包ICMP层的type==3,code==4则表明数据包已经超过了MTU 如果返回数据包ICMP层的...
  • 路径MTU发现在IP首部继承并设置不要分片(DF)比特, 来发现当前路径上的路由器是否需要对正在发送的IP数据报进行分片。如果一个待转发的IP数据报被设置DF比特,而其长度 又超过了MTU,那么路由器将返回ICMP不可达...
  • 一、采用UDP的路径MTU发现 MTU介绍见文章:https://blog.csdn.net/qq_41453285/article/details/95997936 UDP的路径MTU发现 让我们考察使用UDP的应用程序与路径MTU发现机制(PMTUD)之间的交互过程 对一个像UDP...
  • 1、MTU的概念  MTU即Maximum Transmission Unit 最大传输单元。它是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。 2、路径MTU  路径MTU是指一条因特网传输路径中,从源地址到目的地址...
  • 2. 路径MTU发现: 主要参考: http://blog.csdn.net/u011130578/article/details/44629265 原因:过小浪费资源,过大则分片. 何时启用:大多时候发送数据时. 方法:发送pmtu包探测,设置不分片位(DF位).  三种...
  • 家庭应该是爱、欢乐和笑的殿堂。...IP属于网络层,下一层时数据链路层,在数据链路层,不同类型的数据链路的最大传输单元(MTU)都不尽相同。例如,连接两个路由器的通路可以看作时一个链路。从而 ,网络层的...
  • 如何设置don't fragment (DF) flag 在socket上? 我们尝试设置 DF (don't ...getattr(IN, 'IP_MTU_DISCOVER', 10) print 'MTU:', s.getsockopt(socket.IPPROTO_IP, option) s.close() node2:/root/test#python t17.py ...
  • RFC双语计划:rfc1191中文版(中英文对照)............RFC1191 路径MTU发现http://kummerwu.web.officelive.com/Documents/rfc1191-0.html更多RFC中文版,中英文对照版,请查阅...
  • IP属于网络层,下一层时数据链路层,在数据链路层,不同类型的数据链路的最大传输单元(MTU)都不尽相同。例如,连接两个路由器的通路可以看作时一个链路。从而 ,网络层的数据到了数据链路层之后,可能会出现...
  • 1. 简介 2  2. 协议概览 2  3, 主机规范 3  3.1 TCP MSS选项 4  4. 路由器规范 5  5. 主机对老式报文的处理 5  6. 主机实现 6  6.1 分层 7  6.2 存储PMTU信息 7  6.3 清除过时的PMTU信息 8  ...7. 路径MTU
  • 路径MTU(PMTU)发现控制与DF位

    万次阅读 2018-05-15 17:46:18
    路径MTU发现是用来确定到达目的地的路径中最大传输单元(MTU)的大小。通过在IP报头中设置不分片DF(Don't Fragment)标志来探测路径中的MTU值, 如果路径中设备的MTU值小于此报文长度,并且发现DF标志,就会发回一个...
  • 目录 MTU Size 封装开销 Encapsulation...路径MTU发现(PMTUD) 分段 Fragmentation MTU 和 MSS 增加MTU Size进行补偿 推荐建议 摘要和笔记 原文:https://www.networkworld.com/article/2224654/mtu-size-is...
  • MTU、路径MTU、路径MTU发现机制...
  • mtu

    2018-12-25 11:20:00
    通信术语 最大传输单元(Maximum Transmission Unit,MTU)是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。最大传输单元这个参数通常与通信接口有关(网络接口卡、串口等)。 中文名 ...
  • MTU

    千次阅读 2016-04-17 22:28:42
    最大传输单元(Maximum Transmission Unit,MTU)是指一种通信协议的某一层上面所能通过的最大数据报大小(以字节为单位)。最大传输单元这个参数通常与通信接口有关(网络接口卡、串口等)。 MTU也不是越大越好,...
  • TCP/IP 最大路径MTU

    2020-09-22 22:07:08
    一. 最大路径MTU概念 二. 基于ICMP的PMTUD 三.UDP的路径MTU发现 四.TCP的路径MTU发现

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,676
精华内容 12,670
关键字:

MTU发现