Tearing down connections

Summary

Recently a colleague was debugging server software and was looking for a way to simulate dropped TCP connection. The easiest way to drop a TCP connection is terminating either the server or the client process – once the process is terminated, the operating system will “clean up” all resources used by the process, including open TCP connections.

In my colleague’s case, though, killing either a client or a server was not desirable. What she looked for was terminating a connection and having the client establish a new one.

The approach we took was crafting a “TCP reset” packet and sending it to a server. The server was led into belief that the client just hang up on it, and proceeded to establish a new connection, which was exactly what we were looking for.

The gritty details

The TCP protocol provides two ways of closing connections.  The normal way is requires each party (the client and the server) to send a FIN packet, which tells the other party (the server and the client, respectfully) that no more data is goign to come. By sending a FIN packet each way, the parties agree that the conversation is done, and all that was to be said was.

This rule, called “the 4-way teardown” exists to ensure that the nice abstraction of TCP connection is maintained over the messy world of retransmits, delays and dropped bits, which is common in networking.

Every rule has its exception, though. In the case of “send a FIN after you’re done talking” rule, the exception is RST, which is the equivalent of “shut the flock up“. An RST means that someone received data but was not ready for that data to be received.

RST packet was exactly what we were looking for. So the first question was: Where to send this packet?

Each TCP connection has two endpoints, described by the IP address (that is the machine, or rather the network card on the machine) and the TCP port number (this is a channel on the local machine).

The TCP port number on a server is well-known and is advertised as part of the server’s URL. A client, on the other hand, picks up a random (ephemeral in TCP jargon) TCP port number.

A non-obvious design choice is to send the RST packet to a server, and not to a client. This allows us to use the well-known port number on the server, rather than the one on the client.

Once the endpoint is identified, the task is to send a TCP RST segment to the port identified by the IP address and the TCP port. While historically crafting TCP segments is being done by opening a RAW socket and crafting the bits and the checksums of the segment, we used the “hping” utility for that.

The first version of the script did not work, as we missed an important caveat: the sequence numbers had to line up.

TCP protocol ensures integrity of transmitted data by associating position in the stream with every chunk of data it transmits.  If a chunk data has to be re-sent, the stream position will be re-sent along the data, so that the receiving end will know what part of data is being resent.

The TCP protocol calls these position identifiers “sequence numbers”. By default, a TCP endpoint will only accept packets that are in the expected range of sequence numbers, and will reject anything that would fall outside of the range.

These numbers do not have to be guessed, however. If packets are going to be injected from one of the endpoints, then one can intercept the connection and inject RST segment into a valid TCP window.

The following scriptlet does the work, provided you have tcpdump and hping installed on your system:

#!/usr/bin/env perl

use Getopt::Long ;

$DEV="" ;

$PORT=1899 ;

$ADDR="";

$DEBUG = 0 ;

$result = GetOptions( "port=i" => \$PORT,
		      "dev=s" => \$DEV,
		      "addr=s" => \$ADDR,
		      "dbg=i" => \$DEBUG) ;

chomp($DEV);

if ( $DEV eq "" && $ADDR eq "" ) {
	print "Usage: must specify either device or server address. Exiting\n " ;
	exit (1) ;
} elsif ( $DEV eq "" && $ADDR ne "") {
	open (NETSTAT_NR, "netstat -nr|");

	while (<NETSTAT_NR>) {
		my $nline = $_ ;
		if ( $nline =~ /(\w+\.\w+\.\w+\.\w+)\W+([\w\.\:\#]+)\W+(\w+)\W+(\d+)\W+(\d+)\W+(\w+).*/ ) {
			my ($route_dest,$route_gw, $route_flags,$route_refs,$route_use,$route_netif) =
				($1,$2,$3,$4,$5,$6) ;
			if ($DEBUG) {
				print "route dst: $route_dest\n";
				print "route gw: $route_gw\n";
				print "route flags: $route_flags\n";
				print "route refs: $route_refs\n";
				print "route use: $route_use\n";
				print "route netif: $route_netif\n";
				print " ----- \n\n";
			}
			chomp ($route_netif);
			if ( $ADDR eq $route_dest) {
				$DEV = $route_netif ;
				last ;
			}
		}
	}

	close(NETSTAT_NR);
}

print "DEV == $DEV\n" ;

$pkt_count=3; # number of RST packets to send

open (TCPDUMP, " tcpdump -l  -i ${DEV} -A -S -n tcp port ${PORT}  |") ;

print "Waiting for traffic on ${PORT}..." ;

while (<TCPDUMP>) {
	my ($line) = ($_);

	if ( $line =~ /\d+:\d+:\d+.\d+\WIP\W\d+\.\d+\.\d+\.\d+.(\d+) > (\d+\.\d+\.\d+\.\d+)\.(\d+): P (\d+):(\d+)\(\d+\) ack (\d+) win (\d+).*/) {

		my ($src_port,$dst_ip,$dst_port,$seq_num,$next_seq_num,$ack_num,$win) = ($1,$2,$3,$4,$5,$6,$7);

		if ( $DEBUG ) {
			print "src port: $src_portn";
			print "target ip: $dst_ip\n";
			print "dst port: $dst_port\n";
			print "seq num: $seq_num\n";
			print "next seq num: $next_seq_num\n";
			print "ack num: $ack_num\n";
			print "window: $win\n" ;
		}

		my $cmd = "hping -c $pkt_count -s $src_port -p $dst_port -M $next_seq_num -L $ack_num -R  $dst_ip" ;

		print "$cmd\n" ;

		system($cmd);

		last ; # exit the listen loop, we found our target
	}
}
END { close (TCPDUMP) ; }

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s