How can I read all incoming traffic that should get redirected by TPROXY?

160 Views Asked by At

I'm writing a UDP transparent proxy in Rust, I'm using several crates

the machine that is supposed to act as a proxy has been set as the gateway for the interested machines.

The proxy machine has the linux kernel redirection capabilities enabled, see snippet below

#!/bin/sh

# Redirect packets coming to the computer
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

# Disable ICMP redirection
sysctl -w net.ipv4.conf.all.send_redirects=0

The machine also has an NFTABLES rule set up to redirect all of the incoming traffic to the proxy program, see snippet below

#!/bin/sh

# Setting up NFTABLES

nft add table filter
nft add chain filter divert "{ type filter hook prerouting priority -150; }"
nft add rule filter divert meta l4proto udp socket transparent 1 meta mark set 1 accept

#nft add table ip nat
#nft -- add chain ip nat prerouting { type nat hook prerouting priority -100 \; }

# Creating a new rule that redirects UDP traffic from P1-P2 to port 35

nft add rule filter divert udp dport 80-65525 tproxy to :35 meta mark set 1 accept

#nft add rule ip nat prerouting udp dport 80-65525 redirect to :35

As you can see from the commented out parts, I've experimented with both NAT PREROUTING and TPROXY. After extensive research and testing I have found out that when I use NAT PREROUTING my proxy (that's listening on port 35) gets ALL of the incoming traffic from the clients but when I read the packets from the socket the original destination address get overwritten with the proxy machine's ip (which I don't want). But if I instead use TPROXY I don't get all of the incoming traffic, I only get what my machine is "supposed" to see, e.g. broadcast traffic and traffic that has the proxy machine's mac address as the destination, this means that I can't see all of the traffic from the connected clients.

I've followed the instructions given in the linux kernel's tproxy manual.

It has to be noted that I do set IP_TRANSPARENT.

These are the things that I set in my program

use socket2::{Socket, Domain, Type, Protocol};
use std::os::fd::{OwnedFd, RawFd, AsRawFd};
use nix::sys::socket::{
    ...
    sockopt::{IpTransparent, Ipv4OrigDstAddr, Ipv4PacketInfo},
    setsockopt,
    ...
};
...
let client_to_proxy_socket = match Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::UDP)) { ... };
let socket_ref: OwnedFd = client_to_proxy_socket.into();
let active: bool = true;
match setsockopt(&socket_ref, IpTransparent, &active) { ... };
match setsockopt(&socket_ref, Ipv4OrigDstAddr, &active) { ... };
match setsockopt(&socket_ref, Ipv4PacketInfo, &active) { ... };
...

When it comes to what I've tried, I must say that I've tried a lot of possible combinations of both rules and code with variations and different structs.

I'm also gonna post a short list of links that I've already visited and read multiple times

As for what I expect, I expect to get all of the incoming traffic and have that redirected by TPROXY to port 35.

This is the call to the recvmsg function

                    let target_address: SockaddrIn = match recvmsg::<SockaddrIn>(*socket_clone, &mut iov, Some(&mut cmsg_space!(nix_msghdr)), flags) {
                        Ok(t) => {
                            let ctrl_msg: Vec<ControlMessageOwned> = t.cmsgs().collect();

                            let mut res: SockaddrIn = SockaddrIn::new(0, 0, 0, 0, 0);

                            for c in ctrl_msg {
                                if let ControlMessageOwned::Ipv4OrigDstAddr(b) = c {
                                    let addr: [u8; 4] = convert_u32_to_4u8(b.sin_addr.s_addr);
                                    let port: u16 = b.sin_port;

                                    let new_port: u16 = ((port & (0xFF << 8)) >> 8) | ((port & 0xFF) << 8);

                                    res = SockaddrIn::new(addr[0], addr[1], addr[2], addr[3], new_port);
                                }

                                if let ControlMessageOwned::Ipv4PacketInfo(b) = c {
                                    debug!("IP_PKTINFO => {:?}", b);
                                }
                            }

                            res
                        },
                        Err(e) => {
                            error!("Could not extract the original destination from the socket ; Error => {}", e);

                            std::process::exit(15);
                        }
                    };

I know that someone is probably going to ask "Show us some more code", the code I believe to be relevant, I've posted it, the whole program is 400 lines long, I don't think SO allows me to post that many lines of code.

0

There are 0 best solutions below