feat(socket): add raw socket utilities with tests

- Added `socket_create`.
 - Added `socket_configure`.
 - Added socket error utilities
 - Added unit tests.
This commit is contained in:
lohhiiccc 2026-01-24 23:19:57 +01:00
parent 532c4b5dc7
commit 9e35dd86bb
8 changed files with 224 additions and 0 deletions

View file

@ -0,0 +1,16 @@
#ifndef ICMP_SOCKET_H
#define ICMP_SOCKET_H
#include "internal/icmp_internal.h"
/* Create raw ICMP socket */
int socket_create(struct icmp_handle *h);
/* Configure socket (non-blocking, buffer size) */
int socket_configure(struct icmp_handle *h);
/* Internal helpers for error handling */
void socket_clear_error(struct icmp_handle *h);
void socket_set_error(struct icmp_handle *h, int code, const char *fmt, ...);
#endif

View file

42
src/socket/configure.c Normal file
View file

@ -0,0 +1,42 @@
#include "internal/icmp_internal.h"
#include "internal/icmp_socket.h"
#include <fcntl.h>
#include <errno.h>
#include <string.h>
/* Forward declarations */
static void handle_configure_error(struct icmp_handle *h, const char *func);
/* -------------------- */
int
socket_configure(struct icmp_handle *h)
{
int flags;
/* Get current file status flags */
flags = fcntl(h->fd, F_GETFL, 0);
if (-1 == flags)
{
handle_configure_error(h, "fcntl(F_GETFL)");
return -1;
}
/* Set non-blocking flag */
if (-1 == fcntl(h->fd, F_SETFL, flags | O_NONBLOCK))
{
handle_configure_error(h, "fcntl(F_SETFL)");
return -1;
}
/* Clear error state on success */
socket_clear_error(h);
return 0;
}
static void
handle_configure_error(struct icmp_handle *h, const char *func)
{
socket_set_error(h, ICMP_ERR_SOCKET,
"%s failed: %s", func, strerror(errno));
}

48
src/socket/create.c Normal file
View file

@ -0,0 +1,48 @@
#include "internal/icmp_internal.h"
#include "internal/icmp_socket.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
/* Forward declarations */
static void handle_socket_error(struct icmp_handle *h, int err);
/* -------------------- */
int
socket_create(struct icmp_handle *h)
{
int fd;
/* Create raw ICMP socket */
fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (fd < 0)
{
handle_socket_error(h, errno);
return -1;
}
/* Store file descriptor in handle */
h->fd = fd;
/* Clear error state on success */
socket_clear_error(h);
return 0;
}
static void
handle_socket_error(struct icmp_handle *h, int err)
{
if (EPERM == err)
{
socket_set_error(h, ICMP_ERR_PERMISSION,
"Raw socket requires CAP_NET_RAW or root");
}
else
{
socket_set_error(h, ICMP_ERR_SOCKET,
"socket() failed: %s", strerror(err));
}
}

22
src/socket/utils.c Normal file
View file

@ -0,0 +1,22 @@
#include "internal/icmp_internal.h"
#include <stdarg.h>
#include <stdio.h>
void
socket_clear_error(struct icmp_handle *h)
{
h->last_error = ICMP_OK;
h->error_msg[0] = '\0';
}
void
socket_set_error(struct icmp_handle *h, int code, const char *fmt, ...)
{
va_list args;
h->last_error = code;
va_start(args, fmt);
vsnprintf(h->error_msg, sizeof(h->error_msg), fmt, args);
va_end(args);
}

View file

View file

@ -0,0 +1,96 @@
#include <criterion/criterion.h>
#include <fcntl.h>
#include <unistd.h>
#include "internal/icmp_internal.h"
#include "internal/icmp_socket.h"
/* Helper: check if we have CAP_NET_RAW capability */
static int
has_net_raw_capability(void)
{
struct icmp_handle h = {0};
int ret = socket_create(&h);
if (0 == ret)
{
close(h.fd);
return 1;
}
if (ICMP_ERR_PERMISSION == h.last_error)
return 0;
return -1;
}
/* Test 1: socket_create() success (skip without privileges) */
Test(socket, create_success)
{
struct icmp_handle h = {0};
int ret = socket_create(&h);
if (-1 == ret && ICMP_ERR_PERMISSION == h.last_error)
{
cr_skip("Test requires CAP_NET_RAW or root privileges");
}
cr_assert_eq(ret, 0, "socket_create() failed: %s", h.error_msg);
cr_assert_geq(h.fd, 0, "Expected valid file descriptor");
cr_assert_eq(h.last_error, ICMP_OK, "Expected no error");
close(h.fd);
}
/* Test 2: socket_create() sets error message on failure */
Test(socket, create_error_message)
{
struct icmp_handle h = {0};
int ret = socket_create(&h);
if (0 == ret)
{
/* We have privileges - can't test error case */
close(h.fd);
cr_skip("Test requires missing CAP_NET_RAW");
}
/* Without privileges, should get ICMP_ERR_PERMISSION */
cr_assert_eq(h.last_error, ICMP_ERR_PERMISSION);
cr_assert_neq(h.error_msg[0], '\0', "Error message should not be empty");
}
/* Test 3: socket_configure() success (skip without privileges) */
Test(socket, configure_success)
{
if (0 == has_net_raw_capability())
{
cr_skip("Test requires CAP_NET_RAW or root privileges");
}
struct icmp_handle h = {0};
socket_create(&h);
int ret = socket_configure(&h);
cr_assert_eq(ret, 0, "socket_configure() failed: %s", h.error_msg);
cr_assert_eq(h.last_error, ICMP_OK, "Expected no error");
/* Verify O_NONBLOCK flag is set */
int flags = fcntl(h.fd, F_GETFL, 0);
cr_assert(flags & O_NONBLOCK, "O_NONBLOCK flag not set");
close(h.fd);
}
/* Test 4: socket_configure() with invalid fd (always runnable) */
Test(socket, configure_invalid_fd)
{
struct icmp_handle h = {0};
h.fd = -1;
int ret = socket_configure(&h);
cr_assert_eq(ret, -1, "Expected -1 for invalid fd");
cr_assert_eq(h.last_error, ICMP_ERR_SOCKET, "Expected ICMP_ERR_SOCKET");
cr_assert_neq(h.error_msg[0], '\0', "Error message should not be empty");
}