amd64/149488: SCTP streams not working on AMD64 platform
Sebastien
sdecugis at nict.go.jp
Tue Aug 10 05:10:04 UTC 2010
>Number: 149488
>Category: amd64
>Synopsis: SCTP streams not working on AMD64 platform
>Confidential: no
>Severity: serious
>Priority: low
>Responsible: freebsd-amd64
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Tue Aug 10 05:10:04 UTC 2010
>Closed-Date:
>Last-Modified:
>Originator: Sebastien
>Release: 8.1R
>Organization:
freediameter
>Environment:
FreeBSD bsd64.testbed.freediameter.net 8.1-RELEASE FreeBSD 8.1-RELEASE #0: Mon Jul 19 02:36:49 UTC 2010 root at mason.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC amd64
>Description:
The SCTP stack seems unable to handle the stream identifier correctly on amd64 platform (works properly on i386, not tested on other platforms).
More specifically, when sending a message on stream different that 0, it is received as coming on stream 0. Since both sides of the connection are on the same machine, I am not sure if sending or receiving is faulty.
This feature is critical in order to implement TLS over SCTP, since each pair of streams has a different TLS state in that case.
Thank you,
Best regards,
Sebastien.
>How-To-Repeat:
I have attached a sample test program to reproduce the problem:
$ gcc -o sctp -Wall sctp.c
$ ./sctp
Server socket: 3
Client socket: 4
Connection established :
Initiator socket: # streams in = 10
# streams out = 10
Receiver socket: # streams in = 10
# streams out = 10
Data received on stream 0 while expected on 1!
>Fix:
Patch attached with submission follows:
/* Test to show problem on FreeBSD 64bit with SCTP */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <assert.h>
#define TEST_PORT 3868
#define CMSG_BUF_LEN 1024
#define ASSERT(x) assert(x)
/* Set options on the socket to request 10 streams */
void set_use_of_streams(int sock)
{
int ret;
/* Set the number of streams */
#ifdef SCTP_INITMSG
{
struct sctp_initmsg init;
memset(&init, 0, sizeof(init));
init.sinit_num_ostreams = 10; /* desired number of outgoing streams */
/* Set the option to the socket */
ret = setsockopt(sock, IPPROTO_SCTP, SCTP_INITMSG, &init, sizeof(init));
if (ret < 0) {
perror("setsockopt(SCTP_INITMSG)");
exit (1);
}
}
#else /* SCTP_INITMSG */
printf("SCTP_INITMSG unsupported\n");
#endif /* SCTP_INITMSG */
#ifdef SCTP_EVENTS
{
struct sctp_event_subscribe event;
memset(&event, 0, sizeof(event));
event.sctp_data_io_event = 1; /* to receive the stream ID in SCTP_SNDRCV ancilliary data on message reception */
event.sctp_association_event = 0; /* new or closed associations (mostly for one-to-many style sockets) */
event.sctp_address_event = 0; /* address changes */
event.sctp_send_failure_event = 0; /* delivery failures */
event.sctp_peer_error_event = 0; /* remote peer sends an error */
event.sctp_shutdown_event = 0; /* peer has sent a SHUTDOWN */
event.sctp_partial_delivery_event = 0; /* a partial delivery is aborted, probably indicating the connection is being shutdown */
/* Set the option to the socket */
ret = setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(event));
if (ret < 0) {
perror("setsockopt(SCTP_EVENTS)");
exit (1);
}
}
#else /* SCTP_EVENTS */
printf("SCTP_EVENTS unsupported\n");
#endif /* SCTP_EVENTS */
}
/* Create, bind, listen for a server socket */
int create_server(void)
{
int sock;
struct sockaddr_in sin;
/* Create the server socket */
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
if (sock < 0) {
perror("socket");
exit (1);
}
printf("Server socket: %d\n", sock);
set_use_of_streams(sock);
/* Bind the socket to default wildcard address */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(TEST_PORT);
if ( bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("bind");
exit (1);
}
/* Now, accept incoming clients */
if ( listen(sock, 5) < 0) {
perror("listen");
exit (1);
}
return sock;
}
/* Connect to the bound socket */
int create_client(void)
{
int sock;
struct sockaddr_in sin;
/* Create the new socket */
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
if (sock < 0) {
perror("socket");
exit (1);
}
printf("Client socket: %d\n", sock);
set_use_of_streams(sock);
/* Connect to the server */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(TEST_PORT);
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
if ( connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0 ) {
perror("sctp_connectx");
exit (1);
}
return sock;
}
/* Send a message on a socket and a particular stream */
void send_on_stream(int sock, unsigned int strid, unsigned char * msg, size_t sz)
{
struct msghdr mhdr;
struct iovec iov;
struct {
struct cmsghdr hdr;
struct sctp_sndrcvinfo sndrcv;
} anci;
ssize_t ret;
memset(&mhdr, 0, sizeof(mhdr));
memset(&iov, 0, sizeof(iov));
memset(&anci, 0, sizeof(anci));
/* IO Vector: message data */
iov.iov_base = msg;
iov.iov_len = sz;
/* Anciliary data: specify SCTP stream */
anci.hdr.cmsg_len = sizeof(anci);
anci.hdr.cmsg_level = IPPROTO_SCTP;
anci.hdr.cmsg_type = SCTP_SNDRCV;
anci.sndrcv.sinfo_stream = strid;
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = &anci;
mhdr.msg_controllen = sizeof(anci);
if ( (ret = sendmsg(sock, &mhdr, 0)) < 0) {
perror("sendmsg");
exit (1);
}
ASSERT( ret == sz ); /* There should not be partial delivery with sendmsg... */
return;
}
/* Receive the next message from a socket and check it is on the expected stream */
void receive_with_stream(int sock, unsigned int strid, unsigned char * buf, size_t sz)
{
ssize_t ret = 0;
struct msghdr mhdr;
char ancidata[ CMSG_BUF_LEN ];
struct iovec iov;
int verified_str = 0;
/* Prepare header for receiving message */
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = &ancidata;
mhdr.msg_controllen = sizeof(ancidata);
iov.iov_base = buf;
iov.iov_len = sz;
/* Receive data from the socket */
if ((ret = recvmsg(sock, &mhdr, 0)) < 0) {
perror("recvmsg");
exit (1);
}
if (ret != sz) {
printf("Received %zdbytes of data, expected %zdbytes...\n", ret, sz);
}
/* SCTP provides an indication when we received a full record */
if ( ! (mhdr.msg_flags & MSG_EOR) ) {
printf("Received data does not contain End-Of-Record flag\n");
}
/* Handle the case where the data received is a notification */
if (mhdr.msg_flags & MSG_NOTIFICATION) {
printf("Received data is a notification, exiting.\n");
exit (1);
}
/* Get the stream information */
{
struct cmsghdr *hdr;
struct sctp_sndrcvinfo *sndrcv;
/* Handle the anciliary data */
for (hdr = CMSG_FIRSTHDR(&mhdr); hdr; hdr = CMSG_NXTHDR(&mhdr, hdr)) {
/* We deal only with anciliary data at SCTP level */
if (hdr->cmsg_level != IPPROTO_SCTP) {
continue;
}
/* Also only interested in SCTP_SNDRCV message for the moment */
if (hdr->cmsg_type != SCTP_SNDRCV) {
continue;
}
sndrcv = (struct sctp_sndrcvinfo *) CMSG_DATA(hdr);
#if 0
{
printf( "Anciliary block IPPROTO_SCTP / SCTP_SNDRCV\n");
printf( " sinfo_stream : %hu\n", sndrcv->sinfo_stream);
printf( " sinfo_ssn : %hu\n", sndrcv->sinfo_ssn);
printf( " sinfo_flags : %hu\n", sndrcv->sinfo_flags);
/* printf( " sinfo_pr_policy : %hu\n", sndrcv->sinfo_pr_policy); */
printf( " sinfo_ppid : %u\n" , sndrcv->sinfo_ppid);
printf( " sinfo_context : %u\n" , sndrcv->sinfo_context);
/* printf( " sinfo_pr_value : %u\n" , sndrcv->sinfo_pr_value); */
printf( " sinfo_tsn : %u\n" , sndrcv->sinfo_tsn);
printf( " sinfo_cumtsn : %u\n" , sndrcv->sinfo_cumtsn);
}
#endif /* 0 */
if (strid != sndrcv->sinfo_stream ) {
printf("Data received on stream %hu while expected on %hu!\n", sndrcv->sinfo_stream, strid);
exit (1);
} else {
verified_str = 1;
}
}
}
if (!verified_str) {
printf("Data received without the stream information! Unable to verify...\n");
exit (1);
}
return;
}
/* Main test */
int main(int argc, char *argv[])
{
int servsock, inisock, rcvsock;
unsigned char msg[]="abcdef";
unsigned char buf[sizeof(msg)];
/* Bind a server socket */
servsock = create_server();
/* Connect a client socket to this server */
inisock = create_client();
/* And accept this client */
rcvsock = accept(servsock, NULL, NULL);
if (rcvsock < 0) {
perror("accept");
exit (1);
}
/* Informations */
printf("Connection established :\n");
{
struct sctp_status status;
socklen_t sz = sizeof(status);
/* Read the association parameters */
memset(&status, 0, sizeof(status));
if ( (getsockopt(inisock, IPPROTO_SCTP, SCTP_STATUS, &status, &sz) < 0 )
|| (sz != sizeof(status))) {
perror("getsockopt(SCTP_STATUS)");
exit (1);
}
printf(" Initiator socket: # streams in = %hu\n", status.sstat_instrms);
printf(" # streams out = %hu\n", status.sstat_outstrms);
memset(&status, 0, sizeof(status));
if ( (getsockopt(rcvsock, IPPROTO_SCTP, SCTP_STATUS, &status, &sz) < 0 )
|| (sz != sizeof(status))) {
perror("getsockopt(SCTP_STATUS)");
exit (1);
}
printf(" Receiver socket: # streams in = %hu\n", status.sstat_instrms);
printf(" # streams out = %hu\n", status.sstat_outstrms);
}
/* Send a message on stream 1 */
send_on_stream(inisock, 1, msg, sizeof(msg));
/* Receive this message */
receive_with_stream(rcvsock, 1, buf, sizeof(buf));
/* Done! */
shutdown(inisock, SHUT_RDWR);
shutdown(rcvsock, SHUT_RDWR);
close(inisock);
close(rcvsock);
close(servsock);
printf("Test passed\n");
return 0;
}
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-amd64
mailing list