xref: /openbmc/linux/net/netfilter/xt_TPROXY.c (revision 613dbd95)
1e8439270SKOVACS Krisztian /*
2e8439270SKOVACS Krisztian  * Transparent proxy support for Linux/iptables
3e8439270SKOVACS Krisztian  *
46ad78893SBalazs Scheidler  * Copyright (c) 2006-2010 BalaBit IT Ltd.
5e8439270SKOVACS Krisztian  * Author: Balazs Scheidler, Krisztian Kovacs
6e8439270SKOVACS Krisztian  *
7e8439270SKOVACS Krisztian  * This program is free software; you can redistribute it and/or modify
8e8439270SKOVACS Krisztian  * it under the terms of the GNU General Public License version 2 as
9e8439270SKOVACS Krisztian  * published by the Free Software Foundation.
10e8439270SKOVACS Krisztian  *
11e8439270SKOVACS Krisztian  */
12ff67e4e4SJan Engelhardt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13e8439270SKOVACS Krisztian #include <linux/module.h>
14e8439270SKOVACS Krisztian #include <linux/skbuff.h>
15e8439270SKOVACS Krisztian #include <linux/ip.h>
16e8439270SKOVACS Krisztian #include <net/checksum.h>
17e8439270SKOVACS Krisztian #include <net/udp.h>
1893742cf8SFlorian Westphal #include <net/tcp.h>
19e8439270SKOVACS Krisztian #include <net/inet_sock.h>
2093742cf8SFlorian Westphal #include <net/inet_hashtables.h>
21cc6eb433SBalazs Scheidler #include <linux/inetdevice.h>
22e8439270SKOVACS Krisztian #include <linux/netfilter/x_tables.h>
23e8439270SKOVACS Krisztian #include <linux/netfilter_ipv4/ip_tables.h>
24e8439270SKOVACS Krisztian 
25e8439270SKOVACS Krisztian #include <net/netfilter/ipv4/nf_defrag_ipv4.h>
26f6318e55SKOVACS Krisztian 
27c0cd1156SIgor Maravić #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
28f6318e55SKOVACS Krisztian #define XT_TPROXY_HAVE_IPV6 1
29cc6eb433SBalazs Scheidler #include <net/if_inet6.h>
30cc6eb433SBalazs Scheidler #include <net/addrconf.h>
3193742cf8SFlorian Westphal #include <net/inet6_hashtables.h>
32cc6eb433SBalazs Scheidler #include <linux/netfilter_ipv6/ip6_tables.h>
336ad78893SBalazs Scheidler #include <net/netfilter/ipv6/nf_defrag_ipv6.h>
34cc6eb433SBalazs Scheidler #endif
35cc6eb433SBalazs Scheidler 
36cc6eb433SBalazs Scheidler #include <linux/netfilter/xt_TPROXY.h>
37cc6eb433SBalazs Scheidler 
3893742cf8SFlorian Westphal enum nf_tproxy_lookup_t {
3993742cf8SFlorian Westphal 	 NFT_LOOKUP_LISTENER,
4093742cf8SFlorian Westphal 	 NFT_LOOKUP_ESTABLISHED,
4193742cf8SFlorian Westphal };
4293742cf8SFlorian Westphal 
43d503b30bSFlorian Westphal static bool tproxy_sk_is_transparent(struct sock *sk)
44d503b30bSFlorian Westphal {
458b580147SEric Dumazet 	switch (sk->sk_state) {
468b580147SEric Dumazet 	case TCP_TIME_WAIT:
47d503b30bSFlorian Westphal 		if (inet_twsk(sk)->tw_transparent)
48d503b30bSFlorian Westphal 			return true;
498b580147SEric Dumazet 		break;
508b580147SEric Dumazet 	case TCP_NEW_SYN_RECV:
518b580147SEric Dumazet 		if (inet_rsk(inet_reqsk(sk))->no_srccheck)
528b580147SEric Dumazet 			return true;
538b580147SEric Dumazet 		break;
548b580147SEric Dumazet 	default:
558b580147SEric Dumazet 		if (inet_sk(sk)->transparent)
568b580147SEric Dumazet 			return true;
57d503b30bSFlorian Westphal 	}
588b580147SEric Dumazet 
598b580147SEric Dumazet 	sock_gen_put(sk);
60d503b30bSFlorian Westphal 	return false;
61d503b30bSFlorian Westphal }
62d503b30bSFlorian Westphal 
63cc6eb433SBalazs Scheidler static inline __be32
64cc6eb433SBalazs Scheidler tproxy_laddr4(struct sk_buff *skb, __be32 user_laddr, __be32 daddr)
65cc6eb433SBalazs Scheidler {
66cc6eb433SBalazs Scheidler 	struct in_device *indev;
67cc6eb433SBalazs Scheidler 	__be32 laddr;
68cc6eb433SBalazs Scheidler 
69cc6eb433SBalazs Scheidler 	if (user_laddr)
70cc6eb433SBalazs Scheidler 		return user_laddr;
71cc6eb433SBalazs Scheidler 
72cc6eb433SBalazs Scheidler 	laddr = 0;
73cc6eb433SBalazs Scheidler 	rcu_read_lock();
74cc6eb433SBalazs Scheidler 	indev = __in_dev_get_rcu(skb->dev);
75cc6eb433SBalazs Scheidler 	for_primary_ifa(indev) {
76cc6eb433SBalazs Scheidler 		laddr = ifa->ifa_local;
77cc6eb433SBalazs Scheidler 		break;
78cc6eb433SBalazs Scheidler 	} endfor_ifa(indev);
79cc6eb433SBalazs Scheidler 	rcu_read_unlock();
80cc6eb433SBalazs Scheidler 
81cc6eb433SBalazs Scheidler 	return laddr ? laddr : daddr;
82cc6eb433SBalazs Scheidler }
83e8439270SKOVACS Krisztian 
8493742cf8SFlorian Westphal /*
8593742cf8SFlorian Westphal  * This is used when the user wants to intercept a connection matching
8693742cf8SFlorian Westphal  * an explicit iptables rule. In this case the sockets are assumed
8793742cf8SFlorian Westphal  * matching in preference order:
8893742cf8SFlorian Westphal  *
8993742cf8SFlorian Westphal  *   - match: if there's a fully established connection matching the
9093742cf8SFlorian Westphal  *     _packet_ tuple, it is returned, assuming the redirection
9193742cf8SFlorian Westphal  *     already took place and we process a packet belonging to an
9293742cf8SFlorian Westphal  *     established connection
9393742cf8SFlorian Westphal  *
9493742cf8SFlorian Westphal  *   - match: if there's a listening socket matching the redirection
9593742cf8SFlorian Westphal  *     (e.g. on-port & on-ip of the connection), it is returned,
9693742cf8SFlorian Westphal  *     regardless if it was bound to 0.0.0.0 or an explicit
9793742cf8SFlorian Westphal  *     address. The reasoning is that if there's an explicit rule, it
9893742cf8SFlorian Westphal  *     does not really matter if the listener is bound to an interface
9993742cf8SFlorian Westphal  *     or to 0. The user already stated that he wants redirection
10093742cf8SFlorian Westphal  *     (since he added the rule).
10193742cf8SFlorian Westphal  *
10293742cf8SFlorian Westphal  * Please note that there's an overlap between what a TPROXY target
10393742cf8SFlorian Westphal  * and a socket match will match. Normally if you have both rules the
10493742cf8SFlorian Westphal  * "socket" match will be the first one, effectively all packets
10593742cf8SFlorian Westphal  * belonging to established connections going through that one.
10693742cf8SFlorian Westphal  */
10793742cf8SFlorian Westphal static inline struct sock *
108a583636aSCraig Gallek nf_tproxy_get_sock_v4(struct net *net, struct sk_buff *skb, void *hp,
109a583636aSCraig Gallek 		      const u8 protocol,
11093742cf8SFlorian Westphal 		      const __be32 saddr, const __be32 daddr,
11193742cf8SFlorian Westphal 		      const __be16 sport, const __be16 dport,
11293742cf8SFlorian Westphal 		      const struct net_device *in,
11393742cf8SFlorian Westphal 		      const enum nf_tproxy_lookup_t lookup_type)
11493742cf8SFlorian Westphal {
11593742cf8SFlorian Westphal 	struct sock *sk;
116a583636aSCraig Gallek 	struct tcphdr *tcph;
11793742cf8SFlorian Westphal 
11893742cf8SFlorian Westphal 	switch (protocol) {
11993742cf8SFlorian Westphal 	case IPPROTO_TCP:
12093742cf8SFlorian Westphal 		switch (lookup_type) {
12193742cf8SFlorian Westphal 		case NFT_LOOKUP_LISTENER:
122a583636aSCraig Gallek 			tcph = hp;
123a583636aSCraig Gallek 			sk = inet_lookup_listener(net, &tcp_hashinfo, skb,
124a583636aSCraig Gallek 						    ip_hdrlen(skb) +
125a583636aSCraig Gallek 						      __tcp_hdrlen(tcph),
12693742cf8SFlorian Westphal 						    saddr, sport,
12793742cf8SFlorian Westphal 						    daddr, dport,
12893742cf8SFlorian Westphal 						    in->ifindex);
12993742cf8SFlorian Westphal 
130dcbe3590SEric Dumazet 			if (sk && !atomic_inc_not_zero(&sk->sk_refcnt))
131dcbe3590SEric Dumazet 				sk = NULL;
13293742cf8SFlorian Westphal 			/* NOTE: we return listeners even if bound to
13393742cf8SFlorian Westphal 			 * 0.0.0.0, those are filtered out in
13493742cf8SFlorian Westphal 			 * xt_socket, since xt_TPROXY needs 0 bound
13593742cf8SFlorian Westphal 			 * listeners too
13693742cf8SFlorian Westphal 			 */
13793742cf8SFlorian Westphal 			break;
13893742cf8SFlorian Westphal 		case NFT_LOOKUP_ESTABLISHED:
13993742cf8SFlorian Westphal 			sk = inet_lookup_established(net, &tcp_hashinfo,
14093742cf8SFlorian Westphal 						    saddr, sport, daddr, dport,
14193742cf8SFlorian Westphal 						    in->ifindex);
14293742cf8SFlorian Westphal 			break;
14393742cf8SFlorian Westphal 		default:
14493742cf8SFlorian Westphal 			BUG();
14593742cf8SFlorian Westphal 		}
14693742cf8SFlorian Westphal 		break;
14793742cf8SFlorian Westphal 	case IPPROTO_UDP:
14893742cf8SFlorian Westphal 		sk = udp4_lib_lookup(net, saddr, sport, daddr, dport,
14993742cf8SFlorian Westphal 				     in->ifindex);
15093742cf8SFlorian Westphal 		if (sk) {
15193742cf8SFlorian Westphal 			int connected = (sk->sk_state == TCP_ESTABLISHED);
15293742cf8SFlorian Westphal 			int wildcard = (inet_sk(sk)->inet_rcv_saddr == 0);
15393742cf8SFlorian Westphal 
15493742cf8SFlorian Westphal 			/* NOTE: we return listeners even if bound to
15593742cf8SFlorian Westphal 			 * 0.0.0.0, those are filtered out in
15693742cf8SFlorian Westphal 			 * xt_socket, since xt_TPROXY needs 0 bound
15793742cf8SFlorian Westphal 			 * listeners too
15893742cf8SFlorian Westphal 			 */
15993742cf8SFlorian Westphal 			if ((lookup_type == NFT_LOOKUP_ESTABLISHED && (!connected || wildcard)) ||
16093742cf8SFlorian Westphal 			    (lookup_type == NFT_LOOKUP_LISTENER && connected)) {
16193742cf8SFlorian Westphal 				sock_put(sk);
16293742cf8SFlorian Westphal 				sk = NULL;
16393742cf8SFlorian Westphal 			}
16493742cf8SFlorian Westphal 		}
16593742cf8SFlorian Westphal 		break;
16693742cf8SFlorian Westphal 	default:
16793742cf8SFlorian Westphal 		WARN_ON(1);
16893742cf8SFlorian Westphal 		sk = NULL;
16993742cf8SFlorian Westphal 	}
17093742cf8SFlorian Westphal 
17193742cf8SFlorian Westphal 	pr_debug("tproxy socket lookup: proto %u %08x:%u -> %08x:%u, lookup type: %d, sock %p\n",
17293742cf8SFlorian Westphal 		 protocol, ntohl(saddr), ntohs(sport), ntohl(daddr), ntohs(dport), lookup_type, sk);
17393742cf8SFlorian Westphal 
17493742cf8SFlorian Westphal 	return sk;
17593742cf8SFlorian Westphal }
17693742cf8SFlorian Westphal 
177d8b3bfc2SFlorian Westphal #ifdef XT_TPROXY_HAVE_IPV6
17893742cf8SFlorian Westphal static inline struct sock *
179a583636aSCraig Gallek nf_tproxy_get_sock_v6(struct net *net, struct sk_buff *skb, int thoff, void *hp,
180a583636aSCraig Gallek 		      const u8 protocol,
18193742cf8SFlorian Westphal 		      const struct in6_addr *saddr, const struct in6_addr *daddr,
18293742cf8SFlorian Westphal 		      const __be16 sport, const __be16 dport,
18393742cf8SFlorian Westphal 		      const struct net_device *in,
18493742cf8SFlorian Westphal 		      const enum nf_tproxy_lookup_t lookup_type)
18593742cf8SFlorian Westphal {
18693742cf8SFlorian Westphal 	struct sock *sk;
187a583636aSCraig Gallek 	struct tcphdr *tcph;
18893742cf8SFlorian Westphal 
18993742cf8SFlorian Westphal 	switch (protocol) {
19093742cf8SFlorian Westphal 	case IPPROTO_TCP:
19193742cf8SFlorian Westphal 		switch (lookup_type) {
19293742cf8SFlorian Westphal 		case NFT_LOOKUP_LISTENER:
193a583636aSCraig Gallek 			tcph = hp;
194a583636aSCraig Gallek 			sk = inet6_lookup_listener(net, &tcp_hashinfo, skb,
195a583636aSCraig Gallek 						   thoff + __tcp_hdrlen(tcph),
19693742cf8SFlorian Westphal 						   saddr, sport,
19793742cf8SFlorian Westphal 						   daddr, ntohs(dport),
19893742cf8SFlorian Westphal 						   in->ifindex);
19993742cf8SFlorian Westphal 
200dcbe3590SEric Dumazet 			if (sk && !atomic_inc_not_zero(&sk->sk_refcnt))
201dcbe3590SEric Dumazet 				sk = NULL;
20293742cf8SFlorian Westphal 			/* NOTE: we return listeners even if bound to
20393742cf8SFlorian Westphal 			 * 0.0.0.0, those are filtered out in
20493742cf8SFlorian Westphal 			 * xt_socket, since xt_TPROXY needs 0 bound
20593742cf8SFlorian Westphal 			 * listeners too
20693742cf8SFlorian Westphal 			 */
20793742cf8SFlorian Westphal 			break;
20893742cf8SFlorian Westphal 		case NFT_LOOKUP_ESTABLISHED:
20993742cf8SFlorian Westphal 			sk = __inet6_lookup_established(net, &tcp_hashinfo,
21093742cf8SFlorian Westphal 							saddr, sport, daddr, ntohs(dport),
21193742cf8SFlorian Westphal 							in->ifindex);
21293742cf8SFlorian Westphal 			break;
21393742cf8SFlorian Westphal 		default:
21493742cf8SFlorian Westphal 			BUG();
21593742cf8SFlorian Westphal 		}
21693742cf8SFlorian Westphal 		break;
21793742cf8SFlorian Westphal 	case IPPROTO_UDP:
21893742cf8SFlorian Westphal 		sk = udp6_lib_lookup(net, saddr, sport, daddr, dport,
21993742cf8SFlorian Westphal 				     in->ifindex);
22093742cf8SFlorian Westphal 		if (sk) {
22193742cf8SFlorian Westphal 			int connected = (sk->sk_state == TCP_ESTABLISHED);
222efe4208fSEric Dumazet 			int wildcard = ipv6_addr_any(&sk->sk_v6_rcv_saddr);
22393742cf8SFlorian Westphal 
22493742cf8SFlorian Westphal 			/* NOTE: we return listeners even if bound to
22593742cf8SFlorian Westphal 			 * 0.0.0.0, those are filtered out in
22693742cf8SFlorian Westphal 			 * xt_socket, since xt_TPROXY needs 0 bound
22793742cf8SFlorian Westphal 			 * listeners too
22893742cf8SFlorian Westphal 			 */
22993742cf8SFlorian Westphal 			if ((lookup_type == NFT_LOOKUP_ESTABLISHED && (!connected || wildcard)) ||
23093742cf8SFlorian Westphal 			    (lookup_type == NFT_LOOKUP_LISTENER && connected)) {
23193742cf8SFlorian Westphal 				sock_put(sk);
23293742cf8SFlorian Westphal 				sk = NULL;
23393742cf8SFlorian Westphal 			}
23493742cf8SFlorian Westphal 		}
23593742cf8SFlorian Westphal 		break;
23693742cf8SFlorian Westphal 	default:
23793742cf8SFlorian Westphal 		WARN_ON(1);
23893742cf8SFlorian Westphal 		sk = NULL;
23993742cf8SFlorian Westphal 	}
24093742cf8SFlorian Westphal 
24193742cf8SFlorian Westphal 	pr_debug("tproxy socket lookup: proto %u %pI6:%u -> %pI6:%u, lookup type: %d, sock %p\n",
24293742cf8SFlorian Westphal 		 protocol, saddr, ntohs(sport), daddr, ntohs(dport), lookup_type, sk);
24393742cf8SFlorian Westphal 
24493742cf8SFlorian Westphal 	return sk;
24593742cf8SFlorian Westphal }
24693742cf8SFlorian Westphal #endif
24793742cf8SFlorian Westphal 
248106e4c26SBalazs Scheidler /**
2492c53040fSBen Hutchings  * tproxy_handle_time_wait4 - handle IPv4 TCP TIME_WAIT reopen redirections
250106e4c26SBalazs Scheidler  * @skb:	The skb being processed.
2516ad78893SBalazs Scheidler  * @laddr:	IPv4 address to redirect to or zero.
2526ad78893SBalazs Scheidler  * @lport:	TCP port to redirect to or zero.
253106e4c26SBalazs Scheidler  * @sk:		The TIME_WAIT TCP socket found by the lookup.
254106e4c26SBalazs Scheidler  *
255106e4c26SBalazs Scheidler  * We have to handle SYN packets arriving to TIME_WAIT sockets
256106e4c26SBalazs Scheidler  * differently: instead of reopening the connection we should rather
257106e4c26SBalazs Scheidler  * redirect the new connection to the proxy if there's a listener
258106e4c26SBalazs Scheidler  * socket present.
259106e4c26SBalazs Scheidler  *
2606ad78893SBalazs Scheidler  * tproxy_handle_time_wait4() consumes the socket reference passed in.
261106e4c26SBalazs Scheidler  *
262106e4c26SBalazs Scheidler  * Returns the listener socket if there's one, the TIME_WAIT socket if
263106e4c26SBalazs Scheidler  * no such listener is found, or NULL if the TCP header is incomplete.
264106e4c26SBalazs Scheidler  */
265106e4c26SBalazs Scheidler static struct sock *
266686c9b50SEric W. Biederman tproxy_handle_time_wait4(struct net *net, struct sk_buff *skb,
267686c9b50SEric W. Biederman 			 __be32 laddr, __be16 lport, struct sock *sk)
268106e4c26SBalazs Scheidler {
269106e4c26SBalazs Scheidler 	const struct iphdr *iph = ip_hdr(skb);
270106e4c26SBalazs Scheidler 	struct tcphdr _hdr, *hp;
271106e4c26SBalazs Scheidler 
272106e4c26SBalazs Scheidler 	hp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_hdr), &_hdr);
273106e4c26SBalazs Scheidler 	if (hp == NULL) {
274106e4c26SBalazs Scheidler 		inet_twsk_put(inet_twsk(sk));
275106e4c26SBalazs Scheidler 		return NULL;
276106e4c26SBalazs Scheidler 	}
277106e4c26SBalazs Scheidler 
278106e4c26SBalazs Scheidler 	if (hp->syn && !hp->rst && !hp->ack && !hp->fin) {
279106e4c26SBalazs Scheidler 		/* SYN to a TIME_WAIT socket, we'd rather redirect it
280106e4c26SBalazs Scheidler 		 * to a listener socket if there's one */
281106e4c26SBalazs Scheidler 		struct sock *sk2;
282106e4c26SBalazs Scheidler 
283a583636aSCraig Gallek 		sk2 = nf_tproxy_get_sock_v4(net, skb, hp, iph->protocol,
2846ad78893SBalazs Scheidler 					    iph->saddr, laddr ? laddr : iph->daddr,
2856ad78893SBalazs Scheidler 					    hp->source, lport ? lport : hp->dest,
2866ad78893SBalazs Scheidler 					    skb->dev, NFT_LOOKUP_LISTENER);
287106e4c26SBalazs Scheidler 		if (sk2) {
288dbe7faa4SEric Dumazet 			inet_twsk_deschedule_put(inet_twsk(sk));
2896ad78893SBalazs Scheidler 			sk = sk2;
2906ad78893SBalazs Scheidler 		}
2916ad78893SBalazs Scheidler 	}
2926ad78893SBalazs Scheidler 
2936ad78893SBalazs Scheidler 	return sk;
2946ad78893SBalazs Scheidler }
2956ad78893SBalazs Scheidler 
296fd158d79SFlorian Westphal /* assign a socket to the skb -- consumes sk */
297fd158d79SFlorian Westphal static void
298fd158d79SFlorian Westphal nf_tproxy_assign_sock(struct sk_buff *skb, struct sock *sk)
299fd158d79SFlorian Westphal {
300fd158d79SFlorian Westphal 	skb_orphan(skb);
301fd158d79SFlorian Westphal 	skb->sk = sk;
302fd158d79SFlorian Westphal 	skb->destructor = sock_edemux;
303fd158d79SFlorian Westphal }
304fd158d79SFlorian Westphal 
305cc6eb433SBalazs Scheidler static unsigned int
306686c9b50SEric W. Biederman tproxy_tg4(struct net *net, struct sk_buff *skb, __be32 laddr, __be16 lport,
307cc6eb433SBalazs Scheidler 	   u_int32_t mark_mask, u_int32_t mark_value)
308cc6eb433SBalazs Scheidler {
309cc6eb433SBalazs Scheidler 	const struct iphdr *iph = ip_hdr(skb);
310cc6eb433SBalazs Scheidler 	struct udphdr _hdr, *hp;
311cc6eb433SBalazs Scheidler 	struct sock *sk;
312cc6eb433SBalazs Scheidler 
313cc6eb433SBalazs Scheidler 	hp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_hdr), &_hdr);
314cc6eb433SBalazs Scheidler 	if (hp == NULL)
315cc6eb433SBalazs Scheidler 		return NF_DROP;
316cc6eb433SBalazs Scheidler 
317cc6eb433SBalazs Scheidler 	/* check if there's an ongoing connection on the packet
318cc6eb433SBalazs Scheidler 	 * addresses, this happens if the redirect already happened
319cc6eb433SBalazs Scheidler 	 * and the current packet belongs to an already established
320cc6eb433SBalazs Scheidler 	 * connection */
321a583636aSCraig Gallek 	sk = nf_tproxy_get_sock_v4(net, skb, hp, iph->protocol,
322cc6eb433SBalazs Scheidler 				   iph->saddr, iph->daddr,
323cc6eb433SBalazs Scheidler 				   hp->source, hp->dest,
324cc6eb433SBalazs Scheidler 				   skb->dev, NFT_LOOKUP_ESTABLISHED);
325cc6eb433SBalazs Scheidler 
326cc6eb433SBalazs Scheidler 	laddr = tproxy_laddr4(skb, laddr, iph->daddr);
327cc6eb433SBalazs Scheidler 	if (!lport)
328cc6eb433SBalazs Scheidler 		lport = hp->dest;
329cc6eb433SBalazs Scheidler 
330cc6eb433SBalazs Scheidler 	/* UDP has no TCP_TIME_WAIT state, so we never enter here */
331cc6eb433SBalazs Scheidler 	if (sk && sk->sk_state == TCP_TIME_WAIT)
332cc6eb433SBalazs Scheidler 		/* reopening a TIME_WAIT connection needs special handling */
333686c9b50SEric W. Biederman 		sk = tproxy_handle_time_wait4(net, skb, laddr, lport, sk);
334cc6eb433SBalazs Scheidler 	else if (!sk)
335cc6eb433SBalazs Scheidler 		/* no, there's no established connection, check if
336cc6eb433SBalazs Scheidler 		 * there's a listener on the redirected addr/port */
337a583636aSCraig Gallek 		sk = nf_tproxy_get_sock_v4(net, skb, hp, iph->protocol,
338cc6eb433SBalazs Scheidler 					   iph->saddr, laddr,
339cc6eb433SBalazs Scheidler 					   hp->source, lport,
340cc6eb433SBalazs Scheidler 					   skb->dev, NFT_LOOKUP_LISTENER);
341cc6eb433SBalazs Scheidler 
342cc6eb433SBalazs Scheidler 	/* NOTE: assign_sock consumes our sk reference */
343d503b30bSFlorian Westphal 	if (sk && tproxy_sk_is_transparent(sk)) {
344cc6eb433SBalazs Scheidler 		/* This should be in a separate target, but we don't do multiple
345cc6eb433SBalazs Scheidler 		   targets on the same rule yet */
346cc6eb433SBalazs Scheidler 		skb->mark = (skb->mark & ~mark_mask) ^ mark_value;
347cc6eb433SBalazs Scheidler 
348cc6eb433SBalazs Scheidler 		pr_debug("redirecting: proto %hhu %pI4:%hu -> %pI4:%hu, mark: %x\n",
349cc6eb433SBalazs Scheidler 			 iph->protocol, &iph->daddr, ntohs(hp->dest),
350cc6eb433SBalazs Scheidler 			 &laddr, ntohs(lport), skb->mark);
351d503b30bSFlorian Westphal 
352d503b30bSFlorian Westphal 		nf_tproxy_assign_sock(skb, sk);
353cc6eb433SBalazs Scheidler 		return NF_ACCEPT;
354cc6eb433SBalazs Scheidler 	}
355cc6eb433SBalazs Scheidler 
356cc6eb433SBalazs Scheidler 	pr_debug("no socket, dropping: proto %hhu %pI4:%hu -> %pI4:%hu, mark: %x\n",
357cc6eb433SBalazs Scheidler 		 iph->protocol, &iph->saddr, ntohs(hp->source),
358cc6eb433SBalazs Scheidler 		 &iph->daddr, ntohs(hp->dest), skb->mark);
359cc6eb433SBalazs Scheidler 	return NF_DROP;
360cc6eb433SBalazs Scheidler }
361cc6eb433SBalazs Scheidler 
362cc6eb433SBalazs Scheidler static unsigned int
363cc6eb433SBalazs Scheidler tproxy_tg4_v0(struct sk_buff *skb, const struct xt_action_param *par)
364cc6eb433SBalazs Scheidler {
365cc6eb433SBalazs Scheidler 	const struct xt_tproxy_target_info *tgi = par->targinfo;
366cc6eb433SBalazs Scheidler 
367613dbd95SPablo Neira Ayuso 	return tproxy_tg4(xt_net(par), skb, tgi->laddr, tgi->lport,
368613dbd95SPablo Neira Ayuso 			  tgi->mark_mask, tgi->mark_value);
369cc6eb433SBalazs Scheidler }
370cc6eb433SBalazs Scheidler 
371cc6eb433SBalazs Scheidler static unsigned int
372cc6eb433SBalazs Scheidler tproxy_tg4_v1(struct sk_buff *skb, const struct xt_action_param *par)
373cc6eb433SBalazs Scheidler {
374cc6eb433SBalazs Scheidler 	const struct xt_tproxy_target_info_v1 *tgi = par->targinfo;
375cc6eb433SBalazs Scheidler 
376613dbd95SPablo Neira Ayuso 	return tproxy_tg4(xt_net(par), skb, tgi->laddr.ip, tgi->lport,
377613dbd95SPablo Neira Ayuso 			  tgi->mark_mask, tgi->mark_value);
378cc6eb433SBalazs Scheidler }
379cc6eb433SBalazs Scheidler 
380f6318e55SKOVACS Krisztian #ifdef XT_TPROXY_HAVE_IPV6
381cc6eb433SBalazs Scheidler 
382cc6eb433SBalazs Scheidler static inline const struct in6_addr *
383cc6eb433SBalazs Scheidler tproxy_laddr6(struct sk_buff *skb, const struct in6_addr *user_laddr,
384cc6eb433SBalazs Scheidler 	      const struct in6_addr *daddr)
385cc6eb433SBalazs Scheidler {
386cc6eb433SBalazs Scheidler 	struct inet6_dev *indev;
387cc6eb433SBalazs Scheidler 	struct inet6_ifaddr *ifa;
388cc6eb433SBalazs Scheidler 	struct in6_addr *laddr;
389cc6eb433SBalazs Scheidler 
390cc6eb433SBalazs Scheidler 	if (!ipv6_addr_any(user_laddr))
391cc6eb433SBalazs Scheidler 		return user_laddr;
392cc6eb433SBalazs Scheidler 	laddr = NULL;
393cc6eb433SBalazs Scheidler 
394cc6eb433SBalazs Scheidler 	rcu_read_lock();
395cc6eb433SBalazs Scheidler 	indev = __in6_dev_get(skb->dev);
396cc6eb433SBalazs Scheidler 	if (indev)
397cc6eb433SBalazs Scheidler 		list_for_each_entry(ifa, &indev->addr_list, if_list) {
398cc6eb433SBalazs Scheidler 			if (ifa->flags & (IFA_F_TENTATIVE | IFA_F_DEPRECATED))
399cc6eb433SBalazs Scheidler 				continue;
400cc6eb433SBalazs Scheidler 
401cc6eb433SBalazs Scheidler 			laddr = &ifa->addr;
402cc6eb433SBalazs Scheidler 			break;
403cc6eb433SBalazs Scheidler 		}
404cc6eb433SBalazs Scheidler 	rcu_read_unlock();
405cc6eb433SBalazs Scheidler 
406cc6eb433SBalazs Scheidler 	return laddr ? laddr : daddr;
407cc6eb433SBalazs Scheidler }
408cc6eb433SBalazs Scheidler 
4096ad78893SBalazs Scheidler /**
4102c53040fSBen Hutchings  * tproxy_handle_time_wait6 - handle IPv6 TCP TIME_WAIT reopen redirections
4116ad78893SBalazs Scheidler  * @skb:	The skb being processed.
4126ad78893SBalazs Scheidler  * @tproto:	Transport protocol.
4136ad78893SBalazs Scheidler  * @thoff:	Transport protocol header offset.
4146ad78893SBalazs Scheidler  * @par:	Iptables target parameters.
4156ad78893SBalazs Scheidler  * @sk:		The TIME_WAIT TCP socket found by the lookup.
4166ad78893SBalazs Scheidler  *
4176ad78893SBalazs Scheidler  * We have to handle SYN packets arriving to TIME_WAIT sockets
4186ad78893SBalazs Scheidler  * differently: instead of reopening the connection we should rather
4196ad78893SBalazs Scheidler  * redirect the new connection to the proxy if there's a listener
4206ad78893SBalazs Scheidler  * socket present.
4216ad78893SBalazs Scheidler  *
4226ad78893SBalazs Scheidler  * tproxy_handle_time_wait6() consumes the socket reference passed in.
4236ad78893SBalazs Scheidler  *
4246ad78893SBalazs Scheidler  * Returns the listener socket if there's one, the TIME_WAIT socket if
4256ad78893SBalazs Scheidler  * no such listener is found, or NULL if the TCP header is incomplete.
426106e4c26SBalazs Scheidler  */
4276ad78893SBalazs Scheidler static struct sock *
4286ad78893SBalazs Scheidler tproxy_handle_time_wait6(struct sk_buff *skb, int tproto, int thoff,
4296ad78893SBalazs Scheidler 			 const struct xt_action_param *par,
4306ad78893SBalazs Scheidler 			 struct sock *sk)
4316ad78893SBalazs Scheidler {
4326ad78893SBalazs Scheidler 	const struct ipv6hdr *iph = ipv6_hdr(skb);
4336ad78893SBalazs Scheidler 	struct tcphdr _hdr, *hp;
4346ad78893SBalazs Scheidler 	const struct xt_tproxy_target_info_v1 *tgi = par->targinfo;
4356ad78893SBalazs Scheidler 
4366ad78893SBalazs Scheidler 	hp = skb_header_pointer(skb, thoff, sizeof(_hdr), &_hdr);
4376ad78893SBalazs Scheidler 	if (hp == NULL) {
4386ad78893SBalazs Scheidler 		inet_twsk_put(inet_twsk(sk));
4396ad78893SBalazs Scheidler 		return NULL;
4406ad78893SBalazs Scheidler 	}
4416ad78893SBalazs Scheidler 
4426ad78893SBalazs Scheidler 	if (hp->syn && !hp->rst && !hp->ack && !hp->fin) {
4436ad78893SBalazs Scheidler 		/* SYN to a TIME_WAIT socket, we'd rather redirect it
4446ad78893SBalazs Scheidler 		 * to a listener socket if there's one */
4456ad78893SBalazs Scheidler 		struct sock *sk2;
4466ad78893SBalazs Scheidler 
447613dbd95SPablo Neira Ayuso 		sk2 = nf_tproxy_get_sock_v6(xt_net(par), skb, thoff, hp, tproto,
4486ad78893SBalazs Scheidler 					    &iph->saddr,
449cc6eb433SBalazs Scheidler 					    tproxy_laddr6(skb, &tgi->laddr.in6, &iph->daddr),
4506ad78893SBalazs Scheidler 					    hp->source,
4516ad78893SBalazs Scheidler 					    tgi->lport ? tgi->lport : hp->dest,
4526ad78893SBalazs Scheidler 					    skb->dev, NFT_LOOKUP_LISTENER);
4536ad78893SBalazs Scheidler 		if (sk2) {
454dbe7faa4SEric Dumazet 			inet_twsk_deschedule_put(inet_twsk(sk));
455106e4c26SBalazs Scheidler 			sk = sk2;
456106e4c26SBalazs Scheidler 		}
457106e4c26SBalazs Scheidler 	}
458106e4c26SBalazs Scheidler 
459106e4c26SBalazs Scheidler 	return sk;
460106e4c26SBalazs Scheidler }
461106e4c26SBalazs Scheidler 
462e8439270SKOVACS Krisztian static unsigned int
4636ad78893SBalazs Scheidler tproxy_tg6_v1(struct sk_buff *skb, const struct xt_action_param *par)
4646ad78893SBalazs Scheidler {
4656ad78893SBalazs Scheidler 	const struct ipv6hdr *iph = ipv6_hdr(skb);
4666ad78893SBalazs Scheidler 	const struct xt_tproxy_target_info_v1 *tgi = par->targinfo;
4676ad78893SBalazs Scheidler 	struct udphdr _hdr, *hp;
4686ad78893SBalazs Scheidler 	struct sock *sk;
469cc6eb433SBalazs Scheidler 	const struct in6_addr *laddr;
470cc6eb433SBalazs Scheidler 	__be16 lport;
47184018f55SHans Schillstrom 	int thoff = 0;
4726ad78893SBalazs Scheidler 	int tproto;
4736ad78893SBalazs Scheidler 
47484018f55SHans Schillstrom 	tproto = ipv6_find_hdr(skb, &thoff, -1, NULL, NULL);
4756ad78893SBalazs Scheidler 	if (tproto < 0) {
4766ad78893SBalazs Scheidler 		pr_debug("unable to find transport header in IPv6 packet, dropping\n");
4776ad78893SBalazs Scheidler 		return NF_DROP;
4786ad78893SBalazs Scheidler 	}
4796ad78893SBalazs Scheidler 
4806ad78893SBalazs Scheidler 	hp = skb_header_pointer(skb, thoff, sizeof(_hdr), &_hdr);
4816ad78893SBalazs Scheidler 	if (hp == NULL) {
4826ad78893SBalazs Scheidler 		pr_debug("unable to grab transport header contents in IPv6 packet, dropping\n");
4836ad78893SBalazs Scheidler 		return NF_DROP;
4846ad78893SBalazs Scheidler 	}
4856ad78893SBalazs Scheidler 
4866ad78893SBalazs Scheidler 	/* check if there's an ongoing connection on the packet
4876ad78893SBalazs Scheidler 	 * addresses, this happens if the redirect already happened
4886ad78893SBalazs Scheidler 	 * and the current packet belongs to an already established
4896ad78893SBalazs Scheidler 	 * connection */
490613dbd95SPablo Neira Ayuso 	sk = nf_tproxy_get_sock_v6(xt_net(par), skb, thoff, hp, tproto,
4916ad78893SBalazs Scheidler 				   &iph->saddr, &iph->daddr,
4926ad78893SBalazs Scheidler 				   hp->source, hp->dest,
493613dbd95SPablo Neira Ayuso 				   xt_in(par), NFT_LOOKUP_ESTABLISHED);
494106e4c26SBalazs Scheidler 
495cc6eb433SBalazs Scheidler 	laddr = tproxy_laddr6(skb, &tgi->laddr.in6, &iph->daddr);
496cc6eb433SBalazs Scheidler 	lport = tgi->lport ? tgi->lport : hp->dest;
497cc6eb433SBalazs Scheidler 
498106e4c26SBalazs Scheidler 	/* UDP has no TCP_TIME_WAIT state, so we never enter here */
499106e4c26SBalazs Scheidler 	if (sk && sk->sk_state == TCP_TIME_WAIT)
5006ad78893SBalazs Scheidler 		/* reopening a TIME_WAIT connection needs special handling */
5016ad78893SBalazs Scheidler 		sk = tproxy_handle_time_wait6(skb, tproto, thoff, par, sk);
502106e4c26SBalazs Scheidler 	else if (!sk)
5036ad78893SBalazs Scheidler 		/* no there's no established connection, check if
5046ad78893SBalazs Scheidler 		 * there's a listener on the redirected addr/port */
505613dbd95SPablo Neira Ayuso 		sk = nf_tproxy_get_sock_v6(xt_net(par), skb, thoff, hp,
506a583636aSCraig Gallek 					   tproto, &iph->saddr, laddr,
507cc6eb433SBalazs Scheidler 					   hp->source, lport,
508613dbd95SPablo Neira Ayuso 					   xt_in(par), NFT_LOOKUP_LISTENER);
509e8439270SKOVACS Krisztian 
510e8439270SKOVACS Krisztian 	/* NOTE: assign_sock consumes our sk reference */
511d503b30bSFlorian Westphal 	if (sk && tproxy_sk_is_transparent(sk)) {
512e8439270SKOVACS Krisztian 		/* This should be in a separate target, but we don't do multiple
513e8439270SKOVACS Krisztian 		   targets on the same rule yet */
514e8439270SKOVACS Krisztian 		skb->mark = (skb->mark & ~tgi->mark_mask) ^ tgi->mark_value;
515e8439270SKOVACS Krisztian 
5166ad78893SBalazs Scheidler 		pr_debug("redirecting: proto %hhu %pI6:%hu -> %pI6:%hu, mark: %x\n",
517cc6eb433SBalazs Scheidler 			 tproto, &iph->saddr, ntohs(hp->source),
518cc6eb433SBalazs Scheidler 			 laddr, ntohs(lport), skb->mark);
519d503b30bSFlorian Westphal 
520d503b30bSFlorian Westphal 		nf_tproxy_assign_sock(skb, sk);
521e8439270SKOVACS Krisztian 		return NF_ACCEPT;
522e8439270SKOVACS Krisztian 	}
523e8439270SKOVACS Krisztian 
5246ad78893SBalazs Scheidler 	pr_debug("no socket, dropping: proto %hhu %pI6:%hu -> %pI6:%hu, mark: %x\n",
525cc6eb433SBalazs Scheidler 		 tproto, &iph->saddr, ntohs(hp->source),
526cc6eb433SBalazs Scheidler 		 &iph->daddr, ntohs(hp->dest), skb->mark);
527cc6eb433SBalazs Scheidler 
528e8439270SKOVACS Krisztian 	return NF_DROP;
529e8439270SKOVACS Krisztian }
530e8439270SKOVACS Krisztian 
5316ad78893SBalazs Scheidler static int tproxy_tg6_check(const struct xt_tgchk_param *par)
5326ad78893SBalazs Scheidler {
5336ad78893SBalazs Scheidler 	const struct ip6t_ip6 *i = par->entryinfo;
5346ad78893SBalazs Scheidler 
5353d8c6dceSPablo Neira Ayuso 	if ((i->proto == IPPROTO_TCP || i->proto == IPPROTO_UDP) &&
5363d8c6dceSPablo Neira Ayuso 	    !(i->invflags & IP6T_INV_PROTO))
5376ad78893SBalazs Scheidler 		return 0;
5386ad78893SBalazs Scheidler 
5396ad78893SBalazs Scheidler 	pr_info("Can be used only in combination with "
5406ad78893SBalazs Scheidler 		"either -p tcp or -p udp\n");
5416ad78893SBalazs Scheidler 	return -EINVAL;
5426ad78893SBalazs Scheidler }
5436ad78893SBalazs Scheidler #endif
5446ad78893SBalazs Scheidler 
5456ad78893SBalazs Scheidler static int tproxy_tg4_check(const struct xt_tgchk_param *par)
546e8439270SKOVACS Krisztian {
547af5d6dc2SJan Engelhardt 	const struct ipt_ip *i = par->entryinfo;
548e8439270SKOVACS Krisztian 
549e8439270SKOVACS Krisztian 	if ((i->proto == IPPROTO_TCP || i->proto == IPPROTO_UDP)
550e8439270SKOVACS Krisztian 	    && !(i->invflags & IPT_INV_PROTO))
551d6b00a53SJan Engelhardt 		return 0;
552e8439270SKOVACS Krisztian 
553ff67e4e4SJan Engelhardt 	pr_info("Can be used only in combination with "
554e8439270SKOVACS Krisztian 		"either -p tcp or -p udp\n");
555d6b00a53SJan Engelhardt 	return -EINVAL;
556e8439270SKOVACS Krisztian }
557e8439270SKOVACS Krisztian 
5586ad78893SBalazs Scheidler static struct xt_target tproxy_tg_reg[] __read_mostly = {
5596ad78893SBalazs Scheidler 	{
560e8439270SKOVACS Krisztian 		.name		= "TPROXY",
56170a85166SChangli Gao 		.family		= NFPROTO_IPV4,
562e8439270SKOVACS Krisztian 		.table		= "mangle",
5636ad78893SBalazs Scheidler 		.target		= tproxy_tg4_v0,
5646ad78893SBalazs Scheidler 		.revision	= 0,
565e8439270SKOVACS Krisztian 		.targetsize	= sizeof(struct xt_tproxy_target_info),
5666ad78893SBalazs Scheidler 		.checkentry	= tproxy_tg4_check,
567e8439270SKOVACS Krisztian 		.hooks		= 1 << NF_INET_PRE_ROUTING,
568e8439270SKOVACS Krisztian 		.me		= THIS_MODULE,
5696ad78893SBalazs Scheidler 	},
5706ad78893SBalazs Scheidler 	{
5716ad78893SBalazs Scheidler 		.name		= "TPROXY",
5726ad78893SBalazs Scheidler 		.family		= NFPROTO_IPV4,
5736ad78893SBalazs Scheidler 		.table		= "mangle",
5746ad78893SBalazs Scheidler 		.target		= tproxy_tg4_v1,
5756ad78893SBalazs Scheidler 		.revision	= 1,
5766ad78893SBalazs Scheidler 		.targetsize	= sizeof(struct xt_tproxy_target_info_v1),
5776ad78893SBalazs Scheidler 		.checkentry	= tproxy_tg4_check,
5786ad78893SBalazs Scheidler 		.hooks		= 1 << NF_INET_PRE_ROUTING,
5796ad78893SBalazs Scheidler 		.me		= THIS_MODULE,
5806ad78893SBalazs Scheidler 	},
581f6318e55SKOVACS Krisztian #ifdef XT_TPROXY_HAVE_IPV6
5826ad78893SBalazs Scheidler 	{
5836ad78893SBalazs Scheidler 		.name		= "TPROXY",
5846ad78893SBalazs Scheidler 		.family		= NFPROTO_IPV6,
5856ad78893SBalazs Scheidler 		.table		= "mangle",
5866ad78893SBalazs Scheidler 		.target		= tproxy_tg6_v1,
5876ad78893SBalazs Scheidler 		.revision	= 1,
5886ad78893SBalazs Scheidler 		.targetsize	= sizeof(struct xt_tproxy_target_info_v1),
5896ad78893SBalazs Scheidler 		.checkentry	= tproxy_tg6_check,
5906ad78893SBalazs Scheidler 		.hooks		= 1 << NF_INET_PRE_ROUTING,
5916ad78893SBalazs Scheidler 		.me		= THIS_MODULE,
5926ad78893SBalazs Scheidler 	},
5936ad78893SBalazs Scheidler #endif
5946ad78893SBalazs Scheidler 
595e8439270SKOVACS Krisztian };
596e8439270SKOVACS Krisztian 
597e8439270SKOVACS Krisztian static int __init tproxy_tg_init(void)
598e8439270SKOVACS Krisztian {
599e8439270SKOVACS Krisztian 	nf_defrag_ipv4_enable();
600f6318e55SKOVACS Krisztian #ifdef XT_TPROXY_HAVE_IPV6
6016ad78893SBalazs Scheidler 	nf_defrag_ipv6_enable();
6026ad78893SBalazs Scheidler #endif
6036ad78893SBalazs Scheidler 
6046ad78893SBalazs Scheidler 	return xt_register_targets(tproxy_tg_reg, ARRAY_SIZE(tproxy_tg_reg));
605e8439270SKOVACS Krisztian }
606e8439270SKOVACS Krisztian 
607e8439270SKOVACS Krisztian static void __exit tproxy_tg_exit(void)
608e8439270SKOVACS Krisztian {
6096ad78893SBalazs Scheidler 	xt_unregister_targets(tproxy_tg_reg, ARRAY_SIZE(tproxy_tg_reg));
610e8439270SKOVACS Krisztian }
611e8439270SKOVACS Krisztian 
612e8439270SKOVACS Krisztian module_init(tproxy_tg_init);
613e8439270SKOVACS Krisztian module_exit(tproxy_tg_exit);
614e8439270SKOVACS Krisztian MODULE_LICENSE("GPL");
6156ad78893SBalazs Scheidler MODULE_AUTHOR("Balazs Scheidler, Krisztian Kovacs");
616e8439270SKOVACS Krisztian MODULE_DESCRIPTION("Netfilter transparent proxy (TPROXY) target module.");
617e8439270SKOVACS Krisztian MODULE_ALIAS("ipt_TPROXY");
6186ad78893SBalazs Scheidler MODULE_ALIAS("ip6t_TPROXY");
619