]> Pileus Git - ~andy/linux/blob - net/netfilter/xt_TCPMSS.c
afaebc766933a70de1198ce66b987ccdb62be9bd
[~andy/linux] / 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_tcpmss_info *info,
49                      unsigned int in_mtu,
50                      unsigned int tcphoff,
51                      unsigned int minlen)
52 {
53         struct tcphdr *tcph;
54         unsigned int tcplen, i;
55         __be16 oldval;
56         u16 newmss;
57         u8 *opt;
58
59         if (!skb_make_writable(skb, skb->len))
60                 return -1;
61
62         tcplen = skb->len - tcphoff;
63         tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
64
65         /* Header cannot be larger than the packet */
66         if (tcplen < tcph->doff*4)
67                 return -1;
68
69         if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
70                 if (dst_mtu(skb_dst(skb)) <= minlen) {
71                         net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
72                                             dst_mtu(skb_dst(skb)));
73                         return -1;
74                 }
75                 if (in_mtu <= minlen) {
76                         net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
77                                             in_mtu);
78                         return -1;
79                 }
80                 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
81         } else
82                 newmss = info->mss;
83
84         opt = (u_int8_t *)tcph;
85         for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) {
86                 if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS &&
87                     opt[i+1] == TCPOLEN_MSS) {
88                         u_int16_t oldmss;
89
90                         oldmss = (opt[i+2] << 8) | opt[i+3];
91
92                         /* Never increase MSS, even when setting it, as
93                          * doing so results in problems for hosts that rely
94                          * on MSS being set correctly.
95                          */
96                         if (oldmss <= newmss)
97                                 return 0;
98
99                         opt[i+2] = (newmss & 0xff00) >> 8;
100                         opt[i+3] = newmss & 0x00ff;
101
102                         inet_proto_csum_replace2(&tcph->check, skb,
103                                                  htons(oldmss), htons(newmss),
104                                                  0);
105                         return 0;
106                 }
107         }
108
109         /* There is data after the header so the option can't be added
110            without moving it, and doing so may make the SYN packet
111            itself too large. Accept the packet unmodified instead. */
112         if (tcplen > tcph->doff*4)
113                 return 0;
114
115         /*
116          * MSS Option not found ?! add it..
117          */
118         if (skb_tailroom(skb) < TCPOLEN_MSS) {
119                 if (pskb_expand_head(skb, 0,
120                                      TCPOLEN_MSS - skb_tailroom(skb),
121                                      GFP_ATOMIC))
122                         return -1;
123                 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
124         }
125
126         skb_put(skb, TCPOLEN_MSS);
127
128         /* RFC 879 states that the default MSS is 536 without specific
129          * knowledge that the destination host is prepared to accept larger.
130          * Since no MSS was provided, we MUST NOT set a value > 536.
131          */
132         newmss = min(newmss, (u16)536);
133
134         opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
135         memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
136
137         inet_proto_csum_replace2(&tcph->check, skb,
138                                  htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
139         opt[0] = TCPOPT_MSS;
140         opt[1] = TCPOLEN_MSS;
141         opt[2] = (newmss & 0xff00) >> 8;
142         opt[3] = newmss & 0x00ff;
143
144         inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
145
146         oldval = ((__be16 *)tcph)[6];
147         tcph->doff += TCPOLEN_MSS/4;
148         inet_proto_csum_replace2(&tcph->check, skb,
149                                  oldval, ((__be16 *)tcph)[6], 0);
150         return TCPOLEN_MSS;
151 }
152
153 static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
154                                     unsigned int family)
155 {
156         struct flowi fl;
157         const struct nf_afinfo *ai;
158         struct rtable *rt = NULL;
159         u_int32_t mtu     = ~0U;
160
161         if (family == PF_INET) {
162                 struct flowi4 *fl4 = &fl.u.ip4;
163                 memset(fl4, 0, sizeof(*fl4));
164                 fl4->daddr = ip_hdr(skb)->saddr;
165         } else {
166                 struct flowi6 *fl6 = &fl.u.ip6;
167
168                 memset(fl6, 0, sizeof(*fl6));
169                 fl6->daddr = ipv6_hdr(skb)->saddr;
170         }
171         rcu_read_lock();
172         ai = nf_get_afinfo(family);
173         if (ai != NULL)
174                 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
175         rcu_read_unlock();
176
177         if (rt != NULL) {
178                 mtu = dst_mtu(&rt->dst);
179                 dst_release(&rt->dst);
180         }
181         return mtu;
182 }
183
184 static unsigned int
185 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
186 {
187         struct iphdr *iph = ip_hdr(skb);
188         __be16 newlen;
189         int ret;
190
191         ret = tcpmss_mangle_packet(skb, par->targinfo,
192                                    tcpmss_reverse_mtu(skb, PF_INET),
193                                    iph->ihl * 4,
194                                    sizeof(*iph) + sizeof(struct tcphdr));
195         if (ret < 0)
196                 return NF_DROP;
197         if (ret > 0) {
198                 iph = ip_hdr(skb);
199                 newlen = htons(ntohs(iph->tot_len) + ret);
200                 csum_replace2(&iph->check, iph->tot_len, newlen);
201                 iph->tot_len = newlen;
202         }
203         return XT_CONTINUE;
204 }
205
206 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
207 static unsigned int
208 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
209 {
210         struct ipv6hdr *ipv6h = ipv6_hdr(skb);
211         u8 nexthdr;
212         __be16 frag_off;
213         int tcphoff;
214         int ret;
215
216         nexthdr = ipv6h->nexthdr;
217         tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
218         if (tcphoff < 0)
219                 return NF_DROP;
220         ret = tcpmss_mangle_packet(skb, par->targinfo,
221                                    tcpmss_reverse_mtu(skb, PF_INET6),
222                                    tcphoff,
223                                    sizeof(*ipv6h) + sizeof(struct tcphdr));
224         if (ret < 0)
225                 return NF_DROP;
226         if (ret > 0) {
227                 ipv6h = ipv6_hdr(skb);
228                 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
229         }
230         return XT_CONTINUE;
231 }
232 #endif
233
234 /* Must specify -p tcp --syn */
235 static inline bool find_syn_match(const struct xt_entry_match *m)
236 {
237         const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
238
239         if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
240             tcpinfo->flg_cmp & TCPHDR_SYN &&
241             !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
242                 return true;
243
244         return false;
245 }
246
247 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
248 {
249         const struct xt_tcpmss_info *info = par->targinfo;
250         const struct ipt_entry *e = par->entryinfo;
251         const struct xt_entry_match *ematch;
252
253         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
254             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
255                            (1 << NF_INET_LOCAL_OUT) |
256                            (1 << NF_INET_POST_ROUTING))) != 0) {
257                 pr_info("path-MTU clamping only supported in "
258                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
259                 return -EINVAL;
260         }
261         xt_ematch_foreach(ematch, e)
262                 if (find_syn_match(ematch))
263                         return 0;
264         pr_info("Only works on TCP SYN packets\n");
265         return -EINVAL;
266 }
267
268 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
269 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
270 {
271         const struct xt_tcpmss_info *info = par->targinfo;
272         const struct ip6t_entry *e = par->entryinfo;
273         const struct xt_entry_match *ematch;
274
275         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
276             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
277                            (1 << NF_INET_LOCAL_OUT) |
278                            (1 << NF_INET_POST_ROUTING))) != 0) {
279                 pr_info("path-MTU clamping only supported in "
280                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
281                 return -EINVAL;
282         }
283         xt_ematch_foreach(ematch, e)
284                 if (find_syn_match(ematch))
285                         return 0;
286         pr_info("Only works on TCP SYN packets\n");
287         return -EINVAL;
288 }
289 #endif
290
291 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
292         {
293                 .family         = NFPROTO_IPV4,
294                 .name           = "TCPMSS",
295                 .checkentry     = tcpmss_tg4_check,
296                 .target         = tcpmss_tg4,
297                 .targetsize     = sizeof(struct xt_tcpmss_info),
298                 .proto          = IPPROTO_TCP,
299                 .me             = THIS_MODULE,
300         },
301 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
302         {
303                 .family         = NFPROTO_IPV6,
304                 .name           = "TCPMSS",
305                 .checkentry     = tcpmss_tg6_check,
306                 .target         = tcpmss_tg6,
307                 .targetsize     = sizeof(struct xt_tcpmss_info),
308                 .proto          = IPPROTO_TCP,
309                 .me             = THIS_MODULE,
310         },
311 #endif
312 };
313
314 static int __init tcpmss_tg_init(void)
315 {
316         return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
317 }
318
319 static void __exit tcpmss_tg_exit(void)
320 {
321         xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
322 }
323
324 module_init(tcpmss_tg_init);
325 module_exit(tcpmss_tg_exit);