I am trying to write a linux service that captures the packets that go to a specific destination and performs necessary changes to them. More specifically, i want to find all of the tcp segments that contain a TLS handshake payload and re-transmit them in very small fragments to overcome censorship based on SNI filtering.

Basically, there is a destination ip (lets call it 65.65.65.65) and our server has a static ip (lets call it 30.30.30.30), i want to capture all of the ip packets that go to 65.65.65.65 and check them out. I dont care at all about the response and it can go back to the origin process unscanned.

My problem is not the fragmentation now, but that the packets cannot be captured and re-transmitted (let’s assume that for now i don’t change the payload at all).

Up until now I have come up with this plan:

  1. set up a tun interface tun0 and assign the address 10.1.1.1/24 to it and set it up. (i am sure that this part is done correctly)
  2. iptables -t nat -A OUTPUT -d 65.65.65.65 -j DNAT --to-destination 10.1.1.4 all of the outgoing packets to the destination go to 10.1.1.4 instead; 10.1.1.4 is hardcoded to be associated with 65.65.65.65 in the tun application.
  3. iptables -t nat -A POSTROUTING -s 10.1.1.4 -j SNAT --to-source 30.30.30.30
  4. in the tun application, read ip packets from fd, change their destination to 65.65.65.65, their source to 10.1.1.4 and recalculate their checksums (ip header next protocol). in the end write the modified packet back to the tun fd.

This is how i think this works:

  • A program sends a tcp/ip packet to 65.65.65.65
  • conntrack sees this and updates it’s state (it thinks the connection is between 30.30.30.30:sport and 65.65.65.65:dport
  • nat table changes the destination to 10.1.1.4
  • packet is received by system router: destination is 10.1.1.4, route to tun0
  • app reads the packet from tun fd (RX)
  • app changes the destination to 65.65.65.65 and the source to 10.1.1.4
  • app writes the modified packet to tun fd (TX)
  • packet is received by system router: destination is 65.65.65.65, route to default
  • packet enters POSTROUTING chain: source is changed to 30.30.30.30
  • packet is received at 65.65.65.65 and the response is sent to 30.30.30.30
  • packet is received at 30.30.30.30 and the conntrack recognizes the connection and sends the response back to the original program.

However in reality, what i get is different.

When i test this the output from my app, along with the outout of tcpdump -i tun0 looks something like this:

... IP myhost.51440 > 10.1.1.4.10808: Flags [S], seq ##, win ## options [..] length 0
... IP 10.1.1.4.51440 > 65.65.65.65.10808: Flags [s], seq ##, win ## options [..] length 0
... IP myhost.51440 > 10.1.1.4.10808: Flags [S], seq ##, win ## options [..] length 0
... IP 10.1.1.4.51440 > 65.65.65.65.10808: Flags [s], seq ##, win ## options [..] length 0
... IP myhost.51440 > 10.1.1.4.10808: Flags [S], seq ##, win ## options [..] length 0
... IP 10.1.1.4.51440 > 65.65.65.65.10808: Flags [s], seq ##, win ## options [..] length 0

which is exactly what i was expecting except that its just repeating the SYN packet and never receiving the SYN/ACK

when i run tcpdump -i eth0 host 65.65.65.65 at the same time the output looks like this:

... IP 10.1.1.4.51440 > 65.65.65.65.10808: Flags [S], seq ##, win ## options [..] length 0
... IP 10.1.1.4.51440 > 65.65.65.65.10808: Flags [S], seq ##, win ## options [..] length 0
... IP 10.1.1.4.51440 > 65.65.65.65.10808: Flags [S], seq ##, win ## options [..] length 0

which shows that the tcp packet is being transmitted from the eth0 interface too! however the SYN/ACK is never received.

Up until here everything seems to go according to the plan but here is where two weird things happen:

First: when i run conntrack -L conntrack -p tcp the output includes this line:

tcp   6   91  SYN_SENT  src=30.30.30.30 dst=65.65.65.65 sport=51440 dport=10808 [UNREPLIED] src=10.1.1.4 dst=30.30.30.30 sport=10808 dport=51440 mark=0 use=1

which is weird because the two tuples are not symmetrical like the rest of the lines in the output.

Second and the one that is making me lose my mind: when i run wireshark on the destination device (65.65.65.65) i receive nothing. eventhough the output from tcpdump tells me that my modified packet has indeed been transmitted from eth0.

Can someone with more experience in working with netfilter tell me what exactly am i misunderstanding here? Why is conntrack behaving like this? why is tcpdump telling me that the packet is transmitted when i cant receive it on the destination device? Am i making the problem more difficult that it should be? I know that i’ve probably misunderstood somthing and am making a conceptual mistake, but ive spent too much time looking around the internet and reading random website and i still have no clue what and im becoming increasingly desperate.

Leave a Reply

Your email address will not be published. Required fields are marked *