By ​​Pankaj Tanwar​​ on December 29, 2011 in ​​Coding​​, ​​Developers​​ · ​​11 Comments​






This article on socket programming deals with the Stream Control Transmission Protocol (SCTP).


Similar to TCP and UDP, SCTP provides some features of both. It is message-oriented, and provides a reliable sequenced delivery of messages. SCTP supports multi-homing, i.e., multiple IPs on both sides of the connection. So it is called an association instead of a connection, as a connection involves communication between two IPs, while an association refers to communication between two systems that may have multiple IPs.

SCTP can provide multiple streams between connection endpoints, and each stream will have its own reliable sequenced delivery of messages, so any lost message will not block the delivery of messages in any of the other streams. SCTP is not vulnerable to SYN flooding, as it requires a 4-way handshake.

We will discuss the science later, and now jump to the code, as we usually do. But first you need to know the types of SCTP sockets:

  • A one-to-one socket that corresponds to exactly one SCTP association (similar to TCP).
  • A one-to-many socket, where many SCTP associations can be active on a socket simultaneously (similar to UDP receiving datagrams from several endpoints).

One-to-one sockets (also called TCP-style sockets) were developed to ease porting existing TCP applications to SCTP, so the difference between a server implemented using TCP and SCTP is not much. We just need to modify the call to ​​socket()​​ to ​​socket(AF_INET, SOCK_STREAM, and IPPROTO_SCTP)​​ while everything else stays the same — the calls to ​​listen()​​, ​​accept()​​ for the server, and ​​connect()​​ for the client, with ​​read()​​ and ​​write()​​ calls for both.

Now let’s jump to the one-to-many or UDP-style socket, and write a server using multiple streams that the client follows.

First, here is the code for the server, ​​smtpserver.c​​:




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50


51


52


53


54


55


56


57


58


59


60


61


62


63


64




​#include <stdio.h>​


​#include <string.h>​


​#include <time.h>​


​#include <sys/socket.h>​


​#include <sys/types.h>​


​#include <netinet/in.h>​


​#include <netinet/sctp.h>​


​#include <arpa/inet.h>​


 


 


​#define MAX_BUFFER  1024​


 


​int​​ ​​main()​


​{​


​int​​ ​​sfd, cfd, len, i;​


​struct​​ ​​sockaddr_in saddr, caddr;​


​struct​​ ​​sctp_initmsg initmsg;​


​char​​ ​​buff[INET_ADDRSTRLEN];​


​char​​ ​​buffer[MAX_BUFFER+1] = ​​​​"Message ##\n"​​​​;​


 


 


​sfd = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );​


 


​bzero( (​​​​void​​ ​​*)&saddr, ​​​​sizeof​​​​(saddr) );​


​saddr.sin_family = AF_INET;​


​saddr.sin_addr.s_addr = htonl( INADDR_ANY );​


​saddr.sin_port = htons(29008);​


 


​bind( sfd, (​​​​struct​​ ​​sockaddr *)&saddr, ​​​​sizeof​​​​(saddr) );​


 


​/* Maximum of 3 streams will be available per socket */​


​memset​​​​( &initmsg, 0, ​​​​sizeof​​​​(initmsg) );​


​initmsg.sinit_num_ostreams = 3;​


​initmsg.sinit_max_instreams = 3;​


​initmsg.sinit_max_attempts = 2;​


​setsockopt( sfd, IPPROTO_SCTP, SCTP_INITMSG, ​


​&initmsg, ​​​​sizeof​​​​(initmsg) );​


 


​listen( sfd, 5 );​


 


​for​​​​(;;) {​


​printf​​​​(​​​​"Server Running\n"​​​​);​


 


​len=​​​​sizeof​​​​(caddr);​


​cfd=accept(sfd, (​​​​struct​​ ​​sockaddr *)&caddr, &len);​


 


​printf​​​​(​​​​"Connected to %s\n"​​​​,​


​inet_ntop(AF_INET, &caddr.sin_addr, buff,​


​sizeof​​​​(buff)));​


 


 


​for​​​​(i=0; i< 3; i++) {​


​/* Changing 9th character the character after # in the message buffer */​


​buffer[9] = ​​​​'1'​​​​+i;​


 


​sctp_sendmsg( cfd, (​​​​void​​ ​​*)buffer, (​​​​size_t​​​​)​​​​strlen​​​​(buffer),​


​NULL, 0, 0, 0, i ​​​​/* stream */​​​​, 0, 0 );​


​printf​​​​(​​​​"Sent: %s\n"​​​​, buffer);​


​}​


 


​close( cfd );​


​}​


​return​​ ​​0;​


​}​



And here’s the code of the client, ​​sctpclient.c​​:




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50


51


52


53


54


55


56


57


58


59


60


61


62


63




​#include <stdio.h>​


​#include <string.h>​


​#include <sys/socket.h>​


​#include <sys/types.h>​


​#include <netinet/in.h>​


​#include <netinet/sctp.h>​


 


​#define MAX_BUFFER  1024​


 


​int​​ ​​main(​​​​int​​ ​​argc, ​​​​char​​ ​​**argv)​


​{​


​int​​ ​​cfd, i, flags;​


​struct​​ ​​sockaddr_in saddr;​


​struct​​ ​​sctp_sndrcvinfo sndrcvinfo;​


​struct​​ ​​sctp_event_subscribe events;​


​struct​​ ​​sctp_initmsg initmsg;​


​char​​ ​​buffer[MAX_BUFFER+1];​


 


​if​​​​(argc!=2) {​


​printf​​​​(​​​​"Usage: %s ipaddress\n"​​​​, argv[0]);​


​return​​ ​​-1;​


​}​


 


​cfd = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );​


 


​/* Specify that a maximum of 3 streams will be available per socket */​


​memset​​​​( &initmsg, 0, ​​​​sizeof​​​​(initmsg) );​


​initmsg.sinit_num_ostreams = 3;​


​initmsg.sinit_max_instreams = 3;​


​initmsg.sinit_max_attempts = 2;​


​setsockopt( cfd, IPPROTO_SCTP, SCTP_INITMSG,​


​&initmsg, ​​​​sizeof​​​​(initmsg) );​


 


​bzero( (​​​​void​​ ​​*)&saddr, ​​​​sizeof​​​​(saddr) );​


​saddr.sin_family = AF_INET;​


​inet_pton(AF_INET, argv[1], &saddr.sin_addr);​


​saddr.sin_port = htons(29008);​


 


​connect( cfd, (​​​​struct​​ ​​sockaddr *)&saddr, ​​​​sizeof​​​​(saddr) );​


 


​memset​​​​( (​​​​void​​ ​​*)&events, 0, ​​​​sizeof​​​​(events) );​


​events.sctp_data_io_event = 1;​


​setsockopt( cfd, SOL_SCTP, SCTP_EVENTS,​


​(​​​​const​​ ​​void​​ ​​*)&events, ​​​​sizeof​​​​(events) );​


 


 


​/* Sending three messages on different streams */​


 


​for​​ ​​(i=0; i<3; i++) {​


​bzero( (​​​​void​​ ​​*)&buffer, ​​​​sizeof​​​​(buffer) );​


 


​sctp_recvmsg( cfd, (​​​​void​​ ​​*)buffer, ​​​​sizeof​​​​(buffer),​


​(​​​​struct​​ ​​sockaddr *)NULL, 0, &sndrcvinfo, &flags );​


 


​printf​​​​(​​​​"Received following data on stream %d\n\n%s\n"​​​​,​


​sndrcvinfo.sinfo_stream, buffer);​


 


​}​


 


​close(cfd);​


 


​return​​ ​​0;​


​}​



The server is sending three messages on three different streams, and the client is just receiving the messages and printing them on the screen (see Figures 1 and 2).


​​

Figure 1: Server output


 


​​

Figure 2: Client output


The code is similar to the TCP client, as we are again making calls to the same functions [refer to​​Parts 1​​, ​​2​​, ​​3​​ and ​​4​​ of this ​​series​​.]. The difference is that we are creating an iterative server, similar to the one for UDP, but we have an ​​accept()​​ call here. The client does the reverse, and receives the messages coming from the server to the client. Now let’s try to understand the functions that we used:





​#include <sys/types.h>​


​#include <sys/socket.h>​


​#include <netinet/sctp.h>​


 


​int​​ ​​sctp_sendmsg (​​​​int​​ ​​sd, ​​​​const​​ ​​void​​ ​​*msg, ​​​​size_t​​ ​​len,​


​struct​​ ​​sockaddr *to, socklen_t tolen, uint32_t ppid, uint32_t​


​flags, uint16_t stream_no, uint32_t timetolive, uint32_t context);​



We are using this function to send a message from a socket while using the advanced features of SCTP. The first argument to the function is ​​sd​​, the socket descriptor, from which the message​​msg​​ of length ​​len​​ is sent. The fourth argument is to give the destination address — ​​tolen​​specifies the length of the destination address, while ​​stream_no​​ identifies the stream number to send this message to. The ​​flags​​ parameter is used to send some options to the receiver. You can check out the manual pages for ​​sctp_sendmsg()​​.

The ​​timetolive​​ parameter is time in milliseconds after which the message will expire if not sent by then; the zero here indicates that no time-out is set. The context is the value passed to the upper layer along with the undelivered message, if an error occurs while sending the message. When successful, it will return the number of bytes sent, or -1 on error.

Next is the ​​stcp_recvmsg()​​ function:





​#include <sys/types.h>​


​#include <sys/socket.h>​


​#include <netinet/sctp.h>​


 


​int​​ ​​sctp_recvmsg(​​​​int​​ ​​sd, ​​​​void​​ ​​* msg, ​​​​size_t​​ ​​len, ​​​​struct​


​sockaddr * from, socklen_t * fromlen, ​​​​struct​​ ​​sctp_sndrcvinfo​


​* sinfo, ​​​​int​​ ​​* msg_flags);​



This function does the reverse of the ​​sctp_sendmsg​​ function and is used to receive a message. The parameters are similar. The socket ​​sd​​ receives the ​​msg​​ of length ​​len​​ from the address ​​*from​​with a length ​​*fromlen​​, and ​​*sinfo​​ is a pointer to the address, which will be filled upon receipt of the message. ​​mag_flags​​ is a pointer to an integer with flags like ​​MSG_NOTIFICATION​​ or ​​MSG_EOR​​. It returns the number of bytes received, or -1 on error.





​#include <sys/types.h>​


​#include <sys/socket.h>​


 


​int​​ ​​setsockopt(​​​​int​​ ​​sockfd, ​​​​int​​ ​​level, ​​​​int​​ ​​optname,​


​const​​ ​​void​​ ​​*optval, socklen_t optlen);​



This function is used to set the options for the socket ​​sockfd​​. The next argument is the level at which the option resides. To manipulate options at the sockets API level, the level is specified as​​SOL_SOCKET​​. ​​optname​​ and any specified options are passed uninterpreted to the appropriate protocol module for interpretation. The ​​level​​ and ​​optname​​ are defined in ​​sys/sockets.h​​. The arguments ​​optval​​ and ​​optlen​​ are used to access option values for ​​setsockopt()​​ that are stored in the structure. The options we set in the server are:





​initmsg.sinit_num_ostreams = 3;​


​initmsg.sinit_max_instreams = 3;​


​initmsg.sinit_max_attempts = 2;​



Here, the first two lines tell us that the output and input streams available are three, and the maximum attempts will be two. The same options are set in the client program. Other options are set for the events. This structure will be filled when an event like “message received” occurs, and our program is notified. Its counterpart function is ​​getsockopts()​​ (look up the man pages for help). The rest of the code is simple to understand.

Now compile and run the program; make sure you have installed ​​sctp-tools​​ so that you’ll have​​sctp.h​​ at ​​netinet/​​. To compile, use ​​gcc sctpserver.c -lsctp -o server && gcc sctpclient.c -lsctp -o client​​; and to run, use the following code:





​$ ./server &​


​$  ./client​



I’m wrapping up the ​​series​​ for now, but will keep adding more to the topic, on and off.