In Linux, you deal with the network stack via sockets. I assume you are familiar how they work and you have got some basic network programming knowledge in C. What I mean is that you know what functions like socket(), bind(), listen() do, how you should call them and how to implement a simple client-server application. For the purposes of this and the following posts I have created a project in GitHub. You will find there all the code mentioned here. Please check the one-to-one_basic branch now and make sure you understand the code there. It is nothing fancy - client and server application exchanging few messages over SCTP. Anyway if you need a refreshment or you are new to Linux socket programing check some other tutorial on this topic. There are a lot of good ones out there so just pick one you like. My personal best is Beej's Guide to Network Programming.
Disclaimer about the code
Please note that the code is not at all intended to be used on production systems. It is created as a demonstration for the interfaces that I shall write about. It has got some basic checks, but it is far from production ready. If you plan to use it in your project please take some time and add proper error handling. You have been warned.
Building the code
As I already mentioned, this post is based on the one-to-one_basic branch. In this section I will help you build and run it. If you have got experience with git and cmake, feel free to skip it. The packages you need are git, cmake, C compiler, the POSIX thread library and the SCTP tools for your distribution (libsctp-dev for debian and lksctp-tools-devel for redhat). I will not discuss git and cmake here, but I will provide enough information to get the code working. The instructions here should work for every Linux distributions. In case you run into some problems, leave a comment and we shall figure it out together.
So let's begin. First you need to check out the code. Go to a directory of your choice and run:
You should see:
Cloning into 'sctp-sandbox'... remote: Counting objects: 31, done. remote: Total 31 (delta 0), reused 0 (delta 0), pack-reused 30 Unpacking objects: 100% (31/31), done. Checking connectivity... done.
Then cd into the new directory and checkout one-to-one_basic branch:
The output from the last command is:
Branch one-to-one_basic set up to track remote branch one-to-one_basic from origin. Switched to a new branch 'one-to-one_basic'
Then create a build dir:
And run cmake:
You should see something like:
-- The C compiler identification is GNU 4.8.2 -- The CXX compiler identification is GNU 4.8.2 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Looking for include file pthread.h -- Looking for include file pthread.h - found -- Looking for pthread_create -- Looking for pthread_create - not found -- Looking for pthread_create in pthreads -- Looking for pthread_create in pthreads - not found -- Looking for pthread_create in pthread -- Looking for pthread_create in pthread - found -- Found Threads: TRUE -- Configuring done -- Generating done -- Build files have been written to: /tmp/sctp-sandbox/build
Now you are ready to build the code:
The output on my machine is:
Scanning dependencies of target client [ 50%] Building C object CMakeFiles/client.dir/client.c.o Linking C executable client [ 50%] Built target client Scanning dependencies of target server [100%] Building C object CMakeFiles/server.dir/server.c.o Linking C executable server [100%] Built target server
Thats all. If everything is correct you should have got two binaries - server and client. Now start your favourite network analyser and let's run the binaries. Open two terminals and start the server in the first one:
You should see:
Then run the client in the other terminal:
You should see:
Connecting... OK Sending message 1 of 5. Result: OK Sending message 2 of 5. Result: OK Sending message 3 of 5. Result: OK Sending message 4 of 5. Result: OK Sending message 5 of 5. Result: OK Closing...
Now get back to the server terminal and check its output. You should see two new lines:
And finally check your PCAP trace to see the establishment of your SCTP association, the transfer of the messages and the association tear down. If you don't see anything, make sure you are listening on the correct interface. By default the client and the server communicate via localhost.
The code itself
The code in this branch looks exactly like most of the TCP client/server implementations on the Internet, which is intentional. I want to show you how easy it is to convert a TCP client/server program to a SCTP one. As a proof, let's try to rework this program to use TCP instead SCTP. Open common.h and change the PROTO constant. Its current value should be set to IPPROTO_SCTP, change it to IPPROTO_TCP. Now rebuild everything (run make clean && make), run the binaries again and check the PCAP. You should see that the whole communication is now over TCP. Don't forget to revert the change before you go on.
If you haven't already, now is the perfect moment to have a look at the code. I have intentionally skipped some error handling, so don't blame me for coredumps, caused by bad input data. What I want is to focus on the socket API and keep the code as readable as possible. There are two C files for each binary - the client and the server. They share one common header file with some constants (like the one you modified in the previous paragraph). Both server and client are pretty straight-forward, nothing more than the code you will find in every TCP server/client example.
In main() we read the parameters passed to the binary (if any). You can pass port number different from the default one (4444). After that a SCTP socket is created (protocol is IPPROTO_SCTP). On success the server binds on all possible IP addresses and listens for new connections. New thread is spawned for each new client. This approach is not at all scalable, but will do the job for a demo program. The server waits for new connections in infinite loop. The only way to stop the server is to kill it.
The client reads an IP address and optionally a port number from the command line. Then it creates new SCTP socket (with the same parameters used in the server), tries to connect to the server, sends some messages (the count is specified in CLIENT_SEND_COUNT constant in common.h) and reads the responses from the server. All this information is logged on the stdout. When ready, the client closes the association and exits.
SCTP one-to-one basic interface
What you have seen so far is so called 'one-to-one' style interface. It represents one to one relationship between a socket and an association and it is the simplest way to port a TCP application to SCTP. The address family for this interface is AF_INET and the socket type is SOCK_STREAM (the same values that you use for TCP socket). The server executes the following calls:
- socket() - to create new socket.
- bind() - to bind to specific IP address and port.
- listen() - blocking call, waiting for new incoming connection.
- accept() - to accept connection.
The client does the following:
- socket() - again, to obtain new socket for the connection.
- connect() - to connect to the server.
Once the connection (association) is established, each side can close it with the close() call. Data can be sent with send() call and received with receive(), which is a blocking call. I will not discuss these functions, because you should already be familiar with them.
In this post we have created a very simple client/server application, based on SCTP protocol. My main purpose was to demonstrate how easy is to port code, using TCP sockets to SCTP ones and I hope I managed to convince you. However this implementation doesn't use any SCTP specific features and honestly it's almost meaningless to use the protocol in this way. In the following posts I shall demonstrate more useful features which one is likely to see in the real world.