Using a Subset of Addresses in an SCTP Association

This post is about how to use SCTP with only a subset of available addresses and so I will not describe the SCTP protocol in any great detail.

I will digress only briefly to say what SCTP is and to recommend an article in which interested readers can get a basic overview of the protocol. Basically SCTP (Stream Control Transmission Protocol) is a transport layer protocol similar to TCP/UDP and only the third to be ratified by the IETF. One primary feature of SCTP is the ability to allow associations (an association is SCTP parlance for a connection) to span multiple IP addresses. For more information on SCTP I would highly recommend the following article from IBM ‘Better Networking with SCTP’.

So back to the point of this post. During initial association establishment the default behaviour of SCTP is to bind all available addresses to the association. This means that both the client and server will exchange a list of all addresses through which they can be reached, regardless of whether the address is routable by the alternate endpoint. There are a number of reasons why this is undesirable. Firstly it is simply a waste if the machine cannot be reached via that address from the other endpoint, this will result in HEARTBEAT messages to that address failing and hence the address will be flagged as down. A second and more important reason is that of security, a server using SCTP may only want to utilise a subset of its addresses for receiving SCTP associations. The other interfaces may be part of a private network or be used for other purposes and the administrators of the machine may not want to expose these interfaces.

SCTP has the capability to only use a subset of addresses during the establishment of an association, unfortunately this is done in two different ways depending on whether the machine is operating as a client or server. I will first describe the server.

The sctp_bindx function call can be used in place of the normal bind call.

Server Code Snippet
// Create the Socket
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
struct sockaddr_in addr, *addresses;
int addrcount = 2;
int addrsize = sizeof(struct sockaddr_in);

addresses = (struct sockaddr_in*) malloc(addrsize * addrcount);
if (addresses == NULL) {
perror("malloc");
exit(1);
}

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = inet_addr(FIRSTLOCALADDRESS);
memcpy(addresses+0, &addr, addrsize);
addr.sin_addr.s_addr = inet_addr(SECONDLOCALADDRESS);
memcpy(addresses+1, &addr, addrsize);

//Bind with address list
if(sctp_bindx( sockfd, (struct sockaddr *)&addresses, addrcount, SCTP_BINDX_ADD_ADDR ) == -1){
throw ("TunnelManager: bindx failed");
free(addresses);
return -1;
}
listen(sockfd);



In the server case the bind call is simply replaced by sctp_bindx which takes a list off addresses as a parameter; only these specified addresses will be exchanged during association setup. In the example above only two addresses are specified however any number of local interface addresses can be added to the server address list before calling sctp_bindx.

The slightly more tricky and non-obvious case is when using only a subset of addresses on the client side of an association. Normally it is not required to call bind before calling connect, rather connect will automatically call bind with a port number 0. This means that the system will dynamically assign a free port to the connection. In the case of using only a subset of the addresses it is critical that all these addresses use the same port and hence we must obtain the system assigned port before being able to add multiple specific addresses .

The following steps are required to achieve this:
  1. Call bind on the socket giving the initial address that you want to set as the primary for the client
  2. Retrieve the port number that the system dynamically assigned
  3. Create the list of local client addresses to be used in the association making sure to use the same port number obtained in step 2
Client Code Snippet
// Create the Socket
sockfd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);

//Create Server Address
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(SERVERADDRESS);
serv_addr.sin_port = htons(SERVERPORT);

//Bind locally to only a subset of addresses
struct sockaddr_in client_addr;
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr(FIRSTLOCALADDRESS);
client_addr.sin_port = 0;
bind( sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr) );
int port = client_addr.sin_port;

//Add the second local address
//Note a number of addresses can be added in the same manner as shown in the server code
int addrcount = 1; //adding one additional address
int addrsize = sizeof(struct sockaddr_in);

addresses = (struct sockaddr_in*) malloc(addrsize * addrcount);
if (addresses == NULL) {
perror("malloc");
exit(1);
}

client_addr.sin_addr.s_addr = inet_addr(SECONDWIFIADDRESS);
memcpy(addresses+1, &client_addr, addrsize);

//Bindx with address list
if(sctp_bindx( sockfd, (struct sockaddr *)&addresses, addrcount, SCTP_BINDX_ADD_ADDR ) == -1){
throw ("TunnelManager: bindx failed");
free(addresses);
exit(1);
}

//Connect to server
if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("connect to server failed");
exit(1);
}