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.
10 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11 #include <linux/module.h>
12 #include <linux/skbuff.h>
14 #include <linux/gfp.h>
15 #include <linux/ipv6.h>
16 #include <linux/tcp.h>
20 #include <net/route.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)
46 tcpmss_mangle_packet(struct sk_buff *skb,
47 const struct xt_action_param *par,
52 const struct xt_tcpmss_info *info = par->targinfo;
54 unsigned int tcplen, i;
59 /* This is a fragment, no TCP header is available */
60 if (par->fragoff != 0)
63 if (!skb_make_writable(skb, skb->len))
66 tcplen = skb->len - tcphoff;
67 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
69 /* Header cannot be larger than the packet */
70 if (tcplen < tcph->doff*4)
73 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
74 if (dst_mtu(skb_dst(skb)) <= minlen) {
76 pr_err("unknown or invalid path-MTU (%u)\n",
77 dst_mtu(skb_dst(skb)));
80 if (in_mtu <= minlen) {
82 pr_err("unknown or invalid path-MTU (%u)\n",
86 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
90 opt = (u_int8_t *)tcph;
91 for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) {
92 if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS &&
93 opt[i+1] == TCPOLEN_MSS) {
96 oldmss = (opt[i+2] << 8) | opt[i+3];
98 /* Never increase MSS, even when setting it, as
99 * doing so results in problems for hosts that rely
100 * on MSS being set correctly.
102 if (oldmss <= newmss)
105 opt[i+2] = (newmss & 0xff00) >> 8;
106 opt[i+3] = newmss & 0x00ff;
108 inet_proto_csum_replace2(&tcph->check, skb,
109 htons(oldmss), htons(newmss),
115 /* There is data after the header so the option can't be added
116 without moving it, and doing so may make the SYN packet
117 itself too large. Accept the packet unmodified instead. */
118 if (tcplen > tcph->doff*4)
122 * MSS Option not found ?! add it..
124 if (skb_tailroom(skb) < TCPOLEN_MSS) {
125 if (pskb_expand_head(skb, 0,
126 TCPOLEN_MSS - skb_tailroom(skb),
129 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
132 skb_put(skb, TCPOLEN_MSS);
134 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
135 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
137 inet_proto_csum_replace2(&tcph->check, skb,
138 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
140 opt[1] = TCPOLEN_MSS;
141 opt[2] = (newmss & 0xff00) >> 8;
142 opt[3] = newmss & 0x00ff;
144 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
146 oldval = ((__be16 *)tcph)[6];
147 tcph->doff += TCPOLEN_MSS/4;
148 inet_proto_csum_replace2(&tcph->check, skb,
149 oldval, ((__be16 *)tcph)[6], 0);
153 static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
157 const struct nf_afinfo *ai;
158 struct rtable *rt = NULL;
161 if (family == PF_INET) {
162 struct flowi4 *fl4 = &fl.u.ip4;
163 memset(fl4, 0, sizeof(*fl4));
164 fl4->daddr = ip_hdr(skb)->saddr;
166 struct flowi6 *fl6 = &fl.u.ip6;
168 memset(fl6, 0, sizeof(*fl6));
169 ipv6_addr_copy(&fl6->daddr, &ipv6_hdr(skb)->saddr);
172 ai = nf_get_afinfo(family);
174 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
178 mtu = dst_mtu(&rt->dst);
179 dst_release(&rt->dst);
185 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
187 struct iphdr *iph = ip_hdr(skb);
191 ret = tcpmss_mangle_packet(skb, par,
192 tcpmss_reverse_mtu(skb, PF_INET),
194 sizeof(*iph) + sizeof(struct tcphdr));
199 newlen = htons(ntohs(iph->tot_len) + ret);
200 csum_replace2(&iph->check, iph->tot_len, newlen);
201 iph->tot_len = newlen;
206 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
208 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
210 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
215 nexthdr = ipv6h->nexthdr;
216 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
219 ret = tcpmss_mangle_packet(skb, par,
220 tcpmss_reverse_mtu(skb, PF_INET6),
222 sizeof(*ipv6h) + sizeof(struct tcphdr));
226 ipv6h = ipv6_hdr(skb);
227 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
233 /* Must specify -p tcp --syn */
234 static inline bool find_syn_match(const struct xt_entry_match *m)
236 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
238 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
239 tcpinfo->flg_cmp & TCPHDR_SYN &&
240 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
246 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
248 const struct xt_tcpmss_info *info = par->targinfo;
249 const struct ipt_entry *e = par->entryinfo;
250 const struct xt_entry_match *ematch;
252 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
253 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
254 (1 << NF_INET_LOCAL_OUT) |
255 (1 << NF_INET_POST_ROUTING))) != 0) {
256 pr_info("path-MTU clamping only supported in "
257 "FORWARD, OUTPUT and POSTROUTING hooks\n");
260 xt_ematch_foreach(ematch, e)
261 if (find_syn_match(ematch))
263 pr_info("Only works on TCP SYN packets\n");
267 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
268 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
270 const struct xt_tcpmss_info *info = par->targinfo;
271 const struct ip6t_entry *e = par->entryinfo;
272 const struct xt_entry_match *ematch;
274 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
275 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
276 (1 << NF_INET_LOCAL_OUT) |
277 (1 << NF_INET_POST_ROUTING))) != 0) {
278 pr_info("path-MTU clamping only supported in "
279 "FORWARD, OUTPUT and POSTROUTING hooks\n");
282 xt_ematch_foreach(ematch, e)
283 if (find_syn_match(ematch))
285 pr_info("Only works on TCP SYN packets\n");
290 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
292 .family = NFPROTO_IPV4,
294 .checkentry = tcpmss_tg4_check,
295 .target = tcpmss_tg4,
296 .targetsize = sizeof(struct xt_tcpmss_info),
297 .proto = IPPROTO_TCP,
300 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
302 .family = NFPROTO_IPV6,
304 .checkentry = tcpmss_tg6_check,
305 .target = tcpmss_tg6,
306 .targetsize = sizeof(struct xt_tcpmss_info),
307 .proto = IPPROTO_TCP,
313 static int __init tcpmss_tg_init(void)
315 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
318 static void __exit tcpmss_tg_exit(void)
320 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
323 module_init(tcpmss_tg_init);
324 module_exit(tcpmss_tg_exit);