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;
60 /* This is a fragment, no TCP header is available */
61 if (par->fragoff != 0)
64 if (!skb_make_writable(skb, skb->len))
67 len = skb->len - tcphoff;
68 if (len < (int)sizeof(struct tcphdr))
71 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
72 tcp_hdrlen = tcph->doff * 4;
74 if (len < tcp_hdrlen || tcp_hdrlen < sizeof(struct tcphdr))
77 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
78 if (dst_mtu(skb_dst(skb)) <= minlen) {
80 pr_err("unknown or invalid path-MTU (%u)\n",
81 dst_mtu(skb_dst(skb)));
84 if (in_mtu <= minlen) {
86 pr_err("unknown or invalid path-MTU (%u)\n",
90 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
94 opt = (u_int8_t *)tcph;
95 for (i = sizeof(struct tcphdr); i <= tcp_hdrlen - TCPOLEN_MSS; i += optlen(opt, i)) {
96 if (opt[i] == TCPOPT_MSS && opt[i+1] == TCPOLEN_MSS) {
99 oldmss = (opt[i+2] << 8) | opt[i+3];
101 /* Never increase MSS, even when setting it, as
102 * doing so results in problems for hosts that rely
103 * on MSS being set correctly.
105 if (oldmss <= newmss)
108 opt[i+2] = (newmss & 0xff00) >> 8;
109 opt[i+3] = newmss & 0x00ff;
111 inet_proto_csum_replace2(&tcph->check, skb,
112 htons(oldmss), htons(newmss),
118 /* There is data after the header so the option can't be added
119 * without moving it, and doing so may make the SYN packet
120 * itself too large. Accept the packet unmodified instead.
122 if (len > tcp_hdrlen)
125 /* tcph->doff has 4 bits, do not wrap it to 0 */
126 if (tcp_hdrlen >= 15 * 4)
130 * MSS Option not found ?! add it..
132 if (skb_tailroom(skb) < TCPOLEN_MSS) {
133 if (pskb_expand_head(skb, 0,
134 TCPOLEN_MSS - skb_tailroom(skb),
137 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
140 skb_put(skb, TCPOLEN_MSS);
142 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
143 memmove(opt + TCPOLEN_MSS, opt, len - sizeof(struct tcphdr));
145 inet_proto_csum_replace2(&tcph->check, skb,
146 htons(len), htons(len + TCPOLEN_MSS), 1);
148 opt[1] = TCPOLEN_MSS;
149 opt[2] = (newmss & 0xff00) >> 8;
150 opt[3] = newmss & 0x00ff;
152 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
154 oldval = ((__be16 *)tcph)[6];
155 tcph->doff += TCPOLEN_MSS/4;
156 inet_proto_csum_replace2(&tcph->check, skb,
157 oldval, ((__be16 *)tcph)[6], 0);
161 static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
165 const struct nf_afinfo *ai;
166 struct rtable *rt = NULL;
169 if (family == PF_INET) {
170 struct flowi4 *fl4 = &fl.u.ip4;
171 memset(fl4, 0, sizeof(*fl4));
172 fl4->daddr = ip_hdr(skb)->saddr;
174 struct flowi6 *fl6 = &fl.u.ip6;
176 memset(fl6, 0, sizeof(*fl6));
177 ipv6_addr_copy(&fl6->daddr, &ipv6_hdr(skb)->saddr);
180 ai = nf_get_afinfo(family);
182 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
186 mtu = dst_mtu(&rt->dst);
187 dst_release(&rt->dst);
193 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
195 struct iphdr *iph = ip_hdr(skb);
199 ret = tcpmss_mangle_packet(skb, par,
200 tcpmss_reverse_mtu(skb, PF_INET),
202 sizeof(*iph) + sizeof(struct tcphdr));
207 newlen = htons(ntohs(iph->tot_len) + ret);
208 csum_replace2(&iph->check, iph->tot_len, newlen);
209 iph->tot_len = newlen;
214 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
216 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
218 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
223 nexthdr = ipv6h->nexthdr;
224 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
227 ret = tcpmss_mangle_packet(skb, par,
228 tcpmss_reverse_mtu(skb, PF_INET6),
230 sizeof(*ipv6h) + sizeof(struct tcphdr));
234 ipv6h = ipv6_hdr(skb);
235 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
241 /* Must specify -p tcp --syn */
242 static inline bool find_syn_match(const struct xt_entry_match *m)
244 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
246 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
247 tcpinfo->flg_cmp & TCPHDR_SYN &&
248 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
254 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
256 const struct xt_tcpmss_info *info = par->targinfo;
257 const struct ipt_entry *e = par->entryinfo;
258 const struct xt_entry_match *ematch;
260 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
261 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
262 (1 << NF_INET_LOCAL_OUT) |
263 (1 << NF_INET_POST_ROUTING))) != 0) {
264 pr_info("path-MTU clamping only supported in "
265 "FORWARD, OUTPUT and POSTROUTING hooks\n");
268 xt_ematch_foreach(ematch, e)
269 if (find_syn_match(ematch))
271 pr_info("Only works on TCP SYN packets\n");
275 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
276 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
278 const struct xt_tcpmss_info *info = par->targinfo;
279 const struct ip6t_entry *e = par->entryinfo;
280 const struct xt_entry_match *ematch;
282 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
283 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
284 (1 << NF_INET_LOCAL_OUT) |
285 (1 << NF_INET_POST_ROUTING))) != 0) {
286 pr_info("path-MTU clamping only supported in "
287 "FORWARD, OUTPUT and POSTROUTING hooks\n");
290 xt_ematch_foreach(ematch, e)
291 if (find_syn_match(ematch))
293 pr_info("Only works on TCP SYN packets\n");
298 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
300 .family = NFPROTO_IPV4,
302 .checkentry = tcpmss_tg4_check,
303 .target = tcpmss_tg4,
304 .targetsize = sizeof(struct xt_tcpmss_info),
305 .proto = IPPROTO_TCP,
308 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
310 .family = NFPROTO_IPV6,
312 .checkentry = tcpmss_tg6_check,
313 .target = tcpmss_tg6,
314 .targetsize = sizeof(struct xt_tcpmss_info),
315 .proto = IPPROTO_TCP,
321 static int __init tcpmss_tg_init(void)
323 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
326 static void __exit tcpmss_tg_exit(void)
328 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
331 module_init(tcpmss_tg_init);
332 module_exit(tcpmss_tg_exit);