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