sctp-echo - SCTP-over-UDP echo server/client for creating reliable tunnels over UDP.

git clone https://benconnors.ca/git-repos/sctp-echo

About | Log | Files | Refs

sctp_echo.c (7172B) - raw


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <stdbool.h>
      5 #include <sys/types.h>
      6 #include <usrsctp.h>
      7 #include <arpa/inet.h>
      8 #include <unistd.h>
      9 #include <pthread.h>
     10 #include <errno.h>
     11 
     12 int done = 0;
     13 
     14 const size_t send_buffer_size = 1024;
     15 
     16 pthread_mutex_t lock;
     17 
     18 int receive_cb(struct socket *sock, union sctp_sockstore addr, void *data, size_t datalen, struct sctp_rcvinfo info, int flags, void *ulp_info) {
     19     if (data == NULL) {
     20         // Closed connection
     21         done = 1;
     22         usrsctp_close(sock);
     23 
     24         // The mainthread might be stuck, unlock it so it can exit
     25         pthread_mutex_unlock(&lock);
     26     } else {
     27         if (write(fileno(stdout), data, datalen) < 0) {
     28             perror("write");
     29         }
     30     }
     31 
     32     return 1;
     33 }
     34 
     35 int send_cb(struct socket *sock, uint32_t sb_free, void *ulp_info) {
     36     // Callback called when the socket has space in its send cue. When this function is called is
     37     // rather bizarre:
     38     //
     39     // 1. Once called, this function is not called again until something has been added to the send
     40     //    queue;
     41     // 2. On the client side, this will be called BEFORE a connection has been established;
     42     // 3. If it blocks, at least on the client we will get stuck in the establishment process until
     43     //    it unblocks; and
     44     // 4. On the server this is never called after connection establishment until something has
     45     //    been added to the send queue.
     46     //
     47     // The easiest solution here is to do all the sending in the main thread and have this unlock
     48     // a mutex that the main thread will try to lock whenever it gets `EAGAIN` from usrsctp.
     49     
     50     pthread_mutex_unlock(&lock);
     51 
     52     return 1;
     53 }
     54 
     55 int main(int argc, char **argv) {
     56     if (argc < 2 || !(strncmp(argv[1], "-c", 3) == 0 || strncmp(argv[1], "-s", 3) == 0)) {
     57         fprintf(stderr, "Usage: %s [-c|-s] ...\n", argv[0]);
     58         return 1;
     59     }
     60 
     61     const bool is_server = strncmp(argv[1], "-s", 3) == 0;
     62     const bool is_client = !is_server;
     63 
     64     pthread_mutex_init(&lock, NULL);
     65 
     66     int local_udp_port;
     67     int remote_sctp_port;
     68     int remote_ip;
     69 
     70     int local_ip = 0; 
     71     int local_sctp_port = 0;
     72     int remote_udp_port = 0; // Only client
     73 
     74     struct socket *sock;
     75 
     76     // Server-only variables
     77     struct socket *listen_sock = NULL;
     78 
     79     if (is_server) {
     80         if (argc != 7) {
     81             fprintf(stderr, "Usage: %s -s <local IP> <local UDP port> <local SCTP port> <remote IP> <remote SCTP port>\n", argv[0]);
     82             return 1;
     83         }
     84 
     85         local_ip = inet_addr(argv[2]);
     86         local_udp_port = atoi(argv[3]);
     87         local_sctp_port = atoi(argv[4]);
     88         remote_ip = inet_addr(argv[5]);
     89         remote_sctp_port = atoi(argv[6]);
     90     } else {
     91         if (argc != 8) {
     92             fprintf(stderr, "Usage: %s -c <local IP> <local UDP port> <local SCTP port> <remote IP> <remote UDP port> <remote SCTP port>\n", argv[0]);
     93             return 1;
     94         }
     95 
     96         local_ip = inet_addr(argv[2]);
     97         local_udp_port = atoi(argv[3]);
     98         local_sctp_port = atoi(argv[4]);
     99 
    100         remote_ip = inet_addr(argv[5]);
    101         remote_udp_port = atoi(argv[6]);
    102         remote_sctp_port = atoi(argv[7]);
    103     }
    104 
    105     // This is the local UDP encapsulation port
    106     usrsctp_init(local_udp_port, NULL, NULL);
    107 
    108     // We use the callback API since usrsctp doesn't seem to support using poll/select
    109     sock = usrsctp_socket(
    110         PF_INET,
    111         SOCK_STREAM,
    112         IPPROTO_SCTP, 
    113         receive_cb,
    114         send_cb, 
    115         send_buffer_size,
    116         NULL
    117     );
    118 
    119     if (is_server) {
    120         // Server needs two sockets; this is the listening one
    121         listen_sock = sock;
    122     }
    123 
    124     // This is my address
    125     struct sockaddr_in my_addr;
    126     memset(&my_addr, 0, sizeof(my_addr));
    127     my_addr.sin_family = AF_INET;
    128     my_addr.sin_port = htons(local_sctp_port);
    129     my_addr.sin_addr.s_addr = local_ip;
    130 
    131     // Bind to the socket
    132     if (usrsctp_bind(sock, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) {
    133         perror("bind");
    134         return 1;
    135     }
    136 
    137     if (is_client) {
    138         // Set the remote UDP port
    139         struct sctp_udpencaps encaps;
    140         memset(&encaps, 0, sizeof(encaps));
    141         encaps.sue_address.ss_family = AF_INET;
    142         encaps.sue_port = htons(remote_udp_port);
    143 
    144         usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, &encaps, sizeof(encaps));
    145     }
    146 
    147     // This is the remote address
    148     struct sockaddr_in addr;
    149     socklen_t addr_len = sizeof(addr);
    150 
    151     if (is_client) {
    152         // Set the remote address
    153         memset(&addr, 0, sizeof(addr));
    154         addr.sin_family = AF_INET;
    155         addr.sin_port = htons(remote_sctp_port);
    156         addr.sin_addr.s_addr = remote_ip;
    157 
    158         // Connect the socket
    159         fprintf(stderr, "Trying to connect...\n");
    160         if (usrsctp_connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    161             perror("connect");
    162             return 1;
    163         }
    164         fprintf(stderr, "Connected!\n");
    165     } else {
    166         // Wait until we have a connection from the right IP/port
    167         fprintf(stderr, "Waiting for a connection...\n");
    168         while (1) {
    169             if (usrsctp_listen(listen_sock, 2) < 0) {
    170                 perror("listen");
    171                 return 1;
    172             }
    173 
    174             if ((sock = usrsctp_accept(listen_sock, (struct sockaddr *) &addr, &addr_len)) == NULL) {
    175                 perror("accept");
    176                 return 1;
    177             }
    178 
    179             if (remote_ip != 0 && (addr.sin_addr.s_addr != remote_ip || addr.sin_port != htons(remote_sctp_port))) {
    180                 fprintf(stderr, "Bad IP/port tried: %d %s:%d\n", addr_len, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
    181                 usrsctp_close(sock);
    182             } else {
    183                 // Valid connection
    184                 break;
    185             }
    186         }
    187 
    188         fprintf(stderr, "Got a connection!\n");
    189 
    190         // Stop listening for new connections
    191         usrsctp_listen(listen_sock, 0);
    192     }
    193 
    194     // Mainloop: read from stdin and write to the SCTP socket
    195     char buff[send_buffer_size];
    196     int data_read;
    197 
    198     while (done == 0) {
    199         if ((data_read = read(fileno(stdin), &buff, sizeof(buff))) != -1) {
    200             // Send this to the remote
    201             if (data_read == 0) {
    202                 // EOF
    203                 done = 1;
    204             } else {
    205                 while (usrsctp_sendv(sock, buff, data_read, NULL, 0, NULL, 0, 0, 0) < 0) {
    206                     // usrsctp has a rather small send buffer, so on e.g. a file transfer we will
    207                     // run into EAGAIN. Using usrsctp_set_non_blocking(sock, 0) doesn't seem to
    208                     // have any effect on this.
    209                     if (errno == EAGAIN) {
    210                         pthread_mutex_lock(&lock);
    211                         if (done == 1) {
    212                             break;
    213                         }
    214                     } else {
    215                         perror("sendv");
    216                         done = 1;
    217                         break;
    218                     }
    219                 }
    220             }
    221         }
    222     }
    223 
    224     usrsctp_close(sock);
    225 
    226     if (is_server) {
    227         usrsctp_close(listen_sock);
    228     }
    229 
    230     usrsctp_finish();
    231 }