Setting up a new skeleton: re-factoring

Before we go much further with our SOCKS server, we should do a bit of cleaning up in the project: we’ll move the Server and Observer classes to their own library, so we can more easily re-use them, and we’ll copy the Application class over to our new project — the one that will become our next step towards a fully functional SOCKS server: Episode35.
Most of the dreary details are clearly visible in the diff of the main commit but a few interesting details show up when we compare the two Application classes:

We are going to have to manage different kinds of sockets: sockets for command-and-control (which we will call “control sockets” from here on) and sockets that need to be proxied. At first, we’ll just do TCP proxying but, eventually, we will also proxy UDP.

The way this is going to work, we will have only one thread to do most of the work — so we’ll have to be relatively smart about multiplexing our work. In this context, multiplexing means that we tell the sockets API — and therefore the underlying TCP/IP stack — what we want it to do, but we don’t wait around for it to perform its tasks: rather, we tell it to notify us whenever a task is finished and will carry it on from there. For that to work we do, of course, need to know what task it was performing. We do that by associating a state with each socket, which we put in one of the attributes, which we will call socket_state_attribute_id_. That attribute is allocated in the constructor and used throughout the code. For example, in onDataReady, it is used to know what to do with the incoming data:

To see the code click here.To hide the code click here.

132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

// here, according to the state of the socket, dispatch the dataif(socket.get(socket_state_attribute_id_).empty()){
socket.get(socket_state_attribute_id_)= expect_authentication_method_request__;}else{/* the socket already has a state */}switch(any_cast< SocketState >(socket.get(socket_state_attribute_id_).empty())){case expect_authentication_method_request__ :
onAuthenticationMethodRequest(socket);break;case expect_socks_request__ :
onSocksRequest(socket);break;}

This basically turns the socket itself into a state machine. The available states would, of course, be different according to the role of the socket (i.e. a data socket would never be expected to send a SOCKS request).

We will look into state machines in the next installment.

Another important/interesting part of the new code is the parsing of SOCKS requests. We will look at the actions they imply later, but we will take a closer look at the parsing now:

The first step is to read the data from the buffer. If there isn’t enough data in the buffer, the request cannot be parsed and should be set aside. Some preliminary checks can be performed on the request immediately, as the following snippet of code will show:

Now that we know we at least have enough data, we can treat the data as a message and, assuming the data in the buffer is either properly aligned, or we don’t care about the alignment, we can simply cast the buffer to the appropriate message type and do some more preliminary checks:

In this specific message, we’re setting up an authentication method – we won’t support authentication right away, so for now, only method 0 is supported. According to the protocol, this means that at least one of the proposed authentication methods, of which there can be up to 255, has to be 0.

SOCKS requests are handled pretty much the same way, but contain an address — the IP address of the host we will be proxying with. According to the address type, there are different ways to extract the IP address of the host we will be proxying with. It can either contain the actual IPv4 address, as in the next bit of code:

There are a few things you should notice: invalid IP addresses and domain names are not necessarily a problem — they won’t work, of course, but there is really no other way to check for them than to try them out. The only thing we really check is that there’s enough data in the request to contain the information being extracted from the request.

Socks requests are fairly simple and don’t contain anything like checksums or cryptographic authentication (although cryptographic authentication can be part of the protocol, if supported by both sides) so other than these few checks, there is really nothing to be done.

First thing we do is get a buffer to put the data into. That buffer is associated with the socket itself so that, if the socket isn’t ready for data being written to it, we can write the data to the socket when the socket is ready, in onWriteReady. The Server code will know what to do with a call to write if the socket isn’t ready, so we can simply call it and remove, from the buffer, any data that we could send.

About rlc

Software Analyst in embedded systems and C++, C and VHDL developer, I specialize in security, communications protocols and time synchronization, and am interested in concurrency, generic meta-programming and functional programming and their practical applications.
I take a pragmatic approach to project management, focusing on the management of risk and scope.
I have over two decades of experience as a software professional and a background in science.