From 404295a67e45e6c9d2f0ee1a12251e96654832c9 Mon Sep 17 00:00:00 2001 From: Urban Wallasch Date: Tue, 22 Oct 2019 14:44:34 +0200 Subject: [PATCH] * net: inital commit * updated README.html --- README.html | 9 +- net/net.c | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++++ net/net.h | 117 +++++++++++++++++++++ 3 files changed, 409 insertions(+), 3 deletions(-) create mode 100644 net/net.c create mode 100644 net/net.h diff --git a/README.html b/README.html index e88b131..9b87fa7 100644 --- a/README.html +++ b/README.html @@ -1,7 +1,10 @@ -

Various code snippets

+

Various code snippets and single-file libraries

- - + + + + +
trace C11 conforming expression tracing macro
trace - C11 conforming expression tracing macro
net - Simple IPv4 and IPv6 based TCP and UDP client/server handling
diff --git a/net/net.c b/net/net.c new file mode 100644 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 +#include +#include +#include +#include +#include +#include + +#include "net.h" + +/* Logging support macros and helper functions: */ + +#if defined(NET_DBG) && !defined(NET_ERR) +#define NET_ERR +#endif + +#ifdef NET_ERR +#include + +/* 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 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 + +/* + * 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 - local node address (numerical or name); NULL for any + * svc - service (port number or service name) + * st - socket type; one of SOCK_STREAM or SOCK_DGRAM + * af - 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 - remote node address (numerical or host name) + * svc - remote service (port number or service name) + * addr - local node address to bind to, or NULL for none + * st - socket type; one of SOCK_STREAM or SOCK_DGRAM + * af - 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 - 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 - TCP listener socket + * timeout - 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 - error code returned by any of the above functions + * 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 + * 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_*/ -- 2.30.2