* net: inital commit
authorUrban Wallasch <urban.wallasch@freenet.de>
Tue, 22 Oct 2019 12:44:34 +0000 (14:44 +0200)
committerUrban Wallasch <urban.wallasch@freenet.de>
Tue, 22 Oct 2019 12:44:34 +0000 (14:44 +0200)
* updated README.html

README.html
net/net.c [new file with mode: 0644]
net/net.h [new file with mode: 0644]

index e88b13108c30a6a01d8bbc51b8e0b00f9884a3e9..9b87fa778d8d0e98ab815894d0b286dd184455e0 100644 (file)
@@ -1,7 +1,10 @@
-<h2>Various code snippets</h2>
+<h2>Various code snippets and single-file libraries</h2>
 <table>
 
-<tr><td><a href="/?p=oddbits.git;a=tree;f=trace;hb=HEAD"><b>trace</b></a></td>
-<td> C11 conforming expression tracing macro</td></tr>
+<tr><td><a href="/?p=oddbits.git;a=tree;f=trace;hb=HEAD"><b> trace </b></a></td>
+<td>-</td><td> C11 conforming expression tracing macro </td></tr>
+
+<tr><td><a href="/?p=oddbits.git;a=tree;f=net;hb=HEAD"><b> net </b></a></td>
+<td>-</td><td> Simple IPv4 and IPv6 based TCP and UDP client/server handling </td></tr>
 
 </table>
diff --git a/net/net.c b/net/net.c
new file mode 100644 (file)
index 0000000..89e8da0
--- /dev/null
+++ b/net/net.c
@@ -0,0 +1,286 @@
+/*
+ * Collection of functions to help create simple IPv4 or IPv6 based
+ * TCP or UDP servers and clients.  See net.h for further information.
+ *
+ * Build with -DNET_ERR to enable error logging to stderr.
+ * Build with -DNET_DBG to enable error and debug logging to stderr.
+ *
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include "net.h"
+
+/* Logging support macros and helper functions: */
+
+#if defined(NET_DBG) && !defined(NET_ERR)
+#define NET_ERR
+#endif
+
+#ifdef NET_ERR
+#include <stdlib.h>
+
+/* Log helper function: create "host:port" string from socket address: */
+static char *addr2str(const struct sockaddr *addr, socklen_t addrlen) {
+    char host[64], svc[10], *s;
+    int err;
+    err = getnameinfo(addr, addrlen, host, sizeof host, svc, sizeof svc,
+                        NI_NUMERICHOST | NI_NUMERICSERV);
+    if (err != 0) {
+        strcpy(host, "<?>");
+        strcpy(svc, "<?>");
+    }
+    s = malloc(strlen(host) + strlen(svc) + 2);
+    if (s != NULL)
+        sprintf(s, "%s:%s", host, svc);
+    return s;
+}
+
+#define GET_STRADDR(s_,a_,l_) char *(s_) = addr2str((a_),(l_))
+#define FREE_STRADDR(s_) free(s_)
+
+#define NETLOG_MSG(fi_,fn_,ln_,...)     do {                           \
+            fprintf(stderr,"%s:%s@%d: ",fi_,fn_,ln_);                  \
+            fprintf(stderr,__VA_ARGS__);                               \
+        } while(0)
+
+#define NETLOG_ERR(e_,...)      do {                                   \
+            char ebuf[100];                                            \
+            NETLOG_MSG(__FILE__,__func__,__LINE__,__VA_ARGS__);        \
+            fprintf(stderr,": %s\n",net_strerror(e_,ebuf,sizeof ebuf));\
+        } while(0)
+
+#ifdef NET_DBG
+#define NETLOG_DBG(...)     do {                                       \
+            NETLOG_MSG(__FILE__,__func__,__LINE__,__VA_ARGS__);        \
+            fprintf(stderr,"\n");                                      \
+        } while(0)
+#else
+#define NETLOG_DBG(...)
+#endif
+
+#else
+#define GET_STRADDR(...)
+#define FREE_STRADDR(...)
+#define NETLOG_ERR(...)
+#define NETLOG_DBG(...)
+#endif  /* NET_ERR */
+
+
+/* Helper function to bind a client port to local node address: */
+static int net_bind_local(int sock, const char *addr, int st, int af ) {
+    /* Only try to bind if actually requested! */
+    if (addr == NULL || *addr == '\0' || *addr == '*')
+        return 0;
+
+    int err;
+    struct addrinfo hints, *info;
+
+    memset(&hints, 0, sizeof hints);
+    hints.ai_family = af;
+    hints.ai_socktype = st;
+    hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
+    err = getaddrinfo(addr, NULL, &hints, &info);
+    if (err != 0) {
+        NETLOG_ERR(err, "getaddrinfo(%s)", addr);
+        return err;
+    }
+    for (struct addrinfo *ai = info; ai != NULL; ai = ai->ai_next) {
+        err = bind(sock, ai->ai_addr, ai->ai_addrlen);
+        if (err != 0) {
+            GET_STRADDR(straddr_, ai->ai_addr, ai->ai_addrlen);
+            NETLOG_ERR(EAI_SYSTEM, "bind(%d, %s)", sock, straddr_);
+            FREE_STRADDR(straddr_);
+            continue;
+        }
+        else {
+            GET_STRADDR(straddr_, ai->ai_addr, ai->ai_addrlen);
+            NETLOG_DBG("bind(%d, %s) : OK", sock, straddr_);
+            FREE_STRADDR(straddr_);
+        }
+        break;
+    }
+    freeaddrinfo(info);
+    return err < 0 ? EAI_SYSTEM : 0;
+}
+
+/* Create and bind a new listener socket: */
+int net_open_server(const char *addr, const char *svc, int st, int af) {
+    int err, sock = -1;
+    struct addrinfo hints, *info;
+
+    addr = addr && *addr && *addr != '*' ? addr : NULL;
+    memset(&hints, 0, sizeof hints);
+    hints.ai_family = af;
+    hints.ai_socktype = st;
+    hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_PASSIVE;
+    err = getaddrinfo(addr, svc, &hints, &info);
+    if (err != 0) {
+        NETLOG_ERR(err, "getaddrinfo(%s,%s)", addr, svc);
+        return err;
+    }
+    for (struct addrinfo *ai = info; ai != NULL; ai = ai->ai_next) {
+        int set = 1;
+        sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+        if (sock < 0) {
+            NETLOG_ERR(EAI_SYSTEM, "socket()");
+            continue;
+        }
+        err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &set, sizeof set);
+        if (err != 0) {
+            NETLOG_ERR(EAI_SYSTEM, "setsockopt(%d)", sock);
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        err = bind(sock, ai->ai_addr, ai->ai_addrlen);
+        if (err != 0) {
+            GET_STRADDR(straddr_, ai->ai_addr, ai->ai_addrlen);
+            NETLOG_ERR(EAI_SYSTEM, "bind(%d, %s)", sock, straddr_);
+            FREE_STRADDR(straddr_);
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        else {
+            GET_STRADDR(straddr_, ai->ai_addr, ai->ai_addrlen);
+            NETLOG_DBG("bind(%d, %s) : OK", sock, straddr_);
+            FREE_STRADDR(straddr_);
+        }
+        break;
+    }
+    freeaddrinfo(info);
+    if (sock < 0)
+        return EAI_SYSTEM;
+    if (st == SOCK_STREAM) {
+        err = listen(sock, 10);
+        if (err != 0) {
+            NETLOG_ERR(EAI_SYSTEM, "listen(%d)", sock);
+            close(sock);
+            return EAI_SYSTEM;
+        }
+    }
+    return sock;
+}
+
+/* Create and connect a new socket: */
+int net_open_client(const char *host, const char *svc, const char *addr, int st, int af) {
+    int err, sock = -1;
+    struct addrinfo hints, *info;
+
+    host = host && *host ? host : NULL;
+    memset(&hints, 0, sizeof hints);
+    hints.ai_family = af;
+    hints.ai_socktype = st;
+    hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
+    err = getaddrinfo(host, svc, &hints, &info);
+    if (err != 0) {
+        NETLOG_ERR(err, "getaddrinfo(%s,%s)", host, svc);
+        return err;
+    }
+    for (struct addrinfo *ai = info; ai != NULL; ai = ai->ai_next) {
+        int set = 1;
+        sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+        if (sock < 0) {
+            NETLOG_ERR(EAI_SYSTEM, "socket()");
+            continue;
+        }
+        err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &set, sizeof set);
+        if (err != 0) {
+            NETLOG_ERR(EAI_SYSTEM, "setsockopt(%d)", sock);
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        err = net_bind_local(sock, addr, ai->ai_socktype, ai->ai_family);
+        if (err != 0) {
+            /* Errors already logged in net_bind()! */
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        err = connect(sock, ai->ai_addr, ai->ai_addrlen);
+        if (err != 0) {
+            GET_STRADDR(straddr_, ai->ai_addr, ai->ai_addrlen);
+            NETLOG_ERR(EAI_SYSTEM, "connect(%d, %s) ", sock, straddr_);
+            FREE_STRADDR(straddr_);
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        else {
+            GET_STRADDR(straddr_, ai->ai_addr, ai->ai_addrlen);
+            NETLOG_DBG("connect(%d, %s) : OK", sock, straddr_);
+            FREE_STRADDR(straddr_);
+        }
+        break;
+    }
+    freeaddrinfo(info);
+    if (sock < 0)
+        return EAI_SYSTEM;
+    return sock;
+}
+
+/* Close socket: */
+int net_close(int sock) {
+    int err;
+
+    err = shutdown(sock, SHUT_RDWR);
+    if (err < 0) {
+        NETLOG_DBG("shutdown(%d) : %s", sock, strerror(errno));
+    }
+    err = close(sock);
+    if (err < 0) {
+        NETLOG_ERR(EAI_SYSTEM, "close(%d)", sock);
+    }
+    return err < 0 ? EAI_SYSTEM : 0;
+}
+
+/* Wait for and accept a TCP connection request: */
+int tcp_accept(int sock, int timeout) {
+    int pr, conn = -1;
+    struct pollfd pfd;
+    uint8_t a_[128];
+    struct sockaddr *addr = (void *)a_;
+    socklen_t addrlen = sizeof a_;
+
+    pfd.fd = sock;
+    pfd.events = POLLIN;
+    pr = poll(&pfd, 1, timeout);
+    if (pr == 1 && pfd.revents == POLLIN) {
+        conn = accept(sock, addr, &addrlen);
+        GET_STRADDR(straddr_, addr, addrlen);
+        if (conn < 0) {
+            NETLOG_ERR(EAI_SYSTEM, "accept(%d, %s)", sock, straddr_);
+        } else {
+            NETLOG_DBG("accept(%d) -> (%d, %s) : OK", sock, conn, straddr_);
+        }
+        FREE_STRADDR(straddr_);
+    }
+    else if (pr < 0) {
+        NETLOG_ERR(EAI_SYSTEM, "poll(%d, %d)", sock, timeout);
+        return EAI_SYSTEM;
+    }
+    else if (pr == 0) {
+        NETLOG_DBG("poll(%d, %d) timed out", sock, timeout);
+        errno = ETIMEDOUT;
+        return EAI_SYSTEM;
+    }
+    return conn < 0 ? EAI_SYSTEM : conn;
+}
+
+/* Copy textual description of last error to user supplied buffer: */
+const char *net_strerror(int errnum, char *buf, size_t len) {
+    if ( errnum == EAI_SYSTEM )
+        strerror_r(errno, buf, len);
+    else
+        snprintf(buf, len, gai_strerror(errnum));
+    return buf;
+}
+
diff --git a/net/net.h b/net/net.h
new file mode 100644 (file)
index 0000000..c430046
--- /dev/null
+++ b/net/net.h
@@ -0,0 +1,117 @@
+/*
+ * Collection of functions to help create simple IPv4 or IPv6 based
+ * 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
+ *
+ */
+
+#ifndef NET_H_
+#define NET_H_
+
+#ifdef cplusplus
+extern "C" {
+#endif
+
+#include <sys/socket.h>
+
+/*
+ * Create a new socket with the specified characteristics, bind it to
+ * the local node address and, in case of SOCK_STREAM, start listening
+ * for connection requests which can be accepted using tcp_accept().
+ * Returns the listener socket or a negative value on error.
+ *
+ * addr - <string> local node address (numerical or name); NULL for any
+ * svc  - <string> service (port number or service name)
+ * st   - <int> socket type; one of SOCK_STREAM or SOCK_DGRAM
+ * af   - <int> address family; one of AF_UNSPEC, AF_INET, AF_INET6
+ *
+ * Note: Address family AF_INET6 will still allow V4MAPPED connections.
+ */
+extern int net_open_server(const char *addr, const char *svc, int st, int af);
+
+/*
+ * Shorthand macros for socket type and address family permutations:
+ */
+#define tcp_open_server(a_,s_)   net_open_server((a_),(s_),SOCK_STREAM,AF_UNSPEC)
+#define tcp4_open_server(a_,s_)  net_open_server((a_),(s_),SOCK_STREAM,AF_INET)
+#define tcp6_open_server(a_,s_)  net_open_server((a_),(s_),SOCK_STREAM,AF_INET6)
+#define udp_open_server(a_,s_)   net_open_server((a_),(s_),SOCK_DGRAM,AF_UNSPEC)
+#define udp4_open_server(a_,s_)  net_open_server((a_),(s_),SOCK_DGRAM,AF_INET)
+#define udp6_open_server(a_,s_)  net_open_server((a_),(s_),SOCK_DGRAM,AF_INET6)
+
+
+/*
+ * Create a new socket, optionally bind it to a local node, associate
+ * it with a random ephemeral port and connect it to the specified host
+ * and service.
+ * Returns the connected socket or a negative value on error.
+ *
+ * host - <string> remote node address (numerical or host name)
+ * svc  - <string> remote service (port number or service name)
+ * addr - <string> local node address to bind to, or NULL for none
+ * st   - <int> socket type; one of SOCK_STREAM or SOCK_DGRAM
+ * af   - <int> address family; one of AF_UNSPEC, AF_INET, AF_INET6
+ */
+extern int net_open_client(const char *host, const char *svc, const char *addr, int st, int af);
+
+/*
+ * Shorthand macros for socket type and address family permutations:
+ */
+#define tcp_open_client(h_,s_,a_)   net_open_client((h_),(s_),(a_),SOCK_STREAM,AF_UNSPEC)
+#define tcp4_open_client(h_,s_,a_)  net_open_client((h_),(s_),(a_),SOCK_STREAM,AF_INET)
+#define tcp6_open_client(h_,s_,a_)  net_open_client((h_),(s_),(a_),SOCK_STREAM,AF_INET6)
+#define udp_open_client(h_,s_,a_)   net_open_client((h_),(s_),(a_),SOCK_DGRAM,AF_UNSPEC)
+#define udp4_open_client(h_,s_,a_)  net_open_client((h_),(s_),(a_),SOCK_DGRAM,AF_INET)
+#define udp6_open_client(h_,s_,a_)  net_open_client((h_),(s_),(a_),SOCK_DGRAM,AF_INET6)
+
+
+/*
+ * Shutdown and close the specified socket.
+ * Returns 0 on success or a negative value on error.
+ *
+ * sock - <int> socket to close
+ */
+extern int net_close(int sock);
+
+
+/*
+ * Wait at most timeout milliseconds for a TCP connection request on
+ * a listener socket to arrive and accept any such request.
+ * Returns a newly created socket associated with the accepted TCP
+ * connection, or a negative value on error.
+ *
+ * sock    - <int> TCP listener socket
+ * timeout - <int> timeout in milliseconds
+ *
+ * Note: A time-out condition is considered an error, and errno is set
+ * to ETIMEDOUT.  A negative value for timeout lets tcp_accept() block
+ * indefinitely.  Set timeout to zero for immediate and non-blocking
+ * operation.
+ */
+extern int tcp_accept(int sock, int timeout);
+
+
+/*
+ * Copy textual description of last error to user supplied buffer.
+ * 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
+ * len    - <size_t> maximum number of characters buf can hold
+ *
+ * Note: Depending on the actual value of errnum this function will
+ * return a message based on either strerror_r() or gai_strerror().
+ */
+extern const char *net_strerror(int errnum, char *buf, size_t len);
+
+
+#ifdef cplusplus
+}
+#endif
+
+#endif /*ndef NET_H_*/