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:
parent
532c4b5dc7
commit
9e35dd86bb
8 changed files with 224 additions and 0 deletions
16
includes/internal/icmp_socket.h
Normal file
16
includes/internal/icmp_socket.h
Normal 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
|
||||
42
src/socket/configure.c
Normal file
42
src/socket/configure.c
Normal 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
48
src/socket/create.c
Normal 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
22
src/socket/utils.c
Normal 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);
|
||||
}
|
||||
96
tests/socket/test_socket.c
Normal file
96
tests/socket/test_socket.c
Normal 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");
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue