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.

CR  equ 0Dh
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:

main proc uses ebx argc:dword, argv:dword
    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:

RunServer proc uses ebx portNumber:dword
    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:

SetServerSockAddr proc pSockAddr:dword, portNumber:dword

    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:

; Bind socket
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:

; Put socket in listening mode
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).

; Wait for connection
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.

HandleConnection proc uses ebx esi hClientSocket:dword, pSockAddr:dword

    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:

GetHostDescription proc uses ebx esi edi pSockAddr:dword

    ; 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:

rot13 proc pBuffer:dword, len:dword
    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:

    ; Read data
_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:

Blocking server in putty

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.