From 9e35dd86bbdc22e1e53c7b47ccf25ddb777ee901 Mon Sep 17 00:00:00 2001 From: lohhiiccc <96543753+lohhiiccc@users.noreply.github.com> Date: Sat, 24 Jan 2026 23:19:57 +0100 Subject: [PATCH] feat(socket): add raw socket utilities with tests - Added `socket_create`. - Added `socket_configure`. - Added socket error utilities - Added unit tests. --- includes/internal/.gitkeep | 0 includes/internal/icmp_socket.h | 16 ++++++ src/socket/.gitkeep | 0 src/socket/configure.c | 42 +++++++++++++++ src/socket/create.c | 48 +++++++++++++++++ src/socket/utils.c | 22 ++++++++ tests/socket/.gitkeep | 0 tests/socket/test_socket.c | 96 +++++++++++++++++++++++++++++++++ 8 files changed, 224 insertions(+) delete mode 100644 includes/internal/.gitkeep create mode 100644 includes/internal/icmp_socket.h delete mode 100644 src/socket/.gitkeep create mode 100644 src/socket/configure.c create mode 100644 src/socket/create.c create mode 100644 src/socket/utils.c delete mode 100644 tests/socket/.gitkeep create mode 100644 tests/socket/test_socket.c diff --git a/includes/internal/.gitkeep b/includes/internal/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/includes/internal/icmp_socket.h b/includes/internal/icmp_socket.h new file mode 100644 index 0000000..fb8889b --- /dev/null +++ b/includes/internal/icmp_socket.h @@ -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 diff --git a/src/socket/.gitkeep b/src/socket/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/socket/configure.c b/src/socket/configure.c new file mode 100644 index 0000000..094da39 --- /dev/null +++ b/src/socket/configure.c @@ -0,0 +1,42 @@ +#include "internal/icmp_internal.h" +#include "internal/icmp_socket.h" +#include +#include +#include + +/* 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)); +} diff --git a/src/socket/create.c b/src/socket/create.c new file mode 100644 index 0000000..566f387 --- /dev/null +++ b/src/socket/create.c @@ -0,0 +1,48 @@ +#include "internal/icmp_internal.h" +#include "internal/icmp_socket.h" +#include +#include +#include +#include + +/* 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)); + } +} diff --git a/src/socket/utils.c b/src/socket/utils.c new file mode 100644 index 0000000..cbedd17 --- /dev/null +++ b/src/socket/utils.c @@ -0,0 +1,22 @@ +#include "internal/icmp_internal.h" +#include +#include + +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); +} diff --git a/tests/socket/.gitkeep b/tests/socket/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/socket/test_socket.c b/tests/socket/test_socket.c new file mode 100644 index 0000000..32d50eb --- /dev/null +++ b/tests/socket/test_socket.c @@ -0,0 +1,96 @@ +#include +#include +#include +#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"); +}