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