| sctp-echo - SCTP-over-UDP echo server/client for creating reliable tunnels over UDP. |
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 }