socket-util.c

Socket helper utils
git clone git://git.finwo.net/lib/socket-util.c
Log | Files | Refs | README | LICENSE

commit 0f3e3e1cacddf464f6fddfbb25842b3d99c253ae
parent 21f00bea954a015ede9cfffd78c0c6fb7337dc10
Author: Robin Bron <robin.bron@yourhosting.nl>
Date:   Mon, 16 Mar 2026 19:52:15 +0100

Add basic readme

Diffstat:
MLICENSE.md | 5-----
AREADME.md | 183+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 183 insertions(+), 5 deletions(-)

diff --git a/LICENSE.md b/LICENSE.md @@ -1,11 +1,9 @@ Copyright (c) 2026 finwo -<!-- paragraph --> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute the Software, subject to the following conditions: -<!-- list:start --> 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer. @@ -25,12 +23,9 @@ modify, and distribute the Software, subject to the following conditions: 5. Modifications to copies of the Software must carry prominent notices stating that changes were made, the nature of the modifications, and the date of the modifications. -<!-- list:end --> -<!-- paragraph --> Any violation of these conditions terminates the permissions granted herein. -<!-- paragraph --> THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT diff --git a/README.md b/README.md @@ -0,0 +1,183 @@ +# socket-util + +A C library for common socket utilities including TCP/UDP/Unix socket creation and sockaddr manipulation. + +## Installation + +This library can be installed using the [dep](https://github.com/finwo/dep) package manager: + +```bash +dep add finwo/socket-util +``` + +## Usage + +```c +#include "socket-util.h" + +int *fds = tcp_listen(":8080", NULL, NULL); +if (fds) { + // fds[0] = count, fds[1+] = socket fds + for (int i = 1; i <= fds[0]; i++) { + printf("Listening on fd %d\n", fds[i]); + } + free(fds); +} +``` + +## Design Pattern: Config with Defaults + +The `tcp_listen` and `udp_recv` functions are designed to read addresses from config files while providing programmatic fallback defaults: + +```c +int *fds = tcp_listen(config_address, default_host, default_port); +``` + +- **First argument** (`addr`): Read from config file - can be empty/NULL +- **Second argument** (`default_host`): Hardcoded fallback host +- **Third argument** (`default_port`): Hardcoded fallback port + +This allows flexible configuration: + +```c +// Config provides ":8080" -> binds to 0.0.0.0:8080 +int *fds = tcp_listen(":8080", NULL, NULL); + +// Config provides "" (empty) -> uses hardcoded defaults +int *fds = tcp_listen("", "192.168.1.100", "5060"); + +// Config provides ":0" -> binds to random port, caller must detect actual port +int *fds = tcp_listen(":0", NULL, "0"); +``` + +## Features + +### TCP Listening + +```c +// Simple: port from config, no defaults +int *fds = tcp_listen(":8080", NULL, NULL); + +// Config-driven with defaults +int *fds = tcp_listen(config->sip_host, "0.0.0.0", "5060"); + +// Dual-stack (IPv4 + IPv6) +int *fds = tcp_listen(":5060", NULL, NULL); + +// IPv4 only +int *fds = tcp_listen("0.0.0.0:5060", NULL, NULL); + +// IPv6 only +int *fds = tcp_listen("[::]:5060", NULL, NULL); +``` + +### UDP Receiving + +```c +// Simple: port from config +int *fds = udp_recv(":5060", NULL, NULL); + +// With defaults +int *fds = udp_recv(config->rtp_host, "0.0.0.0", "10000"); +``` + +### Unix Domain Sockets + +```c +// Stream socket +int *fds = unix_listen("/run/mydaemon.sock", SOCK_STREAM, NULL); + +// With ownership +int *fds = unix_listen("/run/mydaemon.sock", SOCK_STREAM, "daemonuser"); + +// Datagram socket with group +int *fds = unix_listen("/run/mydaemon.sock", SOCK_DGRAM, "daemonuser:daemogroup"); +``` + +### Socket Address Conversion + +```c +struct sockaddr_in addr; +addr.sin_family = AF_INET; +addr.sin_port = htons(8080); +inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr); + +char buf[INET6_ADDRSTRLEN + 8]; +sockaddr_to_string((struct sockaddr *)&addr, buf, sizeof(buf)); +// buf = "192.168.1.1:8080" +``` + +### String to Socket Address + +```c +struct sockaddr_storage addr; +int result = string_to_sockaddr("192.168.1.1:8080", &addr); +// result = 0 on success, -1 on error + +result = string_to_sockaddr("[::1]:3000", &addr); +// For IPv6, wrap address in brackets +``` + +### Compare Socket Addresses + +```c +struct sockaddr_in a, b; +a.sin_family = AF_INET; +b.sin_family = AF_INET; +// ... set addresses ... + +if (sockaddr_equal((struct sockaddr *)&a, (struct sockaddr *)&b)) { + // Addresses are equal +} +``` + +### Merge File Descriptor Arrays + +```c +int *arr1 = malloc(sizeof(int) * 3); +arr1[0] = 2; +arr1[1] = 5; +arr1[2] = 10; + +int *arr2 = malloc(sizeof(int) * 2); +arr2[0] = 1; +arr2[1] = 3; + +int *arrays[] = { arr1, arr2 }; +int *merged = merge_fd_arrays(arrays, 2); +// merged[0] = 3, merged[1] = 5, merged[2] = 10, merged[3] = 3 +// Note: original arrays are freed +``` + +### Set Non-blocking + +```c +int fd = socket(AF_INET, SOCK_STREAM, 0); +set_socket_nonblocking(fd, 1); // Set non-blocking +set_socket_nonblocking(fd, 0); // Set blocking +``` + +## Address Format + +The address parsing supports: + +- Port only: `:8080`, `8080` (uses defaults for host) +- IPv4: `192.168.1.1:8080` +- IPv6: `[::1]:8080`, `[2001:db8::1]:443` +- With defaults: `8080` with default host `0.0.0.0` + +## Return Value Conventions + +Functions returning `int *` (arrays): +- Returns `NULL` on error +- Index 0 contains the count +- Indices 1+ contain the values +- Caller is responsible for freeing the array + +Functions returning `int`: +- Returns 0 on success +- Returns -1 on error + +## License + +Copyright (c) 2026 finwo. See LICENSE.md for details.