* net: added recvfrom_tm() and sendto_tm()
authorUrban Wallasch <urban.wallasch@freenet.de>
Wed, 23 Oct 2019 17:30:15 +0000 (19:30 +0200)
committerUrban Wallasch <urban.wallasch@freenet.de>
Wed, 23 Oct 2019 17:30:15 +0000 (19:30 +0200)
* net: improved test program

net/net.c
net/net.h
net/net_test.c

index 018df6e0b59fa5c840787d7868e8e0effdfccc3e..5ee1f7514f8de212f920cbd187eda16f98e07e2e 100644 (file)
--- 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;
+}
+
index 66d96fd3b9aced37ccd20eccf82f531b47501e88..946681aeb98607a590980b4d77c078881cd885be 100644 (file)
--- 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 - <int> error code returned by any of the above functions
- * buf    - <char *> user supplied buffer to store the error message
+ * buf    - <char*> user supplied buffer to store the error message
  * len    - <size_t> 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      - <int> file or socket descriptor to read from
+ * buf     - <char*> user supplied buffer to store the received message
+ * len     -  <size_t> maximum number of bytes to store in buf
+ * flags   - <int> additional flags to pass, see: man recv
+ * addr    - <struct sockaddr*> see: man recvfrom
+ * alen    - <socklen_t*> see: man recvfrom
+ * timeout - <int> 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      - <int> file or socket descriptor to read from
+ * buf     - <char*> user supplied buffer to store the received message
+ * len     - <size_t> maximum number of bytes to store in buf
+ * flags   - <int> additional flags to pass, see: man send
+ * addr    - <struct sockaddr*> see: man sendto
+ * alen    - <socklen_t*> see: man sendto
+ * timeout - <int> 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
index 78c29d71a16b24d45fc342a723e101369e756831..66c548466e2c081616c261a505aeb1fc560f5ed7 100644 (file)
@@ -6,17 +6,12 @@
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
+#include <netinet/in.h>
 
 #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__);           \
             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;
     }