dm thin: wake worker when discard is prepared
[linux-2.6.git] / net / netfilter / xt_TCPMSS.c
blob71a266de5fb47352209d7380dcd75dff841e067e
1 /*
2 * This is a module which is used for setting the MSS option in TCP packets.
4 * Copyright (C) 2000 Marc Boucher <marc@mbsi.ca>
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 */
10 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11 #include <linux/module.h>
12 #include <linux/skbuff.h>
13 #include <linux/ip.h>
14 #include <linux/gfp.h>
15 #include <linux/ipv6.h>
16 #include <linux/tcp.h>
17 #include <net/dst.h>
18 #include <net/flow.h>
19 #include <net/ipv6.h>
20 #include <net/route.h>
21 #include <net/tcp.h>
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>
29 MODULE_LICENSE("GPL");
30 MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
31 MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
32 MODULE_ALIAS("ipt_TCPMSS");
33 MODULE_ALIAS("ip6t_TCPMSS");
35 static inline unsigned int
36 optlen(const u_int8_t *opt, unsigned int offset)
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];
45 static int
46 tcpmss_mangle_packet(struct sk_buff *skb,
47 const struct xt_tcpmss_info *info,
48 unsigned int in_mtu,
49 unsigned int tcphoff,
50 unsigned int minlen)
52 struct tcphdr *tcph;
53 unsigned int tcplen, i;
54 __be16 oldval;
55 u16 newmss;
56 u8 *opt;
58 if (!skb_make_writable(skb, skb->len))
59 return -1;
61 tcplen = skb->len - tcphoff;
62 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
64 /* Header cannot be larger than the packet */
65 if (tcplen < tcph->doff*4)
66 return -1;
68 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
69 if (dst_mtu(skb_dst(skb)) <= minlen) {
70 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
71 dst_mtu(skb_dst(skb)));
72 return -1;
74 if (in_mtu <= minlen) {
75 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
76 in_mtu);
77 return -1;
79 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
80 } else
81 newmss = info->mss;
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;
89 oldmss = (opt[i+2] << 8) | opt[i+3];
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.
95 if (oldmss <= newmss)
96 return 0;
98 opt[i+2] = (newmss & 0xff00) >> 8;
99 opt[i+3] = newmss & 0x00ff;
101 inet_proto_csum_replace2(&tcph->check, skb,
102 htons(oldmss), htons(newmss),
104 return 0;
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;
115 * MSS Option not found ?! add it..
117 if (skb_tailroom(skb) < TCPOLEN_MSS) {
118 if (pskb_expand_head(skb, 0,
119 TCPOLEN_MSS - skb_tailroom(skb),
120 GFP_ATOMIC))
121 return -1;
122 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
125 skb_put(skb, TCPOLEN_MSS);
127 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
128 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
130 inet_proto_csum_replace2(&tcph->check, skb,
131 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
132 opt[0] = TCPOPT_MSS;
133 opt[1] = TCPOLEN_MSS;
134 opt[2] = (newmss & 0xff00) >> 8;
135 opt[3] = newmss & 0x00ff;
137 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
139 oldval = ((__be16 *)tcph)[6];
140 tcph->doff += TCPOLEN_MSS/4;
141 inet_proto_csum_replace2(&tcph->check, skb,
142 oldval, ((__be16 *)tcph)[6], 0);
143 return TCPOLEN_MSS;
146 static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
147 unsigned int family)
149 struct flowi fl;
150 const struct nf_afinfo *ai;
151 struct rtable *rt = NULL;
152 u_int32_t mtu = ~0U;
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;
161 memset(fl6, 0, sizeof(*fl6));
162 fl6->daddr = ipv6_hdr(skb)->saddr;
164 rcu_read_lock();
165 ai = nf_get_afinfo(family);
166 if (ai != NULL)
167 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
168 rcu_read_unlock();
170 if (rt != NULL) {
171 mtu = dst_mtu(&rt->dst);
172 dst_release(&rt->dst);
174 return mtu;
177 static unsigned int
178 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
180 struct iphdr *iph = ip_hdr(skb);
181 __be16 newlen;
182 int ret;
184 ret = tcpmss_mangle_packet(skb, par->targinfo,
185 tcpmss_reverse_mtu(skb, PF_INET),
186 iph->ihl * 4,
187 sizeof(*iph) + sizeof(struct tcphdr));
188 if (ret < 0)
189 return NF_DROP;
190 if (ret > 0) {
191 iph = ip_hdr(skb);
192 newlen = htons(ntohs(iph->tot_len) + ret);
193 csum_replace2(&iph->check, iph->tot_len, newlen);
194 iph->tot_len = newlen;
196 return XT_CONTINUE;
199 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
200 static unsigned int
201 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
203 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
204 u8 nexthdr;
205 __be16 frag_off;
206 int tcphoff;
207 int ret;
209 nexthdr = ipv6h->nexthdr;
210 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
211 if (tcphoff < 0)
212 return NF_DROP;
213 ret = tcpmss_mangle_packet(skb, par->targinfo,
214 tcpmss_reverse_mtu(skb, PF_INET6),
215 tcphoff,
216 sizeof(*ipv6h) + sizeof(struct tcphdr));
217 if (ret < 0)
218 return NF_DROP;
219 if (ret > 0) {
220 ipv6h = ipv6_hdr(skb);
221 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
223 return XT_CONTINUE;
225 #endif
227 /* Must specify -p tcp --syn */
228 static inline bool find_syn_match(const struct xt_entry_match *m)
230 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
232 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
233 tcpinfo->flg_cmp & TCPHDR_SYN &&
234 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
235 return true;
237 return false;
240 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
242 const struct xt_tcpmss_info *info = par->targinfo;
243 const struct ipt_entry *e = par->entryinfo;
244 const struct xt_entry_match *ematch;
246 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
247 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
248 (1 << NF_INET_LOCAL_OUT) |
249 (1 << NF_INET_POST_ROUTING))) != 0) {
250 pr_info("path-MTU clamping only supported in "
251 "FORWARD, OUTPUT and POSTROUTING hooks\n");
252 return -EINVAL;
254 xt_ematch_foreach(ematch, e)
255 if (find_syn_match(ematch))
256 return 0;
257 pr_info("Only works on TCP SYN packets\n");
258 return -EINVAL;
261 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
262 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
264 const struct xt_tcpmss_info *info = par->targinfo;
265 const struct ip6t_entry *e = par->entryinfo;
266 const struct xt_entry_match *ematch;
268 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
269 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
270 (1 << NF_INET_LOCAL_OUT) |
271 (1 << NF_INET_POST_ROUTING))) != 0) {
272 pr_info("path-MTU clamping only supported in "
273 "FORWARD, OUTPUT and POSTROUTING hooks\n");
274 return -EINVAL;
276 xt_ematch_foreach(ematch, e)
277 if (find_syn_match(ematch))
278 return 0;
279 pr_info("Only works on TCP SYN packets\n");
280 return -EINVAL;
282 #endif
284 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
286 .family = NFPROTO_IPV4,
287 .name = "TCPMSS",
288 .checkentry = tcpmss_tg4_check,
289 .target = tcpmss_tg4,
290 .targetsize = sizeof(struct xt_tcpmss_info),
291 .proto = IPPROTO_TCP,
292 .me = THIS_MODULE,
294 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
296 .family = NFPROTO_IPV6,
297 .name = "TCPMSS",
298 .checkentry = tcpmss_tg6_check,
299 .target = tcpmss_tg6,
300 .targetsize = sizeof(struct xt_tcpmss_info),
301 .proto = IPPROTO_TCP,
302 .me = THIS_MODULE,
304 #endif
307 static int __init tcpmss_tg_init(void)
309 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
312 static void __exit tcpmss_tg_exit(void)
314 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
317 module_init(tcpmss_tg_init);
318 module_exit(tcpmss_tg_exit);