路徑MTU(PMTU)發現控制與DF位


路徑MTU發現是用來確定到達目的地的路徑中最大傳輸單元(MTU)的大小。通過在IP報頭中設置不分片DF(Don't Fragment)標志來探測路徑中的MTU值, 如果路徑中設備的MTU值小於此報文長度,並且發現DF標志,就會發回一個Internet控制消息協議(ICMP)(類型3、代碼4需要分片的消息ICMP_FRAG_NEEDED),消息中包含它可接受的MTU值。


PMTU發現控制模式

#define IP_PMTUDISC_DONT        0   /* Never send DF frames */
#define IP_PMTUDISC_WANT        1   /* Use per route hints     */
#define IP_PMTUDISC_DO             2   /* Always DF                   */
#define IP_PMTUDISC_PROBE       3   /* Ignore dst pmtu         */
#define IP_PMTUDISC_INTERFACE   4   /* 使用出接口的設備MTU值 */
#define IP_PMTUDISC_OMIT            5   /* 忽略DF位 */

IP_PMTUDISC_DONT策略表示從不設置DF位,即不進行PMTU發現(參見函數ip_dont_fragment)。
IP_PMTUDISC_WANT策略根據路由中表項是否鎖定了MTU,來決定是否設置DF位,如鎖定,不設置DF位。
IP_PMTUDISC_DO策略總是設置DF位,除非內核設置了忽略df(ignore_df),參見以下內容。
IP_PMTUDISC_INTERFACE策略不設置DF位,不發送設置了DF位並且長度超過出接口設備MTU的數據包。
IP_PMTUDISC_OMIT策略與IP_PMTUDISC_INTERFACE策略含義相同,唯一區別在於,即使數據包設置了DF位,內核也會對長度超過出接口設備MTU的數據包進行分片處理並發送。


PMTU默認策略

通過porc文件ip_no_pmtu_disc 設置pmtu的全局默認策略(/proc/sys/net/ipv4/ip_no_pmtu_disc),在sock創建時根據此值初始化pmtudisc變量。內核初始將其設置為0,即系統缺省的pmtu策略為IP_PMTUDISC_WANT,嘗試進行pmtu發現。如果置0,不進行pmtu發現(IP_PMTUDISC_DONT),系統不發送IP頭帶有DF標志的報文。

static int inet_create(struct net *net, struct socket *sock, int protocol, int kern)
{
	struct inet_sock *inet;
	//初始化sock中的pmtudisc值。
    if (net->sysctl_ip_no_pmtu_disc)
        inet->pmtudisc = IP_PMTUDISC_DONT;
    else
        inet->pmtudisc = IP_PMTUDISC_WANT;
}

使能PMTU路徑發現(DF標志置位)


如前所述IP報頭的DF標志,用於PMTU發現,由ip_queue_xmit函數可知,在滿足以下兩個條件之一,
1)pmtudisc等於IP_PMTUDISC_DO;
2)或者pmtudisc等於IP_PMTUDISC_WANT,並且mtu沒有在路由表中鎖定。

而且內核沒有設置忽略DF的條件下,設置IP報頭的DF標志位,進行PMTU發現操作。IP_PMTUDISC_DO為使能PMTU發現策略,IP_PMTUDISC_WANT會根據mtu是否鎖定進行pmtu發現。

路徑發現策略pmtudisc也可設置為IP_PMTUDISC_PROBE,此策略下只有在發送聚合了的分片報文的情況下設置DF位,此發送流程不經過ip_queue_xmit,所以不與前面兩個策略沖突。

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)));
}
int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
{
    if (ip_dont_fragment(sk, &rt->dst) && !skb->ignore_df)
        iph->frag_off = htons(IP_DF);
    else
        iph->frag_off = 0;
}

struct sk_buff *__ip_make_skb(...)
{
    if (inet->pmtudisc == IP_PMTUDISC_DO ||
        inet->pmtudisc == IP_PMTUDISC_PROBE ||
        (skb->len <= dst_mtu(&rt->dst) && ip_dont_fragment(sk, &rt->dst)))
        df = htons(IP_DF);
}

sock結構體中pmtudisc變量可通過setsockopt系統調用進行設置。用戶也可使用ip命令對MTU值進行鎖定,不允許進行修改,如下命令,鎖定lock到網關192.168.1.1的mtu值為1300字節大小:

static int do_ip_setsockopt(...)
{
    struct inet_sock *inet = inet_sk(sk);
	
    case IP_MTU_DISCOVER:
        if (val < IP_PMTUDISC_DONT || val > IP_PMTUDISC_OMIT)
            goto e_inval;
        inet->pmtudisc = val;
}
 
ip route add 0.0.0.0 via 192.168.1.1 mtu lock 1300

關閉路徑MTU發現

路徑mtu發現策略設置為IP_PMTUDISC_INTERFACE或者IP_PMTUDISC_OMIT的時候,內核都不會保存ICMP消息中發送的新MTU值,ipv4_sk_update_pmtu函數判斷之后直接返回。需要注意的是,除去這兩種PMTU策略外,其它情況下,內核還是會保存通過ICMP接收到的新MTU值,這樣在用戶之后修改pmtu策略后能馬上生效。另外在策略配置為非IP_PMTUDISC_DONT時,設置sock錯誤標志。

static inline bool ip_sk_accept_pmtu(const struct sock *sk)
{
    return inet_sk(sk)->pmtudisc != IP_PMTUDISC_INTERFACE &&
           inet_sk(sk)->pmtudisc != IP_PMTUDISC_OMIT;
}
void ipv4_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, u32 mtu)
{
    if (!ip_sk_accept_pmtu(sk))
        goto out;
}
void __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable)
{
    case ICMP_DEST_UNREACH:
        if (code == ICMP_FRAG_NEEDED) { /* Path MTU discovery */
            ipv4_sk_update_pmtu(skb, sk, info);
            if (inet->pmtudisc != IP_PMTUDISC_DONT) {
                err = EMSGSIZE;
                harderr = 1;
                break;
            }
        }
}

強制關閉PMTU發現(忽略DF位)


在ip_queue_xmit函數中看到,如果skb->ignore_df為真,就會清除IP報頭的DF位,ignore_df變量有函數ip_sk_ignore_df賦值。當pmtudisc策略設置成IP_PMTUDISC_DONT、IP_PMTUDISC_WANT或者IP_PMTUDISC_OMIT的時候,ignore_df變量為真,內核將會在發出的報文中清除DF標志位。

static inline bool ip_sk_ignore_df(const struct sock *sk)
{
    return inet_sk(sk)->pmtudisc < IP_PMTUDISC_DO ||
           inet_sk(sk)->pmtudisc == IP_PMTUDISC_OMIT;
}


在分片處理函數中,對於本機產生的報文,在發送時即便設置了不允許分片DF標志位,只要ignore_df為真,強制進行分片。對於轉發的報文,如果設置了DF位,同時接收的時候就是一個已經經過分片的報文,內核進行了重組,但是原始報文的最大分片值大於出接口的mtu值,此時不分片,回復ICMP消息ICMP_FRAG_NEEDED,告知對方內核的MTU值。

static int ip_fragment(...)
{
    if ((iph->frag_off & htons(IP_DF)) == 0)
        return ip_do_fragment(sk, skb, output);

    if (unlikely(!skb->ignore_df ||
             (IPCB(skb)->frag_max_size && IPCB(skb)->frag_max_size > mtu))) {
        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
        return -EMSGSIZE;
    }
    return ip_do_fragment(sk, skb, output);
}


測試PMTU策略

使用ping命令即可測試PMTU策略:

ping 
       -M pmtudisc_opt
           Select Path MTU Discovery strategy.  pmtudisc_option may be
           either do (prohibit fragmentation, even local one), want (do PMTU
           discovery, fragment locally when packet size is large), or dont
           (do not set DF flag).

例如發送長度超過超過MTU值(1500)的數據包,並且設置IP頭的DF位,系統提示message too long:

ping -c 3 -s 1473 -M do 192.168.1.133
PING 192.168.1.133 (192.168.1.133) 1473(1501) bytes of data.
ping: local error: Message too long, mtu=1500
ping: local error: Message too long, mtu=1500
ping: local error: Message too long, mtu=1500

--- 192.168.1.133 ping statistics ---

3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 1999ms


內核版本

linux-3.10.0




注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com