| #define _GNU_SOURCE // for POLLRDHUP |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| /* for OSes which don't have it */ |
| #ifndef POLLRDHUP |
| #define POLLRDHUP 0 |
| #endif |
| |
| #ifndef MSG_NOSIGNAL |
| #define MSG_NOSIGNAL 0 |
| #endif |
| #ifndef MSG_MORE |
| #define MSG_MORE 0 |
| #endif |
| |
| int verbose = 0; |
| int cmd = 0; |
| int cmdstep = 0; |
| int zero = 0; |
| int one = 1; |
| int lfd = -1; |
| int cfd = -1; |
| int sfd = -1; |
| int connected = 0; |
| struct sockaddr_in saddr, caddr; |
| socklen_t salen, calen; |
| |
| static inline const char *side(int fd) |
| { |
| if (fd == lfd) |
| return "l"; |
| if (fd == sfd) |
| return "s"; |
| if (fd == cfd) |
| return "c"; |
| return "?"; |
| } |
| |
| void usage(const char *arg0) |
| { |
| printf("Usage: %s [ arg [<action>[,...]] ] ...\n" |
| "args:\n" |
| " -h display this help\n" |
| " -v verbose mode (shows ret values)\n" |
| " -c <actions> perform <action> on client side socket\n" |
| " -s <actions> perform <action> on server side socket\n" |
| " -l <actions> perform <action> on listening socket\n" |
| "\n" |
| "actions for -c/-s/-l (multiple may be delimited by commas) :\n" |
| " con connect to listener, implicit before first -c/-s\n" |
| " acc accept on listener, implicit before first -s\n" |
| " snd send a few bytes of data\n" |
| " mor send a few bytes of data with MSG_MORE\n" |
| " rcv receive a few bytes of data\n" |
| " drn drain: receive till zero\n" |
| " shr SHUT_RD : shutdown read side\n" |
| " shw SHUT_WR : shutdown write side\n" |
| " shb SHUT_RDWR : shutdown both sides\n" |
| " lin disable lingering on the socket\n" |
| " clo close the file descriptor\n" |
| " pol poll() for any event\n" |
| "\n", arg0); |
| } |
| |
| void die(const char *msg) |
| { |
| if (msg) |
| fprintf(stderr, "%s\n", msg); |
| exit(1); |
| } |
| |
| const char *get_errno(int ret) |
| { |
| static char errmsg[100]; |
| |
| if (ret >= 0) |
| return ""; |
| |
| snprintf(errmsg, sizeof(errmsg), " (%s)", strerror(errno)); |
| return errmsg; |
| } |
| |
| void do_acc(int fd) |
| { |
| int ret; |
| |
| calen = sizeof(caddr); |
| ret = accept(lfd, (struct sockaddr*)&caddr, &calen); |
| if (sfd < 0) |
| sfd = ret; |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| } |
| |
| void do_con(int fd) |
| { |
| int ret; |
| |
| ret = connect(cfd, (const struct sockaddr*)&saddr, salen); |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| connected = 1; |
| } |
| |
| void do_snd(int fd) |
| { |
| int ret; |
| |
| ret = send(fd, "foo", 3, MSG_NOSIGNAL|MSG_DONTWAIT); |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| } |
| |
| void do_mor(int fd) |
| { |
| int ret; |
| |
| ret = send(fd, "foo", 3, MSG_NOSIGNAL|MSG_DONTWAIT|MSG_MORE); |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| } |
| |
| void do_rcv(int fd) |
| { |
| char buf[10]; |
| int ret; |
| |
| ret = recv(fd, buf, sizeof(buf), MSG_DONTWAIT); |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| } |
| |
| void do_drn(int fd) |
| { |
| char buf[16384]; |
| int total = -1; |
| int ret; |
| |
| while (1) { |
| ret = recv(fd, buf, sizeof(buf), 0); |
| if (ret <= 0) |
| break; |
| if (total < 0) |
| total = 0; |
| total += ret; |
| } |
| |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, total, get_errno(ret)); |
| } |
| |
| void do_shr(int fd) |
| { |
| int ret; |
| |
| ret = shutdown(fd, SHUT_RD); |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| } |
| |
| void do_shw(int fd) |
| { |
| int ret; |
| |
| ret = shutdown(fd, SHUT_WR); |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| } |
| |
| void do_shb(int fd) |
| { |
| int ret; |
| |
| ret = shutdown(fd, SHUT_RDWR); |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| } |
| |
| void do_lin(int fd) |
| { |
| struct linger nolinger = { .l_onoff = 1, .l_linger = 0 }; |
| int ret; |
| |
| ret = setsockopt(fd, SOL_SOCKET, SO_LINGER, &nolinger, sizeof(nolinger)); |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| } |
| |
| void do_clo(int fd) |
| { |
| int ret; |
| |
| ret = close(fd); |
| if (verbose) |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret)); |
| } |
| |
| void do_pol(int fd) |
| { |
| struct pollfd fds = { .fd = fd, .events = POLLIN|POLLOUT|POLLRDHUP, .revents=0 }; |
| int ret; |
| |
| ret = poll(&fds, 1, 0); |
| if (verbose) { |
| printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s ev=%#x ", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret), ret > 0 ? fds.revents : 0); |
| if (ret > 0 && fds.revents) { |
| int flags, flag; |
| putchar('('); |
| |
| for (flags = fds.revents; flags; flags ^= flag) { |
| flag = flags ^ (flags & (flags - 1)); // keep lowest bit only |
| switch (flag) { |
| case POLLIN: printf("IN"); break; |
| case POLLOUT: printf("OUT"); break; |
| case POLLPRI: printf("PRI"); break; |
| case POLLHUP: printf("HUP"); break; |
| case POLLERR: printf("ERR"); break; |
| case POLLNVAL: printf("NVAL"); break; |
| #if POLLRDHUP |
| case POLLRDHUP: printf("RDHUP"); break; |
| #endif |
| default: printf("???[%#x]", flag); break; |
| } |
| if (flags ^ flag) |
| putchar(' '); |
| } |
| putchar(')'); |
| } |
| putchar('\n'); |
| } |
| } |
| |
| int main(int argc, char **argv) |
| { |
| const char *arg0; |
| char *word, *next; |
| int fd; |
| |
| /* listener */ |
| lfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); |
| if (lfd < 0) |
| die("socket(l)"); |
| |
| setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
| |
| memset(&saddr, 0, sizeof(saddr)); |
| saddr.sin_family = AF_INET; |
| saddr.sin_port = htons(0); |
| salen = sizeof(saddr); |
| |
| if (bind(lfd, (struct sockaddr *)&saddr, salen) < 0) |
| die("bind()"); |
| |
| if (listen(lfd, 1000) < 0) |
| die("listen()"); |
| |
| if (getsockname(lfd, (struct sockaddr *)&saddr, &salen) < 0) |
| die("getsockname()"); |
| |
| |
| /* client */ |
| cfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); |
| if (cfd < 0) |
| die("socket(c)"); |
| |
| arg0 = argv[0]; |
| if (argc < 2) { |
| usage(arg0); |
| exit(1); |
| } |
| |
| write(1, "#### BEGIN ####\n", 16); // add a visible delimiter in the traces |
| |
| while (argc > 1) { |
| argc--; argv++; |
| if (**argv != '-') { |
| usage(arg0); |
| exit(1); |
| } |
| |
| fd = -1; |
| switch (argv[0][1]) { |
| case 'h' : |
| usage(arg0); |
| exit(0); |
| break; |
| case 'v' : |
| verbose++; |
| break; |
| case 'c' : |
| cmd++; cmdstep = 0; |
| if (!connected) { |
| do_con(cfd); |
| /* connection is pending in accept queue, accept() will either be |
| * explicit with "-l acc" below, or implicit on "-s <cmd>" |
| */ |
| } |
| fd = cfd; |
| break; |
| case 's' : |
| cmd++; cmdstep = 0; |
| if (!connected) |
| do_con(cfd); |
| if (sfd < 0) |
| do_acc(lfd); |
| if (sfd < 0) |
| die("accept()"); |
| fd = sfd; |
| break; |
| case 'l' : |
| cmd++; cmdstep = 0; |
| fd = lfd; |
| break; |
| default : usage(arg0); exit(1); break; |
| } |
| |
| if (fd >= 0) { /* an action is required */ |
| if (argc < 2) { |
| usage(arg0); |
| exit(1); |
| } |
| |
| for (word = argv[1]; word && *word; word = next) { |
| next = strchr(word, ','); |
| if (next) |
| *(next++) = 0; |
| cmdstep++; |
| if (strcmp(word, "acc") == 0) { |
| do_acc(fd); |
| } |
| else if (strcmp(word, "con") == 0) { |
| do_con(fd); |
| } |
| else if (strcmp(word, "snd") == 0) { |
| do_snd(fd); |
| } |
| else if (strcmp(word, "mor") == 0) { |
| do_mor(fd); |
| } |
| else if (strcmp(word, "rcv") == 0) { |
| do_rcv(fd); |
| } |
| else if (strcmp(word, "drn") == 0) { |
| do_drn(fd); |
| } |
| else if (strcmp(word, "shb") == 0) { |
| do_shb(fd); |
| } |
| else if (strcmp(word, "shr") == 0) { |
| do_shr(fd); |
| } |
| else if (strcmp(word, "shw") == 0) { |
| do_shw(fd); |
| } |
| else if (strcmp(word, "lin") == 0) { |
| do_lin(fd); |
| } |
| else if (strcmp(word, "clo") == 0) { |
| do_clo(fd); |
| } |
| else if (strcmp(word, "pol") == 0) { |
| do_pol(fd); |
| } |
| else { |
| printf("Ignoring unknown action '%s' in step #%d of cmd #%d\n", word, cmdstep, cmd); |
| } |
| } |
| argc--; argv++; |
| } |
| } |
| |
| write(1, "#### END ####\n", 14); // add a visible delimiter in the traces |
| |
| if (!cmd) { |
| printf("No command was requested!\n"); |
| usage(arg0); |
| exit(1); |
| } |
| |
| return 0; |
| } |