Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / net / netfilter / xt_TCPMSS.c
CommitLineData
cdd289a2
PM
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 */
8bee4bad 10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
cdd289a2
PM
11#include <linux/module.h>
12#include <linux/skbuff.h>
13#include <linux/ip.h>
5a0e3ad6 14#include <linux/gfp.h>
cdd289a2
PM
15#include <linux/ipv6.h>
16#include <linux/tcp.h>
37c08387
JE
17#include <net/dst.h>
18#include <net/flow.h>
cdd289a2 19#include <net/ipv6.h>
37c08387 20#include <net/route.h>
cdd289a2
PM
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
29MODULE_LICENSE("GPL");
30MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
2ae15b64 31MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
cdd289a2
PM
32MODULE_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
3db05fea 46tcpmss_mangle_packet(struct sk_buff *skb,
cdd289a2 47 const struct xt_tcpmss_info *info,
37c08387 48 unsigned int in_mtu,
cdd289a2
PM
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
3db05fea 58 if (!skb_make_writable(skb, skb->len))
cdd289a2
PM
59 return -1;
60
3db05fea
HX
61 tcplen = skb->len - tcphoff;
62 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
cdd289a2 63
10a19939
SA
64 /* Header cannot be larger than the packet */
65 if (tcplen < tcph->doff*4)
cdd289a2 66 return -1;
cdd289a2
PM
67
68 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
adf30907 69 if (dst_mtu(skb_dst(skb)) <= minlen) {
e87cc472
JP
70 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
71 dst_mtu(skb_dst(skb)));
cdd289a2
PM
72 return -1;
73 }
37c08387 74 if (in_mtu <= minlen) {
e87cc472
JP
75 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
76 in_mtu);
37c08387
JE
77 return -1;
78 }
adf30907 79 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
cdd289a2
PM
80 } 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
17008064
BL
91 /* 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)
cdd289a2
PM
96 return 0;
97
98 opt[i+2] = (newmss & 0xff00) >> 8;
7c4e36bc 99 opt[i+3] = newmss & 0x00ff;
cdd289a2 100
be0ea7d5
PM
101 inet_proto_csum_replace2(&tcph->check, skb,
102 htons(oldmss), htons(newmss),
103 0);
cdd289a2
PM
104 return 0;
105 }
106 }
107
10a19939
SA
108 /* 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
cdd289a2
PM
114 /*
115 * MSS Option not found ?! add it..
116 */
3db05fea
HX
117 if (skb_tailroom(skb) < TCPOLEN_MSS) {
118 if (pskb_expand_head(skb, 0,
119 TCPOLEN_MSS - skb_tailroom(skb),
2ca7b0ac 120 GFP_ATOMIC))
cdd289a2 121 return -1;
3db05fea 122 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
cdd289a2
PM
123 }
124
3db05fea 125 skb_put(skb, TCPOLEN_MSS);
cdd289a2
PM
126
127 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
128 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
129
be0ea7d5
PM
130 inet_proto_csum_replace2(&tcph->check, skb,
131 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
cdd289a2
PM
132 opt[0] = TCPOPT_MSS;
133 opt[1] = TCPOLEN_MSS;
134 opt[2] = (newmss & 0xff00) >> 8;
7c4e36bc 135 opt[3] = newmss & 0x00ff;
cdd289a2 136
be0ea7d5 137 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
cdd289a2
PM
138
139 oldval = ((__be16 *)tcph)[6];
140 tcph->doff += TCPOLEN_MSS/4;
be0ea7d5
PM
141 inet_proto_csum_replace2(&tcph->check, skb,
142 oldval, ((__be16 *)tcph)[6], 0);
cdd289a2
PM
143 return TCPOLEN_MSS;
144}
145
db1a75bd
JE
146static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
147 unsigned int family)
37c08387 148{
a1bbb0e6 149 struct flowi fl;
37c08387
JE
150 const struct nf_afinfo *ai;
151 struct rtable *rt = NULL;
152 u_int32_t mtu = ~0U;
153
a1bbb0e6
DM
154 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;
db1a75bd 160
a1bbb0e6 161 memset(fl6, 0, sizeof(*fl6));
4e3fd7a0 162 fl6->daddr = ipv6_hdr(skb)->saddr;
a1bbb0e6 163 }
37c08387 164 rcu_read_lock();
db1a75bd 165 ai = nf_get_afinfo(family);
37c08387 166 if (ai != NULL)
0fae2e77 167 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
37c08387
JE
168 rcu_read_unlock();
169
170 if (rt != NULL) {
d8d1f30b
CG
171 mtu = dst_mtu(&rt->dst);
172 dst_release(&rt->dst);
37c08387
JE
173 }
174 return mtu;
175}
176
cdd289a2 177static unsigned int
4b560b44 178tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
cdd289a2 179{
3db05fea 180 struct iphdr *iph = ip_hdr(skb);
cdd289a2
PM
181 __be16 newlen;
182 int ret;
183
7eb35586 184 ret = tcpmss_mangle_packet(skb, par->targinfo,
db1a75bd 185 tcpmss_reverse_mtu(skb, PF_INET),
37c08387 186 iph->ihl * 4,
cdd289a2
PM
187 sizeof(*iph) + sizeof(struct tcphdr));
188 if (ret < 0)
189 return NF_DROP;
190 if (ret > 0) {
3db05fea 191 iph = ip_hdr(skb);
cdd289a2 192 newlen = htons(ntohs(iph->tot_len) + ret);
be0ea7d5 193 csum_replace2(&iph->check, iph->tot_len, newlen);
cdd289a2
PM
194 iph->tot_len = newlen;
195 }
196 return XT_CONTINUE;
197}
198
c0cd1156 199#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
cdd289a2 200static unsigned int
4b560b44 201tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
cdd289a2 202{
3db05fea 203 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
cdd289a2 204 u8 nexthdr;
75f2811c 205 __be16 frag_off;
cdd289a2
PM
206 int tcphoff;
207 int ret;
208
209 nexthdr = ipv6h->nexthdr;
75f2811c 210 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
9dc0564e 211 if (tcphoff < 0)
cdd289a2 212 return NF_DROP;
7eb35586 213 ret = tcpmss_mangle_packet(skb, par->targinfo,
db1a75bd 214 tcpmss_reverse_mtu(skb, PF_INET6),
37c08387 215 tcphoff,
cdd289a2
PM
216 sizeof(*ipv6h) + sizeof(struct tcphdr));
217 if (ret < 0)
218 return NF_DROP;
219 if (ret > 0) {
3db05fea 220 ipv6h = ipv6_hdr(skb);
cdd289a2
PM
221 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
222 }
223 return XT_CONTINUE;
224}
225#endif
226
cdd289a2 227/* Must specify -p tcp --syn */
e1931b78 228static inline bool find_syn_match(const struct xt_entry_match *m)
cdd289a2
PM
229{
230 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
231
232 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
a3433f35 233 tcpinfo->flg_cmp & TCPHDR_SYN &&
cdd289a2 234 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
e1931b78 235 return true;
cdd289a2 236
e1931b78 237 return false;
cdd289a2
PM
238}
239
135367b8 240static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
cdd289a2 241{
af5d6dc2
JE
242 const struct xt_tcpmss_info *info = par->targinfo;
243 const struct ipt_entry *e = par->entryinfo;
dcea992a 244 const struct xt_entry_match *ematch;
cdd289a2
PM
245
246 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
af5d6dc2 247 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
6e23ae2a
PM
248 (1 << NF_INET_LOCAL_OUT) |
249 (1 << NF_INET_POST_ROUTING))) != 0) {
8bee4bad
JE
250 pr_info("path-MTU clamping only supported in "
251 "FORWARD, OUTPUT and POSTROUTING hooks\n");
d6b00a53 252 return -EINVAL;
cdd289a2 253 }
dcea992a
JE
254 xt_ematch_foreach(ematch, e)
255 if (find_syn_match(ematch))
d6b00a53 256 return 0;
8bee4bad 257 pr_info("Only works on TCP SYN packets\n");
d6b00a53 258 return -EINVAL;
cdd289a2
PM
259}
260
c0cd1156 261#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
135367b8 262static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
cdd289a2 263{
af5d6dc2
JE
264 const struct xt_tcpmss_info *info = par->targinfo;
265 const struct ip6t_entry *e = par->entryinfo;
dcea992a 266 const struct xt_entry_match *ematch;
cdd289a2
PM
267
268 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
af5d6dc2 269 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
6e23ae2a
PM
270 (1 << NF_INET_LOCAL_OUT) |
271 (1 << NF_INET_POST_ROUTING))) != 0) {
8bee4bad
JE
272 pr_info("path-MTU clamping only supported in "
273 "FORWARD, OUTPUT and POSTROUTING hooks\n");
d6b00a53 274 return -EINVAL;
cdd289a2 275 }
dcea992a
JE
276 xt_ematch_foreach(ematch, e)
277 if (find_syn_match(ematch))
d6b00a53 278 return 0;
8bee4bad 279 pr_info("Only works on TCP SYN packets\n");
d6b00a53 280 return -EINVAL;
cdd289a2
PM
281}
282#endif
283
d3c5ee6d 284static struct xt_target tcpmss_tg_reg[] __read_mostly = {
cdd289a2 285 {
ee999d8b 286 .family = NFPROTO_IPV4,
cdd289a2 287 .name = "TCPMSS",
d3c5ee6d
JE
288 .checkentry = tcpmss_tg4_check,
289 .target = tcpmss_tg4,
cdd289a2
PM
290 .targetsize = sizeof(struct xt_tcpmss_info),
291 .proto = IPPROTO_TCP,
292 .me = THIS_MODULE,
293 },
c0cd1156 294#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
cdd289a2 295 {
ee999d8b 296 .family = NFPROTO_IPV6,
cdd289a2 297 .name = "TCPMSS",
d3c5ee6d
JE
298 .checkentry = tcpmss_tg6_check,
299 .target = tcpmss_tg6,
cdd289a2
PM
300 .targetsize = sizeof(struct xt_tcpmss_info),
301 .proto = IPPROTO_TCP,
302 .me = THIS_MODULE,
303 },
304#endif
305};
306
d3c5ee6d 307static int __init tcpmss_tg_init(void)
cdd289a2 308{
d3c5ee6d 309 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
310}
311
d3c5ee6d 312static void __exit tcpmss_tg_exit(void)
cdd289a2 313{
d3c5ee6d 314 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
315}
316
d3c5ee6d
JE
317module_init(tcpmss_tg_init);
318module_exit(tcpmss_tg_exit);