sorz / moproxy

A transparent TCP to SOCKSv5/HTTP proxy on Linux written in Rust.
MIT License
223 stars 35 forks source link

Basic support for freebsd ipfw firewall #21

Open stuart-mclaren opened 1 year ago

stuart-mclaren commented 1 year ago

FreeBSD's ipfw firewall does not rewrite the destination IP address when forwarding using rules such as:

00100 fwd 127.0.0.1,2080 tcp from me to not me 80,443

This commit adds ipfw support by assuming transparent redirection on freebsd. If you create the above rule and point moproxy at a http proxy at IP address 1.2.3.4

$ moproxy -b 127.0.0.1 -p 2080 -t 1.2.3.4:8080

You can fetch remote 443 or 80 ports without setting proxy environment variables, eg:

$ curl https://example.com:443

...
sorz commented 1 year ago

In current design, the TCP port for transparent proxy also serve as SOCKSv5 server. Is it possible to distinguish connections that redirected by firewall and connections that direct to the port in FreeBSD? If it's a direct connection, pass it to SOCKS handshaking procedure.

stuart-mclaren commented 1 year ago

Thanks for your feedback.

Looking at the ipfw man page (https://www.freebsd.org/cgi/man.cgi?ipfw(8))

For packets forwarded locally, the local address of the socket will be set to the original destination address of the packet.

I think this means that, in principle, we can distinguish between firewall redirected packets and direct connections.

Adding some debug to the code

    while let Some(sock) = clients.next().await {
        println!("sock is {:?}", sock);

and running

$ curl 216.58.193.142:80

produced this log output

sock is Ok(PollEvented { io: Some(TcpStream { addr: 216.58.193.142:80, peer: 10.0.0.1:62950, fd: 10 }) })
listeners: 127.0.0.1:2080 # Address for listeners[0].0.local_addr().unwrap())

Note: addr has been set to 216.58.193.142:80 as per the man page.

This is different to a direct connection, eg

$ curl localhost:2080
sock is Ok(PollEvented { io: Some(TcpStream { addr: 127.0.0.1:2080, peer: 127.0.0.1:10825, fd: 10 }) })
listeners: 127.0.0.1:2080 # unchanged

Note that addr is unchanged (127.0.0.1:2080) because it hasn't been affected by the firewall.

So (hopefully) something like this would work (pseudo code):

 if not (listener.ipv4_addr == sock.ipv4_addr and listener.port == sock.port) {
   // ipfw NAT detected
 }
sorz commented 1 year ago

But if it's bind on 0.0.0.0 or :: rather than a specific address…

I start feeling that reusing the socket for SOCKS proxy is a mistake from the beginning. I should at least give a CLI switch for it.