Compare commits

...

13 Commits

Author SHA1 Message Date
seajee
28f4011780 v1.7.5 2026-01-09 21:16:05 +01:00
seajee
077bd91f98 v1.7.4 Ignore signals in send and sendto 2025-12-13 16:12:56 +01:00
seajee
8c0ccb628e Fix formatting 2025-11-10 17:55:10 +01:00
seajee
440a18edc6 Deleted example 2025-09-20 20:18:33 +02:00
seajee
d24dfd39a7 v1.7.3 Release 2025-09-20 20:06:45 +02:00
seajee
7e55bd0194 v1.7.2 Release 2025-09-17 00:41:38 +02:00
seajee
15334be5bf v1.7.1 - Pass NULL to getaddrinfo if port is 0 in sock_dns() 2025-09-07 15:42:42 +02:00
seajee
ddc2147991 Logo 2 2025-09-06 02:27:45 +02:00
seajee
e7e6f2eb9c Logo 2025-09-06 02:26:13 +02:00
seajee
a9462d0880 v1.7.0 release 2025-09-06 01:30:42 +02:00
seajee
42d126f01a Improve enums 2025-08-25 00:03:23 +02:00
seajee
024377795d Renamed examples for logical ordering 2025-08-24 23:56:09 +02:00
seajee
ad986c777a Add new example for IPv6 with UDP 2025-08-24 23:45:30 +02:00
13 changed files with 602 additions and 145 deletions

View File

@@ -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; }

View File

@@ -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; }

View File

@@ -0,0 +1,31 @@
#include <stdio.h>
#include <errno.h>
#define SOCK_IMPLEMENTATION
#include "sock.h"
int main(void)
{
bool err = false;
Sock *s = sock_create(SOCK_IPV6, SOCK_UDP);
if (s == NULL) { err = "create"; goto defer; }
SockAddr addr = sock_addr("::1", 6969);
const char *msg = "Hello from client!";
char buf[128];
memset(buf, 0, sizeof(buf));
sock_sendto(s, msg, strlen(msg), addr);
sock_recvfrom(s, buf, sizeof(buf), NULL);
printf("%s:%d: %.*s\n", addr.str, addr.port,
(int)sizeof(buf), buf);
defer:
if (err) sock_log_error(s);
sock_close(s);
return 0;
}

View File

@@ -0,0 +1,32 @@
#include <stdio.h>
#define SOCK_IMPLEMENTATION
#include "sock.h"
int main(void)
{
bool err = false;
Sock *server = sock_create(SOCK_IPV6, SOCK_UDP);
if (server == NULL) { err = true; goto close; }
SockAddr addr = sock_addr("::", 6969);
if (!sock_bind(server, addr)) { err = true; goto close; }
char buf[128];
memset(buf, 0, sizeof(buf));
const char *msg = "Hello from server!";
SockAddr client_addr = {0};
sock_recvfrom(server, buf, sizeof(buf), &client_addr);
sock_sendto(server, msg, strlen(msg), client_addr);
printf("%s:%d: %.*s\n", client_addr.str, client_addr.port,
(int)sizeof(buf), buf);
close:
if (err) sock_log_error(server);
sock_close(server);
return 0;
}

View File

@@ -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));

26
examples/06-dig.c Normal file
View File

@@ -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 <address>\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;
}

View File

@@ -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;

613
sock.h
View File

@@ -1,14 +1,175 @@
// sock.h - v1.6.3 - MIT License - https://github.com/seajee/sock.h
/***************************************************************\
# #
# @@@@@@ #
# @ @ #
# @====@ #
# @ @ sock.h - v1.7.5 #
# @ @ 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 = sock_dns("example.com", 0, 0, 0);
// 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_send_all(Sock *sock, const void *buf, size_t size)
//
// Same as sock_send() but ensures that all of the content of buf will be
// sent. On error a negative value is 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_recv_all(Sock *sock, void *buf, size_t size);
//
// Same as sock_recv() but ensures that the specified size of bytes will be
// received. On error a negative value is 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 <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -16,14 +177,16 @@
#include <sys/types.h>
#include <unistd.h>
#define SOCK_ADDR_LIST_INITIAL_CAPACITY 16
#ifdef __cplusplus
extern "C" { // Prevent name mangling
#endif // __cplusplus
typedef enum {
SOCK_ADDR_INVALID,
SOCK_IPV4,
SOCK_IPV6
SOCK_ADDR_INVALID = 0,
SOCK_IPV4 = AF_INET,
SOCK_IPV6 = AF_INET6
} SockAddrType;
typedef struct {
@@ -38,16 +201,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_TCP,
SOCK_UDP
SOCK_TYPE_INVALID = 0,
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);
@@ -64,11 +234,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);
@@ -81,9 +257,11 @@ bool sock_connect(Sock *sock, SockAddr addr);
// Send data through a socket
ssize_t sock_send(Sock *sock, const void *buf, size_t size);
ssize_t sock_send_all(Sock *sock, const void *buf, size_t size);
// Receive data from a socket
ssize_t sock_recv(Sock *sock, void *buf, size_t size);
ssize_t sock_recv_all(Sock *sock, void *buf, size_t size);
// Send data through a socket in connectionless mode
ssize_t sock_sendto(Sock *sock, const void *buf, size_t size, SockAddr addr);
@@ -99,6 +277,7 @@ void sock_log_error(const Sock *sock);
// Private functions
void *sock__accept_thread(void *data);
void sock__convert_addr(SockAddr *addr);
#ifdef __cplusplus
}
@@ -121,29 +300,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;
@@ -152,6 +309,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;
}
@@ -168,46 +326,121 @@ 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;
snprintf(sa.str, sizeof(sa.str), "%s", addr);
}
if (res->ai_family == AF_INET) {
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 *service = NULL;
char port_str[16];
if (port > 0) {
snprintf(port_str, sizeof(port_str), "%d", port);
service = port_str;
}
struct addrinfo *res;
if (getaddrinfo(addr, service, &hints, &res) != 0) {
sock_addr_list_free(&list);
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.ipv4 = *(struct sockaddr_in*)res->ai_addr;
sa.ipv4.sin_port = htons(port);
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));
} else {
} break;
case AF_INET6: {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)ai->ai_addr;
sa.type = SOCK_IPV6;
sa.ipv6 = *(struct sockaddr_in6*)res->ai_addr;
sa.ipv6.sin6_port = htons(port);
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;
}
sa.port = port;
return sa;
free(list->items);
list->count = 0;
list->capacity = 0;
}
bool sock_bind(Sock *sock, SockAddr addr)
@@ -217,7 +450,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;
}
@@ -226,14 +459,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;
}
@@ -246,47 +479,30 @@ 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);
if (sock->type != SOCK_TCP) {
sock->last_errno = EINVAL;
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;
}
@@ -297,6 +513,11 @@ bool sock_async_accept(Sock *sock, SockThreadCallback fn, void *user_data)
return false;
}
if (sock->type != SOCK_TCP) {
sock->last_errno = EINVAL;
return false;
}
Sock *client = sock_accept(sock);
if (client == NULL) {
return false;
@@ -306,7 +527,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;
}
@@ -318,7 +539,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;
}
@@ -333,7 +554,13 @@ bool sock_connect(Sock *sock, SockAddr addr)
return false;
}
if (sock->type != SOCK_TCP) {
sock->last_errno = EINVAL;
return false;
}
if (connect(sock->fd, &addr.sockaddr, addr.len) < 0) {
sock->last_errno = errno;
return false;
}
@@ -344,70 +571,164 @@ bool sock_connect(Sock *sock, SockAddr addr)
ssize_t sock_send(Sock *sock, const void *buf, size_t size)
{
if (sock == NULL || buf == NULL) {
if (sock == NULL || buf == NULL || sock->type != SOCK_TCP) {
if (sock != NULL) {
sock->last_errno = EINVAL;
}
return -1;
}
return send(sock->fd, buf, size, 0);
while (true) {
ssize_t n = send(sock->fd, buf, size, MSG_NOSIGNAL);
if (n < 0) {
if (errno == EINTR) {
continue;
}
sock->last_errno = errno;
return -1;
}
return n;
}
}
ssize_t sock_send_all(Sock *sock, const void *buf, size_t size)
{
if (sock == NULL || buf == NULL || sock->type != SOCK_TCP) {
if (sock != NULL) {
sock->last_errno = EINVAL;
}
return -1;
}
uint8_t *ptr = (uint8_t*)buf;
size_t remaining = size;
while (remaining > 0) {
ssize_t n = sock_send(sock, ptr, remaining);
if (n < 0) {
return -1;
}
ptr += n;
remaining -= n;
}
return size;
}
ssize_t sock_recv(Sock *sock, void *buf, size_t size)
{
if (sock == NULL || buf == NULL) {
if (sock == NULL || buf == NULL || sock->type != SOCK_TCP) {
if (sock != NULL) {
sock->last_errno = EINVAL;
}
return -1;
}
return recv(sock->fd, buf, size, 0);
while (true) {
ssize_t n = recv(sock->fd, buf, size, 0);
if (n < 0) {
if (errno == EINTR) {
continue;
}
sock->last_errno = errno;
return -1;
}
return n;
}
}
ssize_t sock_recv_all(Sock *sock, void *buf, size_t size)
{
if (sock == NULL || buf == NULL || sock->type != SOCK_TCP) {
if (sock != NULL) {
sock->last_errno = EINVAL;
}
return -1;
}
uint8_t *ptr = (uint8_t*)buf;
size_t remaining = size;
ssize_t total = 0;
while (remaining > 0) {
ssize_t n = sock_recv(sock, ptr, remaining);
if (n < 0) {
return -1;
}
if (n == 0) {
return total;
}
ptr += n;
remaining -= n;
total += n;
}
return total;
}
ssize_t sock_sendto(Sock *sock, const void *buf, size_t size, SockAddr addr)
{
if (sock == NULL || buf == NULL) {
if (sock == NULL || buf == NULL || sock->type != SOCK_UDP) {
if (sock != NULL) {
sock->last_errno = EINVAL;
}
return -1;
}
return sendto(sock->fd, buf, size, 0, &addr.sockaddr, addr.len);
while (true) {
ssize_t n = sendto(sock->fd, buf, size, MSG_NOSIGNAL, &addr.sockaddr,
addr.len);
if (n < 0) {
if (errno == EINTR) {
continue;
}
sock->last_errno = errno;
return -1;
}
return n;
}
}
ssize_t sock_recvfrom(Sock *sock, void *buf, size_t size, SockAddr *addr)
{
if (sock == NULL || buf == NULL) {
if (sock == NULL || buf == NULL || sock->type != SOCK_UDP) {
if (sock != NULL) {
sock->last_errno = EINVAL;
}
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);
if (res < 0 || addr == NULL) {
return res;
ssize_t res = 0;
while (true) {
res = recvfrom(sock->fd, buf, size, 0, sa, len_ptr);
if (res < 0) {
if (errno == EINTR) {
continue;
}
sock->last_errno = errno;
return -1;
}
break;
}
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;
@@ -419,6 +740,15 @@ void sock_close(Sock *sock)
return;
}
shutdown(sock->fd, SHUT_WR);
uint8_t buffer[1024];
while (true) {
ssize_t n = sock_recv(sock, buffer, sizeof(buffer));
if (n <= 0) {
break;
}
}
close(sock->fd);
free(sock);
}
@@ -431,13 +761,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)
@@ -459,6 +783,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
}
@@ -469,6 +822,16 @@ void *sock__accept_thread(void *data)
/*
Revision history:
1.7.5 (2025-12-13) Return false instead of NULL when returning a bool;
Fixed memory leak in sock_dns()
1.7.4 (2025-12-13) Ignore signals in send and sendto
1.7.3 (2025-09-20) Changed sock_send_all() signature; drain buffers on
sock_close() to prevent data loss
1.7.2 (2025-09-17) New functions sock_recv_all() and sock_send_all();
Improved error reporting.
1.7.1 (2025-09-07) Pass NULL to getaddrinfo if port is 0 in sock_dns()
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