1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Transparent proxy support for Linux/iptables |
4 | * |
5 | * Copyright (C) 2007-2008 BalaBit IT Ltd. |
6 | * Author: Krisztian Kovacs |
7 | */ |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | #include <linux/module.h> |
10 | #include <linux/skbuff.h> |
11 | #include <linux/netfilter/x_tables.h> |
12 | #include <linux/netfilter_ipv4/ip_tables.h> |
13 | #include <net/tcp.h> |
14 | #include <net/udp.h> |
15 | #include <net/icmp.h> |
16 | #include <net/sock.h> |
17 | #include <net/inet_sock.h> |
18 | #include <net/netfilter/ipv4/nf_defrag_ipv4.h> |
19 | |
20 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
21 | #include <linux/netfilter_ipv6/ip6_tables.h> |
22 | #include <net/inet6_hashtables.h> |
23 | #include <net/netfilter/ipv6/nf_defrag_ipv6.h> |
24 | #endif |
25 | |
26 | #include <net/netfilter/nf_socket.h> |
27 | #include <linux/netfilter/xt_socket.h> |
28 | |
29 | /* "socket" match based redirection (no specific rule) |
30 | * =================================================== |
31 | * |
32 | * There are connections with dynamic endpoints (e.g. FTP data |
33 | * connection) that the user is unable to add explicit rules |
34 | * for. These are taken care of by a generic "socket" rule. It is |
35 | * assumed that the proxy application is trusted to open such |
36 | * connections without explicit iptables rule (except of course the |
37 | * generic 'socket' rule). In this case the following sockets are |
38 | * matched in preference order: |
39 | * |
40 | * - match: if there's a fully established connection matching the |
41 | * _packet_ tuple |
42 | * |
43 | * - match: if there's a non-zero bound listener (possibly with a |
44 | * non-local address) We don't accept zero-bound listeners, since |
45 | * then local services could intercept traffic going through the |
46 | * box. |
47 | */ |
48 | static bool |
49 | socket_match(const struct sk_buff *skb, struct xt_action_param *par, |
50 | const struct xt_socket_mtinfo1 *info) |
51 | { |
52 | struct sk_buff *pskb = (struct sk_buff *)skb; |
53 | struct sock *sk = skb->sk; |
54 | |
55 | if (sk && !net_eq(net1: xt_net(par), net2: sock_net(sk))) |
56 | sk = NULL; |
57 | |
58 | if (!sk) |
59 | sk = nf_sk_lookup_slow_v4(net: xt_net(par), skb, indev: xt_in(par)); |
60 | |
61 | if (sk) { |
62 | bool wildcard; |
63 | bool transparent = true; |
64 | |
65 | /* Ignore sockets listening on INADDR_ANY, |
66 | * unless XT_SOCKET_NOWILDCARD is set |
67 | */ |
68 | wildcard = (!(info->flags & XT_SOCKET_NOWILDCARD) && |
69 | sk_fullsock(sk) && |
70 | inet_sk(sk)->inet_rcv_saddr == 0); |
71 | |
72 | /* Ignore non-transparent sockets, |
73 | * if XT_SOCKET_TRANSPARENT is used |
74 | */ |
75 | if (info->flags & XT_SOCKET_TRANSPARENT) |
76 | transparent = inet_sk_transparent(sk); |
77 | |
78 | if (info->flags & XT_SOCKET_RESTORESKMARK && !wildcard && |
79 | transparent && sk_fullsock(sk)) |
80 | pskb->mark = READ_ONCE(sk->sk_mark); |
81 | |
82 | if (sk != skb->sk) |
83 | sock_gen_put(sk); |
84 | |
85 | if (wildcard || !transparent) |
86 | sk = NULL; |
87 | } |
88 | |
89 | return sk != NULL; |
90 | } |
91 | |
92 | static bool |
93 | socket_mt4_v0(const struct sk_buff *skb, struct xt_action_param *par) |
94 | { |
95 | static struct xt_socket_mtinfo1 xt_info_v0 = { |
96 | .flags = 0, |
97 | }; |
98 | |
99 | return socket_match(skb, par, info: &xt_info_v0); |
100 | } |
101 | |
102 | static bool |
103 | socket_mt4_v1_v2_v3(const struct sk_buff *skb, struct xt_action_param *par) |
104 | { |
105 | return socket_match(skb, par, info: par->matchinfo); |
106 | } |
107 | |
108 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
109 | static bool |
110 | socket_mt6_v1_v2_v3(const struct sk_buff *skb, struct xt_action_param *par) |
111 | { |
112 | const struct xt_socket_mtinfo1 *info = (struct xt_socket_mtinfo1 *) par->matchinfo; |
113 | struct sk_buff *pskb = (struct sk_buff *)skb; |
114 | struct sock *sk = skb->sk; |
115 | |
116 | if (sk && !net_eq(net1: xt_net(par), net2: sock_net(sk))) |
117 | sk = NULL; |
118 | |
119 | if (!sk) |
120 | sk = nf_sk_lookup_slow_v6(net: xt_net(par), skb, indev: xt_in(par)); |
121 | |
122 | if (sk) { |
123 | bool wildcard; |
124 | bool transparent = true; |
125 | |
126 | /* Ignore sockets listening on INADDR_ANY |
127 | * unless XT_SOCKET_NOWILDCARD is set |
128 | */ |
129 | wildcard = (!(info->flags & XT_SOCKET_NOWILDCARD) && |
130 | sk_fullsock(sk) && |
131 | ipv6_addr_any(a: &sk->sk_v6_rcv_saddr)); |
132 | |
133 | /* Ignore non-transparent sockets, |
134 | * if XT_SOCKET_TRANSPARENT is used |
135 | */ |
136 | if (info->flags & XT_SOCKET_TRANSPARENT) |
137 | transparent = inet_sk_transparent(sk); |
138 | |
139 | if (info->flags & XT_SOCKET_RESTORESKMARK && !wildcard && |
140 | transparent && sk_fullsock(sk)) |
141 | pskb->mark = READ_ONCE(sk->sk_mark); |
142 | |
143 | if (sk != skb->sk) |
144 | sock_gen_put(sk); |
145 | |
146 | if (wildcard || !transparent) |
147 | sk = NULL; |
148 | } |
149 | |
150 | return sk != NULL; |
151 | } |
152 | #endif |
153 | |
154 | static int socket_mt_enable_defrag(struct net *net, int family) |
155 | { |
156 | switch (family) { |
157 | case NFPROTO_IPV4: |
158 | return nf_defrag_ipv4_enable(net); |
159 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
160 | case NFPROTO_IPV6: |
161 | return nf_defrag_ipv6_enable(net); |
162 | #endif |
163 | } |
164 | WARN_ONCE(1, "Unknown family %d\n" , family); |
165 | return 0; |
166 | } |
167 | |
168 | static int socket_mt_v1_check(const struct xt_mtchk_param *par) |
169 | { |
170 | const struct xt_socket_mtinfo1 *info = (struct xt_socket_mtinfo1 *) par->matchinfo; |
171 | int err; |
172 | |
173 | err = socket_mt_enable_defrag(net: par->net, family: par->family); |
174 | if (err) |
175 | return err; |
176 | |
177 | if (info->flags & ~XT_SOCKET_FLAGS_V1) { |
178 | pr_info_ratelimited("unknown flags 0x%x\n" , |
179 | info->flags & ~XT_SOCKET_FLAGS_V1); |
180 | return -EINVAL; |
181 | } |
182 | return 0; |
183 | } |
184 | |
185 | static int socket_mt_v2_check(const struct xt_mtchk_param *par) |
186 | { |
187 | const struct xt_socket_mtinfo2 *info = (struct xt_socket_mtinfo2 *) par->matchinfo; |
188 | int err; |
189 | |
190 | err = socket_mt_enable_defrag(net: par->net, family: par->family); |
191 | if (err) |
192 | return err; |
193 | |
194 | if (info->flags & ~XT_SOCKET_FLAGS_V2) { |
195 | pr_info_ratelimited("unknown flags 0x%x\n" , |
196 | info->flags & ~XT_SOCKET_FLAGS_V2); |
197 | return -EINVAL; |
198 | } |
199 | return 0; |
200 | } |
201 | |
202 | static int socket_mt_v3_check(const struct xt_mtchk_param *par) |
203 | { |
204 | const struct xt_socket_mtinfo3 *info = |
205 | (struct xt_socket_mtinfo3 *)par->matchinfo; |
206 | int err; |
207 | |
208 | err = socket_mt_enable_defrag(net: par->net, family: par->family); |
209 | if (err) |
210 | return err; |
211 | if (info->flags & ~XT_SOCKET_FLAGS_V3) { |
212 | pr_info_ratelimited("unknown flags 0x%x\n" , |
213 | info->flags & ~XT_SOCKET_FLAGS_V3); |
214 | return -EINVAL; |
215 | } |
216 | return 0; |
217 | } |
218 | |
219 | static void socket_mt_destroy(const struct xt_mtdtor_param *par) |
220 | { |
221 | if (par->family == NFPROTO_IPV4) |
222 | nf_defrag_ipv4_disable(net: par->net); |
223 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
224 | else if (par->family == NFPROTO_IPV6) |
225 | nf_defrag_ipv6_disable(net: par->net); |
226 | #endif |
227 | } |
228 | |
229 | static struct xt_match socket_mt_reg[] __read_mostly = { |
230 | { |
231 | .name = "socket" , |
232 | .revision = 0, |
233 | .family = NFPROTO_IPV4, |
234 | .match = socket_mt4_v0, |
235 | .hooks = (1 << NF_INET_PRE_ROUTING) | |
236 | (1 << NF_INET_LOCAL_IN), |
237 | .me = THIS_MODULE, |
238 | }, |
239 | { |
240 | .name = "socket" , |
241 | .revision = 1, |
242 | .family = NFPROTO_IPV4, |
243 | .match = socket_mt4_v1_v2_v3, |
244 | .destroy = socket_mt_destroy, |
245 | .checkentry = socket_mt_v1_check, |
246 | .matchsize = sizeof(struct xt_socket_mtinfo1), |
247 | .hooks = (1 << NF_INET_PRE_ROUTING) | |
248 | (1 << NF_INET_LOCAL_IN), |
249 | .me = THIS_MODULE, |
250 | }, |
251 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
252 | { |
253 | .name = "socket" , |
254 | .revision = 1, |
255 | .family = NFPROTO_IPV6, |
256 | .match = socket_mt6_v1_v2_v3, |
257 | .checkentry = socket_mt_v1_check, |
258 | .matchsize = sizeof(struct xt_socket_mtinfo1), |
259 | .destroy = socket_mt_destroy, |
260 | .hooks = (1 << NF_INET_PRE_ROUTING) | |
261 | (1 << NF_INET_LOCAL_IN), |
262 | .me = THIS_MODULE, |
263 | }, |
264 | #endif |
265 | { |
266 | .name = "socket" , |
267 | .revision = 2, |
268 | .family = NFPROTO_IPV4, |
269 | .match = socket_mt4_v1_v2_v3, |
270 | .checkentry = socket_mt_v2_check, |
271 | .destroy = socket_mt_destroy, |
272 | .matchsize = sizeof(struct xt_socket_mtinfo1), |
273 | .hooks = (1 << NF_INET_PRE_ROUTING) | |
274 | (1 << NF_INET_LOCAL_IN), |
275 | .me = THIS_MODULE, |
276 | }, |
277 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
278 | { |
279 | .name = "socket" , |
280 | .revision = 2, |
281 | .family = NFPROTO_IPV6, |
282 | .match = socket_mt6_v1_v2_v3, |
283 | .checkentry = socket_mt_v2_check, |
284 | .destroy = socket_mt_destroy, |
285 | .matchsize = sizeof(struct xt_socket_mtinfo1), |
286 | .hooks = (1 << NF_INET_PRE_ROUTING) | |
287 | (1 << NF_INET_LOCAL_IN), |
288 | .me = THIS_MODULE, |
289 | }, |
290 | #endif |
291 | { |
292 | .name = "socket" , |
293 | .revision = 3, |
294 | .family = NFPROTO_IPV4, |
295 | .match = socket_mt4_v1_v2_v3, |
296 | .checkentry = socket_mt_v3_check, |
297 | .destroy = socket_mt_destroy, |
298 | .matchsize = sizeof(struct xt_socket_mtinfo1), |
299 | .hooks = (1 << NF_INET_PRE_ROUTING) | |
300 | (1 << NF_INET_LOCAL_IN), |
301 | .me = THIS_MODULE, |
302 | }, |
303 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
304 | { |
305 | .name = "socket" , |
306 | .revision = 3, |
307 | .family = NFPROTO_IPV6, |
308 | .match = socket_mt6_v1_v2_v3, |
309 | .checkentry = socket_mt_v3_check, |
310 | .destroy = socket_mt_destroy, |
311 | .matchsize = sizeof(struct xt_socket_mtinfo1), |
312 | .hooks = (1 << NF_INET_PRE_ROUTING) | |
313 | (1 << NF_INET_LOCAL_IN), |
314 | .me = THIS_MODULE, |
315 | }, |
316 | #endif |
317 | }; |
318 | |
319 | static int __init socket_mt_init(void) |
320 | { |
321 | return xt_register_matches(match: socket_mt_reg, ARRAY_SIZE(socket_mt_reg)); |
322 | } |
323 | |
324 | static void __exit socket_mt_exit(void) |
325 | { |
326 | xt_unregister_matches(match: socket_mt_reg, ARRAY_SIZE(socket_mt_reg)); |
327 | } |
328 | |
329 | module_init(socket_mt_init); |
330 | module_exit(socket_mt_exit); |
331 | |
332 | MODULE_LICENSE("GPL" ); |
333 | MODULE_AUTHOR("Krisztian Kovacs, Balazs Scheidler" ); |
334 | MODULE_DESCRIPTION("x_tables socket match module" ); |
335 | MODULE_ALIAS("ipt_socket" ); |
336 | MODULE_ALIAS("ip6t_socket" ); |
337 | |