clash-rs icon indicating copy to clipboard operation
clash-rs copied to clipboard

Feat: transparent proxy support (linux only)

Open VendettaReborn opened this issue 1 year ago • 4 comments

🤔 This is a ...

  • [x] New feature
  • [ ] Bug fix
  • [ ] Performance optimization
  • [ ] Enhancement feature
  • [ ] Refactoring
  • [ ] Code style optimization
  • [ ] Test Case
  • [ ] Branch merge
  • [ ] Workflow
  • [ ] Other (about what?)

🔗 Related issue link

https://github.com/Watfaq/clash-rs/issues/73

💡 Background and solution

on linux, the system provides a native way of handling the global proxy: IP_TRANSPARENT & tproxy. compared with tun, it has a better performance, since it will not need one more traverse of network stack. some introductions and documents can be found bellow:

there are 3 main parts of this PR:

  1. support get_orig_dst option for udp socket
  2. utilize the iptables to redirect the socket to tproxy listener
  3. use so_mark and policy routing to avoid the endless loop

i have test this PR on my linux machine, but the compatibility tests with other tools that depend on iptables, like docker, wireguard haven't been done

to be discussed

  1. should we support nftables as well?
  2. should we support IP_RECVTOS option in socket?
  3. can we provide a better experience, to avoid the conflict in iptables' rules?

📝 Changelog

Support Transparent proxy on linux(Ipv4 only)

☑️ Self-Check before Merge

⚠️ Please check all items below before requesting a reviewing. ⚠️

  • [ ] Doc is updated/provided or not needed
  • [ ] Changelog is provided or not needed

VendettaReborn avatar Mar 27 '24 14:03 VendettaReborn

i find this post is useful https://powerdns.org/tproxydoc/tproxy.md.html

ibigbug avatar Apr 03 '24 14:04 ibigbug

this is the iptables created by the tproxy

-> % sudo iptables -L -t mangle
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
CLASH_TPROXY_PREROUTING  tcp  --  anywhere             anywhere
CLASH_TPROXY_PREROUTING  udp  --  anywhere             anywhere

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
CLASH_TPROXY_OUTPUT  tcp  --  anywhere             anywhere
CLASH_TPROXY_OUTPUT  udp  --  anywhere             anywhere

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

Chain CLASH_TPROXY_OUTPUT (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             0.0.0.0/8
RETURN     all  --  anywhere             10.0.0.0/8
RETURN     all  --  anywhere             100.64.0.0/10
RETURN     all  --  anywhere             127.0.0.0/8
RETURN     all  --  anywhere             link-local/16
RETURN     all  --  anywhere             172.16.0.0/12
RETURN     all  --  anywhere             192.0.0.0/24
RETURN     all  --  anywhere             192.0.2.0/24
RETURN     all  --  anywhere             192.88.99.0/24
RETURN     all  --  anywhere             192.168.0.0/16
RETURN     all  --  anywhere             198.18.0.0/15
RETURN     all  --  anywhere             198.51.100.0/24
RETURN     all  --  anywhere             203.0.113.0/24
RETURN     all  --  anywhere             base-address.mcast.net/4
RETURN     all  --  anywhere             240.0.0.0/4
RETURN     all  --  anywhere             255.255.255.255
RETURN     all  --  anywhere             anywhere             mark match 0xff
RETURN     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL
RETURN     all  --  anywhere             anywhere             ADDRTYPE match dst-type BROADCAST
MARK       tcp  --  anywhere             anywhere             MARK set 0x1
MARK       udp  --  anywhere             anywhere             MARK set 0x1

Chain CLASH_TPROXY_OUTPUT_DIVERT (0 references)
target     prot opt source               destination
MARK       all  --  anywhere             anywhere             MARK set 0x1
ACCEPT     all  --  anywhere             anywhere

Chain CLASH_TPROXY_PREROUTING (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             0.0.0.0/8
RETURN     all  --  anywhere             10.0.0.0/8
RETURN     all  --  anywhere             100.64.0.0/10
RETURN     all  --  anywhere             127.0.0.0/8
RETURN     all  --  anywhere             link-local/16
RETURN     all  --  anywhere             172.16.0.0/12
RETURN     all  --  anywhere             192.0.0.0/24
RETURN     all  --  anywhere             192.0.2.0/24
RETURN     all  --  anywhere             192.88.99.0/24
RETURN     all  --  anywhere             192.168.0.0/16
RETURN     all  --  anywhere             198.18.0.0/15
RETURN     all  --  anywhere             198.51.100.0/24
RETURN     all  --  anywhere             203.0.113.0/24
RETURN     all  --  anywhere             base-address.mcast.net/4
RETURN     all  --  anywhere             240.0.0.0/4
RETURN     all  --  anywhere             255.255.255.255
LOG        all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL LOG level warning prefix ""LOCAL IN""
RETURN     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL
RETURN     all  --  anywhere             anywhere             mark match 0xff
TPROXY     tcp  --  anywhere             anywhere             TPROXY redirect 0.0.0.0:7893 mark 0x1/0x1
TPROXY     udp  --  anywhere             anywhere             TPROXY redirect 0.0.0.0:7893 mark 0x1/0x1

ibigbug avatar Apr 08 '24 08:04 ibigbug

to be discussed

1. should we support nftables as well? - let's only do iptables for now, it would be rather straighforward to impl using other commands when anyone requests.
2. should we support IP_RECVTOS option in socket? - is this for us to do advanced routing in the app or there's any other benefits to let the kernel do optimization? not very sure.
3. can we provide a better experience, to avoid the conflict in iptables' rules? - i think managing users' firewall rules can be tricky, but you currently have a name space for each chain which i think should be fine. some edge cases that i can think of is you are doing append here rather than insert which might be a lower match, but still I consider this to be fine until we hit a real issue

ibigbug avatar Apr 08 '24 08:04 ibigbug

have tried this out on my box and it's working as expected!

overall looks good to me.

would be great please see the comments and clean up the code a bit esp the #[allow(warning)] dead_code related things.

i will try my best to resolve some trivial issues and warnings here.

VendettaReborn avatar Apr 09 '24 11:04 VendettaReborn

@VendettaReborn What's still missing for this to merge?

ibigbug avatar Sep 11 '24 13:09 ibigbug

I created a new on on top of this without the iptables integration since the code base has changed a lot and it takes some effort to rebase #615

ibigbug avatar Sep 30 '24 15:09 ibigbug