Controlling Socket Connections
When you're dealing with sockets and other network connections, you want to be selective about which hosts you accept connections from.
July 5, 2000
When you're dealing with sockets and other network connections, you want to be selective about which hosts you accept connections from. In some cases, you can assume that a firewall or packet-filtering router exists in front of your system (or if you’re running Windows 2000, IP Security—IPSec—filters can help restrict who can connect). Various OSs have different conventions for dealing with this situation. For example, UNIX systems typically run most services through a process named inetd, which accepts incoming connections and refuses any that the configuration file won't allow. Although this approach lets you configure socket-level security in one place, all your applications must work correctly with inetd. Because Windows-based applications typically don't work with an inetd process and because using multiple threads with Windows-based applications is typically more efficient than using multiple processes, the applications you write are on their own when it comes to restricting connections.
Let’s review how a typical TCP connection works. First, the client sends a packet with the SYN flag set to the listening port on the server. The server responds with a SYN-ACK (SYN acknowledgement) packet, and the client replies with an ACK to establish the connection. This process is known as a three-way handshake. The client and server can then send and receive data. Once they are done, the connection can gracefully shut down with both sides sending the other a FIN packet (the final response is a FIN-ACK). In all, the client and server must transfer seven packets to complete and shut down the simplest of TCP connections. If a client sends a SYN packet to a port on the server that isn’t listening, the server replies with a RST (reset) packet.
Before you learn how to restrict connections, it helps to understand how a sockets-based server typically functions. First, create the TCP socket, bind it to a port (for information on binding, see my previous article, "Bind Basics", and listen for connections. Some typical code might look like the following:
while(1){ //allow some way to get out of the loop ret = listen(sock, backlog); if(ret == 0) { sock2 = accept(sock, (struct sockaddr*)sa_client, &size); //trap errors here //spawn new thread to handle new socket, or assign socket to worker } else { //handle listen errors here }}
This code has the advantage of being portable; however, one disadvantage is that we have completed the three-way TCP handshake and put the TCP/IP subsystem through the process of creating several structures. The client also knows that the server is listening on this port because the server responded to the connection request.
If you want to control who can connect to your system in this scenario, it helps to know that the sa_client structure is a sockaddr_in that contains the client IP address and originating port number. You can compare this information with an access control function. If you don't want to accept connections from this individual, you can drop the connection by calling closesocket(), which exchanges several more packets to gracefully shut down the connection on both sides. That’s a lot of work and a lot of network traffic just to tell someone that you won’t accept his or her connection. An easier way of handling this situation within your application is to use WSAAccept(). This function is similar to a regular accept() call except that it is prototyped.
SOCKET WSAAccept ( SOCKET s, //the listening socketstruct sockaddr FAR * addr, //the client addressLPINT addrlen, //length of aboveLPCONDITIONPROC lpfnCondition, //your custom functionDWORD dwCallbackData ); //additional data to pass
The Win32 software development kit (SDK) documents the LPCONDITIONPROC prototype under the WSAAccept() Help topic. Your custom conditional function executes within the same thread as the function calling WSAAccept(), so pay attention to performance and timing issues. This function returns one of three values—CF_ACCEPT, CF_DEFER, or CF_REJECT.
If you call setsockopt() with a value of SO_CONDITIONAL_ACCEPT on the listening socket, the socket delays acknowledgement of the SYN packet until the custom function decides the results of WSAAccept(). When you set this socket option, your client doesn't know that this socket is listening unless you are willing to accept connections from the client. Note that if your function is not well tuned, the client might time out and attempt a re-send. If performance is a concern, thorough testing and optimization is very important. You can also use the WSAAccept function to determine Quality of Service (QOS). If you’re writing code for Windows 98, see "BUG: Must Set QOS in WSAAccept Condition Func If IpSQos Not Null", which documents a known issue where you have to set an output value if you’re given an input for QOS.
Although the Microsoft extensions to the traditional BSD socket calls can impact portability, performance and flexibility are more important for many applications. Understanding all the options that both the traditional sockets libraries and the Microsoft Winsock extensions provide can help you make solid design decisions before you start coding.
About the Author
You May Also Like