1/* iptables module for the IPv4 and TCP ECN bits, Version 1.2
2 *
3 * (C) 2002 by Harald Welte <laforge@gnumonks.org>
4 *
5 * This software is distributed under GNU GPL v2, 1991
6 *
7 * ipt_ECN.c,v 1.4 2002/08/05 19:36:51 laforge Exp
8*/
9
10#include <linux/module.h>
11#include <linux/skbuff.h>
12#include <linux/ip.h>
13#include <net/checksum.h>
14
15#include <linux/netfilter_ipv4/ip_tables.h>
16#include <linux/netfilter_ipv4/ipt_ECN.h>
17
18MODULE_LICENSE("GPL");
19
20/* set ECT codepoint from IP header.
21 * 	return 0 in case there was no ECT codepoint
22 * 	return 1 in case ECT codepoint has been overwritten
23 * 	return < 0 in case there was error */
24static int inline
25set_ect_ip(struct sk_buff **pskb, struct iphdr *iph,
26	   const struct ipt_ECN_info *einfo)
27{
28	if ((iph->tos & IPT_ECN_IP_MASK)
29	    != (einfo->ip_ect & IPT_ECN_IP_MASK)) {
30		u_int16_t diffs[2];
31
32		/* raw socket (tcpdump) may have clone of incoming
33		 * skb: don't disturb it --RR */
34		if (skb_cloned(*pskb) && !(*pskb)->sk) {
35			struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
36			if (!nskb)
37				return NF_DROP;
38			kfree_skb(*pskb);
39			*pskb = nskb;
40			iph = (*pskb)->nh.iph;
41		}
42
43		diffs[0] = htons(iph->tos) ^ 0xFFFF;
44		iph->tos = iph->tos & ~IPT_ECN_IP_MASK;
45		iph->tos = iph->tos | (einfo->ip_ect & IPT_ECN_IP_MASK);
46		diffs[1] = htons(iph->tos);
47		iph->check = csum_fold(csum_partial((char *)diffs,
48		                                    sizeof(diffs),
49		                                    iph->check^0xFFFF));
50		(*pskb)->nfcache |= NFC_ALTERED;
51
52		return 1;
53	}
54	return 0;
55}
56
57static int inline
58set_ect_tcp(struct sk_buff **pskb, struct iphdr *iph,
59	    const struct ipt_ECN_info *einfo)
60{
61
62	struct tcphdr *tcph = (void *) iph + iph->ihl * 4;
63	u_int16_t *tcpflags = (u_int16_t *)tcph + 6;
64	u_int16_t diffs[2];
65
66	/* raw socket (tcpdump) may have clone of incoming
67	 * skb: don't disturb it --RR */
68	if (skb_cloned(*pskb) && !(*pskb)->sk) {
69		struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
70		if (!nskb)
71			return NF_DROP;
72		kfree_skb(*pskb);
73		*pskb = nskb;
74		iph = (*pskb)->nh.iph;
75	}
76
77	diffs[0] = *tcpflags;
78
79	if (einfo->operation & IPT_ECN_OP_SET_ECE
80	    && tcph->ece != einfo->proto.tcp.ece) {
81		tcph->ece = einfo->proto.tcp.ece;
82	}
83
84	if (einfo->operation & IPT_ECN_OP_SET_CWR
85	    && tcph->cwr != einfo->proto.tcp.cwr) {
86		tcph->cwr = einfo->proto.tcp.cwr;
87	}
88
89	if (diffs[0] != *tcpflags) {
90		diffs[0] = diffs[0] ^ 0xFFFF;
91		diffs[1] = *tcpflags;
92		tcph->check = csum_fold(csum_partial((char *)diffs,
93		                                    sizeof(diffs),
94		                                    tcph->check^0xFFFF));
95		(*pskb)->nfcache |= NFC_ALTERED;
96
97		return 1;
98	}
99
100	return 0;
101}
102
103static unsigned int
104target(struct sk_buff **pskb,
105       unsigned int hooknum,
106       const struct net_device *in,
107       const struct net_device *out,
108       const void *targinfo,
109       void *userinfo)
110{
111	struct iphdr *iph = (*pskb)->nh.iph;
112	const struct ipt_ECN_info *einfo = targinfo;
113
114	if (einfo->operation & IPT_ECN_OP_SET_IP)
115		set_ect_ip(pskb, iph, einfo);
116
117	if (einfo->operation & (IPT_ECN_OP_SET_ECE | IPT_ECN_OP_SET_CWR)
118	    && iph->protocol == IPPROTO_TCP)
119		set_ect_tcp(pskb, iph, einfo);
120
121	return IPT_CONTINUE;
122}
123
124static int
125checkentry(const char *tablename,
126	   const struct ipt_entry *e,
127           void *targinfo,
128           unsigned int targinfosize,
129           unsigned int hook_mask)
130{
131	const struct ipt_ECN_info *einfo = (struct ipt_ECN_info *)targinfo;
132
133	if (targinfosize != IPT_ALIGN(sizeof(struct ipt_ECN_info))) {
134		printk(KERN_WARNING "ECN: targinfosize %u != %Zu\n",
135		       targinfosize,
136		       IPT_ALIGN(sizeof(struct ipt_ECN_info)));
137		return 0;
138	}
139
140	if (strcmp(tablename, "mangle") != 0) {
141		printk(KERN_WARNING "ECN: can only be called from \"mangle\" table, not \"%s\"\n", tablename);
142		return 0;
143	}
144
145	if (einfo->operation & IPT_ECN_OP_MASK) {
146		printk(KERN_WARNING "ECN: unsupported ECN operation %x\n",
147			einfo->operation);
148		return 0;
149	}
150	if (einfo->ip_ect & ~IPT_ECN_IP_MASK) {
151		printk(KERN_WARNING "ECN: new ECT codepoint %x out of mask\n",
152			einfo->ip_ect);
153		return 0;
154	}
155
156	if ((einfo->operation & (IPT_ECN_OP_SET_ECE|IPT_ECN_OP_SET_CWR))
157	    && e->ip.proto != IPPROTO_TCP) {
158		printk(KERN_WARNING "ECN: cannot use TCP operations on a "
159		       "non-tcp rule\n");
160		return 0;
161	}
162
163	return 1;
164}
165
166static struct ipt_target ipt_ecn_reg
167= { { NULL, NULL }, "ECN", target, checkentry, NULL, THIS_MODULE };
168
169static int __init init(void)
170{
171	if (ipt_register_target(&ipt_ecn_reg))
172		return -EINVAL;
173
174	return 0;
175}
176
177static void __exit fini(void)
178{
179	ipt_unregister_target(&ipt_ecn_reg);
180}
181
182module_init(init);
183module_exit(fini);
184