9

Forcefully Close TCP Connections in Golang

 3 years ago
source link: https://itnext.io/forcefully-close-tcp-connections-in-golang-e5f5b1b14ce6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Forcefully Close TCP Connections in Golang

Working with TCP servers is a great way to become familiar with low-level network communications. When writing applications that communicate directly over TCP sockets a multitude of socket management steps are required. Actions that you typically don’t have to take for a typical HTTP-based application.

Sometimes, those extra management steps include having to close a TCP session forcefully.

Today’s article will discuss two ways to close TCP sessions; the traditional default close and a forceful close using the SetLinger() method. This article will also explain why these are different and when someone should use each method.

A basic TCP server in Go

The programming language we will use in this article will be Golang, but the concepts discussed are universal and apply to networking as a whole.

Below is an elementary TCP server in Go. This server listens on a single port, echo’s back any data sent to it, and of course, to better showcase closing connections, it will close connections after a short period of time.

We can break down the code above into four main parts.

The first creates the net.Listener by starting to listen for TCP connections on Port 9000. Internally, Go is telling the system kernel to bind port 9000 on all of the available interfaces.

If successful, the code moves to part two, where the program waits for new TCP connections by calling the net.Listener.Accept() method. The Accept() method will wait until a new connection arrives and returns that connection as a net.Conn. From here, our program starts parts three and four.

In part three, our code starts a new goroutine; this goroutine takes the net.Conn referred to as c and starts reading and writing to the connection. For everything written to this TCP connection, this goroutine will write back to the same connection.

Part four is where most of our focus will be within this article. Here, our program creates yet another goroutine, but this goroutine is using time.After to wait for 15 seconds. Once those 15 seconds are over, our goroutine will call net.Conn.Close() via the defer function. Effectively, closing our TCP session from the server-side.

A basic TCP client in Go

To connect to our TCP server, we will be using the below client code, also written in Go.

Our client can be broken down into three distinct parts.

The first, where we use the net.Dial() method to open a TCP connection to the samelocalhost:9000 address our TCP server is listening on.

With the returned net.Conn, the second section is writing our sample message using the net.Conn.Write() method.

And finally, the third part is simply looping a net.Conn.Read() method to continuously read data sent from the TCP server.

Running our Client and Server

With our Client and Server ready to go, let’s look at what happens when we run them.

The below output is from our TCP server.

We can see from the above that our server saw an open TCP session, and after a little bit of time, output an error stating our server tried to read from a closed network connection.

This behavior is expected considering we have one goroutine reading from the TCP connection, and in another goroutine, we closed that connection.

So for this exercise, that error indicates we successfully closed the session on the server-side.

Now let’s take a look at the TCP client output.

From this output, we can see our TCP session was opened, and shortly after (matching the timestamp from the TCP server), we received an EOF error from the connection. Again, this is what we expect. Our client received an error type (io.EOF) that we would expect when the remote side nicely closed our TCP session.

Learning how net.Conn.Close() works

According to the Go Documentation, by default, after the net.Conn.Close() method is executed; in the background, the Operating System will finish sending any data and then close the TCP session.

What this means is, when we execute our net.Conn.Close() method, the TCP session we execute it against will start a connection termination sequence which includes handling (discarding) any outstanding data. That is, until we receive the final FIN-ACK packet.

To get a better picture of this behavior, let’s take a look at a network capture of the TCP client and server communication using the tcpdump command.

The tcpdump command is a handy tool when investigating network communications. It allows us to capture and see the network packets.

In the example above, I used the -i flag to specify my network interface as lo0 my loopback interface. And I used port 9000 to filter the network capture to only traffic that is talking to or coming from the port 9000.

On line 3, we can see that the client (source port 50796) sent a SYN packet (shown as Flags [S]) to port 9000, the TCP server. Over the next few lines, we can see the server send back a SYN-ACK (shown as Flags [S.]), and the client sends a ACK (shown as Flags [.]) acknowledging the SYN-ACK packet. Which then completes the 3-way TCP handshake.

From here, our client and server are sending data back and forth until line 11, where our TCP server sends a FIN packet to the TCP Client.

In the next two lines, we can see the TCP Client send another ACK followed by a FIN-ACK. To which the TCP server replies with an ACK acknowledging the FIN-ACK.

From the network capture, we can see how a standard TCP closure works. We can see that even though our program might have executed the net.Conn.Close() method, in the background on the OS, the connection is still alive until we receive the final FIN-ACK packet.

Forcefully close TCP sessions

While the above examples have shown the default behavior, performing a more “forceful” socket closure is possible. In Go, we can control this behavior using the net.TCPConn.SetLinger() method.

This method is changing the SO_LINGER socket option value using system calls against the Operating System.

To show how all this works, let’s make some simple modifications to our original TCP server.

For the most part, this version of the TCP server is the same as the previous, with one key difference. Before running net.Conn.Close() on line 59 our application is executing net.TCPConn.SetLinger() passing the value 0 to the method.

We define the operating systems' behavior when closing the TCP connection by passing a value to thenet.TCPConn.SetLinger() method.

The value passed can be thought of as a timer value in seconds.

When passing anything greater than 0, in some Operating Systems, this will cause the connection to act in the same way as the default behavior except that after the defined number of seconds, any outstanding traffic is rejected.

When set to exactly 0, the Operating System will immediately close the connection and drop any outstanding packets. To better understand what is happening, let’s look at this example using our still running tcpdump command.

With the above output, we can once again see a TCP session is established with a SYN, SYN-ACK, and ACK 3-way handshake. And, we can also see the closure in this capture, but this time, the closure process looks quite a bit different.

Rather than sending a FIN packet, the TCP server sent a RST (shown as Flags [R.]) packet to the TCP client.

This is the last packet in the conversation; there is no acknowledgment in this flow. Which is by design.

A RST packet is a special type of packet used for “resetting” TCP connections. It is a way for the sender to tell the remote side that it will neither accept nor receive new data for this connection. There is no need for the TCP client to acknowledge this closure, as the TCP server will reject all data received.

Summary

At this point, we’ve explored the default process of closing a TCP session and show the process of forcefully closing a TCP Session.

We also took a deep dive into the underlying process within the Operating System and how the two methods differ at a network level.

Yet, we haven’t explored why we should know the difference and when each process should be used.

In general, as a TCP server, when closing a TCP session, it is best to use the default net.Conn.Close() process without changing any SO_LINGER options. This is true for most use cases like a timeout, in response to a final message, or any other typical behavior.

A RST should be reserved for when client behavior is non-typical or when all communication must be complete. An example could be an application with many, many short-lived connections.

When closing using the default method, each connection will go through a series of connection states such as FIN_WAIT_1, FIN_WAIT_2, TIME_WAIT, etc., leaving the connection lingering on the system for some time. Taking resources. Using a forceful closure closes those connections immediately, freeing up resources such as open files, etc.

Another example could be when a connection is opened against a port that is reserved or special. In these cases, it’s typical to use the RST packet to indicate undesired behavior.

For most applications, and closure scenarios the TCP client should initiate connection closure. So often TCP servers will use RST packets for special circumstances that require the server to initiate a connection closure.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK