]>
Commit | Line | Data |
---|---|---|
e281b198 JE |
1 | /* |
2 | * "TEE" target extension for Xtables | |
3 | * Copyright © Sebastian Claßen, 2007 | |
4 | * Jan Engelhardt, 2007-2010 | |
5 | * | |
6 | * based on ipt_ROUTE.c from Cédric de Launois | |
7 | * <delaunois@info.ucl.be> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU General Public License | |
11 | * version 2 or later, as published by the Free Software Foundation. | |
12 | */ | |
13 | #include <linux/ip.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/route.h> | |
16 | #include <linux/skbuff.h> | |
17 | #include <net/checksum.h> | |
18 | #include <net/icmp.h> | |
19 | #include <net/ip.h> | |
20 | #include <net/ipv6.h> | |
21 | #include <net/ip6_route.h> | |
22 | #include <net/route.h> | |
23 | #include <linux/netfilter/x_tables.h> | |
24 | #include <linux/netfilter/xt_TEE.h> | |
25 | ||
26 | #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) | |
27 | # define WITH_CONNTRACK 1 | |
28 | # include <net/netfilter/nf_conntrack.h> | |
29 | #endif | |
30 | #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) | |
31 | # define WITH_IPV6 1 | |
32 | #endif | |
33 | ||
34 | static const union nf_inet_addr tee_zero_address; | |
35 | ||
36 | static struct net *pick_net(struct sk_buff *skb) | |
37 | { | |
38 | #ifdef CONFIG_NET_NS | |
39 | const struct dst_entry *dst; | |
40 | ||
41 | if (skb->dev != NULL) | |
42 | return dev_net(skb->dev); | |
43 | dst = skb_dst(skb); | |
44 | if (dst != NULL && dst->dev != NULL) | |
45 | return dev_net(dst->dev); | |
46 | #endif | |
47 | return &init_net; | |
48 | } | |
49 | ||
50 | static bool tee_tg_route_oif(struct flowi *f, struct net *net, | |
51 | const struct xt_tee_tginfo *info) | |
52 | { | |
53 | const struct net_device *dev; | |
54 | ||
55 | if (*info->oif != '\0') | |
56 | return true; | |
57 | dev = dev_get_by_name(net, info->oif); | |
58 | if (dev == NULL) | |
59 | return false; | |
60 | f->oif = dev->ifindex; | |
61 | return true; | |
62 | } | |
63 | ||
64 | static bool | |
65 | tee_tg_route4(struct sk_buff *skb, const struct xt_tee_tginfo *info) | |
66 | { | |
67 | const struct iphdr *iph = ip_hdr(skb); | |
68 | struct net *net = pick_net(skb); | |
69 | struct rtable *rt; | |
70 | struct flowi fl; | |
71 | ||
72 | memset(&fl, 0, sizeof(fl)); | |
73 | if (!tee_tg_route_oif(&fl, net, info)) | |
74 | return false; | |
75 | fl.nl_u.ip4_u.daddr = info->gw.ip; | |
76 | fl.nl_u.ip4_u.tos = RT_TOS(iph->tos); | |
77 | fl.nl_u.ip4_u.scope = RT_SCOPE_UNIVERSE; | |
78 | if (ip_route_output_key(net, &rt, &fl) != 0) | |
79 | return false; | |
80 | ||
81 | dst_release(skb_dst(skb)); | |
82 | skb_dst_set(skb, &rt->u.dst); | |
83 | skb->dev = rt->u.dst.dev; | |
84 | skb->protocol = htons(ETH_P_IP); | |
85 | return true; | |
86 | } | |
87 | ||
88 | static unsigned int | |
89 | tee_tg4(struct sk_buff *skb, const struct xt_target_param *par) | |
90 | { | |
91 | const struct xt_tee_tginfo *info = par->targinfo; | |
92 | struct iphdr *iph; | |
93 | ||
94 | /* | |
95 | * Copy the skb, and route the copy. Will later return %XT_CONTINUE for | |
96 | * the original skb, which should continue on its way as if nothing has | |
97 | * happened. The copy should be independently delivered to the TEE | |
98 | * --gateway. | |
99 | */ | |
100 | skb = pskb_copy(skb, GFP_ATOMIC); | |
101 | if (skb == NULL) | |
102 | return XT_CONTINUE; | |
103 | ||
104 | #ifdef WITH_CONNTRACK | |
105 | /* Avoid counting cloned packets towards the original connection. */ | |
106 | nf_conntrack_put(skb->nfct); | |
107 | skb->nfct = &nf_conntrack_untracked.ct_general; | |
108 | skb->nfctinfo = IP_CT_NEW; | |
109 | nf_conntrack_get(skb->nfct); | |
110 | #endif | |
111 | /* | |
112 | * If we are in PREROUTING/INPUT, the checksum must be recalculated | |
113 | * since the length could have changed as a result of defragmentation. | |
114 | * | |
115 | * We also decrease the TTL to mitigate potential TEE loops | |
116 | * between two hosts. | |
117 | * | |
118 | * Set %IP_DF so that the original source is notified of a potentially | |
119 | * decreased MTU on the clone route. IPv6 does this too. | |
120 | */ | |
121 | iph = ip_hdr(skb); | |
122 | iph->frag_off |= htons(IP_DF); | |
123 | if (par->hooknum == NF_INET_PRE_ROUTING || | |
124 | par->hooknum == NF_INET_LOCAL_IN) | |
125 | --iph->ttl; | |
126 | ip_send_check(iph); | |
127 | ||
128 | /* | |
129 | * Xtables is not reentrant currently, so a choice has to be made: | |
130 | * 1. return absolute verdict for the original and let the cloned | |
131 | * packet travel through the chains | |
132 | * 2. let the original continue travelling and not pass the clone | |
133 | * to Xtables. | |
134 | * #2 is chosen. Normally, we would use ip_local_out for the clone. | |
135 | * Because iph->check is already correct and we don't pass it to | |
136 | * Xtables anyway, a shortcut to dst_output [forwards to ip_output] can | |
137 | * be taken. %IPSKB_REROUTED needs to be set so that ip_output does not | |
138 | * invoke POSTROUTING on the cloned packet. | |
139 | */ | |
140 | IPCB(skb)->flags |= IPSKB_REROUTED; | |
141 | if (tee_tg_route4(skb, info)) | |
142 | ip_output(skb); | |
143 | else | |
144 | kfree_skb(skb); | |
145 | ||
146 | return XT_CONTINUE; | |
147 | } | |
148 | ||
149 | #ifdef WITH_IPV6 | |
150 | static bool | |
151 | tee_tg_route6(struct sk_buff *skb, const struct xt_tee_tginfo *info) | |
152 | { | |
153 | const struct ipv6hdr *iph = ipv6_hdr(skb); | |
154 | struct net *net = pick_net(skb); | |
155 | struct dst_entry *dst; | |
156 | struct flowi fl; | |
157 | ||
158 | memset(&fl, 0, sizeof(fl)); | |
159 | if (!tee_tg_route_oif(&fl, net, info)) | |
160 | return false; | |
161 | fl.nl_u.ip6_u.daddr = info->gw.in6; | |
162 | fl.nl_u.ip6_u.flowlabel = ((iph->flow_lbl[0] & 0xF) << 16) | | |
163 | (iph->flow_lbl[1] << 8) | iph->flow_lbl[2]; | |
164 | dst = ip6_route_output(net, NULL, &fl); | |
165 | if (dst == NULL) | |
166 | return false; | |
167 | ||
168 | dst_release(skb_dst(skb)); | |
169 | skb_dst_set(skb, dst); | |
170 | skb->dev = dst->dev; | |
171 | skb->protocol = htons(ETH_P_IPV6); | |
172 | return true; | |
173 | } | |
174 | ||
175 | static unsigned int | |
176 | tee_tg6(struct sk_buff *skb, const struct xt_target_param *par) | |
177 | { | |
178 | const struct xt_tee_tginfo *info = par->targinfo; | |
179 | ||
180 | skb = pskb_copy(skb, GFP_ATOMIC); | |
181 | if (skb == NULL) | |
182 | return XT_CONTINUE; | |
183 | ||
184 | #ifdef WITH_CONNTRACK | |
185 | nf_conntrack_put(skb->nfct); | |
186 | skb->nfct = &nf_conntrack_untracked.ct_general; | |
187 | skb->nfctinfo = IP_CT_NEW; | |
188 | nf_conntrack_get(skb->nfct); | |
189 | #endif | |
190 | if (par->hooknum == NF_INET_PRE_ROUTING || | |
191 | par->hooknum == NF_INET_LOCAL_IN) { | |
192 | struct ipv6hdr *iph = ipv6_hdr(skb); | |
193 | --iph->hop_limit; | |
194 | } | |
195 | IP6CB(skb)->flags |= IP6SKB_REROUTED; | |
196 | if (tee_tg_route6(skb, info)) | |
197 | ip6_output(skb); | |
198 | else | |
199 | kfree_skb(skb); | |
200 | ||
201 | return XT_CONTINUE; | |
202 | } | |
203 | #endif /* WITH_IPV6 */ | |
204 | ||
205 | static int tee_tg_check(const struct xt_tgchk_param *par) | |
206 | { | |
207 | const struct xt_tee_tginfo *info = par->targinfo; | |
208 | ||
209 | if (info->oif[sizeof(info->oif)-1] != '\0') | |
210 | return -EINVAL; | |
211 | /* 0.0.0.0 and :: not allowed */ | |
212 | return (memcmp(&info->gw, &tee_zero_address, | |
213 | sizeof(tee_zero_address)) == 0) ? -EINVAL : 0; | |
214 | } | |
215 | ||
216 | static struct xt_target tee_tg_reg[] __read_mostly = { | |
217 | { | |
218 | .name = "TEE", | |
219 | .revision = 1, | |
220 | .family = NFPROTO_IPV4, | |
221 | .target = tee_tg4, | |
222 | .targetsize = sizeof(struct xt_tee_tginfo), | |
223 | .checkentry = tee_tg_check, | |
224 | .me = THIS_MODULE, | |
225 | }, | |
226 | #ifdef WITH_IPV6 | |
227 | { | |
228 | .name = "TEE", | |
229 | .revision = 1, | |
230 | .family = NFPROTO_IPV6, | |
231 | .target = tee_tg6, | |
232 | .targetsize = sizeof(struct xt_tee_tginfo), | |
233 | .checkentry = tee_tg_check, | |
234 | .me = THIS_MODULE, | |
235 | }, | |
236 | #endif | |
237 | }; | |
238 | ||
239 | static int __init tee_tg_init(void) | |
240 | { | |
241 | return xt_register_targets(tee_tg_reg, ARRAY_SIZE(tee_tg_reg)); | |
242 | } | |
243 | ||
244 | static void __exit tee_tg_exit(void) | |
245 | { | |
246 | xt_unregister_targets(tee_tg_reg, ARRAY_SIZE(tee_tg_reg)); | |
247 | } | |
248 | ||
249 | module_init(tee_tg_init); | |
250 | module_exit(tee_tg_exit); | |
251 | MODULE_AUTHOR("Sebastian Claßen <sebastian.classen@freenet.ag>"); | |
252 | MODULE_AUTHOR("Jan Engelhardt <jengelh@medozas.de>"); | |
253 | MODULE_DESCRIPTION("Xtables: Reroute packet copy"); | |
254 | MODULE_LICENSE("GPL"); | |
255 | MODULE_ALIAS("ipt_TEE"); | |
256 | MODULE_ALIAS("ip6t_TEE"); |