Merge tag 'please-pull-misc-4.2' of git://git.kernel.org/pub/scm/linux/kernel/git...
[firefly-linux-kernel-4.4.55.git] / net / ipv4 / ip_fragment.c
index 83424f1742b84481f2e3b0876fad8089d5d1d3d6..a50dc6d408d11c339b38f2436216c8568c4149cf 100644 (file)
@@ -75,6 +75,7 @@ struct ipq {
        __be16          id;
        u8              protocol;
        u8              ecn; /* RFC3168 support */
+       u16             max_df_size; /* largest frag with DF set seen */
        int             iif;
        unsigned int    rid;
        struct inet_peer *peer;
@@ -177,7 +178,9 @@ static bool frag_expire_skip_icmp(u32 user)
 {
        return user == IP_DEFRAG_AF_PACKET ||
               ip_defrag_user_in_between(user, IP_DEFRAG_CONNTRACK_IN,
-                                        __IP_DEFRAG_CONNTRACK_IN_END);
+                                        __IP_DEFRAG_CONNTRACK_IN_END) ||
+              ip_defrag_user_in_between(user, IP_DEFRAG_CONNTRACK_BRIDGE_IN,
+                                        __IP_DEFRAG_CONNTRACK_BRIDGE_IN);
 }
 
 /*
@@ -324,6 +327,7 @@ static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
 {
        struct sk_buff *prev, *next;
        struct net_device *dev;
+       unsigned int fragsize;
        int flags, offset;
        int ihl, end;
        int err = -ENOENT;
@@ -479,9 +483,14 @@ found:
        if (offset == 0)
                qp->q.flags |= INET_FRAG_FIRST_IN;
 
+       fragsize = skb->len + ihl;
+
+       if (fragsize > qp->q.max_size)
+               qp->q.max_size = fragsize;
+
        if (ip_hdr(skb)->frag_off & htons(IP_DF) &&
-           skb->len + ihl > qp->q.max_size)
-               qp->q.max_size = skb->len + ihl;
+           fragsize > qp->max_df_size)
+               qp->max_df_size = fragsize;
 
        if (qp->q.flags == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
            qp->q.meat == qp->q.len) {
@@ -611,13 +620,27 @@ static int ip_frag_reasm(struct ipq *qp, struct sk_buff *prev,
        head->next = NULL;
        head->dev = dev;
        head->tstamp = qp->q.stamp;
-       IPCB(head)->frag_max_size = qp->q.max_size;
+       IPCB(head)->frag_max_size = max(qp->max_df_size, qp->q.max_size);
 
        iph = ip_hdr(head);
-       /* max_size != 0 implies at least one fragment had IP_DF set */
-       iph->frag_off = qp->q.max_size ? htons(IP_DF) : 0;
        iph->tot_len = htons(len);
        iph->tos |= ecn;
+
+       /* When we set IP_DF on a refragmented skb we must also force a
+        * call to ip_fragment to avoid forwarding a DF-skb of size s while
+        * original sender only sent fragments of size f (where f < s).
+        *
+        * We only set DF/IPSKB_FRAG_PMTU if such DF fragment was the largest
+        * frag seen to avoid sending tiny DF-fragments in case skb was built
+        * from one very small df-fragment and one large non-df frag.
+        */
+       if (qp->max_df_size == qp->q.max_size) {
+               IPCB(head)->flags |= IPSKB_FRAG_PMTU;
+               iph->frag_off = htons(IP_DF);
+       } else {
+               iph->frag_off = 0;
+       }
+
        IP_INC_STATS_BH(net, IPSTATS_MIB_REASMOKS);
        qp->q.fragments = NULL;
        qp->q.fragments_tail = NULL;