From a9462d0880ade7e46c208330215377f938820984 Mon Sep 17 00:00:00 2001 From: seajee Date: Sat, 6 Sep 2025 01:30:42 +0200 Subject: [PATCH] v1.7.0 release --- examples/01-hello_server.c | 2 +- examples/02-hello_server_v6.c | 2 +- examples/05-http_request.c | 13 +- examples/06-dig.c | 26 ++ examples/10-chat_server.c | 2 +- sock.h | 462 ++++++++++++++++++++++++---------- 6 files changed, 372 insertions(+), 135 deletions(-) create mode 100644 examples/06-dig.c diff --git a/examples/01-hello_server.c b/examples/01-hello_server.c index 2aaedf2..cb3cc33 100644 --- a/examples/01-hello_server.c +++ b/examples/01-hello_server.c @@ -13,7 +13,7 @@ int main(void) SockAddr addr = sock_addr("0.0.0.0", 6969); if (!sock_bind(server, addr)) { err = "bind"; goto defer; } - if (!sock_listen(server, 16)) { err = "listen"; goto defer; } + if (!sock_listen(server)) { err = "listen"; goto defer; } Sock *client = sock_accept(server); if (client == NULL) { err = "accept"; goto defer; } diff --git a/examples/02-hello_server_v6.c b/examples/02-hello_server_v6.c index 8bd4329..9aaaf47 100644 --- a/examples/02-hello_server_v6.c +++ b/examples/02-hello_server_v6.c @@ -12,7 +12,7 @@ int main(void) SockAddr addr = sock_addr("::", 6969); if (!sock_bind(server, addr)) { err = true; goto close; } - if (!sock_listen(server, 16)) { err = true; goto close; } + if (!sock_listen(server)) { err = true; goto close; } Sock *client = sock_accept(server); if (client == NULL) { err = true; goto close; } diff --git a/examples/05-http_request.c b/examples/05-http_request.c index d440947..34634d5 100644 --- a/examples/05-http_request.c +++ b/examples/05-http_request.c @@ -5,12 +5,16 @@ int main(void) { - SockAddr addr = sock_addr("example.com", 80); - if (addr.type == SOCK_ADDR_INVALID) { - fprintf(stderr, "ERROR: Could not resolve address %s\n", addr.str); + const char *domain_name = "example.com"; + SockAddrList addr_list = sock_dns(domain_name, 80, SOCK_IPV4, SOCK_TCP); + if (addr_list.count == 0) { + fprintf(stderr, "ERROR: Could not resolve address %s\n", domain_name); return 1; } + SockAddr addr = addr_list.items[0]; + sock_addr_list_free(&addr_list); + printf("%s:%d\n", addr.str, addr.port); Sock *s = sock_create(addr.type, SOCK_TCP); @@ -30,7 +34,8 @@ int main(void) const char *req = "GET / HTTP/1.1\r\n" "Host: example.com\r\n" - "Connection: close\r\n\r\n"; + "Connection: close\r\n" + "\r\n"; sock_send(s, req, strlen(req)); diff --git a/examples/06-dig.c b/examples/06-dig.c new file mode 100644 index 0000000..d3f51d7 --- /dev/null +++ b/examples/06-dig.c @@ -0,0 +1,26 @@ +#define SOCK_IMPLEMENTATION +#include "sock.h" + +int main(int argc, char **argv) +{ + const char *program_name = argv[0]; + + if (argc <= 1) { + fprintf(stderr, "USAGE: %s
\n", program_name); + fprintf(stderr, "ERROR: No address was provided\n"); + return 1; + } + + const char *arg_address = argv[1]; + + SockAddrList addrs = sock_dns(arg_address, 0, 0, SOCK_TCP); + + for (size_t i = 0; i < addrs.count; ++i) { + SockAddr it = addrs.items[i]; + printf("%s\n", it.str); + } + + sock_addr_list_free(&addrs); + + return 0; +} diff --git a/examples/10-chat_server.c b/examples/10-chat_server.c index d09294e..80c0c27 100644 --- a/examples/10-chat_server.c +++ b/examples/10-chat_server.c @@ -138,7 +138,7 @@ int main(void) printf("INFO: Bind socket\n"); - if (!sock_listen(server, 10)) { + if (!sock_listen(server)) { fprintf(stderr, "ERROR: Could not listen on socket\n"); sock_close(server); return EXIT_FAILURE; diff --git a/sock.h b/sock.h index 5235219..065a537 100644 --- a/sock.h +++ b/sock.h @@ -1,10 +1,160 @@ -// sock.h - v1.6.3 - MIT License - https://github.com/seajee/sock.h + + /***************************************************************\ + # # + # @@@@@ # + # @ @ # + # @@@@@ # + # @ @ sock.h - v1.7.0 # + # @ @ MIT License # + # @@% .@ @ # + # @@ @ @ https://github.com/seajee/sock.h # + # @ \ .@@ # + # @ @@@# # + # # + \***************************************************************/ + + +// sock.h - A single header library for network sockets management. +// +// [License and changelog] +// +// See end of file. +// +// [Single header library usage] +// +// Including this header file as such does what you normally expect. +// +// If you also need to include all of the function implementations you +// need to #define SOCK_IMPLEMENTATION before including the header file +// like this: +// +// #define SOCK_IMPLEMENTATION +// #include "sock.h" +// +// [Structure documentation] +// +// Sock: can be treated as a normal socket +// +// SockAddr: an address composed of an IP (v4 or v6) and a port +// +// The fields you will commonly refer to in this structure are: +// +// SockAddr addr; +// addr.port; // The port of the SockAddr +// addr.str; // String representation of the address +// +// SockAddrList: a dynamic array of SockAddr +// +// A SockAddrList can be iterated like so: +// +// SockAddrList list = ...; +// for (size_t i = 0; i < list.count; ++i) { +// SockAddr addr = list.items[i]; +// // Try to connect to addr or just inspect it +// } +// +// [Function documentation] +// +// In this library all of the major functions are abstraction layers to common +// standard functions, so their usage remain the same. Structures and utility +// functions are put in place to simplify the usage of the standard C API for +// socket and address handling. +// +// Sock related functions: +// +// Sock *sock_create(SockAddrType domain, SockType type) +// +// Allocates and initializes a Sock with the corresponding domain and type. +// When you're done using it you should close it with sock_close(). Returns +// NULL on error. +// +// bool sock_bind(Sock *sock, SockAddr addr) +// +// Binds a sock to the specified address. Returns false on error. +// +// bool sock_listen(Sock *sock) +// +// Makes a sock listen for incoming connections. Returns false on error. +// +// Sock *sock_accept(Sock *sock) +// +// Accepts a new connection on a sock in a blocking way. For handling new +// connections asynchronously you can use sock_async_accept(). Returns NULL on +// error. +// +// bool sock_async_accept(Sock *sock, SockThreadCallback fn, void *user_data) +// +// Same as sock_accept but creates a new pthread that will own the client +// sock. It will be the callback job to close the new sock. A function +// callback shall be passed with the following signature: +// void callback(Sock *sock, void *user_data) +// Returns false on error. +// +// bool sock_connect(Sock *sock, SockAddr addr) +// +// Connects a sock on a connection-mode sock (e.g. TCP). Returns false on +// error. +// +// ssize_t sock_send(Sock *sock, const void *buf, size_t size) +// +// Same as send() but with socks: sends the content of buf to the specified +// sock. On success returns the number of bytes sent. On error a negative +// number shall be returned. +// +// ssize_t sock_recv(Sock *sock, void *buf, size_t size) +// +// Same as recv() but with socks: receives a message from sock end writes it +// into the specified buffer. On sucess returns the number of bytes received. +// On error a negative number shall be returned. +// +// ssize_t sock_sendto(Sock *sock, const void *buf, size_t size, SockAddr addr) +// +// Same as sendto() but with socks: the return value works the same way as +// sock_send(). The addr parameter specifies the address to send the message +// to. +// +// ssize_t sock_recvfrom(Sock *sock, void *buf, size_t size, SockAddr *addr) +// +// Same as recvfrom() but with socks: the return value works the same way as +// sock_recv(). The addr parameter will be filled with the address information +// of the sender. +// +// void sock_close(Sock *sock); +// +// Closes a sock and releases its memory. +// +// void sock_log_error(const Sock *sock) +// +// Prints the last error message of the specified Sock in stderr. This +// functions uses errno to get the error message. +// +// SockAddr related functions: +// +// SockAddr sock_addr(const char *addr, int port) +// +// Given a valid IP (v4 or v6) address as a string and a port returns a +// correctly initialized SockAddr structure. On invalid input, the resulting +// SockAddr type will be set to SOCK_ADDR_INVALID. +// +// SockAddrList sock_dns(const char *addr, +// int port, SockAddrType addr_hint, SockType sock_hint) +// +// This functions queries information about the specified address (e.g. +// "example.com") and returns a list of possible valid SockAddr. The only +// required parameter is addr, while the rest are optional hints that can be +// set to 0. Hints can be provided to further filter the resulting +// SockAddrList. An empty list will be returned in case of no results or on +// errors. Freeing the list is required after using it with +// sock_addr_list_free(). +// +// void sock_addr_list_free(SockAddrList *list) +// +// Releases the memory of the specified SockAddrList. #ifndef SOCK_H_ #define SOCK_H_ #include -#include #include #include #include @@ -16,14 +166,16 @@ #include #include +#define SOCK_ADDR_LIST_INITIAL_CAPACITY 16 + #ifdef __cplusplus extern "C" { // Prevent name mangling #endif // __cplusplus typedef enum { SOCK_ADDR_INVALID = 0, - SOCK_IPV4, - SOCK_IPV6 + SOCK_IPV4 = AF_INET, + SOCK_IPV6 = AF_INET6 } SockAddrType; typedef struct { @@ -38,17 +190,23 @@ typedef struct { socklen_t len; } SockAddr; +typedef struct { + SockAddr *items; // Dynamic array of SockAddr + size_t count; + size_t capacity; +} SockAddrList; + typedef enum { SOCK_TYPE_INVALID = 0, - SOCK_TCP, - SOCK_UDP + SOCK_TCP = SOCK_STREAM, + SOCK_UDP = SOCK_DGRAM } SockType; typedef struct { SockType type; // Socket type SockAddr addr; // Socket address int fd; // File descriptor - char *last_error; // Last error about this socket + int last_errno; // Last error about this socket } Sock; typedef void (*SockThreadCallback)(Sock *sock, void *user_data); @@ -65,11 +223,17 @@ Sock *sock_create(SockAddrType domain, SockType type); // Create a SockAddr structure from primitives SockAddr sock_addr(const char *addr, int port); +// Get all possible addresses from DNS with optional hints +SockAddrList sock_dns(const char *addr, int port, SockAddrType addr_hint, SockType sock_hint); + +// Free a SockAddrList structure +void sock_addr_list_free(SockAddrList *list); + // Bind a socket to a specific address bool sock_bind(Sock *sock, SockAddr addr); // Make the socket listen to incoming connections -bool sock_listen(Sock *sock, int backlog); +bool sock_listen(Sock *sock); // Accept connections from a socket Sock *sock_accept(Sock *sock); @@ -100,6 +264,7 @@ void sock_log_error(const Sock *sock); // Private functions void *sock__accept_thread(void *data); +void sock__convert_addr(SockAddr *addr); #ifdef __cplusplus } @@ -122,29 +287,7 @@ Sock *sock_create(SockAddrType domain, SockType type) memset(sock, 0, sizeof(*sock)); sock->type = type; - - int sock_domain = 0; - int sock_type = 0; - - switch (domain) { - case SOCK_IPV4: sock_domain = AF_INET; break; - case SOCK_IPV6: sock_domain = AF_INET6; break; - default: { - free(sock); - return NULL; - } break; - } - - switch (type) { - case SOCK_TCP: sock_type = SOCK_STREAM; break; - case SOCK_UDP: sock_type = SOCK_DGRAM; break; - default: { - free(sock); - return NULL; - } break; - } - - sock->fd = socket(sock_domain, sock_type, 0); + sock->fd = socket(domain, type, 0); if (sock->fd < 0) { free(sock); return NULL; @@ -153,6 +296,7 @@ Sock *sock_create(SockAddrType domain, SockType type) int enable = 1; if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) { + close(sock->fd); free(sock); return NULL; } @@ -169,48 +313,118 @@ SockAddr sock_addr(const char *addr, int port) return sa; } + sa.port = port; + if (inet_pton(AF_INET, addr, &sa.ipv4.sin_addr) == 1) { sa.type = SOCK_IPV4; sa.ipv4.sin_family = AF_INET; sa.ipv4.sin_port = htons(port); sa.len = sizeof(sa.ipv4); - strncpy(sa.str, addr, sizeof(sa.str)); + snprintf(sa.str, sizeof(sa.str), "%s", addr); } else if (inet_pton(AF_INET6, addr, &sa.ipv6.sin6_addr) == 1) { sa.type = SOCK_IPV6; sa.ipv6.sin6_family = AF_INET6; sa.ipv6.sin6_port = htons(port); sa.len = sizeof(sa.ipv6); - strncpy(sa.str, addr, sizeof(sa.str)); - } else { - struct addrinfo *res; - if (getaddrinfo(addr, NULL, NULL, &res) != 0) { - // TODO: specific errors from getaddrinfo are ignored for logging - sa.type = SOCK_ADDR_INVALID; - return sa; - } - - if (res->ai_family == AF_INET) { - sa.type = SOCK_IPV4; - sa.ipv4 = *(struct sockaddr_in*)res->ai_addr; - sa.ipv4.sin_port = htons(port); - sa.len = sizeof(sa.ipv4); - inet_ntop(AF_INET, &sa.ipv4.sin_addr, sa.str, sizeof(sa.str)); - } else { - sa.type = SOCK_IPV6; - sa.ipv6 = *(struct sockaddr_in6*)res->ai_addr; - sa.ipv6.sin6_port = htons(port); - sa.len = sizeof(sa.ipv6); - inet_ntop(AF_INET6, &sa.ipv6.sin6_addr, sa.str, sizeof(sa.str)); - } - - freeaddrinfo(res); + snprintf(sa.str, sizeof(sa.str), "%s", addr); } - sa.port = port; - return sa; } +SockAddrList sock_dns(const char *addr, int port, SockAddrType addr_hint, SockType sock_hint) +{ + SockAddrList list; + memset(&list, 0, sizeof(list)); + + if (addr == NULL) { + return list; + } + + list.items = (SockAddr*)malloc( + sizeof(*list.items) * SOCK_ADDR_LIST_INITIAL_CAPACITY); + if (list.items == NULL) { + return list; + } + list.capacity = SOCK_ADDR_LIST_INITIAL_CAPACITY; + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = (addr_hint != SOCK_ADDR_INVALID ? addr_hint : AF_UNSPEC); + hints.ai_socktype = sock_hint; + + char port_str[8]; + snprintf(port_str, sizeof(port_str), "%d", port); + + struct addrinfo *res; + if (getaddrinfo(addr, port_str, &hints, &res) != 0) { + return list; + } + + for (struct addrinfo *ai = res; ai != NULL; ai = ai->ai_next) { + SockAddr sa; + memset(&sa, 0, sizeof(sa)); + + bool skip = false; + + switch (ai->ai_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in*)ai->ai_addr; + sa.type = SOCK_IPV4; + sa.port = ntohs(sin->sin_port); + sa.ipv4 = *sin; + sa.len = sizeof(sa.ipv4); + inet_ntop(AF_INET, &sa.ipv4.sin_addr, sa.str, sizeof(sa.str)); + } break; + + case AF_INET6: { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)ai->ai_addr; + sa.type = SOCK_IPV6; + sa.port = ntohs(sin6->sin6_port); + sa.ipv6 = *sin6; + sa.len = sizeof(sa.ipv6); + inet_ntop(AF_INET6, &sa.ipv6.sin6_addr, sa.str, sizeof(sa.str)); + } break; + + // Unreachable address family, skip address + default: { + skip = true; + } break; + } + + if (skip) { + continue; + } + + if (list.count >= list.capacity) { + list.capacity *= 2; + SockAddr *new_items = (SockAddr*)realloc( + list.items, list.capacity * sizeof(*list.items)); + if (new_items == NULL) { + sock_addr_list_free(&list); + return list; + } + list.items = new_items; + } + list.items[list.count++] = sa; + } + + freeaddrinfo(res); + + return list; +} + +void sock_addr_list_free(SockAddrList *list) +{ + if (list == NULL) { + return; + } + + free(list->items); + list->count = 0; + list->capacity = 0; +} + bool sock_bind(Sock *sock, SockAddr addr) { if (sock == NULL) { @@ -218,7 +432,7 @@ bool sock_bind(Sock *sock, SockAddr addr) } if (bind(sock->fd, &addr.sockaddr, addr.len) < 0) { - sock->last_error = strerror(errno); + sock->last_errno = errno; return false; } @@ -227,14 +441,14 @@ bool sock_bind(Sock *sock, SockAddr addr) return true; } -bool sock_listen(Sock *sock, int backlog) +bool sock_listen(Sock *sock) { if (sock == NULL) { return false; } - if (listen(sock->fd, backlog) < 0) { - sock->last_error = strerror(errno); + if (listen(sock->fd, SOMAXCONN) < 0) { + sock->last_errno = errno; return false; } @@ -247,47 +461,25 @@ Sock *sock_accept(Sock *sock) return NULL; } - int fd = accept(sock->fd, &sock->addr.sockaddr, &sock->addr.len); - if (fd < 0) { - sock->last_error = strerror(errno); - return NULL; - } - Sock *res = (Sock*)malloc(sizeof(*res)); if (res == NULL) { - sock->last_error = strerror(errno); + sock->last_errno = errno; return NULL; } memset(res, 0, sizeof(*res)); + res->addr.len = sock->addr.len; + + int fd = accept(sock->fd, &res->addr.sockaddr, &res->addr.len); + if (fd < 0) { + free(res); + sock->last_errno = errno; + return NULL; + } + res->type = sock->type; res->fd = fd; - - SockAddr *addr = &res->addr; - addr->len = sizeof(addr->sockaddr); - getpeername(res->fd, &addr->sockaddr, &addr->len); - // TODO: This is duplicated code from sock_recvfrom - switch (addr->sockaddr.sa_family) { - case AF_INET: { - struct sockaddr_in *ipv4 = &addr->ipv4; - addr->type = SOCK_IPV4; - addr->port = ntohs(ipv4->sin_port); - addr->len = sizeof(*ipv4); - inet_ntop(AF_INET, &ipv4->sin_addr, addr->str, sizeof(addr->str)); - } break; - - case AF_INET6: { - struct sockaddr_in6 *ipv6 = &addr->ipv6; - addr->type = SOCK_IPV6; - addr->port = ntohs(ipv6->sin6_port); - addr->len = sizeof(*ipv6); - inet_ntop(AF_INET6, &ipv6->sin6_addr, addr->str, sizeof(addr->str)); - } break; - - default: { - assert(false && "Unreachable address family"); - } break; - } + sock__convert_addr(&res->addr); return res; } @@ -307,7 +499,7 @@ bool sock_async_accept(Sock *sock, SockThreadCallback fn, void *user_data) (SockThreadData*)malloc(sizeof(*thread_data)); if (thread_data == NULL) { sock_close(client); - sock->last_error = strerror(errno); + sock->last_errno = errno; return false; } @@ -319,7 +511,7 @@ bool sock_async_accept(Sock *sock, SockThreadCallback fn, void *user_data) if (pthread_create(&thread, NULL, sock__accept_thread, thread_data) != 0) { free(thread_data); sock_close(client); - sock->last_error = strerror(errno); + sock->last_errno = errno; return false; } @@ -335,6 +527,7 @@ bool sock_connect(Sock *sock, SockAddr addr) } if (connect(sock->fd, &addr.sockaddr, addr.len) < 0) { + sock->last_errno = errno; return false; } @@ -376,39 +569,27 @@ ssize_t sock_recvfrom(Sock *sock, void *buf, size_t size, SockAddr *addr) return -1; } + struct sockaddr_storage sa_storage; struct sockaddr *sa = NULL; - socklen_t *len = NULL; + socklen_t sa_len = 0; + socklen_t *len_ptr = NULL; if (addr != NULL) { - sa = &addr->sockaddr; - len = &sock->addr.len; + sa = (struct sockaddr*)&sa_storage; + sa_len = sizeof(sa_storage); + len_ptr = &sa_len; } - ssize_t res = recvfrom(sock->fd, buf, size, 0, sa, len); + ssize_t res = recvfrom(sock->fd, buf, size, 0, sa, len_ptr); if (res < 0 || addr == NULL) { return res; } - switch (addr->sockaddr.sa_family) { - case AF_INET: { - struct sockaddr_in *ipv4 = &addr->ipv4; - addr->type = SOCK_IPV4; - addr->port = ntohs(ipv4->sin_port); - addr->len = sizeof(*ipv4); - inet_ntop(AF_INET, &ipv4->sin_addr, addr->str, sizeof(addr->str)); - } break; - - case AF_INET6: { - struct sockaddr_in6 *ipv6 = &addr->ipv6; - addr->type = SOCK_IPV6; - addr->port = ntohs(ipv6->sin6_port); - addr->len = sizeof(*ipv6); - inet_ntop(AF_INET6, &ipv6->sin6_addr, addr->str, sizeof(addr->str)); - } break; - - default: { - assert(false && "Unreachable address family"); - } break; + if (addr != NULL) { + memset(addr, 0, sizeof(*addr)); + memcpy(&addr->sockaddr, sa, sa_len); + addr->len = sa_len; + sock__convert_addr(addr); } return res; @@ -432,13 +613,7 @@ void sock_log_error(const Sock *sock) return; } - if (sock->last_error == NULL) { - fprintf(stderr, "SOCK ERROR: socket is empty. errno says: %s\n", - strerror(errno)); - return; - } - - fprintf(stderr, "SOCK ERROR: %s\n", sock->last_error); + fprintf(stderr, "SOCK ERROR: %s\n", strerror(sock->last_errno)); } void *sock__accept_thread(void *data) @@ -460,6 +635,35 @@ void *sock__accept_thread(void *data) return NULL; } +void sock__convert_addr(SockAddr *addr) +{ + if (addr == NULL) { + return; + } + + sa_family_t family = addr->sockaddr.sa_family; + switch (family) { + case AF_INET: { + struct sockaddr_in *ipv4 = &addr->ipv4; + addr->type = SOCK_IPV4; + addr->port = ntohs(ipv4->sin_port); + addr->len = sizeof(*ipv4); + inet_ntop(family, &ipv4->sin_addr, addr->str, sizeof(addr->str)); + } break; + + case AF_INET6: { + struct sockaddr_in6 *ipv6 = &addr->ipv6; + addr->type = SOCK_IPV6; + addr->port = ntohs(ipv6->sin6_port); + addr->len = sizeof(*ipv6); + inet_ntop(family, &ipv6->sin6_addr, addr->str, sizeof(addr->str)); + } break; + + default: { + addr->type = SOCK_ADDR_INVALID; + } break; + } +} #ifdef __cplusplus } @@ -470,6 +674,8 @@ void *sock__accept_thread(void *data) /* Revision history: + 1.7.0 (2025-09-06) Header now includes documentation; New sock_dns() + function; major improvements and refactoring 1.6.3 (2025-08-24) Change SockThreadFn -> SockThreadCallback 1.6.2 (2025-08-03) Log errno error if sock last error is NULL 1.6.1 (2025-08-03) Log errno error if sock is NULL in sock_log_error