diff options
Diffstat (limited to 'net/ipv4/ip_forward.c')
-rw-r--r-- | net/ipv4/ip_forward.c | 71 |
1 files changed, 69 insertions, 2 deletions
diff --git a/net/ipv4/ip_forward.c b/net/ipv4/ip_forward.c index e9f1217a8afd..f3869c186d97 100644 --- a/net/ipv4/ip_forward.c +++ b/net/ipv4/ip_forward.c | |||
@@ -39,6 +39,71 @@ | |||
39 | #include <net/route.h> | 39 | #include <net/route.h> |
40 | #include <net/xfrm.h> | 40 | #include <net/xfrm.h> |
41 | 41 | ||
42 | static bool ip_may_fragment(const struct sk_buff *skb) | ||
43 | { | ||
44 | return unlikely((ip_hdr(skb)->frag_off & htons(IP_DF)) == 0) || | ||
45 | !skb->local_df; | ||
46 | } | ||
47 | |||
48 | static bool ip_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu) | ||
49 | { | ||
50 | if (skb->len <= mtu || skb->local_df) | ||
51 | return false; | ||
52 | |||
53 | if (skb_is_gso(skb) && skb_gso_network_seglen(skb) <= mtu) | ||
54 | return false; | ||
55 | |||
56 | return true; | ||
57 | } | ||
58 | |||
59 | static bool ip_gso_exceeds_dst_mtu(const struct sk_buff *skb) | ||
60 | { | ||
61 | unsigned int mtu; | ||
62 | |||
63 | if (skb->local_df || !skb_is_gso(skb)) | ||
64 | return false; | ||
65 | |||
66 | mtu = ip_dst_mtu_maybe_forward(skb_dst(skb), true); | ||
67 | |||
68 | /* if seglen > mtu, do software segmentation for IP fragmentation on | ||
69 | * output. DF bit cannot be set since ip_forward would have sent | ||
70 | * icmp error. | ||
71 | */ | ||
72 | return skb_gso_network_seglen(skb) > mtu; | ||
73 | } | ||
74 | |||
75 | /* called if GSO skb needs to be fragmented on forward */ | ||
76 | static int ip_forward_finish_gso(struct sk_buff *skb) | ||
77 | { | ||
78 | struct dst_entry *dst = skb_dst(skb); | ||
79 | netdev_features_t features; | ||
80 | struct sk_buff *segs; | ||
81 | int ret = 0; | ||
82 | |||
83 | features = netif_skb_dev_features(skb, dst->dev); | ||
84 | segs = skb_gso_segment(skb, features & ~NETIF_F_GSO_MASK); | ||
85 | if (IS_ERR(segs)) { | ||
86 | kfree_skb(skb); | ||
87 | return -ENOMEM; | ||
88 | } | ||
89 | |||
90 | consume_skb(skb); | ||
91 | |||
92 | do { | ||
93 | struct sk_buff *nskb = segs->next; | ||
94 | int err; | ||
95 | |||
96 | segs->next = NULL; | ||
97 | err = dst_output(segs); | ||
98 | |||
99 | if (err && ret == 0) | ||
100 | ret = err; | ||
101 | segs = nskb; | ||
102 | } while (segs); | ||
103 | |||
104 | return ret; | ||
105 | } | ||
106 | |||
42 | static int ip_forward_finish(struct sk_buff *skb) | 107 | static int ip_forward_finish(struct sk_buff *skb) |
43 | { | 108 | { |
44 | struct ip_options *opt = &(IPCB(skb)->opt); | 109 | struct ip_options *opt = &(IPCB(skb)->opt); |
@@ -49,6 +114,9 @@ static int ip_forward_finish(struct sk_buff *skb) | |||
49 | if (unlikely(opt->optlen)) | 114 | if (unlikely(opt->optlen)) |
50 | ip_forward_options(skb); | 115 | ip_forward_options(skb); |
51 | 116 | ||
117 | if (ip_gso_exceeds_dst_mtu(skb)) | ||
118 | return ip_forward_finish_gso(skb); | ||
119 | |||
52 | return dst_output(skb); | 120 | return dst_output(skb); |
53 | } | 121 | } |
54 | 122 | ||
@@ -91,8 +159,7 @@ int ip_forward(struct sk_buff *skb) | |||
91 | 159 | ||
92 | IPCB(skb)->flags |= IPSKB_FORWARDED; | 160 | IPCB(skb)->flags |= IPSKB_FORWARDED; |
93 | mtu = ip_dst_mtu_maybe_forward(&rt->dst, true); | 161 | mtu = ip_dst_mtu_maybe_forward(&rt->dst, true); |
94 | if (unlikely(skb->len > mtu && !skb_is_gso(skb) && | 162 | if (!ip_may_fragment(skb) && ip_exceeds_mtu(skb, mtu)) { |
95 | (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) { | ||
96 | IP_INC_STATS(dev_net(rt->dst.dev), IPSTATS_MIB_FRAGFAILS); | 163 | IP_INC_STATS(dev_net(rt->dst.dev), IPSTATS_MIB_FRAGFAILS); |
97 | icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, | 164 | icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, |
98 | htonl(mtu)); | 165 | htonl(mtu)); |