]> Pileus Git - ~andy/linux/blobdiff - net/ipv4/ip_output.c
ipv4: introduce ip_dst_mtu_maybe_forward and protect forwarding path against pmtu...
[~andy/linux] / net / ipv4 / ip_output.c
index 3982eabf61e126060fc7c5b48042bea2f0417135..9a78804cfe9c457a94c3b4c5f2e7d8fe0eb604dd 100644 (file)
@@ -449,6 +449,7 @@ int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
        __be16 not_last_frag;
        struct rtable *rt = skb_rtable(skb);
        int err = 0;
+       bool forwarding = IPCB(skb)->flags & IPSKB_FORWARDED;
 
        dev = rt->dst.dev;
 
@@ -458,12 +459,13 @@ int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
 
        iph = ip_hdr(skb);
 
+       mtu = ip_dst_mtu_maybe_forward(&rt->dst, forwarding);
        if (unlikely(((iph->frag_off & htons(IP_DF)) && !skb->local_df) ||
                     (IPCB(skb)->frag_max_size &&
-                     IPCB(skb)->frag_max_size > dst_mtu(&rt->dst)))) {
+                     IPCB(skb)->frag_max_size > mtu))) {
                IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
                icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
-                         htonl(ip_skb_dst_mtu(skb)));
+                         htonl(mtu));
                kfree_skb(skb);
                return -EMSGSIZE;
        }
@@ -473,7 +475,7 @@ int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
         */
 
        hlen = iph->ihl * 4;
-       mtu = dst_mtu(&rt->dst) - hlen; /* Size of data space */
+       mtu = mtu - hlen;       /* Size of data space */
 #ifdef CONFIG_BRIDGE_NETFILTER
        if (skb->nf_bridge)
                mtu -= nf_bridge_mtu_reduction(skb);
@@ -810,7 +812,7 @@ static int __ip_append_data(struct sock *sk,
        int copy;
        int err;
        int offset = 0;
-       unsigned int maxfraglen, fragheaderlen;
+       unsigned int maxfraglen, fragheaderlen, maxnonfragsize;
        int csummode = CHECKSUM_NONE;
        struct rtable *rt = (struct rtable *)cork->dst;
 
@@ -823,10 +825,12 @@ static int __ip_append_data(struct sock *sk,
 
        fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
        maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
+       maxnonfragsize = (inet->pmtudisc >= IP_PMTUDISC_DO) ?
+                        mtu : 0xFFFF;
 
-       if (cork->length + length > 0xFFFF - fragheaderlen) {
+       if (cork->length + length > maxnonfragsize - fragheaderlen) {
                ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
-                              mtu-exthdrlen);
+                              mtu - (opt ? opt->optlen : 0));
                return -EMSGSIZE;
        }
 
@@ -1035,7 +1039,6 @@ error:
 static int ip_setup_cork(struct sock *sk, struct inet_cork *cork,
                         struct ipcm_cookie *ipc, struct rtable **rtp)
 {
-       struct inet_sock *inet = inet_sk(sk);
        struct ip_options_rcu *opt;
        struct rtable *rt;
 
@@ -1061,10 +1064,13 @@ static int ip_setup_cork(struct sock *sk, struct inet_cork *cork,
         * We steal reference to this route, caller should not release it
         */
        *rtp = NULL;
-       cork->fragsize = inet->pmtudisc == IP_PMTUDISC_PROBE ?
-                        rt->dst.dev->mtu : dst_mtu(&rt->dst);
+       cork->fragsize = ip_sk_use_pmtu(sk) ?
+                        dst_mtu(&rt->dst) : rt->dst.dev->mtu;
        cork->dst = &rt->dst;
        cork->length = 0;
+       cork->ttl = ipc->ttl;
+       cork->tos = ipc->tos;
+       cork->priority = ipc->priority;
        cork->tx_flags = ipc->tx_flags;
 
        return 0;
@@ -1119,7 +1125,7 @@ ssize_t   ip_append_page(struct sock *sk, struct flowi4 *fl4, struct page *page,
        int mtu;
        int len;
        int err;
-       unsigned int maxfraglen, fragheaderlen, fraggap;
+       unsigned int maxfraglen, fragheaderlen, fraggap, maxnonfragsize;
 
        if (inet->hdrincl)
                return -EPERM;
@@ -1143,9 +1149,12 @@ ssize_t  ip_append_page(struct sock *sk, struct flowi4 *fl4, struct page *page,
 
        fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
        maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
+       maxnonfragsize = (inet->pmtudisc >= IP_PMTUDISC_DO) ?
+                        mtu : 0xFFFF;
 
-       if (cork->length + size > 0xFFFF - fragheaderlen) {
-               ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport, mtu);
+       if (cork->length + size > maxnonfragsize - fragheaderlen) {
+               ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
+                              mtu - (opt ? opt->optlen : 0));
                return -EMSGSIZE;
        }
 
@@ -1308,7 +1317,8 @@ struct sk_buff *__ip_make_skb(struct sock *sk,
        /* DF bit is set when we want to see DF on outgoing frames.
         * If local_df is set too, we still allow to fragment this frame
         * locally. */
-       if (inet->pmtudisc >= IP_PMTUDISC_DO ||
+       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);
@@ -1316,7 +1326,9 @@ struct sk_buff *__ip_make_skb(struct sock *sk,
        if (cork->flags & IPCORK_OPT)
                opt = cork->opt;
 
-       if (rt->rt_type == RTN_MULTICAST)
+       if (cork->ttl != 0)
+               ttl = cork->ttl;
+       else if (rt->rt_type == RTN_MULTICAST)
                ttl = inet->mc_ttl;
        else
                ttl = ip_select_ttl(inet, &rt->dst);
@@ -1324,7 +1336,7 @@ struct sk_buff *__ip_make_skb(struct sock *sk,
        iph = ip_hdr(skb);
        iph->version = 4;
        iph->ihl = 5;
-       iph->tos = inet->tos;
+       iph->tos = (cork->tos != -1) ? cork->tos : inet->tos;
        iph->frag_off = df;
        iph->ttl = ttl;
        iph->protocol = sk->sk_protocol;
@@ -1336,7 +1348,7 @@ struct sk_buff *__ip_make_skb(struct sock *sk,
                ip_options_build(skb, opt, cork->addr, rt, 0);
        }
 
-       skb->priority = sk->sk_priority;
+       skb->priority = (cork->tos != -1) ? cork->priority: sk->sk_priority;
        skb->mark = sk->sk_mark;
        /*
         * Steal rt from cork.dst to avoid a pair of atomic_inc/atomic_dec
@@ -1486,6 +1498,8 @@ void ip_send_unicast_reply(struct net *net, struct sk_buff *skb, __be32 daddr,
        ipc.addr = daddr;
        ipc.opt = NULL;
        ipc.tx_flags = 0;
+       ipc.ttl = 0;
+       ipc.tos = -1;
 
        if (replyopts.opt.opt.optlen) {
                ipc.opt = &replyopts.opt;