blob: 71a266de5fb47352209d7380dcd75dff841e067e [file] [log] [blame]
Patrick McHardycdd289a2007-02-07 15:09:46 -08001/*
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 */
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +010010#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
Patrick McHardycdd289a2007-02-07 15:09:46 -080011#include <linux/module.h>
12#include <linux/skbuff.h>
13#include <linux/ip.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090014#include <linux/gfp.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080015#include <linux/ipv6.h>
16#include <linux/tcp.h>
Jan Engelhardt37c08382008-01-31 04:06:10 -080017#include <net/dst.h>
18#include <net/flow.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080019#include <net/ipv6.h>
Jan Engelhardt37c08382008-01-31 04:06:10 -080020#include <net/route.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080021#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
29MODULE_LICENSE("GPL");
30MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
Jan Engelhardt2ae15b62008-01-14 23:42:28 -080031MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
Patrick McHardycdd289a2007-02-07 15:09:46 -080032MODULE_ALIAS("ipt_TCPMSS");
33MODULE_ALIAS("ip6t_TCPMSS");
34
35static inline unsigned int
36optlen(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
45static int
Herbert Xu3db05fe2007-10-15 00:53:15 -070046tcpmss_mangle_packet(struct sk_buff *skb,
Patrick McHardycdd289a2007-02-07 15:09:46 -080047 const struct xt_tcpmss_info *info,
Jan Engelhardt37c08382008-01-31 04:06:10 -080048 unsigned int in_mtu,
Patrick McHardycdd289a2007-02-07 15:09:46 -080049 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
Herbert Xu3db05fe2007-10-15 00:53:15 -070058 if (!skb_make_writable(skb, skb->len))
Patrick McHardycdd289a2007-02-07 15:09:46 -080059 return -1;
60
Herbert Xu3db05fe2007-10-15 00:53:15 -070061 tcplen = skb->len - tcphoff;
62 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -080063
Simon Arlott10a19932010-02-02 15:33:38 +010064 /* Header cannot be larger than the packet */
65 if (tcplen < tcph->doff*4)
Patrick McHardycdd289a2007-02-07 15:09:46 -080066 return -1;
Patrick McHardycdd289a2007-02-07 15:09:46 -080067
68 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
Eric Dumazetadf30902009-06-02 05:19:30 +000069 if (dst_mtu(skb_dst(skb)) <= minlen) {
Joe Perchese87cc472012-05-13 21:56:26 +000070 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
71 dst_mtu(skb_dst(skb)));
Patrick McHardycdd289a2007-02-07 15:09:46 -080072 return -1;
73 }
Jan Engelhardt37c08382008-01-31 04:06:10 -080074 if (in_mtu <= minlen) {
Joe Perchese87cc472012-05-13 21:56:26 +000075 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
76 in_mtu);
Jan Engelhardt37c08382008-01-31 04:06:10 -080077 return -1;
78 }
Eric Dumazetadf30902009-06-02 05:19:30 +000079 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
Patrick McHardycdd289a2007-02-07 15:09:46 -080080 } else
81 newmss = info->mss;
82
83 opt = (u_int8_t *)tcph;
84 for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) {
85 if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS &&
86 opt[i+1] == TCPOLEN_MSS) {
87 u_int16_t oldmss;
88
89 oldmss = (opt[i+2] << 8) | opt[i+3];
90
Benjamin LaHaise17008062007-12-17 22:27:36 -080091 /* Never increase MSS, even when setting it, as
92 * doing so results in problems for hosts that rely
93 * on MSS being set correctly.
94 */
95 if (oldmss <= newmss)
Patrick McHardycdd289a2007-02-07 15:09:46 -080096 return 0;
97
98 opt[i+2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -070099 opt[i+3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800100
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100101 inet_proto_csum_replace2(&tcph->check, skb,
102 htons(oldmss), htons(newmss),
103 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800104 return 0;
105 }
106 }
107
Simon Arlott10a19932010-02-02 15:33:38 +0100108 /* There is data after the header so the option can't be added
109 without moving it, and doing so may make the SYN packet
110 itself too large. Accept the packet unmodified instead. */
111 if (tcplen > tcph->doff*4)
112 return 0;
113
Patrick McHardycdd289a2007-02-07 15:09:46 -0800114 /*
115 * MSS Option not found ?! add it..
116 */
Herbert Xu3db05fe2007-10-15 00:53:15 -0700117 if (skb_tailroom(skb) < TCPOLEN_MSS) {
118 if (pskb_expand_head(skb, 0,
119 TCPOLEN_MSS - skb_tailroom(skb),
Herbert Xu2ca7b0a2007-10-14 00:39:55 -0700120 GFP_ATOMIC))
Patrick McHardycdd289a2007-02-07 15:09:46 -0800121 return -1;
Herbert Xu3db05fe2007-10-15 00:53:15 -0700122 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800123 }
124
Herbert Xu3db05fe2007-10-15 00:53:15 -0700125 skb_put(skb, TCPOLEN_MSS);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800126
127 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
128 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
129
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100130 inet_proto_csum_replace2(&tcph->check, skb,
131 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800132 opt[0] = TCPOPT_MSS;
133 opt[1] = TCPOLEN_MSS;
134 opt[2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -0700135 opt[3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800136
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100137 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800138
139 oldval = ((__be16 *)tcph)[6];
140 tcph->doff += TCPOLEN_MSS/4;
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100141 inet_proto_csum_replace2(&tcph->check, skb,
142 oldval, ((__be16 *)tcph)[6], 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800143 return TCPOLEN_MSS;
144}
145
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700146static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
147 unsigned int family)
Jan Engelhardt37c08382008-01-31 04:06:10 -0800148{
David S. Millera1bbb0e2011-03-12 02:16:48 -0500149 struct flowi fl;
Jan Engelhardt37c08382008-01-31 04:06:10 -0800150 const struct nf_afinfo *ai;
151 struct rtable *rt = NULL;
152 u_int32_t mtu = ~0U;
153
David S. Millera1bbb0e2011-03-12 02:16:48 -0500154 if (family == PF_INET) {
155 struct flowi4 *fl4 = &fl.u.ip4;
156 memset(fl4, 0, sizeof(*fl4));
157 fl4->daddr = ip_hdr(skb)->saddr;
158 } else {
159 struct flowi6 *fl6 = &fl.u.ip6;
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700160
David S. Millera1bbb0e2011-03-12 02:16:48 -0500161 memset(fl6, 0, sizeof(*fl6));
Alexey Dobriyan4e3fd7a2011-11-21 03:39:03 +0000162 fl6->daddr = ipv6_hdr(skb)->saddr;
David S. Millera1bbb0e2011-03-12 02:16:48 -0500163 }
Jan Engelhardt37c08382008-01-31 04:06:10 -0800164 rcu_read_lock();
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700165 ai = nf_get_afinfo(family);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800166 if (ai != NULL)
Florian Westphal0fae2e72011-04-04 17:00:54 +0200167 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800168 rcu_read_unlock();
169
170 if (rt != NULL) {
Changli Gaod8d1f302010-06-10 23:31:35 -0700171 mtu = dst_mtu(&rt->dst);
172 dst_release(&rt->dst);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800173 }
174 return mtu;
175}
176
Patrick McHardycdd289a2007-02-07 15:09:46 -0800177static unsigned int
Jan Engelhardt4b560b42009-07-05 19:43:26 +0200178tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800179{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700180 struct iphdr *iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800181 __be16 newlen;
182 int ret;
183
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200184 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700185 tcpmss_reverse_mtu(skb, PF_INET),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800186 iph->ihl * 4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800187 sizeof(*iph) + sizeof(struct tcphdr));
188 if (ret < 0)
189 return NF_DROP;
190 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700191 iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800192 newlen = htons(ntohs(iph->tot_len) + ret);
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100193 csum_replace2(&iph->check, iph->tot_len, newlen);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800194 iph->tot_len = newlen;
195 }
196 return XT_CONTINUE;
197}
198
Igor Maravićc0cd1152011-12-12 02:58:24 +0000199#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800200static unsigned int
Jan Engelhardt4b560b42009-07-05 19:43:26 +0200201tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800202{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700203 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800204 u8 nexthdr;
Jesse Gross75f28112011-11-30 17:05:51 -0800205 __be16 frag_off;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800206 int tcphoff;
207 int ret;
208
209 nexthdr = ipv6h->nexthdr;
Jesse Gross75f28112011-11-30 17:05:51 -0800210 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
Patrick McHardy9dc05642007-11-30 23:58:03 +1100211 if (tcphoff < 0)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800212 return NF_DROP;
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200213 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700214 tcpmss_reverse_mtu(skb, PF_INET6),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800215 tcphoff,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800216 sizeof(*ipv6h) + sizeof(struct tcphdr));
217 if (ret < 0)
218 return NF_DROP;
219 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700220 ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800221 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
222 }
223 return XT_CONTINUE;
224}
225#endif
226
Patrick McHardycdd289a2007-02-07 15:09:46 -0800227/* Must specify -p tcp --syn */
Jan Engelhardte1931b72007-07-07 22:16:26 -0700228static inline bool find_syn_match(const struct xt_entry_match *m)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800229{
230 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
231
232 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
Changli Gaoa3433f32010-06-12 14:01:43 +0000233 tcpinfo->flg_cmp & TCPHDR_SYN &&
Patrick McHardycdd289a2007-02-07 15:09:46 -0800234 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
Jan Engelhardte1931b72007-07-07 22:16:26 -0700235 return true;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800236
Jan Engelhardte1931b72007-07-07 22:16:26 -0700237 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800238}
239
Jan Engelhardt135367b2010-03-19 17:16:42 +0100240static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800241{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200242 const struct xt_tcpmss_info *info = par->targinfo;
243 const struct ipt_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100244 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800245
246 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200247 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800248 (1 << NF_INET_LOCAL_OUT) |
249 (1 << NF_INET_POST_ROUTING))) != 0) {
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100250 pr_info("path-MTU clamping only supported in "
251 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100252 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800253 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100254 xt_ematch_foreach(ematch, e)
255 if (find_syn_match(ematch))
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100256 return 0;
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100257 pr_info("Only works on TCP SYN packets\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100258 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800259}
260
Igor Maravićc0cd1152011-12-12 02:58:24 +0000261#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
Jan Engelhardt135367b2010-03-19 17:16:42 +0100262static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800263{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200264 const struct xt_tcpmss_info *info = par->targinfo;
265 const struct ip6t_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100266 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800267
268 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200269 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800270 (1 << NF_INET_LOCAL_OUT) |
271 (1 << NF_INET_POST_ROUTING))) != 0) {
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100272 pr_info("path-MTU clamping only supported in "
273 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100274 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800275 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100276 xt_ematch_foreach(ematch, e)
277 if (find_syn_match(ematch))
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100278 return 0;
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100279 pr_info("Only works on TCP SYN packets\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100280 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800281}
282#endif
283
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800284static struct xt_target tcpmss_tg_reg[] __read_mostly = {
Patrick McHardycdd289a2007-02-07 15:09:46 -0800285 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200286 .family = NFPROTO_IPV4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800287 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800288 .checkentry = tcpmss_tg4_check,
289 .target = tcpmss_tg4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800290 .targetsize = sizeof(struct xt_tcpmss_info),
291 .proto = IPPROTO_TCP,
292 .me = THIS_MODULE,
293 },
Igor Maravićc0cd1152011-12-12 02:58:24 +0000294#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800295 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200296 .family = NFPROTO_IPV6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800297 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800298 .checkentry = tcpmss_tg6_check,
299 .target = tcpmss_tg6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800300 .targetsize = sizeof(struct xt_tcpmss_info),
301 .proto = IPPROTO_TCP,
302 .me = THIS_MODULE,
303 },
304#endif
305};
306
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800307static int __init tcpmss_tg_init(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800308{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800309 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800310}
311
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800312static void __exit tcpmss_tg_exit(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800313{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800314 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800315}
316
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800317module_init(tcpmss_tg_init);
318module_exit(tcpmss_tg_exit);