1. Overview

In this tutorial, we’ll learn about different tools in Linux that we can use for generating TCP traffic.

2. TCP Client

Transmission control protocol (TCP) is a transport layer protocol that provides reliable and connection-oriented communication between hosts over the network. Due to its ability to guarantee the delivery of a packet, many high-level protocols that require a strict guarantee of delivery use TCP as their transport layer protocol. These protocols include HTTP, RESP, and SSH.

Generally speaking, any command-line tool that’s capable of generating traffic that uses TCP as its transport layer protocol can be considered a TCP traffic generator. For example, the wget command-line tool is capable of generating HTTP requests and uses TCP as its transport layer protocol.

However, the scope of this article is to explore command-line tools that allow us to craft and generate TCP packets on the transport layer directly. In this sense, the wget command-line tool doesn’t qualify as a TCP client. This is because the options on the command only concern HTTP and not TCP.

These TCP clients aid in diagnosing any network connection issue. For example, an HTTP server might not respond to an HTTP request even when the network connectivity is intact. With the help of lower-level tools, we can quickly eliminate the possible cause of the issue we’re seeing.

Besides that, the port scanning functionality offered by most of the TCP client tools is useful in discovering any unexpected opened ports.

3. A Simple TCP Listener

Before we start looking into the different TCP clients, let’s set up some TCP listeners. With an actual TCP listener, we can observe in real time the interactions between the TCP traffic generator and the listener.

We’ll use the ncat command to create a minimal working server that listens to TCP connections:

$ ncat -l 8888 -k -c "printf 'HTTP/1.1 200 OK\n\nServer 1 date: $(date)\n\n'"

The command above starts an ncat process that listens to port 8888. Then, on each connection, we instruct the process to run the command printf ‘HTTP/1.1 200…’ using the -c option.

The content we return on each connection mimics an HTTP server that returns a response on each connection. Finally, the -k option configures the server to accept multiple connections simultaneously.

Then, on a new terminal session, we start another process that listens to TCP packets on port 8887:

$ ncat -l 8887 -k -c "printf 'HTTP/1.1 200 OK\n\nServer 2 date: $(date)\n\n'"

These two servers can help us in some of the later demonstrations, which involve sending traffic to multiple ports.

We can quickly test out the server by sending an HTTP request to the port using the curl command:

$ curl http://localhost:8888
Server 1 date: Sat Jan 20 03:49:14 GMT 2024

Notably, the reason we’re not seeing the HTTP/1.1 200 OK from the response is because that’s the HTTP protocol specification that’s interpreted by the curl command.

4. Sending Traffic With the nc Command

The nc command is a versatile TCP UDP connection command. Specifically, the nc command is capable of handling the TCP connection procedure and establishing a connection to TCP servers.

Besides acting as the client, the nc command can also be used to start a process to listen to TCP traffic, very much like the ncat command we’ve seen in the previous section.

4.1. Connecting to the TCP Server

Using the nc command, we can establish a TCP connection to a server. Specifically, we pass the hostname and port number as positional arguments one and two, respectively, to the nc command:

$ nc localhost 8888
HTTP/1.1 200 OK

Server 1 date: Sat Jan 20 03:49:14 GMT 2024

The command above sends a connection request to the TCP server that’s listening on port 8888 on localhost. After the connection is established, we’re greeted with a message from the server.

One thing that stands out is the fact that we’re now seeing the HTTP headers. This is because the nc is a transport layer client and doesn’t interpret the HTTP headers. In other words, to the nc command, those HTTP headers are part of the TCP payload and don’t carry any special meaning.

Additionally, we can see that the command doesn’t return immediately. This is because, in TCP, both the client and server need to send the FIN packet to close the connection.

4.2. Terminating the Connection

To send a FIN packet back to the server, we can close nc‘s standard input using CTRL+D. Alternatively, we can instruct nc to automatically close the connection after the server responds using the -d option:

$ nc -d localhost 8888

With the -d option, the nc command automatically sends a FIN packet after the connection is successfully established.

4.3. Sending Traffic to Multiple Ports

We can send TCP connection requests to multiple ports on the same host by specifying a range of port numbers:

$ nc -v -d localhost 8885-8890
nc: connect to localhost (127.0.0.1) port 8885 (tcp) failed: Connection refused
nc: connect to localhost (127.0.0.1) port 8886 (tcp) failed: Connection refused
Connection to localhost (127.0.0.1) 8887 port [tcp/*] succeeded!
HTTP/1.1 200 OK

Server 2 date: Sat Jan 20 04:14:57 GMT 2024

Connection to localhost (127.0.0.1) 8888 port [tcp/*] succeeded!
HTTP/1.1 200 OK

Server date: Sat Jan 20 03:49:14 GMT 2024

nc: connect to localhost (127.0.0.1) port 8889 (tcp) failed: Connection refused
nc: connect to localhost (127.0.0.1) port 8890 (tcp) failed: Connection refused

In the command above, we instruct nc to send a TCP connection request to the ports between 8885 and 8890, inclusive. Additionally, we’ve passed in the -v option to check the response from other ports in the range.

From the output, we can see that only ports 8888 and 8887 respond to the connection requests, whereas the rest of the ports in the range simply refuse the connection.

4.4. Port Scanning

Typically, we’re only interested in whether the ports are open and not what they return in a port scanning exercise. For this purpose, we can use the -z option, which simply tests if a port accepts a TCP connection without checking its response.

Behind the scenes, the nc command sends out a SYN packet to the target port and considers it open if the port replies with an ACK packet.

Let’s run the nc -z command on the port range 8885 to 8890 again:

$ nc -v -z localhost 8885-8890
...
nc: connect to localhost (127.0.0.1) port 8886 (tcp) failed: Connection refused
Connection to localhost (127.0.0.1) 8887 port [tcp/*] succeeded!
Connection to localhost (127.0.0.1) 8888 port [tcp/*] succeeded!
nc: connect to localhost (127.0.0.1) port 8889 (tcp) failed: Connection refused
...

As expected, only connection requests to ports 8887 and 8888 succeeded.

5. Sending Traffic With the hping3 Command

The hping3 command is a much more advanced transport layer client. Similar to the ping command, it can send packets to the target and provide diagnostic information. However, the hping3 command can also send TCP and UDP packets in contrast to the ping command, which sends the ICMP echo packet.

When compared to the nc command, hping3 offers more options for tweaking the TCP packets. Let’s look at some examples.

5.1. Installation

To obtain the hping3 command, we’ll need to install the hping3 package using our package manager on the system. For example, in Ubuntu Linux, we can use the apt-get install command:

$ apt-get install -y hping3

To verify the installation, run the hping3 –version command and check if it runs successfully:

$ hping3 --version
hping3 version 3.0.0-alpha-2

5.2. Basic Usage

To ping a target host with a TCP packet, we pass the hostname as the first positional argument to the hping3 command:

$ hping3 -S -p 8888 localhost

The command above specifies the port to ping against using the -p option. Then, the -S option sends the target with the SYN packet to check if the port is open.

5.3. Port Scanning

To perform port scanning over a range of ports, we can use the –scan option of the hping3 command followed by the port ranges.  For example, we can scan the port range 8885 to 8890 on localhost:

$ hping3 --scan 8885-8890 -S localhost
Scanning localhost (127.0.0.1), port 8885-8890
6 ports to scan, use -V to see all the replies
+----+-----------+---------+---+-----+-----+-----+
|port| serv name |  flags  |ttl| id  | win | len |
+----+-----------+---------+---+-----+-----+-----+
 8887            : .S..A...  64     0 65495    44
 8888            : .S..A...  64     0 65495    44
All replies received. Done.

Let’s break down the output from the command.

First, the command tells us there are six ports in total to scan. Then, in the table that follows, we can see that only ports 8887 and 8888 responded to the connection requests.

For those ports in the range that do not show up in the table, it means the connection requests failed.

5.4. Flooding

The hping3 has a –flood option flag that, when specified, can simulate the SYN flood attack:

$ hping3 -S --flood -p 8888 localhost

In flood mode, hping3 generates a massive number of SYN packets to the target. This functionality allows us to evaluate the resiliency of the system against a potential SYN flood attack.

Notably, the hping3 command will not collect the response statistics when operating in flood mode. Therefore, the packet loss statistics will always show 100% packet loss in flood mode.

5.5. TCP Packet Crafting

The hping3 command offers various options for tweaking the TCP packets. For example, the –win option allows us to set the window size to other values:

$ hping3 -S --win 512 -p 8888 localhost

Besides that, we can set various TCP flags using different options. For example, the -S option we’ve been using sets the SYN TCP flag on our packet. To set the FIN flag, we can use the -F option. For the complete list of options for setting different flags, we can refer to the hping3 man page.

5.6. Tweaking TCP Packet Fields

Another powerful feature of the hping3 command is that it offers options that allow us to mess with the TCP packet fields. These options allow us to study the behavior of the system when it encounters packets with invalid values.

For example, in each TCP packet, there’s a checksum field that tells the receiver if the packet they receive is corrupted or not. We can craft a TCP packet with an invalid checksum value by using the hping3 command with the -b option:

$ hping3 -b -p 8888 localhost

This option allows us to study how the receiver would deal with a TCP packet that has an invalid checksum value. Typically, the receiver discards TCP packets if they fail the checksum verification.

Besides that, we can use the -O option to set the data offset value in an attempt to craft a bad TCP packet. Furthermore, the -M and -L options allow us to set the TCP sequence number and TCP ack number, respectively.

All these options provide a way for us to craft and send a malformed TCP packet, which can help us test the resiliency of the target.

6. Conclusion

In this article, we’ve demonstrated two different command-line tools in Linux for generating TCP traffic.

We first explained how TCP clients allow us to craft and send TCP packets to receivers for various purposes. Then, we demonstrated the steps for setting up a simple TCP server that listens to TCP traffic.

Next, we introduced the nc command as a TCP client tool that can send TCP packets to a target. We demonstrated its basic usage, as well as its capability to perform port scanning.

Finally, we demonstrated various advanced features of hping3 on top of its basic usage. Specifically, we showed that hping3 is capable of tweaking the various fields in the TCP packets that would otherwise be impossible when using the nc command.