How to set up TCP/UDP transparent proxy with iptables
enev turg
Posted on October 3, 2024
Abstract
We sometimes want to transparently proxy TCP or UDP traffic of Linux machines. This article explains how to do this with iptables' "TPROXY" function.
SOCKS5
SOCKS is a type of proxy protocol, and its version 5 is called SOCKS5. Here we first set up SOCKS5 proxy.
SOCKS5 supports "UDP Associate" function, which can be used to proxy UDP traffic. UDP Associate works as following: 1) the client request a UDP port (relay port) for proxying 2) the SOCKS5 server replies a port number 3) the client starts sending UDP packets to the port 4) the server deliver them to the original destination and send replied packets (if any) back to the port.
This mean that the client machine should be able to access all UDP ports of the server machine that are used for UDP associate relay ports, and there should not be a NAT between the client the server which may cause the server to reply an IP inaccessible from the client.
Also, not all SOCKS5 servers support UDP associate. Dante, 3proxy and gost support it, but ssh -D
doesn't.
- As for Dante, we need
udpassociate
option, and when Full cone NAT (EIM/EIF) behavior is required (such situations are not so frequent), we needudp.connectdst: no
option, in its config file.
Here we assume a SOCKS5 server with UDP associate support is running at 192.168.31.2:3129
.
Transparent proxy softwares
As far as I know, there are two transparent proxy softwares that can make SOCKS5 proxy available as transparent proxy supporting TCP and UDP: redsocks (forked from old redsocks, which doesn't support TCP transparent proxy) and hev-socks5-tproxy.
This is an example config file for hev-socks5-tproxy to host TCP transparent proxy on port 22222 and UDP transparent proxy on port 22224. Debug output is enabled.
misc:
log-file: stderr
log-level: debug
socks5:
# Socks5 server port
port: 3129
# Socks5 server address
address: 192.168.31.2
# Socks5 UDP relay mode (tcp|udp)
udp: 'udp'
# Socks5 handshake using pipeline mode
tcp:
# TCP port
port: 22222
# TCP address
address: '::'
udp:
# UDP port
port: 22224
# UDP address
address: '::'
As for redsocks, see documentation.
Note that transparent proxy usually requires root privilege.
Proxy traffic from other devices
Now we can move on to setting up iptables and iproute2. First, let's see cases of proxying traffic from other devices, since it's easier than proxying local traffic.
Here we assume there is an external computer 192.168.1.90, whose traffic we want to proxy.
First, run the following commands.
sudo iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.90 -j TPROXY --on-ip 127.0.0.1 --on-port 22222
sudo iptables -t mangle -A PREROUTING -p udp -s 192.168.1.90 -j TPROXY --on-ip 127.0.0.1 --on-port 22224
These TPROXY targets force all traffic from 192.168.1.90 to go to 22222 (TCP) or 22224 (UDP).
However, this is insufficient. We need to send packets onto the loopback device to use transparent proxies (see documentation for detail).
So we also need this (after editing /etc/iproute2/rt_tables, if needed):
sudo ip route add local default dev lo table 100
sudo ip rule add from 192.168.1.90 lookup 100
-
ip_forward=1
is not required. Now transparent proxy should work. I recommend testing with a stun client likehttps://www.stunprotocol.org/
, because it's lightweight, quick, informative (respond external IP and port), and available both in TCP and UDP.
We can also do the same thing with fwmark, like this:
sudo iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.90 -j TPROXY --on-ip 127.0.0.1 --on-port 22222 --tproxy-mark 0x111
sudo iptables -t mangle -A PREROUTING -p udp -s 192.168.1.90 -j TPROXY --on-ip 127.0.0.1 --on-port 22224 --tproxy-mark 0x111
sudo ip rule add fwmark 0x111 lookup 100
--tproxy-mark
works similarly to --set-mark
.
tcpdump result of UDP transparent proxy
This is a sample result of tcpdump
for UDP transparent proxy. SOCKS5 is hosted at 127.0.0.3 (sorry it's not 192.168.31.2). The first UDP packet is actually sent to 127.0.0.1:22224, but the number 22224 is not present here. That's why debug output of transparent proxy software is very helpful.
127.0.0.3:39410 is the relay port of UDP associate.
09:18:24.316021 enp3s0 In IP 192.168.1.90.42874 > 3.132.228.249.3479: UDP, length 28
09:18:24.316771 lo In IP 127.0.0.1.51066 > 127.0.0.3.3130: Flags [S], seq 3104424580, win 65495, options [mss 65495,sackOK,TS val 591900081 ecr 0,nop,wscale 7], length 0
09:18:24.316810 lo In IP 127.0.0.3.3130 > 127.0.0.1.51066: Flags [S.], seq 1895898628, ack 3104424581, win 65483, options [mss 65495,sackOK,TS val 1315171637 ecr 591900081,nop,wscale 7], length 0
09:18:24.316842 lo In IP 127.0.0.1.51066 > 127.0.0.3.3130: Flags [.], ack 1, win 512, options [nop,nop,TS val 591900081 ecr 1315171637], length 0
09:18:24.316970 lo In IP 127.0.0.1.51066 > 127.0.0.3.3130: Flags [P.], seq 1:4, ack 1, win 512, options [nop,nop,TS val 591900081 ecr 1315171637], length 3
09:18:24.317006 lo In IP 127.0.0.3.3130 > 127.0.0.1.51066: Flags [.], ack 4, win 512, options [nop,nop,TS val 1315171637 ecr 591900081], length 0
09:18:24.317145 lo In IP 127.0.0.3.3130 > 127.0.0.1.51066: Flags [P.], seq 1:3, ack 4, win 512, options [nop,nop,TS val 1315171637 ecr 591900081], length 2
09:18:24.317176 lo In IP 127.0.0.1.51066 > 127.0.0.3.3130: Flags [.], ack 3, win 512, options [nop,nop,TS val 591900081 ecr 1315171637], length 0
09:18:24.317295 lo In IP 127.0.0.1.51066 > 127.0.0.3.3130: Flags [P.], seq 4:14, ack 3, win 512, options [nop,nop,TS val 591900081 ecr 1315171637], length 10
09:18:24.317469 lo In IP 127.0.0.3.3130 > 127.0.0.1.51066: Flags [P.], seq 3:13, ack 14, win 512, options [nop,nop,TS val 1315171637 ecr 591900081], length 10
09:18:24.317803 lo In IP 127.0.0.1.44277 > 127.0.0.3.39410: UDP, length 38
09:18:24.317938 enp3s0 Out IP 192.168.1.9.49280 > 3.132.228.249.3479: UDP, length 28
09:18:24.358632 lo In IP 127.0.0.1.51066 > 127.0.0.3.3130: Flags [.], ack 13, win 512, options [nop,nop,TS val 591900123 ecr 1315171637], length 0
09:18:24.489589 enp3s0 In IP 3.132.228.249.3479 > 192.168.1.9.49280: UDP, length 68
09:18:24.489823 lo In IP 127.0.0.3.39410 > 127.0.0.1.44277: UDP, length 78
09:18:24.490063 enp3s0 Out IP 3.132.228.249.3479 > 192.168.1.90.42874: UDP, length 68
Caveat: ufw blocks TPROXY packets
There is a caveat relating a popular Linux firewall, ufw. It has a chain called ufw-not-local
, which (by default) denies packets that are routed to the machine and whose destination doesn't match the local machine's. Therefore we need a command like:
sudo iptables -I ufw-not-local -s 192.168.1.90 -j ACCEPT
(Use fwmark if needed)
Note that usual commands like ufw
(route
) allow 192.168.1.90
doesn't work.
Proxy traffic of local machine itself
TPROXY target is only allowed in mangle
table of PREROUTING
chain, and locally generated packets don't go through it. However, if we send local packets to the loopback device, then they go out from and return back to the machine, so can be processed by PREROUTING
chain.
We have to be careful not to create a dead loop, that is, applying proxy to packets from transparent proxy or SOCKS5 server. Normal way to avoid it is filtering packets with destination IP or using iptables matches like --uid-owner
or --cgroup
, but here we assign a new private IP address to some device and apply proxy to all packets from the IP.
I strongly recommend trying this first to make sure other things are going well.
First, assign a private IP.
sudo ip addr add 192.168.5.5 dev lo
Then, add iproute2 and iptables settings as in the previous section.
sudo ip rule add from 192.168.5.5 lookup 100
sudo iptables -t mangle -A PREROUTING -p tcp -s 192.168.5.5 -j TPROXY --on-ip 127.0.0.1 --on-port 22222
sudo iptables -t mangle -A PREROUTING -p udp -s 192.168.5.5 -j TPROXY --on-ip 127.0.0.1 --on-port 22224
(sudo ip route add local default dev lo table 100
is also necessary if not done)
That's all.
We can use fwmark here too, but tproxy-mark is useless, because we have to mark packets before they go into the machine.
A possible setting will be like this:
sudo iptables -t mangle -A OUTPUT -p tcp -s 192.168.5.5 -j MARK --set-mark 0x111
sudo iptables -t mangle -A OUTPUT -p udp -s 192.168.5.5 -j MARK --set-mark 0x111
sudo iptables -t mangle -A PREROUTING -p tcp -m mark --mark 0x111 -j TPROXY --on-ip 127.0.0.1 --on-port 22222
sudo iptables -t mangle -A PREROUTING -p udp -m mark --mark 0x111 -j TPROXY --on-ip 127.0.0.1 --on-port 22224
(sudo ip rule add fwmark 0x111 lookup 100
is also necessary if not done)
This is a tcpdump result for this case.
10:13:38.897424 lo In IP 192.168.5.5.48202 > 3.132.228.249.3479: UDP, length 28
10:13:38.897566 lo In IP 127.0.0.1.60698 > 127.0.0.3.3130: Flags [S], seq 188947714, win 65495, options [mss 65495,sackOK,TS val 841486027 ecr 0,nop,wscale 7], length 0
10:13:38.897596 lo In IP 127.0.0.3.3130 > 127.0.0.1.60698: Flags [S.], seq 1277796748, ack 188947715, win 65483, options [mss 65495,sackOK,TS val 3344603128 ecr 841486027,nop,wscale 7], length 0
10:13:38.897623 lo In IP 127.0.0.1.60698 > 127.0.0.3.3130: Flags [.], ack 1, win 512, options [nop,nop,TS val 841486028 ecr 3344603128], length 0
10:13:38.897722 lo In IP 127.0.0.1.60698 > 127.0.0.3.3130: Flags [P.], seq 1:4, ack 1, win 512, options [nop,nop,TS val 841486028 ecr 3344603128], length 3
10:13:38.897739 lo In IP 127.0.0.3.3130 > 127.0.0.1.60698: Flags [.], ack 4, win 512, options [nop,nop,TS val 3344603128 ecr 841486028], length 0
10:13:38.897955 lo In IP 127.0.0.3.3130 > 127.0.0.1.60698: Flags [P.], seq 1:3, ack 4, win 512, options [nop,nop,TS val 3344603128 ecr 841486028], length 2
10:13:38.897977 lo In IP 127.0.0.1.60698 > 127.0.0.3.3130: Flags [.], ack 3, win 512, options [nop,nop,TS val 841486028 ecr 3344603128], length 0
10:13:38.898047 lo In IP 127.0.0.1.60698 > 127.0.0.3.3130: Flags [P.], seq 4:14, ack 3, win 512, options [nop,nop,TS val 841486028 ecr 3344603128], length 10
10:13:38.898129 lo In IP 127.0.0.3.3130 > 127.0.0.1.60698: Flags [P.], seq 3:13, ack 14, win 512, options [nop,nop,TS val 3344603128 ecr 841486028], length 10
10:13:38.898253 lo In IP 127.0.0.1.49384 > 127.0.0.3.53298: UDP, length 38
10:13:38.898317 enp1s0 Out IP 192.168.1.13.52394 > 3.132.228.249.3479: UDP, length 28
10:13:38.939622 lo In IP 127.0.0.1.60698 > 127.0.0.3.3130: Flags [.], ack 13, win 512, options [nop,nop,TS val 841486070 ecr 3344603128], length 0
10:13:39.061935 enp1s0 In IP 3.132.228.249.3479 > 192.168.1.13.52394: UDP, length 68
10:13:39.062134 lo In IP 127.0.0.3.53298 > 127.0.0.1.49384: UDP, length 78
10:13:39.062243 lo In IP 3.132.228.249.3479 > 192.168.5.5.48202: UDP, length 68
Apply proxy for specific apps
iptables has "uid-owner", "gid-owner" or "cgroup" match. These are useful for sending traffic from specific apps through transparent proxy (or VPN or other NICs).
References
- official documentation https://docs.kernel.org/networking/tproxy.html
- example by Hysteria https://v2.hysteria.network/docs/advanced/TPROXY/
- an example in GitHub https://gist.github.com/superstes/c4fefbf403f61812abf89165d7bc4000
Posted on October 3, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.