I’m running a Linux machine having multiple NICs, one for wired connection, one for WWAN(Wireless WAN). The latter is connected to the cellular network using Sierra Wireless LTE modem with Qualcomm Snapdragon X55 Modem and is for scrapping tasks. The reason I use this structure is that some websites implemented intelligent anti-crawling mechanisms thus repetitive requests will be likely detected and banned. So I believed cellular network can deal with this issue easily because it's used by so many devices around in a small cell but also because of its dynamic IP assignments – The external IP will be randomly changed as time goes on.
At first, I thought it would be easy to set up. I tried to run a container with a network driver created using brctl and iptables' masquerade but it failed because there was one problem that I've forgotten: Routing Tables. (Note: Of course I did under the condition IP forwarding is enabled.)
Because gateway routing is not so smart enough - maybe I was - that the answer is not sent back via the same route a request sent the packet will effectively lose when I send a request using the second NIC that has its own gateway.
Then I changed the plan to set up a SOCKS proxy server using Dante because it supports specifying an outgoing network interface.
This one with Dante seems like it could be easy but it wasn't because the same problem happened. Dante support specifying the address to be used for outgoing connections but the packet didn't traverse as intended, just port unreachable
starts to appear in tcpdump.
Well, I switched the plan again and decided to go with iptables' mangle, which is used to modify or mark packets. In order to achieve this with Dante, I created a new user 'proxy' and then make all network traffic established by this user to go through the wwp42s0f3u2i12
interface, which has a dynamically assigned internal IP. The 'owner' module of iptables can be used to match packets from specific users, and iproute2 can be used for marking outgoing packets when they arrive at the firewall. So I added another routing table called ltegw
(201) which will be used for packets marked 0x1:
$ cat /etc/iproute2/rt_tables
201 ltegw
And added ip rule so all marked packets will go to the new routing table:
$ sudo ip rule add from all fwmark 1 table 201
$ ip rule
1: from all lookup local
32765: from all fwmark 0x1 lookup 201
32766: from all lookup main
32767: from all lookup default
$ sudo ip route add 0.0.0.0/0 dev wwp42s0f3u2i12 table 200
$ ip r
default via 10.100.0.1 dev enp35s0f0 proto dhcp metric 101
default via 10.181.223.216 dev wwp42s0f3u2i12 proto static metric 700
10.100.0.0/24 dev enp35s0f0 proto kernel scope link src 10.100.0.230 metric 101
10.181.223.208/28 dev wwp42s0f3u2i12 proto kernel scope link src 10.181.223.215 metric 700
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
Next, I added a new iptables OUTPUT rule to mangle table so that packets from UID 1001 (proxy) are marked with 0x1 as they pass through the firewall:
$ sudo iptables -A OUTPUT -t mangle -j MARK --set-mark 1 -m owner --uid-owner proxy
$ sudo iptables -L OUTPUT -t mangle --line-numbers
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
1 MARK all -- anywhere !anywhere owner UID match proxy MARK set 0x1
2 MARK all -- anywhere anywhere owner UID match proxy MARK set 0x
After this, I see requests from the proxy user get sent on wwp42s0f3u2i12
and a response is successfully received:
$ docker run --rm -it -u 1001:1001 --network host alpine wget -q -O - ifconfig.io/ip
175.223.10.145
# simultaneous session
$ sudo tcpdump -nni wwp42s0f3u2i12
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wwp42s0f3u2i12, link-type EN10MB (Ethernet), capture size 262144 bytes
09:00:01.409730 IP 10.181.223.215.54007 > 211.246.100.49.53: 26241+ A? ifconfig.io. (29)
09:00:01.409740 IP 10.181.223.215.54007 > 168.126.63.1.53: 26241+ A? ifconfig.io. (29)
09:00:01.409745 IP 10.181.223.215.54007 > 10.100.0.1.53: 26241+ A? ifconfig.io. (29)
09:00:01.409748 IP 10.181.223.215.54007 > 211.246.100.49.53: 26431+ AAAA? ifconfig.io. (29)
09:00:01.409750 IP 10.181.223.215.54007 > 168.126.63.1.53: 26431+ AAAA? ifconfig.io. (29)
09:00:01.409752 IP 10.181.223.215.54007 > 10.100.0.1.53: 26431+ AAAA? ifconfig.io. (29)
09:00:01.474017 IP 211.246.100.49.53 > 10.181.223.215.54007: 26241 3/2/12 A 104.24.123.146, A 172.67.189.102, A 104.24.122.146 (396)
09:00:01.480844 IP 168.126.63.1.53 > 10.181.223.215.54007: 26241 3/2/12 A 172.67.189.102, A 104.24.123.146, A 104.24.122.146 (396)
09:00:01.480848 IP 168.126.63.1.53 > 10.181.223.215.54007: 26431 3/2/12 AAAA 2606:4700:3030::ac43:bd66, AAAA 2606:4700:3034::6818:7b92, AAAA 2606:4700:3030::6818:7a92 (432)
09:00:01.480932 IP 10.181.223.215.45974 > 104.24.123.146.80: Flags [S], seq 21127835, win 64240, options [mss 1460,sackOK,TS val 1542252473 ecr 0,nop,wscale 9], length 0
09:00:01.483772 IP 211.246.100.49.53 > 10.181.223.215.54007: 26431 3/2/12 AAAA 2606:4700:3034::6818:7b92, AAAA 2606:4700:3030::6818:7a92, AAAA 2606:4700:3030::ac43:bd66 (432)
09:00:01.758615 IP 104.24.123.146.80 > 10.181.223.215.45974: Flags [S.], seq 2571105432, ack 21127836, win 65535, options [mss 1400,nop,nop,sackOK,nop,wscale 10], length 0
09:00:01.758703 IP 10.181.223.215.45974 > 104.24.123.146.80: Flags [.], ack 1, win 126, length 0
09:00:01.758758 IP 10.181.223.215.45974 > 104.24.123.146.80: Flags [P.], seq 1:77, ack 1, win 126, length 76: HTTP: GET /ip HTTP/1.1
09:00:02.079214 IP 104.24.123.146.80 > 10.181.223.215.45974: Flags [.], ack 77, win 64, length 0
09:00:02.079248 IP 104.24.123.146.80 > 10.181.223.215.45974: Flags [P.], seq 1:713, ack 77, win 64, length 712: HTTP: HTTP/1.1 200 OK
09:00:02.079269 IP 10.181.223.215.45974 > 104.24.123.146.80: Flags [.], ack 713, win 126, length 0
09:00:02.079279 IP 104.24.123.146.80 > 10.181.223.215.45974: Flags [F.], seq 713, ack 77, win 64, length 0
09:00:02.079358 IP 10.181.223.215.45974 > 104.24.123.146.80: Flags [F.], seq 77, ack 714, win 126, length 0
09:00:02.400265 IP 104.24.123.146.80 > 10.181.223.215.45974: Flags [.], ack 78, win 64, length 0
And from what I mean to do, I modified sockd.conf to make sure Dante uses proxy
user:
$ cat /etc/sockd.conf
logoutput: stderr
internal: 127.0.0.1 port = 9898
external: wwp42s0f3u2i12
method: pam none
clientmethod: none
user.notprivileged: proxy
logoutput: stdout
client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
}
pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
protocol: tcp udp
log: connect disconnect
}
Finally, I get:
$ curl --proxy 'socks5h://127.0.0.1:9898' https://ifconfig.io/ip
175.223.10.145