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