Does TCP have to be TCP?It might be a trick!
It's raining so hard that you can write a joke.
I wrote two essays about this article the other day:
It is very complex to want packets to be transmitted and processed as TCP.
However, TCP is an end-to-end stateful protocol, which means that intermediate forwarding devices cannot handle the details of TCP. If the end system does not need to handle the details of TCP, a stream just needs to make the intermediate forwarding devices look like TCP!
When does the end system not need to handle TCP details, and why should a stream that is not TCP look like TCP?
We need to understand some behavior of intermediate forwarding devices.Intermediate forwarding devices take actions against TCP, such as:
- Packet dropout limits for protocols other than TCP during peak traffic periods (excessive TCP packet dropouts can cause extreme reactions to blind CC algorithms on the end system and exacerbate network congestion).
- Limit the speed of a single stream according to the TCP quaternion.
- Single stream is reshaped according to TCP quaternion.
These actions are based on the following facts:
- Behavior eventually acts on end-to-end systems, which correctly handle TCP details and converge the network.
Instead of limiting, we can benefit from these actions by ignoring end-to-end processing:
- Encapsulate a TCP header for UDP to avoid being speed-limited during peak times.
- Encapsulate different quaternion TCP headers for the same stream without being speed-limited.
- Encapsulates TCP headers of different quaternions for the same stream without being shaped.
This paper presents a POC that disguises any traffic as TCP traffic.I call this a dummy TCP tunnel.
I wanted to do it with Netfilter, but I was tired of writing kernel modules. I wanted to do it with eBPF, but I thought it was a bit of an uproar, so I did it with scapy+tun because it was simple!
Here is the dummy TCP tunnel python code (data-side only):
#!/usr/bin/python # dtun.py from scapy.all import * import socket import fcntl IFF_TUN = 0x0001 IFF_NO_PI = 0x1000 TUNSETIFF = 0x400454ca src = '0.0.0.0' peer = '0.0.0.0' sport = 0 dport = 0 seq = 12345 # Should have been random, but for simplicity's sake ack_seq = 12345 # Should have been random, but for simplicity's sake # Receive dummy TCP tunnel encapsulated message from network, directly remove TCP header, send to tun network card def net2tun(packet): global ack_seq, src, peer, sport, dport flags = packet[TCP].flags # Processing if branch of analog SYNACK if flags & 0x02 != 0 and flags & 0x10 == 0: ip = IP(src = src, dst = peer) # Receive SYN packet, get ack, reply directly to SYNACK tcp = ip/TCP(sport = sport, dport = dport, flags = "SA", seq = seq, ack = ack_seq) ack_seq = packet[TCP].seq send(tcp) # if branch handling normal communication elif flags & 0x02 == 0: os.write(tun.fileno(), str(packet[TCP].payload)) ack_seq = packet[TCP].seq + len(packet[TCP].payload) def recv(): filter = "src " + peer + " and tcp and tcp port 12345" sniff(filter = filter,iface="enp0s8", prn = net2tun) # Simulates a TCP handshake, which mainly negotiates serial numbers and allows intermediate state devices to initialize stream tables. def handshake(src, dst): global seq, ack_seq, sport, dport ip = IP(src = src, dst = dst) tcp = ip/TCP(sport = sport, dport = dport, flags = "S", seq = seq) pkt = sr1(tcp, iface = "enp0s8") ack_seq = pkt[TCP].ack print pkt[TCP].seq print pkt[TCP].ack # Send to network after encapsulating TCP headers directly def tun2net(src, dst, payload): global seq, ack_seq ip = IP(src = src, dst = dst) tcp = ip/TCP(sport = sport, dport = dport, flags = "A", seq = seq, ack = ack_seq)/payload seq += len(payload) send(tcp) if __name__ == "__main__": global peer tunip = sys.argv peer = sys.argv src = sys.argv syn = sys.argv dport = int("12345") sport = int("12345") tun = open('/dev/net/tun', 'r+w') iface = "tun0" # Open and set tun device ifr = struct.pack('16sH', iface, IFF_TUN|IFF_NO_PI) fcntl.ioctl(tun, TUNSETIFF, ifr) ip = socket.inet_aton(tunip) sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0); ifr = struct.pack('16sH2s4s8s', iface, socket.AF_INET, '\x00'*2, ip, '\x00'*8) fcntl.ioctl(sockfd, 0x8916, ifr) ifr = struct.pack('16sH2s4s8s', iface, socket.AF_INET, '\x00'*2, socket.inet_aton("255.255.255.0"), '\x00' * 8) fcntl.ioctl(sockfd, 0x891C, ifr) ifr = struct.pack('16sH', iface, IFF_UP) fcntl.ioctl(sockfd, SIOCSIFFLAGS, ifr) try: threading.Thread(target = recv).start() if syn == "1": handshake(src, peer) # Receives bare packets from tun devices, encapsulates TCP headers, and sends directly to dummy TCP tunnels while True: packet = os.read(tun.fileno(), 2048) tun2net(src, peer, packet) except KeyboardInterrupt: os._exit(0)
See the effect.
Prepare two virtual machines, hostA and hostB, connect directly, and configure as follows:
# hostA enp0s8:192.168.56.110/24 # hostB enp0s8:192.168.56.101/24
The following scripts are launched on hostA and hostB respectively:
# hostB (Start B first because it waits to receive SYN) root@zhaoya-VirtualBox:/home/zhaoya/tun# ./dtun.py 18.104.22.168 192.168.56.110 192.168.56.101 0 # hostA [root@localhost tun]# ./dtun.py 22.214.171.124 192.168.56.101 192.168.56.110 1
The tunnels for both machines are now set up. Since there is no real TCP connection, two additional filtering rules need to be added on hostA and hostB to avoid end-to-end RST:
iptables -t mangle -A POSTROUTING -p tcp -m tcp --tcp-flags RST RST -j DROP
OK, now you can experiment.
Address of tun network card pinging hostB on hostA:
[root@localhost ~]# ping 126.96.36.199 -c 2 PING 188.8.131.52 (184.108.40.206) 56(84) bytes of data. 64 bytes from 220.127.116.11: icmp_seq=1 ttl=64 time=42.1 ms 64 bytes from 18.104.22.168: icmp_seq=2 ttl=64 time=50.2 ms --- 22.214.171.124 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 999ms rtt min/avg/max/mdev = 42.196/46.243/50.290/4.047 ms
tcpdump captures the following:
[root@localhost tun]# tcpdump -i enp0s8 tcp port 12345 -n tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on enp0s8, link-type EN10MB (Ethernet), capture size 262144 bytes # Looks like a TCP handshake package 06:24:40.483759 IP 192.168.56.110.12345 > 192.168.56.101.12345: Flags [S], seq 12345, win 8192, length 0 06:24:40.522107 IP 192.168.56.101.12345 > 192.168.56.110.12345: Flags [S.], seq 12889, ack 12345, win 8192, length 0 # Oh, the 3rd ACK is missing, well, it needs to be improved... 06:24:46.525279 IP 192.168.56.101.12345 > 192.168.56.110.12345: Flags [.], seq 0:48, ack 1, win 8192, length 48 06:24:46.989491 IP 192.168.56.110.12345 > 192.168.56.101.12345: Flags [.], seq 1:85, ack 48, win 8192, length 84 06:24:47.027375 IP 192.168.56.101.12345 > 192.168.56.110.12345: Flags [.], seq 48:132, ack 85, win 8192, length 84 06:24:47.989780 IP 192.168.56.110.12345 > 192.168.56.101.12345: Flags [.], seq 85:169, ack 132, win 8192, length 84 06:24:48.034897 IP 192.168.56.101.12345 > 192.168.56.110.12345: Flags [.], seq 132:216, ack 169, win 8192, length 84
TCP streaming, as it is...
In fact, we have not established any TCP connection, which is false.
When an intermediate device sees such a packet, it thinks it comes from a real TCP connection. When it wants to restrict the flow, it performs queuing, dropping packets, and so on, in order to expect end-to-end TCP cc algorithms to sense this repression by measuring RTT, detecting packet dropouts, and so on, and make gentle concessions.However, there is no end-to-end TCP processing at all and they are all installed.
Now see what you can do further.
If I set up 10 such dummy TCP tunnels with 10 different source addresses, then the transit package of the tunnel only needs to pick one of the 10 tunnels at a time.In theory, this scheme can increase throughput 10 times if the intermediate device limits TCP single-stream speed!
Well, you might say that an intermediate device can limit the speed of an IP segment. That's not impossible either. As long as one end of the tunnel has a loudspeaker opening, you can use that end to simulate a SYN to build multiple tunnels:
Many years ago, when I played OpenU++Q-N, I kept shouting that TCP tunnels would cause connection crashes, and I kept shouting that UDP tunnels should be used. Well, at that time, I should actually play like this. I didn't think of these techniques, perhaps because the financial network is a quasi-internal network, all aspects of QoS are guaranteed, and there are not too many restrictions on UDP, ifAt that time, the production environment was switched to the public Internet, and trick was probably already on the line by me.
Left and right hands fight, one foot tall, the Devil is very tall, hit his face, then grab the manager's lead and push him into the ditch.
Zhejiang Wenzhou leather shoes are wet, so you won't get fat when it rains.