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