]> git.karo-electronics.de Git - karo-tx-linux.git/blob - net/netfilter/xt_TCPMSS.c
Merge remote-tracking branch 'asoc/fix/atmel' into asoc-linus
[karo-tx-linux.git] / net / netfilter / xt_TCPMSS.c
1 /*
2  * This is a module which is used for setting the MSS option in TCP packets.
3  *
4  * Copyright (C) 2000 Marc Boucher <marc@mbsi.ca>
5  * Copyright (C) 2007 Patrick McHardy <kaber@trash.net>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 2 as
9  * published by the Free Software Foundation.
10  */
11 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12 #include <linux/module.h>
13 #include <linux/skbuff.h>
14 #include <linux/ip.h>
15 #include <linux/gfp.h>
16 #include <linux/ipv6.h>
17 #include <linux/tcp.h>
18 #include <net/dst.h>
19 #include <net/flow.h>
20 #include <net/ipv6.h>
21 #include <net/route.h>
22 #include <net/tcp.h>
23
24 #include <linux/netfilter_ipv4/ip_tables.h>
25 #include <linux/netfilter_ipv6/ip6_tables.h>
26 #include <linux/netfilter/x_tables.h>
27 #include <linux/netfilter/xt_tcpudp.h>
28 #include <linux/netfilter/xt_TCPMSS.h>
29
30 MODULE_LICENSE("GPL");
31 MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
32 MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
33 MODULE_ALIAS("ipt_TCPMSS");
34 MODULE_ALIAS("ip6t_TCPMSS");
35
36 static inline unsigned int
37 optlen(const u_int8_t *opt, unsigned int offset)
38 {
39         /* Beware zero-length options: make finite progress */
40         if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0)
41                 return 1;
42         else
43                 return opt[offset+1];
44 }
45
46 static int
47 tcpmss_mangle_packet(struct sk_buff *skb,
48                      const struct xt_action_param *par,
49                      unsigned int in_mtu,
50                      unsigned int tcphoff,
51                      unsigned int minlen)
52 {
53         const struct xt_tcpmss_info *info = par->targinfo;
54         struct tcphdr *tcph;
55         unsigned int tcplen, i;
56         __be16 oldval;
57         u16 newmss;
58         u8 *opt;
59
60         /* This is a fragment, no TCP header is available */
61         if (par->fragoff != 0)
62                 return XT_CONTINUE;
63
64         if (!skb_make_writable(skb, skb->len))
65                 return -1;
66
67         tcplen = skb->len - tcphoff;
68         tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
69
70         /* Header cannot be larger than the packet */
71         if (tcplen < tcph->doff*4)
72                 return -1;
73
74         if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
75                 if (dst_mtu(skb_dst(skb)) <= minlen) {
76                         net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
77                                             dst_mtu(skb_dst(skb)));
78                         return -1;
79                 }
80                 if (in_mtu <= minlen) {
81                         net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
82                                             in_mtu);
83                         return -1;
84                 }
85                 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
86         } else
87                 newmss = info->mss;
88
89         opt = (u_int8_t *)tcph;
90         for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) {
91                 if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS &&
92                     opt[i+1] == TCPOLEN_MSS) {
93                         u_int16_t oldmss;
94
95                         oldmss = (opt[i+2] << 8) | opt[i+3];
96
97                         /* Never increase MSS, even when setting it, as
98                          * doing so results in problems for hosts that rely
99                          * on MSS being set correctly.
100                          */
101                         if (oldmss <= newmss)
102                                 return 0;
103
104                         opt[i+2] = (newmss & 0xff00) >> 8;
105                         opt[i+3] = newmss & 0x00ff;
106
107                         inet_proto_csum_replace2(&tcph->check, skb,
108                                                  htons(oldmss), htons(newmss),
109                                                  0);
110                         return 0;
111                 }
112         }
113
114         /* There is data after the header so the option can't be added
115            without moving it, and doing so may make the SYN packet
116            itself too large. Accept the packet unmodified instead. */
117         if (tcplen > tcph->doff*4)
118                 return 0;
119
120         /*
121          * MSS Option not found ?! add it..
122          */
123         if (skb_tailroom(skb) < TCPOLEN_MSS) {
124                 if (pskb_expand_head(skb, 0,
125                                      TCPOLEN_MSS - skb_tailroom(skb),
126                                      GFP_ATOMIC))
127                         return -1;
128                 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
129         }
130
131         skb_put(skb, TCPOLEN_MSS);
132
133         /*
134          * IPv4: RFC 1122 states "If an MSS option is not received at
135          * connection setup, TCP MUST assume a default send MSS of 536".
136          * IPv6: RFC 2460 states IPv6 has a minimum MTU of 1280 and a minimum
137          * length IPv6 header of 60, ergo the default MSS value is 1220
138          * Since no MSS was provided, we must use the default values
139          */
140         if (par->family == NFPROTO_IPV4)
141                 newmss = min(newmss, (u16)536);
142         else
143                 newmss = min(newmss, (u16)1220);
144
145         opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
146         memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
147
148         inet_proto_csum_replace2(&tcph->check, skb,
149                                  htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
150         opt[0] = TCPOPT_MSS;
151         opt[1] = TCPOLEN_MSS;
152         opt[2] = (newmss & 0xff00) >> 8;
153         opt[3] = newmss & 0x00ff;
154
155         inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
156
157         oldval = ((__be16 *)tcph)[6];
158         tcph->doff += TCPOLEN_MSS/4;
159         inet_proto_csum_replace2(&tcph->check, skb,
160                                  oldval, ((__be16 *)tcph)[6], 0);
161         return TCPOLEN_MSS;
162 }
163
164 static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
165                                     unsigned int family)
166 {
167         struct flowi fl;
168         const struct nf_afinfo *ai;
169         struct rtable *rt = NULL;
170         u_int32_t mtu     = ~0U;
171
172         if (family == PF_INET) {
173                 struct flowi4 *fl4 = &fl.u.ip4;
174                 memset(fl4, 0, sizeof(*fl4));
175                 fl4->daddr = ip_hdr(skb)->saddr;
176         } else {
177                 struct flowi6 *fl6 = &fl.u.ip6;
178
179                 memset(fl6, 0, sizeof(*fl6));
180                 fl6->daddr = ipv6_hdr(skb)->saddr;
181         }
182         rcu_read_lock();
183         ai = nf_get_afinfo(family);
184         if (ai != NULL)
185                 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
186         rcu_read_unlock();
187
188         if (rt != NULL) {
189                 mtu = dst_mtu(&rt->dst);
190                 dst_release(&rt->dst);
191         }
192         return mtu;
193 }
194
195 static unsigned int
196 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
197 {
198         struct iphdr *iph = ip_hdr(skb);
199         __be16 newlen;
200         int ret;
201
202         ret = tcpmss_mangle_packet(skb, par,
203                                    tcpmss_reverse_mtu(skb, PF_INET),
204                                    iph->ihl * 4,
205                                    sizeof(*iph) + sizeof(struct tcphdr));
206         if (ret < 0)
207                 return NF_DROP;
208         if (ret > 0) {
209                 iph = ip_hdr(skb);
210                 newlen = htons(ntohs(iph->tot_len) + ret);
211                 csum_replace2(&iph->check, iph->tot_len, newlen);
212                 iph->tot_len = newlen;
213         }
214         return XT_CONTINUE;
215 }
216
217 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
218 static unsigned int
219 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
220 {
221         struct ipv6hdr *ipv6h = ipv6_hdr(skb);
222         u8 nexthdr;
223         __be16 frag_off;
224         int tcphoff;
225         int ret;
226
227         nexthdr = ipv6h->nexthdr;
228         tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
229         if (tcphoff < 0)
230                 return NF_DROP;
231         ret = tcpmss_mangle_packet(skb, par,
232                                    tcpmss_reverse_mtu(skb, PF_INET6),
233                                    tcphoff,
234                                    sizeof(*ipv6h) + sizeof(struct tcphdr));
235         if (ret < 0)
236                 return NF_DROP;
237         if (ret > 0) {
238                 ipv6h = ipv6_hdr(skb);
239                 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
240         }
241         return XT_CONTINUE;
242 }
243 #endif
244
245 /* Must specify -p tcp --syn */
246 static inline bool find_syn_match(const struct xt_entry_match *m)
247 {
248         const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
249
250         if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
251             tcpinfo->flg_cmp & TCPHDR_SYN &&
252             !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
253                 return true;
254
255         return false;
256 }
257
258 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
259 {
260         const struct xt_tcpmss_info *info = par->targinfo;
261         const struct ipt_entry *e = par->entryinfo;
262         const struct xt_entry_match *ematch;
263
264         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
265             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
266                            (1 << NF_INET_LOCAL_OUT) |
267                            (1 << NF_INET_POST_ROUTING))) != 0) {
268                 pr_info("path-MTU clamping only supported in "
269                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
270                 return -EINVAL;
271         }
272         xt_ematch_foreach(ematch, e)
273                 if (find_syn_match(ematch))
274                         return 0;
275         pr_info("Only works on TCP SYN packets\n");
276         return -EINVAL;
277 }
278
279 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
280 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
281 {
282         const struct xt_tcpmss_info *info = par->targinfo;
283         const struct ip6t_entry *e = par->entryinfo;
284         const struct xt_entry_match *ematch;
285
286         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
287             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
288                            (1 << NF_INET_LOCAL_OUT) |
289                            (1 << NF_INET_POST_ROUTING))) != 0) {
290                 pr_info("path-MTU clamping only supported in "
291                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
292                 return -EINVAL;
293         }
294         xt_ematch_foreach(ematch, e)
295                 if (find_syn_match(ematch))
296                         return 0;
297         pr_info("Only works on TCP SYN packets\n");
298         return -EINVAL;
299 }
300 #endif
301
302 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
303         {
304                 .family         = NFPROTO_IPV4,
305                 .name           = "TCPMSS",
306                 .checkentry     = tcpmss_tg4_check,
307                 .target         = tcpmss_tg4,
308                 .targetsize     = sizeof(struct xt_tcpmss_info),
309                 .proto          = IPPROTO_TCP,
310                 .me             = THIS_MODULE,
311         },
312 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
313         {
314                 .family         = NFPROTO_IPV6,
315                 .name           = "TCPMSS",
316                 .checkentry     = tcpmss_tg6_check,
317                 .target         = tcpmss_tg6,
318                 .targetsize     = sizeof(struct xt_tcpmss_info),
319                 .proto          = IPPROTO_TCP,
320                 .me             = THIS_MODULE,
321         },
322 #endif
323 };
324
325 static int __init tcpmss_tg_init(void)
326 {
327         return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
328 }
329
330 static void __exit tcpmss_tg_exit(void)
331 {
332         xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
333 }
334
335 module_init(tcpmss_tg_init);
336 module_exit(tcpmss_tg_exit);