nbeng.c (9422B)
1 /* 2 * Non-blocking network engine for tcp/udp/ssl connections 3 * The design uses the same socket for sending and receiving 4 * data. The relevant data are stored in a connection context 5 * which is passed around the functions for dealing with it. 6 * Also provided are sample reading and writing functions. 7 * Copyleft: 8 * DsP <dsp@2f30.org> 9 * Lazaros Koromilas <lostd@2f30.org> 10 * Cheers go to PhotoRec for helping recover this file :) 11 */ 12 13 #include <sys/types.h> 14 #include <sys/socket.h> 15 16 #include <netdb.h> 17 18 #include <err.h> 19 #include <fcntl.h> 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <unistd.h> 24 #include <errno.h> 25 26 /* global flags */ 27 unsigned int tflag = 0; 28 unsigned int sflag = 0; 29 unsigned int qflag = 0; 30 31 /* connection context */ 32 typedef struct concontxt_t { 33 #define UDPCON 1 34 #define TCPCON 2 35 #define SSLCON 3 36 unsigned int contype; /* connection type */ 37 int confd; /* net connection fd */ 38 int clifd; 39 int clifds[FD_SETSIZE]; /* for tcp connected clients */ 40 fd_set lset; /* the set that we select() on */ 41 struct addrinfo *clinfo; /* info on where we connect */ 42 struct addrinfo *srvinfo; /* info for the local part */ 43 #define INBUFSIZ 512 44 char *buf; /* buffer used for input/output */ 45 unsigned int buflen; 46 int wfd, lfd; /* local file descriptors */ 47 } concontxt; 48 49 void 50 usage(int ret) 51 { 52 fprintf(stderr, "usage: nbeng [-ht] rhost rport\n" 53 "\t-t Use TCP for transport\n" 54 "\t-h This help screen\n"); 55 if (ret) 56 exit(1); 57 } 58 59 static void 60 set_nonblocking(int fd) 61 { 62 int opts; 63 opts = fcntl(fd, F_GETFL); 64 if (opts < 0) 65 err(1, "fcntl"); 66 opts = (opts | O_NONBLOCK); 67 if (fcntl(fd, F_SETFL, opts) < 0) 68 err(1, "fcntl"); 69 } 70 71 static void 72 set_blocking(int fd) 73 { 74 int opts; 75 opts = fcntl(fd, F_GETFL); 76 if (opts < 0) 77 err(1, "fcntl"); 78 opts &= (~O_NONBLOCK); 79 if (fcntl(fd, F_SETFL, opts) < 0) 80 err(1, "fcntl"); 81 } 82 83 /* 84 * This is the main function that prepares a socket for: 85 * a) connection to the other endpoint. 86 * b) being able to receive data. 87 * When it is ready it sets the fields in the context. 88 * The flag for the connection type lives in the context. 89 */ 90 static void 91 prepare_socket(concontxt *con, char *host, char *port, int lflag) 92 { 93 struct addrinfo cli_hints, *cli_servinfo, srv_hints, *srv_servinfo, *p0; 94 int rv, cli_sockfd, optval = 1; 95 96 if ((con == NULL) || (host == NULL) || (port == NULL)) 97 errx(1, "prepare_socket was passed a null arg"); 98 memset(&cli_hints, 0, sizeof (cli_hints)); 99 memset(&srv_hints, 0, sizeof (srv_hints)); 100 cli_hints.ai_family = srv_hints.ai_family = AF_INET; 101 srv_hints.ai_flags = AI_PASSIVE; 102 cli_hints.ai_socktype = srv_hints.ai_socktype = 103 ((con->contype == TCPCON) || (con->contype == SSLCON)) 104 ? SOCK_STREAM : SOCK_DGRAM; 105 rv = getaddrinfo(host, port, &cli_hints, &cli_servinfo); 106 if (rv) 107 errx(1, "getaddrinfo: %s", gai_strerror(rv)); 108 rv = getaddrinfo(NULL, port, &srv_hints, &srv_servinfo); 109 if (rv) 110 errx(1, "getaddrinfo: %s", gai_strerror(rv)); 111 /* getaddrinfo returns a list of results so we iterate on it */ 112 for (p0 = cli_servinfo; p0; p0 = p0->ai_next) { 113 cli_sockfd = socket(p0->ai_family, p0->ai_socktype, 114 p0->ai_protocol); 115 if (cli_sockfd < 0) 116 continue; /* until the socket is ready */ 117 rv = setsockopt(cli_sockfd, SOL_SOCKET, 118 SO_REUSEADDR, &optval, sizeof (optval)); 119 if (rv < 0) { 120 close(cli_sockfd); 121 warn("setsockopt"); 122 continue; 123 } 124 break; 125 } 126 if (!p0) 127 err(1, "socket"); 128 /* the same for our local part */ 129 for (p0 = srv_servinfo; p0; p0 = p0->ai_next) { 130 if (bind(cli_sockfd, p0->ai_addr, p0->ai_addrlen) < 0) { 131 close(cli_sockfd); 132 warn("bind"); 133 continue; 134 } 135 break; 136 } 137 if (!p0) 138 err(1, "bind"); 139 /* if it's a tcp connection we have to listen() */ 140 if (con->contype != UDPCON && lflag) 141 listen(cli_sockfd, 5); 142 /* all was ok, so we register the socket to the context */ 143 con->confd = cli_sockfd; 144 con->clinfo = cli_servinfo; 145 } 146 147 /* 148 * Reads data from standard input and saves them in the context. 149 * Sets the qflag on "Q". 150 */ 151 static void 152 readstdin(concontxt *con) 153 { 154 char buf[INBUFSIZ]; 155 ssize_t n; 156 157 /* handle input data */ 158 do { 159 n = read(con->wfd, buf, INBUFSIZ); 160 if (n < 0) { 161 if (errno != EAGAIN) 162 err(1, "read"); 163 continue; 164 } 165 break; 166 } while (1); 167 buf[n] = '\0'; 168 169 /* user quits? */ 170 if (n == 0 || strncmp(buf, "Q\n", 3) == 0) { 171 qflag = 1; 172 return; 173 } 174 printf("n=%zd buf=%s", n, buf); 175 176 /* copy to context */ 177 if ((con->buf = malloc(n)) == NULL) { 178 warn("malloc"); 179 goto freex; 180 } 181 memcpy(con->buf, buf, n); 182 con->buflen = n; 183 return; 184 185 freex: 186 if (con->buf != NULL) 187 free(con->buf); 188 con->buf = NULL; 189 con->buflen = 0; 190 } 191 192 /* 193 * Sends data available in the context to all peers. 194 */ 195 static void 196 writesock(concontxt *con) 197 { 198 ssize_t n; 199 200 /* nothing to do */ 201 if (con->buf == NULL) 202 return; 203 204 if (con->contype == UDPCON) { 205 n = sendto(con->confd, con->buf, con->buflen, 0, 206 con->clinfo->ai_addr, 207 con->clinfo->ai_addrlen); 208 if (n < 0) 209 warn("sendto"); 210 } else { 211 n = write(con->clifd, con->buf, con->buflen); 212 if (n < 0) { 213 warn("write"); 214 close(con->clifd); 215 con->clifd = -1; 216 goto freex; 217 } 218 } 219 220 freex: 221 if (con->buf != NULL) 222 free(con->buf); 223 con->buf = NULL; 224 con->buflen = 0; 225 } 226 227 /* 228 * Reads data from the socket and saves them in the context. 229 */ 230 static void 231 readsock(concontxt *con) 232 { 233 char buf[INBUFSIZ]; 234 ssize_t n; 235 int r; 236 char host[NI_MAXHOST]; 237 socklen_t addr_len; 238 struct sockaddr_storage their_addr; 239 240 addr_len = sizeof (their_addr); 241 if (con->contype == UDPCON) { 242 n = recvfrom(con->confd, buf, sizeof (buf), MSG_DONTWAIT, 243 (struct sockaddr *)&their_addr, &addr_len); 244 if (n < 0) { 245 warn("recvfrom"); 246 goto freex; 247 } 248 249 /* resolv */ 250 r = getnameinfo((struct sockaddr *)&their_addr, addr_len, 251 host, sizeof (host), NULL, 0, 0); 252 if (r < 0) { 253 warn("getnameinfo"); 254 snprintf(host, sizeof (host), "unknown"); 255 } 256 printf("host=%s\n", host); 257 } else { 258 do { 259 n = read(con->clifd, buf, sizeof (buf)); 260 if (n < 0) { 261 if (errno != EAGAIN) 262 err(1, "read"); 263 continue; 264 } 265 if (n == 0) { 266 qflag = 1; 267 close(con->clifd); 268 con->clifd = -1; 269 goto freex; 270 } 271 break; 272 } while (1); 273 } 274 buf[n] = '\0'; 275 276 /* copy to context */ 277 if ((con->buf = malloc(n)) == NULL) { 278 warn("malloc"); 279 goto freex; 280 } 281 memcpy(con->buf, buf, n); 282 con->buflen = n; 283 printf("n=%zd buf=%s", n, buf); 284 return; 285 286 freex: 287 if (con->buf != NULL) 288 free(con->buf); 289 con->buf = NULL; 290 con->buflen = 0; 291 } 292 293 /* 294 * Writes data available in the context to standard output. 295 */ 296 static void 297 writestdout(concontxt *con) 298 { 299 write(con->lfd, con->buf, con->buflen); 300 301 if (con->buf != NULL) 302 free(con->buf); 303 con->buf = NULL; 304 con->buflen = 0; 305 } 306 307 static void 308 myaccept(concontxt *con) 309 { 310 int r; 311 char host[NI_MAXHOST]; 312 int newfd; 313 struct sockaddr_storage sa; 314 socklen_t salen = sizeof (sa); 315 316 newfd = accept(con->confd, (struct sockaddr *)&sa, &salen); 317 printf("newfd=%d\n", newfd); 318 if (newfd < 0) 319 err(1, "accept"); 320 con->clifd = newfd; 321 322 /* resolv */ 323 r = getnameinfo((struct sockaddr *)&sa, salen, 324 host, sizeof (host), NULL, 0, 0); 325 if (r < 0) { 326 warn("getnameinfo"); 327 snprintf(host, sizeof (host), "unknown"); 328 } 329 printf("host=%s\n", host); 330 } 331 332 static void 333 myconnect(concontxt *con) 334 { 335 int r; 336 337 r = connect(con->confd, 338 con->clinfo->ai_addr, 339 con->clinfo->ai_addrlen); 340 if (r < 0) 341 warn("connect"); 342 con->clifd = con->confd; 343 con->confd = -1; 344 } 345 346 int 347 main(int argc, char *argv[]) 348 { 349 int c; 350 int highsock, readsocks; 351 struct timeval timeout; 352 char *host, *port; 353 concontxt *con; 354 355 while ((c = getopt(argc, argv, "ht")) != -1) { 356 switch (c) { 357 case 'h': 358 usage(1); 359 break; 360 case 't': 361 tflag = 1; 362 break; 363 default: 364 usage(1); 365 } 366 } 367 argc -= optind; 368 argv += optind; 369 370 if (argc != 2) 371 usage(1); 372 host = argv[0]; 373 port = argv[1]; 374 375 con = malloc(sizeof (concontxt)); 376 if (con == NULL) 377 err(1, "malloc"); 378 /* defaults */ 379 con->wfd = STDIN_FILENO; 380 con->lfd = STDOUT_FILENO; 381 con->contype = (tflag || sflag) ? TCPCON : UDPCON; 382 con->clifd = -1; 383 384 prepare_socket(con, host, port, 1); 385 386 /* file descriptors for reading are stdin and confd */ 387 while (1) { 388 timeout.tv_sec = 1; 389 timeout.tv_usec = 0; 390 FD_ZERO(&(con->lset)); 391 FD_SET(con->wfd, &con->lset); 392 highsock = con->wfd; 393 if (con->confd != -1) { 394 FD_SET(con->confd, &con->lset); 395 highsock = con->confd; 396 } 397 if (con->clifd != -1) { 398 FD_SET(con->clifd, &con->lset); 399 highsock = con->clifd; 400 } 401 if (qflag) 402 goto freex; 403 readsocks = select(highsock + 1, &con->lset, NULL, 404 NULL, &timeout); 405 if (readsocks < 0) { 406 warn("select"); 407 goto freex; 408 } 409 /* change this to == 0 and put default printout if necessary */ 410 if (readsocks != 0) { 411 if (FD_ISSET(con->wfd, &con->lset)) { 412 if (con->clifd == -1 413 && con->contype != UDPCON) { 414 close(con->confd); 415 prepare_socket(con, host, port, 0); 416 myconnect(con); 417 } 418 readstdin(con); 419 writesock(con); 420 } 421 if (con->confd != -1 && 422 FD_ISSET(con->confd, &con->lset)) { 423 if (con->contype == UDPCON) { 424 readsock(con); 425 writestdout(con); 426 } else { 427 myaccept(con); 428 } 429 } 430 if (con->clifd != -1 && 431 FD_ISSET(con->clifd, &con->lset)) { 432 readsock(con); 433 writestdout(con); 434 } 435 } 436 } 437 438 freex: 439 printf("exiting\n"); 440 if (con != NULL) { 441 close(con->confd); 442 close(con->clifd); 443 free(con); 444 } 445 return 0; 446 }