348 lines
9.3 KiB
C
348 lines
9.3 KiB
C
// sock - v1.4.0 - MIT License - https://github.com/seajee/sock.h
|
|
|
|
#ifndef SOCK_H_
|
|
#define SOCK_H_
|
|
|
|
#include <arpa/inet.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
typedef enum {
|
|
SOCK_ADDR_INVALID,
|
|
SOCK_IPV4,
|
|
SOCK_IPV6
|
|
} SockAddrType;
|
|
|
|
typedef struct {
|
|
SockAddrType type; // Address type
|
|
int port; // Address port
|
|
char str[INET6_ADDRSTRLEN]; // String representation of the address
|
|
union {
|
|
struct sockaddr sockaddr;
|
|
struct sockaddr_in ipv4;
|
|
struct sockaddr_in6 ipv6;
|
|
};
|
|
socklen_t len;
|
|
} SockAddr;
|
|
|
|
typedef enum {
|
|
SOCK_TCP,
|
|
SOCK_UDP
|
|
} SockType;
|
|
|
|
typedef struct {
|
|
SockType type; // Socket type
|
|
SockAddr addr; // Socket address
|
|
int fd; // File descriptor
|
|
} Sock;
|
|
|
|
// Create a SockAddr structure from primitives
|
|
SockAddr sock_addr(const char *addr, int port);
|
|
|
|
// Create a socket with the corresponding domain and type
|
|
Sock *sock(SockAddrType domain, SockType type);
|
|
|
|
// 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);
|
|
|
|
// Accept connections from a socket
|
|
Sock *sock_accept(Sock *sock);
|
|
|
|
// Connect a socket to a specific address
|
|
bool sock_connect(Sock *sock, SockAddr addr);
|
|
|
|
// Send data through a socket
|
|
ssize_t sock_send(Sock *sock, const void *buf, size_t size);
|
|
|
|
// Receive data from a socket
|
|
ssize_t sock_recv(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);
|
|
|
|
// Receive data from a socket in connectionless mode
|
|
ssize_t sock_recvfrom(Sock *sock, void *buf, size_t size, SockAddr *addr);
|
|
|
|
// Close a socket
|
|
void sock_close(Sock *sock);
|
|
|
|
// Log last error to stderr
|
|
void sock_log_error(void);
|
|
|
|
#endif // SOCK_H_
|
|
|
|
#ifdef SOCK_IMPLEMENTATION
|
|
|
|
SockAddr sock_addr(const char *addr, int port)
|
|
{
|
|
SockAddr sa = {0};
|
|
|
|
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));
|
|
} 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);
|
|
}
|
|
|
|
sa.port = port;
|
|
|
|
return sa;
|
|
}
|
|
|
|
Sock *sock(SockAddrType domain, SockType type)
|
|
{
|
|
Sock *sock = malloc(sizeof(*sock));
|
|
if (sock == NULL) {
|
|
return NULL;
|
|
}
|
|
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);
|
|
if (sock->fd < 0) {
|
|
free(sock);
|
|
return NULL;
|
|
}
|
|
|
|
return sock;
|
|
}
|
|
|
|
bool sock_bind(Sock *sock, SockAddr addr)
|
|
{
|
|
if (bind(sock->fd, &addr.sockaddr, addr.len) < 0) {
|
|
return false;
|
|
}
|
|
|
|
sock->addr = addr;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool sock_listen(Sock *sock, int backlog)
|
|
{
|
|
if (listen(sock->fd, backlog) < 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Sock *sock_accept(Sock *sock)
|
|
{
|
|
int fd = accept(sock->fd, &sock->addr.sockaddr, &sock->addr.len);
|
|
if (fd < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
Sock *res = malloc(sizeof(*res));
|
|
if (res == NULL) {
|
|
return NULL;
|
|
}
|
|
memset(res, 0, sizeof(*res));
|
|
|
|
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;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
bool sock_connect(Sock *sock, SockAddr addr)
|
|
{
|
|
if (connect(sock->fd, &addr.sockaddr, addr.len) < 0) {
|
|
return false;
|
|
}
|
|
|
|
sock->addr = addr;
|
|
|
|
return true;
|
|
}
|
|
|
|
ssize_t sock_send(Sock *sock, const void *buf, size_t size)
|
|
{
|
|
return send(sock->fd, buf, size, 0);
|
|
}
|
|
|
|
ssize_t sock_recv(Sock *sock, void *buf, size_t size)
|
|
{
|
|
return recv(sock->fd, buf, size, 0);
|
|
}
|
|
|
|
ssize_t sock_sendto(Sock *sock, const void *buf, size_t size, SockAddr addr)
|
|
{
|
|
return sendto(sock->fd, buf, size, 0, &addr.sockaddr, addr.len);
|
|
}
|
|
|
|
ssize_t sock_recvfrom(Sock *sock, void *buf, size_t size, SockAddr *addr)
|
|
{
|
|
ssize_t res = recvfrom(sock->fd, buf, size, 0, &addr->sockaddr,
|
|
&sock->addr.len);
|
|
if (res < 0) {
|
|
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;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void sock_close(Sock *sock)
|
|
{
|
|
close(sock->fd);
|
|
free(sock);
|
|
}
|
|
|
|
void sock_log_error(void)
|
|
{
|
|
fprintf(stderr, "SOCK ERROR: %s\n", strerror(errno));
|
|
}
|
|
|
|
#endif // SOCK_IMPLEMENTATION
|
|
|
|
/*
|
|
Revision history:
|
|
|
|
1.4.0 (2025-04-26) Renamed sock_create() to sock()
|
|
1.3.0 (2025-04-26) Renamed sock_log_errors to sock_log_error
|
|
1.2.0 (2025-04-26) sock_addr can now resolve hostnames
|
|
1.1.0 (2025-04-26) New sock_log_errors function
|
|
Fill new Sock's SockAddr after a sock_accept
|
|
Save remove SockAddr in Sock in sock_connect
|
|
1.0.3 (2025-04-25) Fix incorrect usage of inet_pton
|
|
1.0.2 (2025-04-25) Fix sock_recvfrom not recognizing the address
|
|
family
|
|
1.0.1 (2025-04-25) Handle different kind of sockaddr with a union
|
|
1.0.0 (2025-04-25) Initial release
|
|
*/
|
|
|
|
/*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2025 seajee
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE 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
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|