File: /usr/src/linux/net/ipv4/netfilter/ipt_TCPMSS.c
1 /*
2 * This is a module which is used for setting the MSS option in TCP packets.
3 *
4 * Copyright (c) 2000 Marc Boucher
5 */
6 #include <linux/module.h>
7 #include <linux/skbuff.h>
8
9 #include <linux/ip.h>
10 #include <net/tcp.h>
11
12 #include <linux/netfilter_ipv4/ip_tables.h>
13 #include <linux/netfilter_ipv4/ipt_TCPMSS.h>
14
15 #if 0
16 #define DEBUGP printk
17 #else
18 #define DEBUGP(format, args...)
19 #endif
20
21 static u_int16_t
22 cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
23 {
24 u_int32_t diffs[] = { oldvalinv, newval };
25 return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
26 oldcheck^0xFFFF));
27 }
28
29 static inline unsigned int
30 optlen(const u_int8_t *opt, unsigned int offset)
31 {
32 /* Beware zero-length options: make finite progress */
33 if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0) return 1;
34 else return opt[offset+1];
35 }
36
37 static unsigned int
38 ipt_tcpmss_target(struct sk_buff **pskb,
39 unsigned int hooknum,
40 const struct net_device *in,
41 const struct net_device *out,
42 const void *targinfo,
43 void *userinfo)
44 {
45 const struct ipt_tcpmss_info *tcpmssinfo = targinfo;
46 struct tcphdr *tcph;
47 struct iphdr *iph = (*pskb)->nh.iph;
48 u_int16_t tcplen, newtotlen, oldval, newmss;
49 unsigned int i;
50 u_int8_t *opt;
51
52 tcplen = (*pskb)->len - iph->ihl*4;
53
54 tcph = (void *)iph + iph->ihl*4;
55
56 /* Since it passed flags test in tcp match, we know it is is
57 not a fragment, and has data >= tcp header length. SYN
58 packets should not contain data: if they did, then we risk
59 running over MTU, sending Frag Needed and breaking things
60 badly. --RR */
61 if (tcplen != tcph->doff*4) {
62 if (net_ratelimit())
63 printk(KERN_ERR
64 "ipt_tcpmss_target: bad length (%d bytes)\n",
65 (*pskb)->len);
66 return NF_DROP;
67 }
68
69 if(tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) {
70 if(!(*pskb)->dst) {
71 if (net_ratelimit())
72 printk(KERN_ERR
73 "ipt_tcpmss_target: no dst?! can't determine path-MTU\n");
74 return NF_DROP; /* or IPT_CONTINUE ?? */
75 }
76
77 if((*pskb)->dst->pmtu <= (sizeof(struct iphdr) + sizeof(struct tcphdr))) {
78 if (net_ratelimit())
79 printk(KERN_ERR
80 "ipt_tcpmss_target: unknown or invalid path-MTU (%d)\n", (*pskb)->dst->pmtu);
81 return NF_DROP; /* or IPT_CONTINUE ?? */
82 }
83
84 newmss = (*pskb)->dst->pmtu - sizeof(struct iphdr) - sizeof(struct tcphdr);
85 } else
86 newmss = tcpmssinfo->mss;
87
88 opt = (u_int8_t *)tcph;
89 for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)){
90 if ((opt[i] == TCPOPT_MSS) &&
91 ((tcph->doff*4 - i) >= TCPOLEN_MSS) &&
92 (opt[i+1] == TCPOLEN_MSS)) {
93 u_int16_t oldmss;
94
95 oldmss = (opt[i+2] << 8) | opt[i+3];
96
97 if((tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) &&
98 (oldmss <= newmss))
99 return IPT_CONTINUE;
100
101 opt[i+2] = (newmss & 0xff00) >> 8;
102 opt[i+3] = (newmss & 0x00ff);
103
104 tcph->check = cheat_check(htons(oldmss)^0xFFFF,
105 htons(newmss),
106 tcph->check);
107
108 DEBUGP(KERN_INFO "ipt_tcpmss_target: %u.%u.%u.%u:%hu"
109 "->%u.%u.%u.%u:%hu changed TCP MSS option"
110 " (from %u to %u)\n",
111 NIPQUAD((*pskb)->nh.iph->saddr),
112 ntohs(tcph->source),
113 NIPQUAD((*pskb)->nh.iph->daddr),
114 ntohs(tcph->dest),
115 oldmss, newmss);
116 goto retmodified;
117 }
118 }
119
120 /*
121 * MSS Option not found ?! add it..
122 */
123 if (skb_tailroom((*pskb)) < TCPOLEN_MSS) {
124 struct sk_buff *newskb;
125
126 newskb = skb_copy_expand(*pskb, skb_headroom(*pskb),
127 TCPOLEN_MSS, GFP_ATOMIC);
128 if (!newskb) {
129 if (net_ratelimit())
130 printk(KERN_ERR "ipt_tcpmss_target:"
131 " unable to allocate larger skb\n");
132 return NF_DROP;
133 }
134
135 kfree_skb(*pskb);
136 *pskb = newskb;
137 iph = (*pskb)->nh.iph;
138 tcph = (void *)iph + iph->ihl*4;
139 }
140
141 skb_put((*pskb), TCPOLEN_MSS);
142
143 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
144 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
145
146 tcph->check = cheat_check(htons(tcplen) ^ 0xFFFF,
147 htons(tcplen + TCPOLEN_MSS), tcph->check);
148 tcplen += TCPOLEN_MSS;
149
150 opt[0] = TCPOPT_MSS;
151 opt[1] = TCPOLEN_MSS;
152 opt[2] = (newmss & 0xff00) >> 8;
153 opt[3] = (newmss & 0x00ff);
154
155 tcph->check = cheat_check(~0, *((u_int32_t *)opt), tcph->check);
156
157 oldval = ((u_int16_t *)tcph)[6];
158 tcph->doff += TCPOLEN_MSS/4;
159 tcph->check = cheat_check(oldval ^ 0xFFFF,
160 ((u_int16_t *)tcph)[6], tcph->check);
161
162 newtotlen = htons(ntohs(iph->tot_len) + TCPOLEN_MSS);
163 iph->check = cheat_check(iph->tot_len ^ 0xFFFF,
164 newtotlen, iph->check);
165 iph->tot_len = newtotlen;
166
167 DEBUGP(KERN_INFO "ipt_tcpmss_target: %u.%u.%u.%u:%hu"
168 "->%u.%u.%u.%u:%hu added TCP MSS option (%u)\n",
169 NIPQUAD((*pskb)->nh.iph->saddr),
170 ntohs(tcph->source),
171 NIPQUAD((*pskb)->nh.iph->daddr),
172 ntohs(tcph->dest),
173 newmss);
174
175 retmodified:
176 /* If we had a hardware checksum before, it's now invalid */
177 (*pskb)->ip_summed = CHECKSUM_NONE;
178 (*pskb)->nfcache |= NFC_UNKNOWN | NFC_ALTERED;
179 return IPT_CONTINUE;
180 }
181
182 #define TH_SYN 0x02
183
184 static inline int find_syn_match(const struct ipt_entry_match *m)
185 {
186 const struct ipt_tcp *tcpinfo = (const struct ipt_tcp *)m->data;
187
188 if (strcmp(m->u.kernel.match->name, "tcp") == 0
189 && (tcpinfo->flg_cmp & TH_SYN)
190 && !(tcpinfo->invflags & IPT_TCP_INV_FLAGS))
191 return 1;
192
193 return 0;
194 }
195
196 /* Must specify -p tcp --syn/--tcp-flags SYN */
197 static int
198 ipt_tcpmss_checkentry(const char *tablename,
199 const struct ipt_entry *e,
200 void *targinfo,
201 unsigned int targinfosize,
202 unsigned int hook_mask)
203 {
204 const struct ipt_tcpmss_info *tcpmssinfo = targinfo;
205
206 if (targinfosize != IPT_ALIGN(sizeof(struct ipt_tcpmss_info))) {
207 DEBUGP("ipt_tcpmss_checkentry: targinfosize %u != %u\n",
208 targinfosize, IPT_ALIGN(sizeof(struct ipt_tcpmss_info)));
209 return 0;
210 }
211
212
213 if((tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) &&
214 ((hook_mask & ~((1 << NF_IP_FORWARD)
215 | (1 << NF_IP_LOCAL_OUT)
216 | (1 << NF_IP_POST_ROUTING))) != 0)) {
217 printk("TCPMSS: path-MTU clamping only supported in FORWARD, OUTPUT and POSTROUTING hooks\n");
218 return 0;
219 }
220
221 if (e->ip.proto == IPPROTO_TCP
222 && !(e->ip.invflags & IPT_INV_PROTO)
223 && IPT_MATCH_ITERATE(e, find_syn_match))
224 return 1;
225
226 printk("TCPMSS: Only works on TCP SYN packets\n");
227 return 0;
228 }
229
230 static struct ipt_target ipt_tcpmss_reg
231 = { { NULL, NULL }, "TCPMSS",
232 ipt_tcpmss_target, ipt_tcpmss_checkentry, NULL, THIS_MODULE };
233
234 static int __init init(void)
235 {
236 return ipt_register_target(&ipt_tcpmss_reg);
237 }
238
239 static void __exit fini(void)
240 {
241 ipt_unregister_target(&ipt_tcpmss_reg);
242 }
243
244 module_init(init);
245 module_exit(fini);
246