Blocking sockets: server
Now that you've seen how a blocking client works, it's time for the blocking server example. This chapter will explain how to build a simple server that ROT13 encodes the received data and then sends it back. ROT13 (rot stands for rotate) is a very simple encryption method used by Caesar. Each character in the alphabet is replaced by the character 13 positions farther (the characters rotate 13 places). The encryption is symmetric, that is encryption works exactly the same as decrypting. You can use rot13.com if you want to play with it.
1. Program flow
The program flow is as follows:
- The server creates a server socket
- The server socket is bound to an address
- The server socket is put into the listening state
- On connection attempt, the connection is accepted and a client socket is available
- The client socket is read, every byte is ROT13'd and sent back.
- When the client closes the connection, the program ends
2. Framework
The framework is exactly the same as that of the blocking client example from chapter 6.
3. Constants and global data
The program uses a few constants for the default server port number (4444), the required winsock version and receive buffer size. Then there's a set of strings for displaying messages and errors.
LF equ 0Ah
DEFAULT_PORT equ 4444
REQ_WINSOCK_VER equ 2
TEMP_BUFFER_SIZE equ 128
.data
g_msgCreateSock db "Creating socket... ",0
g_msgCreated db "created.",CR,LF,0
g_msgInitWinsock db "Initializing winsock... ",0
g_msgInitialized db "initialized.",CR,LF,0
g_msgDone db "done.",CR,LF,0
g_msgCleanup db "Cleaning up winsock... ",0
g_msgBindSocket db "Binding socket... ",0
g_msgBound db "bound.",CR,LF,0
g_msgListen db "Putting socket in listening mode... ",0
g_msgWaitConn db "Waiting for incoming connection... ",0
g_msgAccepted db "accepted.",CR,LF,0
g_msgDesc db "Connected with %s.",CR,LF,0
g_msgConnClosed db "Connection closed.",CR,LF,0
g_fmtSockDesc db "%s:%u",0
g_errCreateSock db "could not create socket.",CR,LF,0
g_errSend db "socket error while sending.",CR,LF,0
g_errRead db "socket error while receiving.",CR,LF,0
g_errStartup db "startup failed!",0
g_errVersion db "required version not supported!",0
g_errCleanup db "cleanup failed!",CR,LF,0
g_errBind db "could not bind socket.",CR,LF,0
g_errListen db "could not put socket in listening mode.",CR,LF,0
g_errAccept db "accept function failed.",CR,LF,0
4. The main function
The main function too has a lot of common with the blocking client from the previous chapter:
local wsaData:WSADATA
invoke printf, addr g_msgInitWinsock
invoke WSAStartup, REQ_WINSOCK_VER, addr wsaData
mov ecx, offset g_errStartup
test eax, eax
jnz _error
; Check if major version (low byte) is at least REQ_WINSOCK_VER
mov byte ptr [wsaData.wVersion], REQ_WINSOCK_VER
mov ecx, offset g_errVersion
jb _error_cleanup
invoke printf, addr g_msgInitialized
; Check if port number is given as the program's parameter,
; otherwise use the default port number.
mov eax, DEFAULT_PORT
cmp [argc], 2 ; at least 1 argument?
mov ecx, [argv] ; get argument vector
jb @F
mov eax, [ecx][1*4] ; get first argument
invoke atoi, eax ; convert string into an int
@@:
invoke RunServer, eax
mov ebx, eax
xor ebx, 1
; ebx now is 0 on success, 1 on failure of RunServer
_cleanup:
invoke printf, addr g_msgCleanup
invoke WSACleanup
test eax, eax
jz _done
invoke printf, addr g_errCleanup
_done:
invoke printf, addr g_msgDone
mov eax, ebx ; return code in ebx
ret
_error_cleanup:
mov ebx, _cleanup
jmp _printError
_error:
mov ebx, _done
_printError:
invoke printf, ecx
mov eax, ebx
mov ebx, 1 ; return 1 (error)
jmp eax
main endp
Winsock is initialized, and cleaned up again when the program is finished. In between is the server startup code. The program allows an optional parameter that specifies the port the server should run on. If it is not set, the default port number is used (4444). Finally, RunServer is called with the final port number as its parameter. The RunServer function contains the actual server code.
5. RunServer
RunServer is the function where the server is setup and connections are accepted. The basic framework of this function is:
local hSocket:dword,
hClientSocket:dword,
sockAddr:sockaddr_in,
clientSockAddr:sockaddr_in,
clientSockSize:dword
; bSuccess:dword = ebx,
mov [hSocket], INVALID_SOCKET
mov [hClientSocket], INVALID_SOCKET
mov ebx, 1 ; success return code
; Create socket:
invoke printf, addr g_msgCreateSock
invoke socket, AF_INET, SOCK_STREAM, IPPROTO_TCP
mov ecx, offset g_errCreateSock
cmp eax, INVALID_SOCKET
je _error
mov [hSocket], eax
invoke printf, addr g_msgCreated
; ---- code goes here ----
_cleanup:
; close socket if it was created:
mov eax, [hSocket]
cmp eax, INVALID_SOCKET
je @F
invoke closesocket, eax
@@:
; close client socket if it was set:
mov eax, [hClientSocket]
cmp eax, INVALID_SOCKET
je @F
invoke closesocket, eax
@@:
mov eax, ebx
ret
_error:
invoke printf, ecx
xor ebx, ebx ; return code (0 = error)
jmp _cleanup
ret
RunServer endp
A server socket is created in the usual way, and a variable to hold the client socket is reserved too. The client socket doesn't have to be created, since winsock will do that for us later. You do have to close both socket handles when you won't use them anymore though, this is done in the cleanup part at the end of the code.
6. Binding the socket
After the socket is created, we will bind it to an address. As I've explained in the first chapters, a server listens on a specific port number and possibly on a specific IP number as well. Before you can let the server socket listen, it must be bound. The winsock API bind will do that for you. In this example, the socket will be bound to the port specified by the portNumber parameter of RunServer, the IP number is set to INADDR_ANY, indicating that the server will listen on all available IP numbers. To bind a socket with bind, you need to fill in a sockaddr_in structure with the address you want the socket be bound to. Setting up this structure is done in a separate function called SetServerSockAddr:
mov edx, [pSockAddr]
mov ecx, [portNumber]
xchg cl, ch ; convert to network byte order
mov [edx][sockaddr_in.sin_family], AF_INET
mov [edx][sockaddr_in.sin_port], cx
mov [edx][sockaddr_in.sin_addr.S_un.S_addr], INADDR_ANY
ret
SetServerSockAddr endp
This function is called in RunServer in the following way:
invoke printf, addr g_msgBindSocket
invoke SetServerSockAddr, addr sockAddr, [portNumber]
invoke bind, [hSocket], addr sockAddr, sizeof sockAddr
test eax, eax
mov ecx, offset g_errBind
jnz _error
invoke printf, addr g_msgBound
7. Letting the socket listen
If the binding succeeds, the socket is put into listening mode. As soon as it's in this state, any client can make a connection attempt to the server on the port the socket is bound to. Setting the listening mode is simply done by calling the listen winsock function:
invoke printf, addr g_msgListen
invoke listen, [hSocket], SOMAXCONN
mov ecx, offset g_errListen
jnz _error
invoke printf, addr g_msgDone
Listen has two parameters. The first is the socket you want to listen, the second is the length of the queue of pending connections. Usually the default value of SOMAXCONN is okay for the latter parameter. This value is the maximum number of connections that winsock will hold pending until your program accepts them. You probably don't need to worry about this value most of the time.
8. Accepting connections
When the server socket is in the listening state, you need to accept the incoming connections using the accept function. The accept function blocks until a connection request comes in, establishes the connection and then returns a client socket handle. It is important to know that the server socket's only purpose is now to listen for connections and accept them. As soon as you accept a connection, a new socket is created by winsock. This socket is usually called the client socket and that's the socket you will be receiving and sending data on. This often confuses winsock beginners, some try to receive or send data on the listening socket, while they should use the client socket.
Besides accepting a connection and returning a client socket handle, accept also fills in a sockaddr_in structure with information about the client. Our example will use this information to print a short description of the client that connected (in the form IP:port).
invoke printf, addr g_msgWaitConn
; Accept connection
mov [clientSockSize], sizeof clientSockAddr
invoke accept, [hSocket], addr clientSockAddr, addr clientSockSize
cmp eax, INVALID_SOCKET
mov ecx, offset g_errAccept
je _error
mov [hClientSocket], eax
invoke printf, addr g_msgAccepted
; Wait for and accept a connection:
invoke HandleConnection, [hClientSocket], addr clientSockAddr
mov ebx, eax ; pass return code from HandleConnection
The above code calls accept, and then handles the both the client socket handle and the sockaddr_in structure to a new function, HandleConnection, which will deal with the connection. After this code has executed, RunServer returns and closes the sockets, as shown earlier.
9. HandleConnection
The HandleConnection function handles the connection. The first thing it does is showing a short description of the client. A separate function (GetHostDescription) is used to create this description.
local tempBuffer[TEMP_BUFFER_SIZE]:byte
mov esi, [hClientSocket]
; Print description (IP:port) of connected client
invoke GetHostDescription, [pSockAddr]
mov ebx, eax
invoke printf, addr g_msgDesc, eax
invoke free, ebx ; free memory occupied by string from GetHostDescription
; todo
invoke printf, addr g_msgConnClosed
xor eax, eax
inc eax ; return code (1 = no error)
ret
_error:
invoke printf, ecx
xor eax, eax
ret
HandleConnection endp
The GetHostDescription function looks like this:
; Allocate description string space
invoke malloc, 22 ; enough bytes for "xxx.xxx.xxx.xxx:yyyyy",0
mov edi, eax
mov ebx, [pSockAddr]
movzx eax, [ebx][sockaddr_in.sin_port]
invoke ntohs, eax
movzx esi, ax ; port number
; Get IP in dotted string format
invoke inet_ntoa, [ebx][sockaddr_in.sin_addr.S_un.S_addr]
; Format string
invoke sprintf, edi, addr g_fmtSockDesc, eax, esi
mov eax, edi
ret
GetHostDescription endp
We will now write the part marked as 'todo' in the above HandleConnection framework. The function should loop on recv until the connection is closed (recv returns 0). Every time data is received, it is ROT13 encoded and sent back to the client. First of all, a simple function is written to deal with the ROT13 encryption:
push esi
mov esi, [pBuffer]
mov ecx, [len]
test ecx, ecx
jz _done
align 16
_char:
movzx eax, byte ptr [esi]
cmp eax, 'A'
jb _next
cmp eax, 'N'
jb _p13
cmp eax, 'Z'
jbe _m13
cmp eax, 'a'
jb _next
cmp eax, 'n'
jb _p13
cmp eax, 'z'
ja _next
_m13:
sub eax, 13
jmp _write
_p13:
add eax, 13
_write:
mov [esi], al
_next:
inc esi
dec ecx
jnz _char
_done:
pop esi
ret
rot13 endp
Then the main loop is simple. First a recv call, that will receive data from the client. The rot13 function is called to encrypt the received data and finally send is used to send the encrypted data back to the client:
_readData:
invoke recv, esi, addr tempBuffer, sizeof tempBuffer, 0
test eax, eax
mov ecx, offset g_errRead
jz _connectionClosed ; return value 0 means connection closed
cmp eax, SOCKET_ERROR
je _error
mov ebx, eax
; ebx/eax is the number of bytes received.
; rot13 the data and send it back to the client
invoke rot13, addr tempBuffer, eax
invoke send, esi, addr tempBuffer, ebx, 0
mov ecx, offset g_errSend
cmp eax, SOCKET_ERROR
je _error
jmp _readData
_connectionClosed:
10. Testing
That's all, it should work now. To test the program, you could use the telnet client supplied by windows but you have to get the settings right. If you switch off the local echo you don't see what you type but you do see what data you receive. This means you see the encrypted text directly. However, I prefer a better client called PuTTY, you can find it here. I recommend you to download it as well. Compile the program, run it and you will (hopefully) see a message that the program is waiting for a connection:
X:\asm\rot13server>rot13server
Initializing winsock... initialized.
Creating socket... created.
Binding socket... bound.
Putting socket in listening mode... done.
Fire up putty, and in the configuration screen, type in localhost as the hostname, 4444 as the port number (or a different one if you choose to run the program with some other port). Set the protocol to Raw. Finally press Open to connect.
You will now see putty's console window. Here you can type text that will be send to the server. Any data received will be printed in the same window. Note: putty by default has local line editing enabled. This means that you can type and even edit the text you type as long as you stay on the same line, since it's not send until you press enter. If you use a client that immediately sends every character, you also get a response immediately. If you have such a client you should disable local echo (ie. showing the text you type), otherwise you get your text and the received text interleaved, which is pretty hard to read. This is not the case with putty. Here's a screenshot of the connection in action:
11. Source code
Finally, the source code:
Download the source zip file here: rot13server_asm.zip
12. Conclusion
Now you've seen both a blocking client and a blocking server. Blocking sockets are relatively easy to use because they fit in nicely in the program flow. Still, you've only seen pretty simple examples, since both the client and the server we showed did practically nothing with the data other than print it or in this case, encrypt and then send it back. It gets harder when we have to extract meaningful information from the received data like when dealing with a protocol like POP3.