From 3c56ea024b92204e0f56fe1eab769b12ac0d3008 Mon Sep 17 00:00:00 2001 From: lohhiiccc <96543753+lohhiiccc@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:12:42 +0100 Subject: [PATCH] feat: implement ICMP send/receive modules --- includes/icmp_types.h | 28 ++++++++++++ includes/internal/icmp_recv.h | 33 ++++++++++++++ includes/internal/icmp_send.h | 16 +++++++ src/recv/.gitkeep | 0 src/recv/api/process.c | 28 ++++++++++++ src/recv/core/parse_packet.c | 20 +++++++++ src/recv/core/process_single_packet.c | 20 +++++++++ src/recv/core/receive_packet.c | 20 +++++++++ src/recv/helpers/build_reply.c | 25 +++++++++++ src/recv/helpers/handle_receive_error.c | 15 +++++++ src/recv/helpers/validate_params.c | 24 +++++++++++ src/send/.gitkeep | 0 src/send/api/echo.c | 28 ++++++++++++ src/send/api/raw.c | 57 +++++++++++++++++++++++++ src/send/core/send_packet.c | 39 +++++++++++++++++ src/send/core/send_to_destination.c | 20 +++++++++ src/send/helpers/prepare_destination.c | 10 +++++ src/send/helpers/set_socket_ttl.c | 21 +++++++++ src/send/helpers/validate_handle.c | 18 ++++++++ tests/recv/.gitkeep | 0 tests/send/.gitkeep | 0 21 files changed, 422 insertions(+) create mode 100644 includes/icmp_types.h create mode 100644 includes/internal/icmp_recv.h create mode 100644 includes/internal/icmp_send.h delete mode 100644 src/recv/.gitkeep create mode 100644 src/recv/api/process.c create mode 100644 src/recv/core/parse_packet.c create mode 100644 src/recv/core/process_single_packet.c create mode 100644 src/recv/core/receive_packet.c create mode 100644 src/recv/helpers/build_reply.c create mode 100644 src/recv/helpers/handle_receive_error.c create mode 100644 src/recv/helpers/validate_params.c delete mode 100644 src/send/.gitkeep create mode 100644 src/send/api/echo.c create mode 100644 src/send/api/raw.c create mode 100644 src/send/core/send_packet.c create mode 100644 src/send/core/send_to_destination.c create mode 100644 src/send/helpers/prepare_destination.c create mode 100644 src/send/helpers/set_socket_ttl.c create mode 100644 src/send/helpers/validate_handle.c delete mode 100644 tests/recv/.gitkeep delete mode 100644 tests/send/.gitkeep diff --git a/includes/icmp_types.h b/includes/icmp_types.h new file mode 100644 index 0000000..3aef9c8 --- /dev/null +++ b/includes/icmp_types.h @@ -0,0 +1,28 @@ +#ifndef ICMP_TYPES_H +#define ICMP_TYPES_H + +/* ICMP Message Types */ +#define ICMP_TYPE_ECHO_REPLY 0 +#define ICMP_TYPE_DEST_UNREACHABLE 3 +#define ICMP_TYPE_SOURCE_QUENCH 4 +#define ICMP_TYPE_REDIRECT 5 +#define ICMP_TYPE_ECHO_REQUEST 8 +#define ICMP_TYPE_ROUTER_ADVERTISEMENT 9 +#define ICMP_TYPE_ROUTER_SOLICITATION 10 +#define ICMP_TYPE_TIME_EXCEEDED 11 +#define ICMP_TYPE_PARAMETER_PROBLEM 12 +#define ICMP_TYPE_TIMESTAMP_REQUEST 13 +#define ICMP_TYPE_TIMESTAMP_REPLY 14 + +/* Type 3 (Destination Unreachable) Codes */ +#define ICMP_CODE_NET_UNREACHABLE 0 +#define ICMP_CODE_HOST_UNREACHABLE 1 +#define ICMP_CODE_PROTOCOL_UNREACHABLE 2 +#define ICMP_CODE_PORT_UNREACHABLE 3 +#define ICMP_CODE_FRAG_NEEDED 4 + +/* Type 11 (Time Exceeded) Codes */ +#define ICMP_CODE_TTL_EXCEEDED 0 +#define ICMP_CODE_FRAG_REASM_EXCEEDED 1 + +#endif diff --git a/includes/internal/icmp_recv.h b/includes/internal/icmp_recv.h new file mode 100644 index 0000000..c14653b --- /dev/null +++ b/includes/internal/icmp_recv.h @@ -0,0 +1,33 @@ +#ifndef ICMP_RECV_H +#define ICMP_RECV_H + +#include "icmp.h" +#include "internal/icmp_internal.h" +#include +#include + +/* Validation helpers */ +int recv_validate_params(struct icmp_handle *h, icmp_callback_t cb); +int recv_handle_receive_error(struct icmp_handle *h); + +/* Core receive functions */ +ssize_t recv_receive_packet(int fd, void *buffer, size_t buffer_len, + struct sockaddr_in *from); + +int recv_parse_packet(const void *buffer, size_t buffer_len, + uint8_t *type, uint8_t *code, + uint8_t *ttl, struct in_addr *src_addr, + const void **payload, size_t *payload_len, + size_t *ip_hdr_len); + +/* Reply building */ +void recv_build_reply(icmp_reply_t *reply, uint8_t type, uint8_t code, + uint8_t ttl, struct in_addr src_addr, + const void *payload, size_t payload_len, + const void *buffer, size_t ip_hdr_len); + +/* Process single packet */ +void recv_process_single_packet(uint8_t *buffer, ssize_t len, + icmp_callback_t cb, void *userdata); + +#endif diff --git a/includes/internal/icmp_send.h b/includes/internal/icmp_send.h new file mode 100644 index 0000000..3de83d3 --- /dev/null +++ b/includes/internal/icmp_send.h @@ -0,0 +1,16 @@ +#ifndef ICMP_SEND_H +#define ICMP_SEND_H + +#include "internal/icmp_internal.h" +#include + +void send_prepare_destination(struct sockaddr_in *addr, struct in_addr dest); +int send_set_socket_ttl(struct icmp_handle *h, uint8_t ttl); +int send_packet(struct icmp_handle *h, const void *buffer, size_t len, + struct sockaddr_in *dest); + +int send_validate_handle(struct icmp_handle *h); +int send_to_destination(struct icmp_handle *h, const void *packet, size_t len, + struct in_addr dest, uint8_t ttl); + +#endif diff --git a/src/recv/.gitkeep b/src/recv/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/recv/api/process.c b/src/recv/api/process.c new file mode 100644 index 0000000..b4a8422 --- /dev/null +++ b/src/recv/api/process.c @@ -0,0 +1,28 @@ +#include "icmp.h" +#include "internal/icmp_internal.h" +#include "internal/icmp_recv.h" +#include + +#define MAX_PACKET_SIZE 1500 + +int +icmp_process(icmp_handle_t *h, icmp_callback_t cb, void *userdata) +{ + uint8_t buffer[MAX_PACKET_SIZE]; + struct sockaddr_in from; + ssize_t recv_len; + + if (0 == recv_validate_params(h, cb)) + return -1; + while (1) + { + recv_len = recv_receive_packet(h->fd, buffer, sizeof(buffer), &from); + if (0 == recv_len) + break; + if (recv_len < 0) + return recv_handle_receive_error(h); + recv_process_single_packet(buffer, recv_len, cb, userdata); + } + icmp_clear_error(h); + return 0; +} diff --git a/src/recv/core/parse_packet.c b/src/recv/core/parse_packet.c new file mode 100644 index 0000000..d5d1e70 --- /dev/null +++ b/src/recv/core/parse_packet.c @@ -0,0 +1,20 @@ +#include "internal/icmp_recv.h" +#include "internal/icmp_packet_internal.h" + +int +recv_parse_packet(const void *buffer, size_t buffer_len, + uint8_t *type, uint8_t *code, + uint8_t *ttl, struct in_addr *src_addr, + const void **payload, size_t *payload_len, + size_t *ip_hdr_len) +{ + if (icmp_parse_ip_header(buffer, buffer_len, ttl, src_addr, + ip_hdr_len) < 0) + return -1; + + if (icmp_parse_icmp_payload(buffer, buffer_len, *ip_hdr_len, + type, code, payload, payload_len) < 0) + return -1; + + return 0; +} diff --git a/src/recv/core/process_single_packet.c b/src/recv/core/process_single_packet.c new file mode 100644 index 0000000..b51d4c1 --- /dev/null +++ b/src/recv/core/process_single_packet.c @@ -0,0 +1,20 @@ +#include "internal/icmp_recv.h" + +void +recv_process_single_packet(uint8_t *buffer, ssize_t len, icmp_callback_t cb, + void *userdata) +{ + icmp_reply_t reply; + uint8_t type, code, ttl; + struct in_addr src_addr; + const void *payload; + size_t payload_len, ip_hdr_len; + + if (recv_parse_packet(buffer, len, &type, &code, &ttl, &src_addr, + &payload, &payload_len, &ip_hdr_len) < 0) + return; + recv_build_reply(&reply, type, code, ttl, src_addr, payload, payload_len, + buffer, ip_hdr_len); + reply.ip_payload_len = len - ip_hdr_len; + cb(&reply, userdata); +} diff --git a/src/recv/core/receive_packet.c b/src/recv/core/receive_packet.c new file mode 100644 index 0000000..dc6a510 --- /dev/null +++ b/src/recv/core/receive_packet.c @@ -0,0 +1,20 @@ +#include "internal/icmp_recv.h" +#include +#include +#include + +ssize_t +recv_receive_packet(int fd, void *buffer, size_t buffer_len, + struct sockaddr_in *from) +{ + socklen_t from_len; + ssize_t n; + + from_len = sizeof(*from); + memset(from, 0, sizeof(*from)); + n = recvfrom(fd, buffer, buffer_len, 0, (struct sockaddr *)from, + &from_len); + if (n < 0) + return (EAGAIN == errno || EWOULDBLOCK == errno) ? 0 : -1; + return n; +} diff --git a/src/recv/helpers/build_reply.c b/src/recv/helpers/build_reply.c new file mode 100644 index 0000000..41bd927 --- /dev/null +++ b/src/recv/helpers/build_reply.c @@ -0,0 +1,25 @@ +#include "internal/icmp_recv.h" +#include "internal/icmp_utils.h" +#include + +void +recv_build_reply(icmp_reply_t *reply, uint8_t type, uint8_t code, + uint8_t ttl, struct in_addr src_addr, + const void *payload, size_t payload_len, + const void *buffer, size_t ip_hdr_len) +{ + memset(reply, 0, sizeof(icmp_reply_t)); + reply->type = type; + reply->code = code; + reply->from = src_addr; + reply->ttl = ttl; + reply->payload = (void *)payload; + reply->payload_len = payload_len; + reply->ip_payload = (void *)((const uint8_t *)buffer + ip_hdr_len); + reply->ip_payload_len = 0; + if (icmp_get_time(&reply->timestamp) < 0) + { + reply->timestamp.tv_sec = 0; + reply->timestamp.tv_nsec = 0; + } +} diff --git a/src/recv/helpers/handle_receive_error.c b/src/recv/helpers/handle_receive_error.c new file mode 100644 index 0000000..e52cefb --- /dev/null +++ b/src/recv/helpers/handle_receive_error.c @@ -0,0 +1,15 @@ +#include "internal/icmp_internal.h" +#include "internal/icmp_recv.h" +#include +#include + +int +recv_handle_receive_error(struct icmp_handle *h) +{ + int saved_errno; + + saved_errno = errno; + icmp_set_error_fmt(h, ICMP_ERR_RECV, "recvfrom() failed: %s", + strerror(saved_errno)); + return -1; +} diff --git a/src/recv/helpers/validate_params.c b/src/recv/helpers/validate_params.c new file mode 100644 index 0000000..cedba51 --- /dev/null +++ b/src/recv/helpers/validate_params.c @@ -0,0 +1,24 @@ +#include "internal/icmp_internal.h" +#include "internal/icmp_recv.h" +#include + +int +recv_validate_params(struct icmp_handle *h, icmp_callback_t cb) +{ + if (NULL == h) + return 0; + + if (NULL == cb) + { + icmp_set_error_fmt(h, ICMP_ERR_INVALID, "Callback cannot be NULL"); + return 0; + } + + if (h->fd < 0) + { + icmp_set_error_fmt(h, ICMP_ERR_INVALID, "Invalid socket FD"); + return 0; + } + + return 1; +} diff --git a/src/send/.gitkeep b/src/send/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/send/api/echo.c b/src/send/api/echo.c new file mode 100644 index 0000000..2746e99 --- /dev/null +++ b/src/send/api/echo.c @@ -0,0 +1,28 @@ +#include "icmp.h" +#include "internal/icmp_send.h" +#include "icmp_types.h" +#include +#include + +/* ICMP echo header: type(1) + code(1) + checksum(2) + id(2) + seq(2) = 8 */ +#define ECHO_HEADER_SIZE 4 + +int +icmp_send_echo(icmp_handle_t *h, struct in_addr dest, uint16_t id, + uint16_t seq, uint8_t ttl) +{ + if (0 == send_validate_handle(h)) + return -1; + + uint16_t id_net = htons(id); + uint16_t seq_net = htons(seq); + uint8_t payload[ECHO_HEADER_SIZE]; + + payload[0] = (id_net >> 8) & 0xFF; + payload[1] = id_net & 0xFF; + payload[2] = (seq_net >> 8) & 0xFF; + payload[3] = seq_net & 0xFF; + + return icmp_send_raw(h, ICMP_TYPE_ECHO_REQUEST, 0, + payload, ECHO_HEADER_SIZE, dest, ttl); +} diff --git a/src/send/api/raw.c b/src/send/api/raw.c new file mode 100644 index 0000000..a3b4184 --- /dev/null +++ b/src/send/api/raw.c @@ -0,0 +1,57 @@ +#include "icmp.h" +#include "internal/icmp_internal.h" +#include "internal/icmp_packet_internal.h" +#include "internal/icmp_send.h" +#include + +/* Maximum payload size for simplicity */ +#define MAX_PAYLOAD_SIZE 1024 +#define MAX_PACKET_SIZE (8 + MAX_PAYLOAD_SIZE) + +/* Forward declarations */ +static int validate_payload(struct icmp_handle *h, const void *payload, + size_t len); +/* -------------------- */ + +int +icmp_send_raw(icmp_handle_t *h, uint8_t type, uint8_t code, + const void *payload, size_t len, struct in_addr dest, + uint8_t ttl) +{ + if (0 == send_validate_handle(h)) + return -1; + + if (0 == validate_payload(h, payload, len)) + return -1; + + uint8_t buffer[MAX_PACKET_SIZE]; + + int packet_len = icmp_build_packet(buffer, sizeof(buffer), + type, code, 0, 0, payload, len); + if (-1 == packet_len) + { + icmp_set_error_fmt(h, ICMP_ERR_INVALID, + "Failed to build ICMP packet"); + return -1; + } + + return send_to_destination(h, buffer, packet_len, dest, ttl); +} + +static int +validate_payload(struct icmp_handle *h, const void *payload, size_t len) +{ + if (len > 0 && NULL == payload) + { + icmp_set_error_fmt(h, ICMP_ERR_INVALID, "Payload is NULL"); + return 0; + } + + if (len > MAX_PAYLOAD_SIZE) + { + icmp_set_error_fmt(h, ICMP_ERR_INVALID, + "Payload too large (max %d bytes)", MAX_PAYLOAD_SIZE); + return 0; + } + return 1; +} diff --git a/src/send/core/send_packet.c b/src/send/core/send_packet.c new file mode 100644 index 0000000..d8cf15b --- /dev/null +++ b/src/send/core/send_packet.c @@ -0,0 +1,39 @@ +#include "internal/icmp_send.h" +#include +#include +#include +#include + +int +send_packet(struct icmp_handle *h, const void *buffer, size_t len, + struct sockaddr_in *dest) +{ + ssize_t bytes_sent; + + bytes_sent = sendto(h->fd, buffer, len, 0, (struct sockaddr *)dest, + sizeof(struct sockaddr_in)); + + if (bytes_sent < 0) + { + int saved_errno = errno; + + if (EAGAIN == saved_errno || EWOULDBLOCK == saved_errno) + { + icmp_set_error_fmt(h, ICMP_ERR_EAGAIN, + "Send would block (buffer full, retry later)"); + return -1; + } + icmp_set_error_fmt(h, ICMP_ERR_SEND, "sendto() failed: %s", + strerror(saved_errno)); + return -1; + } + + if ((size_t)bytes_sent != len) + { + icmp_set_error_fmt(h, ICMP_ERR_SEND, "Partial send (%zd/%zu bytes)", + bytes_sent, len); + return -1; + } + + return 0; +} diff --git a/src/send/core/send_to_destination.c b/src/send/core/send_to_destination.c new file mode 100644 index 0000000..3f6ee95 --- /dev/null +++ b/src/send/core/send_to_destination.c @@ -0,0 +1,20 @@ +#include "internal/icmp_internal.h" +#include "internal/icmp_send.h" +#include + +int +send_to_destination(struct icmp_handle *h, const void *packet, size_t len, + struct in_addr dest, uint8_t ttl) +{ + if (send_set_socket_ttl(h, ttl) < 0) + return -1; + + struct sockaddr_in addr; + send_prepare_destination(&addr, dest); + + if (send_packet(h, packet, len, &addr) < 0) + return -1; + + icmp_clear_error(h); + return 0; +} diff --git a/src/send/helpers/prepare_destination.c b/src/send/helpers/prepare_destination.c new file mode 100644 index 0000000..39546e7 --- /dev/null +++ b/src/send/helpers/prepare_destination.c @@ -0,0 +1,10 @@ +#include "internal/icmp_send.h" +#include + +void +send_prepare_destination(struct sockaddr_in *addr, struct in_addr dest) +{ + memset(addr, 0, sizeof(struct sockaddr_in)); + addr->sin_family = AF_INET; + addr->sin_addr = dest; +} diff --git a/src/send/helpers/set_socket_ttl.c b/src/send/helpers/set_socket_ttl.c new file mode 100644 index 0000000..dada3d4 --- /dev/null +++ b/src/send/helpers/set_socket_ttl.c @@ -0,0 +1,21 @@ +#include "internal/icmp_send.h" +#include +#include +#include +#include + +int +send_set_socket_ttl(struct icmp_handle *h, uint8_t ttl) +{ + int ttl_val = ttl; + + if (setsockopt(h->fd, IPPROTO_IP, IP_TTL, &ttl_val, sizeof(ttl_val)) < 0) + { + int saved_errno = errno; + icmp_set_error_fmt(h, ICMP_ERR_SOCKET, "Failed to set TTL: %s", + strerror(saved_errno)); + return -1; + } + + return 0; +} diff --git a/src/send/helpers/validate_handle.c b/src/send/helpers/validate_handle.c new file mode 100644 index 0000000..4b7defc --- /dev/null +++ b/src/send/helpers/validate_handle.c @@ -0,0 +1,18 @@ +#include "internal/icmp_internal.h" +#include "internal/icmp_send.h" +#include + +int +send_validate_handle(struct icmp_handle *h) +{ + if (NULL == h) + return 0; + + if (h->fd < 0) + { + icmp_set_error_fmt(h, ICMP_ERR_INVALID, "Invalid socket FD"); + return 0; + } + + return 1; +} diff --git a/tests/recv/.gitkeep b/tests/recv/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/send/.gitkeep b/tests/send/.gitkeep deleted file mode 100644 index e69de29..0000000