net: Add net_ratelimited_function and net_<level>_ratelimited macros
[deliverable/linux.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
c0cd1156 201#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
cdd289a2 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 206 u8 nexthdr;
75f2811c 207 __be16 frag_off;
cdd289a2
PM
208 int tcphoff;
209 int ret;
210
211 nexthdr = ipv6h->nexthdr;
75f2811c 212 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
9dc0564e 213 if (tcphoff < 0)
cdd289a2 214 return NF_DROP;
7eb35586 215 ret = tcpmss_mangle_packet(skb, par->targinfo,
db1a75bd 216 tcpmss_reverse_mtu(skb, PF_INET6),
37c08387 217 tcphoff,
cdd289a2
PM
218 sizeof(*ipv6h) + sizeof(struct tcphdr));
219 if (ret < 0)
220 return NF_DROP;
221 if (ret > 0) {
3db05fea 222 ipv6h = ipv6_hdr(skb);
cdd289a2
PM
223 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
224 }
225 return XT_CONTINUE;
226}
227#endif
228
cdd289a2 229/* Must specify -p tcp --syn */
e1931b78 230static inline bool find_syn_match(const struct xt_entry_match *m)
cdd289a2
PM
231{
232 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
233
234 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
a3433f35 235 tcpinfo->flg_cmp & TCPHDR_SYN &&
cdd289a2 236 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
e1931b78 237 return true;
cdd289a2 238
e1931b78 239 return false;
cdd289a2
PM
240}
241
135367b8 242static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
cdd289a2 243{
af5d6dc2
JE
244 const struct xt_tcpmss_info *info = par->targinfo;
245 const struct ipt_entry *e = par->entryinfo;
dcea992a 246 const struct xt_entry_match *ematch;
cdd289a2
PM
247
248 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
af5d6dc2 249 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
6e23ae2a
PM
250 (1 << NF_INET_LOCAL_OUT) |
251 (1 << NF_INET_POST_ROUTING))) != 0) {
8bee4bad
JE
252 pr_info("path-MTU clamping only supported in "
253 "FORWARD, OUTPUT and POSTROUTING hooks\n");
d6b00a53 254 return -EINVAL;
cdd289a2 255 }
dcea992a
JE
256 xt_ematch_foreach(ematch, e)
257 if (find_syn_match(ematch))
d6b00a53 258 return 0;
8bee4bad 259 pr_info("Only works on TCP SYN packets\n");
d6b00a53 260 return -EINVAL;
cdd289a2
PM
261}
262
c0cd1156 263#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
135367b8 264static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
cdd289a2 265{
af5d6dc2
JE
266 const struct xt_tcpmss_info *info = par->targinfo;
267 const struct ip6t_entry *e = par->entryinfo;
dcea992a 268 const struct xt_entry_match *ematch;
cdd289a2
PM
269
270 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
af5d6dc2 271 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
6e23ae2a
PM
272 (1 << NF_INET_LOCAL_OUT) |
273 (1 << NF_INET_POST_ROUTING))) != 0) {
8bee4bad
JE
274 pr_info("path-MTU clamping only supported in "
275 "FORWARD, OUTPUT and POSTROUTING hooks\n");
d6b00a53 276 return -EINVAL;
cdd289a2 277 }
dcea992a
JE
278 xt_ematch_foreach(ematch, e)
279 if (find_syn_match(ematch))
d6b00a53 280 return 0;
8bee4bad 281 pr_info("Only works on TCP SYN packets\n");
d6b00a53 282 return -EINVAL;
cdd289a2
PM
283}
284#endif
285
d3c5ee6d 286static struct xt_target tcpmss_tg_reg[] __read_mostly = {
cdd289a2 287 {
ee999d8b 288 .family = NFPROTO_IPV4,
cdd289a2 289 .name = "TCPMSS",
d3c5ee6d
JE
290 .checkentry = tcpmss_tg4_check,
291 .target = tcpmss_tg4,
cdd289a2
PM
292 .targetsize = sizeof(struct xt_tcpmss_info),
293 .proto = IPPROTO_TCP,
294 .me = THIS_MODULE,
295 },
c0cd1156 296#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
cdd289a2 297 {
ee999d8b 298 .family = NFPROTO_IPV6,
cdd289a2 299 .name = "TCPMSS",
d3c5ee6d
JE
300 .checkentry = tcpmss_tg6_check,
301 .target = tcpmss_tg6,
cdd289a2
PM
302 .targetsize = sizeof(struct xt_tcpmss_info),
303 .proto = IPPROTO_TCP,
304 .me = THIS_MODULE,
305 },
306#endif
307};
308
d3c5ee6d 309static int __init tcpmss_tg_init(void)
cdd289a2 310{
d3c5ee6d 311 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
312}
313
d3c5ee6d 314static void __exit tcpmss_tg_exit(void)
cdd289a2 315{
d3c5ee6d 316 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
317}
318
d3c5ee6d
JE
319module_init(tcpmss_tg_init);
320module_exit(tcpmss_tg_exit);
This page took 0.477882 seconds and 5 git commands to generate.