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.
11 #include <linux/module.h>
12 #include <linux/skbuff.h>
14 #include <linux/ipv6.h>
15 #include <linux/tcp.h>
19 #include <net/route.h>
22 #include <linux/netfilter_ipv4/ip_tables.h>
23 #include <linux/netfilter_ipv6/ip6_tables.h>
24 #include <linux/netfilter/x_tables.h>
25 #include <linux/netfilter/xt_tcpudp.h>
26 #include <linux/netfilter/xt_TCPMSS.h>
28 MODULE_LICENSE("GPL");
29 MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
30 MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
31 MODULE_ALIAS("ipt_TCPMSS");
32 MODULE_ALIAS("ip6t_TCPMSS");
34 static inline unsigned int
35 optlen(const u_int8_t
*opt
, unsigned int offset
)
37 /* Beware zero-length options: make finite progress */
38 if (opt
[offset
] <= TCPOPT_NOP
|| opt
[offset
+1] == 0)
45 tcpmss_mangle_packet(struct sk_buff
*skb
,
46 const struct xt_tcpmss_info
*info
,
52 unsigned int tcplen
, i
;
57 if (!skb_make_writable(skb
, skb
->len
))
60 tcplen
= skb
->len
- tcphoff
;
61 tcph
= (struct tcphdr
*)(skb_network_header(skb
) + tcphoff
);
63 /* Header cannot be larger than the packet */
64 if (tcplen
< tcph
->doff
*4)
67 if (info
->mss
== XT_TCPMSS_CLAMP_PMTU
) {
68 if (dst_mtu(skb_dst(skb
)) <= minlen
) {
70 printk(KERN_ERR
"xt_TCPMSS: "
71 "unknown or invalid path-MTU (%u)\n",
72 dst_mtu(skb_dst(skb
)));
75 if (in_mtu
<= minlen
) {
77 printk(KERN_ERR
"xt_TCPMSS: unknown or "
78 "invalid path-MTU (%u)\n", in_mtu
);
81 newmss
= min(dst_mtu(skb_dst(skb
)), in_mtu
) - minlen
;
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
) {
91 oldmss
= (opt
[i
+2] << 8) | opt
[i
+3];
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.
100 opt
[i
+2] = (newmss
& 0xff00) >> 8;
101 opt
[i
+3] = newmss
& 0x00ff;
103 inet_proto_csum_replace2(&tcph
->check
, skb
,
104 htons(oldmss
), htons(newmss
),
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)
117 * MSS Option not found ?! add it..
119 if (skb_tailroom(skb
) < TCPOLEN_MSS
) {
120 if (pskb_expand_head(skb
, 0,
121 TCPOLEN_MSS
- skb_tailroom(skb
),
124 tcph
= (struct tcphdr
*)(skb_network_header(skb
) + tcphoff
);
127 skb_put(skb
, TCPOLEN_MSS
);
129 opt
= (u_int8_t
*)tcph
+ sizeof(struct tcphdr
);
130 memmove(opt
+ TCPOLEN_MSS
, opt
, tcplen
- sizeof(struct tcphdr
));
132 inet_proto_csum_replace2(&tcph
->check
, skb
,
133 htons(tcplen
), htons(tcplen
+ TCPOLEN_MSS
), 1);
135 opt
[1] = TCPOLEN_MSS
;
136 opt
[2] = (newmss
& 0xff00) >> 8;
137 opt
[3] = newmss
& 0x00ff;
139 inet_proto_csum_replace4(&tcph
->check
, skb
, 0, *((__be32
*)opt
), 0);
141 oldval
= ((__be16
*)tcph
)[6];
142 tcph
->doff
+= TCPOLEN_MSS
/4;
143 inet_proto_csum_replace2(&tcph
->check
, skb
,
144 oldval
, ((__be16
*)tcph
)[6], 0);
148 static u_int32_t
tcpmss_reverse_mtu(const struct sk_buff
*skb
,
151 struct flowi fl
= {};
152 const struct nf_afinfo
*ai
;
153 struct rtable
*rt
= NULL
;
156 if (family
== PF_INET
)
157 fl
.fl4_dst
= ip_hdr(skb
)->saddr
;
159 fl
.fl6_dst
= ipv6_hdr(skb
)->saddr
;
162 ai
= nf_get_afinfo(family
);
164 ai
->route((struct dst_entry
**)&rt
, &fl
);
168 mtu
= dst_mtu(&rt
->u
.dst
);
169 dst_release(&rt
->u
.dst
);
175 tcpmss_tg4(struct sk_buff
*skb
, const struct xt_target_param
*par
)
177 struct iphdr
*iph
= ip_hdr(skb
);
181 ret
= tcpmss_mangle_packet(skb
, par
->targinfo
,
182 tcpmss_reverse_mtu(skb
, PF_INET
),
184 sizeof(*iph
) + sizeof(struct tcphdr
));
189 newlen
= htons(ntohs(iph
->tot_len
) + ret
);
190 csum_replace2(&iph
->check
, iph
->tot_len
, newlen
);
191 iph
->tot_len
= newlen
;
196 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
198 tcpmss_tg6(struct sk_buff
*skb
, const struct xt_target_param
*par
)
200 struct ipv6hdr
*ipv6h
= ipv6_hdr(skb
);
205 nexthdr
= ipv6h
->nexthdr
;
206 tcphoff
= ipv6_skip_exthdr(skb
, sizeof(*ipv6h
), &nexthdr
);
209 ret
= tcpmss_mangle_packet(skb
, par
->targinfo
,
210 tcpmss_reverse_mtu(skb
, PF_INET6
),
212 sizeof(*ipv6h
) + sizeof(struct tcphdr
));
216 ipv6h
= ipv6_hdr(skb
);
217 ipv6h
->payload_len
= htons(ntohs(ipv6h
->payload_len
) + ret
);
225 /* Must specify -p tcp --syn */
226 static inline bool find_syn_match(const struct xt_entry_match
*m
)
228 const struct xt_tcp
*tcpinfo
= (const struct xt_tcp
*)m
->data
;
230 if (strcmp(m
->u
.kernel
.match
->name
, "tcp") == 0 &&
231 tcpinfo
->flg_cmp
& TH_SYN
&&
232 !(tcpinfo
->invflags
& XT_TCP_INV_FLAGS
))
238 static bool tcpmss_tg4_check(const struct xt_tgchk_param
*par
)
240 const struct xt_tcpmss_info
*info
= par
->targinfo
;
241 const struct ipt_entry
*e
= par
->entryinfo
;
243 if (info
->mss
== XT_TCPMSS_CLAMP_PMTU
&&
244 (par
->hook_mask
& ~((1 << NF_INET_FORWARD
) |
245 (1 << NF_INET_LOCAL_OUT
) |
246 (1 << NF_INET_POST_ROUTING
))) != 0) {
247 printk("xt_TCPMSS: path-MTU clamping only supported in "
248 "FORWARD, OUTPUT and POSTROUTING hooks\n");
251 if (IPT_MATCH_ITERATE(e
, find_syn_match
))
253 printk("xt_TCPMSS: Only works on TCP SYN packets\n");
257 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
258 static bool tcpmss_tg6_check(const struct xt_tgchk_param
*par
)
260 const struct xt_tcpmss_info
*info
= par
->targinfo
;
261 const struct ip6t_entry
*e
= par
->entryinfo
;
263 if (info
->mss
== XT_TCPMSS_CLAMP_PMTU
&&
264 (par
->hook_mask
& ~((1 << NF_INET_FORWARD
) |
265 (1 << NF_INET_LOCAL_OUT
) |
266 (1 << NF_INET_POST_ROUTING
))) != 0) {
267 printk("xt_TCPMSS: path-MTU clamping only supported in "
268 "FORWARD, OUTPUT and POSTROUTING hooks\n");
271 if (IP6T_MATCH_ITERATE(e
, find_syn_match
))
273 printk("xt_TCPMSS: Only works on TCP SYN packets\n");
278 static struct xt_target tcpmss_tg_reg
[] __read_mostly
= {
280 .family
= NFPROTO_IPV4
,
282 .checkentry
= tcpmss_tg4_check
,
283 .target
= tcpmss_tg4
,
284 .targetsize
= sizeof(struct xt_tcpmss_info
),
285 .proto
= IPPROTO_TCP
,
288 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
290 .family
= NFPROTO_IPV6
,
292 .checkentry
= tcpmss_tg6_check
,
293 .target
= tcpmss_tg6
,
294 .targetsize
= sizeof(struct xt_tcpmss_info
),
295 .proto
= IPPROTO_TCP
,
301 static int __init
tcpmss_tg_init(void)
303 return xt_register_targets(tcpmss_tg_reg
, ARRAY_SIZE(tcpmss_tg_reg
));
306 static void __exit
tcpmss_tg_exit(void)
308 xt_unregister_targets(tcpmss_tg_reg
, ARRAY_SIZE(tcpmss_tg_reg
));
311 module_init(tcpmss_tg_init
);
312 module_exit(tcpmss_tg_exit
);