Forcefully Close TCP Connections in Golang
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.
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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK