From fd3e36b9b455837cda6ff6d8b09b09248a1a787e Mon Sep 17 00:00:00 2001 From: Urban Wallasch Date: Wed, 23 Oct 2019 19:30:15 +0200 Subject: [PATCH] * net: added recvfrom_tm() and sendto_tm() * net: improved test program --- net/net.c | 88 ++++++++++++++++++++++++++ net/net.h | 74 +++++++++++++++++++++- net/net_test.c | 166 +++++++++++++++++++++++++++++++++---------------- 3 files changed, 273 insertions(+), 55 deletions(-) diff --git a/net/net.c b/net/net.c index 018df6e..5ee1f75 100644 --- a/net/net.c +++ b/net/net.c @@ -284,3 +284,91 @@ char *net_strerror(int errnum, char *buf, size_t len) { return buf; } +/* Read from socket or file descriptor: */ +ssize_t recvfrom_tm(int fd, void *buf, size_t len, int flags, + struct sockaddr *addr, socklen_t *alen, int timeout) { + ssize_t r; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLIN; + r = poll(&pfd, 1, timeout); + if (r == 1 && pfd.revents & (POLLIN | POLLERR | POLLHUP)) { + /* In case of POLLERR or POLLHUP we let recv() catch the error! */ + r = recvfrom(fd, buf, len, flags | MSG_DONTWAIT, addr, alen); + if (r < 0 && errno == ENOTSOCK) { + NETLOG_DBG("recvfrom_tm(%d): not a socket, trying read()", fd); + r = read(fd, buf, len); + if (r >= 0 && alen != NULL) + *alen = 0; + } + r = r < 0 ? EAI_SYSTEM : r; + } + else if (r == 0) { + errno = ETIME; + r = EAI_SYSTEM; + } + else if (r < 0) { + r = EAI_SYSTEM; + } + else if (pfd.revents & POLLNVAL) { + errno = EBADF; + r = EAI_SYSTEM; + } + else { + NETLOG_DBG("This should never happen! (r=%zd, revents=%hd)", r, pfd.revents); + errno = EIO; + r = EAI_SYSTEM; + } + if (r < 0) { + NETLOG_ERR(r, "recv_tm(%d)", fd); + } + else { + NETLOG_DBG("recv_tm(%d): %zd bytes read", fd, r); + } + return r; +} + +/* Write to socket or file descriptor: */ +ssize_t sendto_tm(int fd, void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t alen, int timeout) { + ssize_t r; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLOUT; + r = poll(&pfd, 1, timeout); + if (r == 1 && pfd.revents & (POLLOUT | POLLERR | POLLHUP)) { + /* In case of POLLERR or POLLHUP we let send() catch the error! */ + r = sendto(fd, buf, len, flags | MSG_DONTWAIT | MSG_NOSIGNAL, addr, alen); + if (r < 0 && errno == ENOTSOCK) { + NETLOG_DBG("sendto_tm(%d): not a socket, trying write()", fd); + r = write(fd, buf, len); + } + r = r < 0 ? EAI_SYSTEM : r; + } + else if (r == 0) { + errno = ETIME; + r = EAI_SYSTEM; + } + else if (r < 0) { + r = EAI_SYSTEM; + } + else if (pfd.revents & POLLNVAL) { + errno = EBADF; + r = EAI_SYSTEM; + } + else { + NETLOG_DBG("This should never happen! (r=%zd, revents=%hd)", r, pfd.revents); + errno = EIO; + r = EAI_SYSTEM; + } + if (r < 0) { + NETLOG_ERR(r, "send_tm(%d)", fd); + } + else { + NETLOG_DBG("sendto_tm(%d): %zd bytes written", fd, r); + } + return r; +} + diff --git a/net/net.h b/net/net.h index 66d96fd..946681a 100644 --- a/net/net.h +++ b/net/net.h @@ -3,11 +3,21 @@ * TCP or UDP servers and clients. * * XXX_open_server - functions and macros to set up a simple server + * * XXX_open_client - functions and macros to connect to a network host + * * net_close - close a network socket + * * tcp_accept - accept a TCP connection request on listener socket + * * net_strerror - get textual error message * + * recvfrom_tm, recv_tm, read_tm + * - functions and macros to read with timeout + * + * sendto_tm, send_tm, write_tm + * - functions and macros to write with timeout + * */ #ifndef NET_H_ @@ -97,8 +107,8 @@ extern int net_close(int sock); * * Note: When the time-out expires a negative value is returned and * errno is set to ETIME. A negative value for timeout will cause - * tcp_accept() to wait indefinitely. Set timeout to zero for - * non-blocking operation (immediate return). + * tcp_accept() to wait indefinitely. Set timeout to zero for immediate + * return. */ extern int tcp_accept(int sock, int timeout); @@ -109,7 +119,7 @@ extern int tcp_accept(int sock, int timeout); * Returns a pointer to the buffer. * * errnum - error code returned by any of the above functions - * buf - user supplied buffer to store the error message + * buf - user supplied buffer to store the error message * len - maximum number of characters buf can hold * * Note: Depending on the actual value of errnum this function will @@ -118,6 +128,64 @@ extern int tcp_accept(int sock, int timeout); extern char *net_strerror(int errnum, char *buf, size_t len); +/* + * Read from file or socket descriptor with timeout. + * + * Returns the number of bytes received, or a negative value on error. + * + * fd - file or socket descriptor to read from + * buf - user supplied buffer to store the received message + * len - maximum number of bytes to store in buf + * flags - additional flags to pass, see: man recv + * addr - see: man recvfrom + * alen - see: man recvfrom + * timeout - timeout in milliseconds + * + * Note: A zero return value indicates the equivalent of an EOF condition. + * When reading from a socket, the MSG_DONTWAIT flag is added in the + * underlying call to recvfrom(). When the time-out expires, a negative + * value is returned and errno is set to ETIME. A negative value for + * timeout will cause indefinite wait. Set timeout to zero for immediate + * return. + */ +extern ssize_t recvfrom_tm(int fd, void *buf, size_t len, int flags, + struct sockaddr *addr, socklen_t *alen, int timeout); +/* + * Shorthand macros to closely match recv() and read() prototypes: + */ +#define recv_tm(d_,b_,l_,f_,t_) recvfrom_tm((d_),(b_),(l_),(f_),NULL,NULL,(t_)) +#define read_tm(d_,b_,l_,t_) recvfrom_tm((d_),(b_),(l_),0,NULL,NULL,(t_)) + + +/* + * Write to file or socket descriptor with timeout. + * + * Returns the number of bytes sent, or a negative value on error. + * + * fd - file or socket descriptor to read from + * buf - user supplied buffer to store the received message + * len - maximum number of bytes to store in buf + * flags - additional flags to pass, see: man send + * addr - see: man sendto + * alen - see: man sendto + * timeout - timeout in milliseconds + * + * Note: A zero return value indicates the equivalent of an EOF condition. + * When writing to a socket, the MSG_DONTWAIT and MSG_NOSIGNAL flags are + * added in the underlying call to sendto(). When the time-out expires, + * a negative value is returned and errno is set to ETIME. A negative + * value for timeout will cause indefinite wait. Set timeout to zero + * for immediate return. + */ +extern ssize_t sendto_tm(int fd, void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t alen, int timeout); +/* + * Shorthand macros to closely match send() and write() prototypes: + */ +#define send_tm(d_,b_,l_,f_,t_) sendto_tm((d_),(b_),(l_),(f_),NULL,0,(t_)) +#define write_tm(d_,b_,l_,t_) sendto_tm((d_),(b_),(l_),0,NULL,0,(t_)) + + #ifdef cplusplus } #endif diff --git a/net/net_test.c b/net/net_test.c index 78c29d7..66c5484 100644 --- a/net/net_test.c +++ b/net/net_test.c @@ -6,17 +6,12 @@ #include #include #include +#include #include "net.h" #define LOG(...) fprintf(stderr, __VA_ARGS__) -#define LOG_E(...) do{ \ - LOG("%s:%s@%d: ", __FILE__, __func__, __LINE__); \ - LOG(__VA_ARGS__); \ - LOG(": %s\n", strerror(errno)); \ - }while(0) - #define LOG_NET(e_,...) do{ \ char ebuf[100]; \ LOG("%s:%s@%d: ", __FILE__, __func__, __LINE__); \ @@ -24,46 +19,77 @@ LOG(": %s\n", net_strerror(e_,ebuf,sizeof ebuf)); \ }while(0) +#define AFTOS(a_) ((a_)==AF_INET?"4":(a_)==AF_INET6?"6":"*") + int main(int argc, char *argv[]) { int n, c, l; + int arg = 1, af = AF_UNSPEC, mode = 't'; + char *nod = "localhost"; + char *svc = "12345"; char buf[4000]; - signal(SIGPIPE, SIG_IGN); - if (argc < 2) { - LOG("Usage: %s t|T|u|U\n", argv[0]); + LOG("Usage: %s [4|6] [t|T|u|U [address [service]]]\n", argv[0]); exit(EXIT_FAILURE); } + if (*argv[arg] == '4') { + af = AF_INET; + ++arg; + } + else if (*argv[arg] == '6') { + af = AF_INET6; + ++arg; + } + if (argv[arg]) + mode = argv[arg++][0]; + if (argv[arg]) + nod = argv[arg++]; + if (argv[arg]) + svc = argv[arg++]; - switch (*argv[1]) { + switch (mode) { default: case 't': - // TCP client - // Remote: ncat -l localhost 12345 - c = tcp_open_client("localhost", "12345", "localhost"); + // TCP generator client + printf("TCP%s client\n", AFTOS(af)); + c = net_open_client(nod, svc, NULL, SOCK_STREAM, af); if (c < 0) { LOG_NET(c, "tcp_open_client"); exit(EXIT_FAILURE); } while (1) { + memset(buf, 0, sizeof buf); sprintf(buf, "hello %ld\n", time(NULL)); - n = send(c, buf, strlen(buf), 0); + n = send_tm(c, buf, strlen(buf), 0, 5000); if (n <= 0) { - LOG_E("send"); + LOG_NET(n, "send_tm"); + break; + } + memset(buf, 0, sizeof buf); + n = recv_tm(c, buf, sizeof buf-1, 0, 5000); + if (n > 0) { + buf[n] = '\0'; + printf("%s", buf); + } + else if (n < 0) { + LOG_NET(n, "recv"); break; } - if (getchar() == 'q') + else { + LOG("connection reset by peer\n"); break; + } + sleep(1); } c = net_close(c); LOG_NET(c, "net_close"); break; case 'T': - // TCP server - // Remote: ncat localhost 12345 - l = tcp6_open_server("", "12345"); + // TCP echo server + printf("TCP%s server\n", AFTOS(af)); + l = net_open_server(nod, svc, SOCK_STREAM, af); if (l < 0) { LOG_NET(l, "tcp6_open_server"); exit(EXIT_FAILURE); @@ -74,40 +100,61 @@ int main(int argc, char *argv[]) { if (c < 0) { continue; } - memset(buf, 0, sizeof buf); - n = recv(c, buf, sizeof buf-1, 0); - if (n > 0) { - printf("%s", buf); - n = send(c, buf, n, 0); - if (n <= 0) { - LOG_E("send"); + for (n = 1; n > 0; ) { + memset(buf, 0, sizeof buf); + n = recv_tm(c, buf, sizeof buf-1, 0, 5000); + if (n > 0) { + buf[n] = '\0'; + printf("%s", buf); + n = send_tm(c, buf, n, 0, 5000); + if (n <= 0) { + LOG_NET(n, "send"); + break; + } + } + else if (n < 0) { + LOG_NET(n, "recv"); + break; + } + else { + LOG("connection reset by peer\n"); break; } - } - else if (n < 0) { - LOG_E("recv"); - break; } c = net_close(c); LOG_NET(c, "net_close"); } - c = net_close(c); - LOG_NET(c, "net_close"); + l = net_close(l); + LOG_NET(l, "net_close"); break; case 'u': - // UDP client - // Remote: ncat -4 -u -l localhost 12345 - c = udp4_open_client(NULL, "12345", NULL); + // UDP generator client + printf("UDP%s client\n", AFTOS(af)); + c = net_open_client(nod, svc, NULL, SOCK_DGRAM, af); if (c < 0) { LOG_NET(c, "udp4_open_client"); exit(EXIT_FAILURE); } while (1) { sprintf(buf, "hello %ld\n", time(NULL)); - n = send(c, buf, strlen(buf), 0); + n = send_tm(c, buf, strlen(buf), 0, 5000); if (n <= 0) { - LOG_E("send"); + LOG_NET(n, "send_tm"); + break; + } + memset(buf, 0, sizeof buf); + n = recv_tm(c, buf, sizeof buf-1, 0, 5000); + if (n > 0) { + buf[n] = '\0'; + printf("%s", buf); + } + else if (n < 0) { + LOG_NET(n, "recv"); + break; + } + else { + LOG("connection reset by peer\n"); break; } sleep(1); @@ -117,26 +164,41 @@ int main(int argc, char *argv[]) { break; case 'U': - // UDP server - // Remote: ncat -4 -u localhost 12345 - l = udp4_open_server(NULL, "12345"); - if (l < 0) { - LOG_NET(l, "udp4_open_server"); + // UDP echo server + printf("UDP%s server\n", AFTOS(af)); + c = net_open_server(nod, svc, SOCK_DGRAM, af); + if (c < 0) { + LOG_NET(c, "udp4_open_server"); exit(EXIT_FAILURE); } while (1) { - memset(buf, 0, sizeof buf); - n = recv(l, buf, sizeof buf-1, 0); - if (n > 0) { - printf("%s", buf); - } - else { - LOG_E("recv"); - break; + struct sockaddr_in6 addr; + struct sockaddr *paddr = (struct sockaddr *)&addr; + socklen_t addrlen = sizeof addr; + for (n = 1; n > 0; ) { + memset(buf, 0, sizeof buf); + n = recvfrom_tm(c, buf, sizeof buf-1, 0, paddr, &addrlen, 5000); + if (n > 0) { + buf[n] = '\0'; + printf("%s", buf); + n = sendto_tm(c, buf, n, 0, paddr, addrlen, 5000); + if (n <= 0) { + LOG_NET(n, "send"); + break; + } + } + else if (n < 0) { + LOG_NET(n, "recv"); + break; + } + else { + LOG("connection reset by peer\n"); + break; + } } } - l = net_close(l); - LOG_NET(l, "net_close"); + c = net_close(c); + LOG_NET(c, "net_close"); break; } -- 2.30.2