365 Days of Code - Day 041

Project Status

ProjectLanguageStatusDue DateLatest Update
Personal WebsiteHugoOngoingNoneThe site is live. There are some TODOs. Need to work on categorization, tagging, and layout improvements.
Laravel From ScratchLaravel (PHP)In-Progress2026-03-31Episode 8
PRMLaravel (PHP)In-Progress2026-03-31Working alongside other Laravel projects.
Client Website (J.L.)Laravel (PHP)In-Progress2026-03-31Working alongside other Laravel projects.
Project EulerCOngoingNoneWorking on P25. BigInt (AI gen) was a waste of time, need to rewrite
Practice JavaJavaPausedNoneInstalled, need to find a good project.
Practice PythonPythonPausedNoneInstalled, need to find a good project.
Learn GoGoPausedNoneInstalled, work on LDAP Injector from ippsec.
Learn RustRustHaven’t StartedNoneInstalled, will try network protocols after finishing in C and Zig.
Learn ElixirElixirHaven’t StartedNoneInstalled, need a good tutorial project.
Learn HaskellHaskellHaven’t StartedNoneInstalled, need a good tutorial project.
Learn ZigZigHaven’t StartedNoneInstalled, will try network protocols after finishing in C.
Linux+N/AIn-Progress2026-03-31Reading Chapter 4.
Cyber Quest 2026N/AIn-Progress2026-02-28Finished quiz 1 with 75%.
Operating SystemsN/AIn-Progress2026-03-31Reading Chapter 4: Abstraction
Grey-Hat HackingVariousIn-Progress2026-03-31Reading Chapter 8: Threat Hunting Lab
PHP Time TrackerPHPBeta FinishedNoneWorking on a basic level. Could use a couple more updates to make it fully functional.
HTTP Status Code ReaderCComplete2026-02-18Complete. Could potentially upgrade for more advanced functions or follow redirects.
ZSH Configurationbash/zshCompleteNoneSort of an ongoing process, but complete for now. Works good.
Network ProtocolsCIn-ProgressNoneCustom ICMP packet sent on the wire. Working on V2.

Network Protocols Version 2

Further refinements to version 2 of IP Protocol From Scratch (opens in a new tab). I have refined all the functions up to socket programming. It is still far from a perfect application, but it is getting there slowly. Some of the values are still hardcoded, such as source and destination addresses, but the structure is being put into place to make this a dynamic application. I think the goal at this time is to have a minimal ping-style application. Once I am able to run the program from the command line with custom arguments for source and destination addresses, number of pings, and variable payload sizes… I think I’ll consider this project complete. One of the most significant parts to build will be handling fragmentation. That I’m reserving for last.

Today’s code focused on getting the packet out on a socket. Listening for the echo reply is left to be implemented. The test was successful, and I can send a packet to any other device on the network.

c
/***
 * @file ip_icmp_stack_v2.c
 * @brief Minimal IPv4 + ICMP packet construction (learning exercise).
 *
 * IP / ICMP Stack V2
 *
 * This program is built using the lessons from V1, but completely refactored
 * to be more dynamic.
 *
 * @note This source is intended for educational purposes and currently only
 *       constructs headers (sending/receiving is not implemented here).
 */

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>

/**
 * @def PACKET_MAX_SIZE
 * @brief Maximum supported packet buffer size (bytes).
 */
#define PACKET_MAX_SIZE 65535

/**
 * @def PAYLOAD_MAX_SIZE
 * @brief Maximum supported payload size (bytes).
 *        PACKET_MAX_SIZE - (20 byte IP header + 4 byte ICMP header + 4 byte ICMP data)
 */
#define PAYLOAD_MAX_SIZE 65507

/**
 * @def IP_HEADER_LENGTH
 * @brief IPv4 IHL value in 32-bit words for a standard 20-byte header.
 */
#define IP_HEADER_LENGTH 5

/**
 * @def STANDARD_TTL
 * @brief Default IPv4 Time-To-Live used for constructed packets.
 */
#define STANDARD_TTL 64

/**
 * @enum ip_version
 * @brief IP protocol version identifiers used in the IPv4 header.
 */
enum ip_version
{

    IPV4 = 4, /** IPv4 */
    IPV6 = 6  /** IPv6 */
};

/**
 * @enum ip_protocol
 * @brief Layer-4 protocol identifiers for the IPv4 header "protocol" field.
 */
enum ip_protocol
{
    /** ICMP (Internet Control Message Protocol) */
    IP_PROTO_ICMP = 1
};

/**
 * @enum icmp_message_type
 * @brief ICMP message "type" field identifiers.
 */
enum icmp_message_type
{
    /** Echo Reply (ping response) */
    ECHO_REPLY = 0,
    /** Echo Request (ping request) */
    ECHO = 8
};

/**
 * @struct ipv4_header
 * @brief IPv4 header (RFC 791), packed wire layout.
 *
 * @note This struct models the 20-byte IPv4 header without options (IHL=5).
 *       Multi-byte fields are typically stored in network byte order when
 *       serialized on the wire.
 */
struct ipv4_header
{
    uint8_t version_ihl;        /** IP Version (4 bits) + Internet Header Length (4 bits). */
    uint8_t type_of_service;    /** Type of Service / DSCP+ECN (QoS). */
    uint16_t total_length;      /** Total length of the IPv4 packet (header + payload), in bytes. */
    uint16_t identification;    /** Identification value for fragmentation/reassembly. */
    uint16_t flags_frag_offset; /** Flags (3 bits) + Fragment Offset (13 bits). */
    uint8_t time_to_live;       /** Time To Live (TTL). */
    uint8_t protocol;           /** Encapsulated L4 protocol (e.g., ICMP=1). */
    uint16_t checksum;          /** IPv4 header checksum (ones' complement). */
    uint32_t src_addr;          /** Source IPv4 address (network order). */
    uint32_t dst_addr;          /** Destination IPv4 address (network order). */
} __attribute__((packed));

/**
 * @struct icmp_header
 * @brief ICMP header (type/code/checksum), packed wire layout. Required for all ICMP packets.
 *
 * @note For many ICMP messages, additional fields follow this header (e.g.,
 *       echo identifier/sequence).
 */
struct icmp_header
{

    uint8_t type;      /** ICMP message type (e.g., Echo=8, Echo Reply=0). */
    uint8_t code;      /** ICMP message code (meaning depends on @ref type). */
    uint16_t checksum; /** ICMP checksum over ICMP header + ICMP data (ones' complement). */
} __attribute__((packed));

/***
 * @struct icmp_echo
 * @brief ICMP Echo message fixed fields (identifier + sequence), packed.
 *
 * Required header information for an echo / echo reply request. Not payload data.
 *
 * @field identifier  Session ID, may be 0
 * @field sequence    Sequence number, may be 0
 */
struct icmp_echo
{
    uint16_t identifier; /** Session identifier (often used to match requests/replies). */
    uint16_t sequence;   /** Sequence number (often incremented per request). */
} __attribute__((packed));

/**
 * @brief Computes a ones' complement checksum over an arbitrary byte buffer.
 *
 * Important: Ensure checksum field is zeroed before computing new checksum
 * Important: htons is unnecessary with checksum, as algorithm is byte-order agnostic
 *
 * This computes the Internet checksum commonly used by IPv4 and ICMP:
 * - Interprets the buffer as 16-bit words
 * - Sums them using 32-bit accumulation
 * - Folds carries into 16 bits
 * - Returns the ones' complement of the final 16-bit sum
 *
 * @param data    Pointer to the data to checksum.
 * @param length  Number of bytes in @p data.
 * @return        16-bit ones' complement checksum.
 */
uint16_t compute_checksum(const void *data, size_t length)
{
    const uint16_t *word_ptr = (const uint16_t *)data;
    uint32_t sum = 0;

    while (length > 1)
    {
        sum += *word_ptr++;
        length -= 2;
    }

    if (length == 1)
    {
        uint16_t padded_word = 0;
        ((uint8_t *)&padded_word)[0] = *(const uint8_t *)word_ptr;
        sum += padded_word;
    }

    while (sum >> 16)
    {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }

    return (uint16_t)~sum;
}

/**
 * @brief Populates an IPv4 header in the provided packet buffer.
 *
 * This writes a minimal 20-byte IPv4 header (no options) at the start of the
 * packet buffer. It sets:
 * - version/IHL
 * - TOS
 * - total length (ipv4 hdr + icmp hdr + icmp echo data + payload)
 * - identification (currently hard-coded)
 * - flags/fragment offset
 * - TTL
 * - protocol (ICMP)
 * - checksum (computed over the header)
 * - source/destination addresses
 *
 * @param buffer           Pointer to packet buffer to populate.
 * @param buffer_capacity  Total size of packet buffer (currently unused).
 * @param src              IPv4 source address string.
 * @param dst              IPv4 destination address string.
 * @param data_len         Packet payload string length.
 * @return                 void
 */
void construct_ipv4_header(uint8_t *buffer, const char *src, const char *dst, int data_len)
{
    struct ipv4_header *iphdr = (struct ipv4_header *)buffer;
    uint8_t version = IPV4;
    iphdr->version_ihl = (version << 4) | IP_HEADER_LENGTH;
    iphdr->type_of_service = 0x00;
    iphdr->total_length = htons(sizeof(struct ipv4_header) +
                                sizeof(struct icmp_header) +
                                sizeof(struct icmp_echo) +
                                data_len);
    iphdr->identification = htons(0xBEEF); // BEEF == 48879
    iphdr->flags_frag_offset = htons(0x0000);
    iphdr->time_to_live = STANDARD_TTL;
    iphdr->protocol = IP_PROTO_ICMP;
    iphdr->checksum = htons(0x0000);
    inet_pton(AF_INET, src, &iphdr->src_addr);
    inet_pton(AF_INET, dst, &iphdr->dst_addr);
    iphdr->checksum = 0;
    iphdr->checksum = compute_checksum(iphdr, sizeof(struct ipv4_header));
    return;
}

void construct_icmp_header(uint8_t *buffer, int payload_len)
{
    struct icmp_header *icmphdr = (struct icmp_header *)(buffer + sizeof(struct ipv4_header));
    icmphdr->type = ECHO;
    icmphdr->code = 0;
    icmphdr->checksum = 0;
    icmphdr->checksum = compute_checksum(icmphdr, sizeof(struct icmp_header) + sizeof(struct icmp_echo) + payload_len);
    return;
}

void construct_icmp_data(uint8_t *buffer, int seq)
{
    struct icmp_echo *icmpdata = (struct icmp_echo *)(buffer + (sizeof(struct ipv4_header)) + sizeof(struct icmp_header));
    icmpdata->identifier = htons(0x1234);
    icmpdata->sequence = htons(seq);
    return;
}

void construct_icmp_payload(uint8_t *buffer, const char *data, int len)
{
    uint8_t *payload_ptr = buffer + sizeof(struct ipv4_header) + sizeof(struct icmp_header) + sizeof(struct icmp_echo);
    memcpy(payload_ptr, data, len);
    return;
}

/**
 * @brief Constructs a complete packet into the provided buffer.
 *
 * Currently this function constructs only the IPv4 header (no ICMP header/payload
 * are written yet in this snippet).
 *
 * @param buffer           Pointer to the packet buffer to populate.
 * @param capacity         TODO: Total size of the packet buffer in bytes.
 * @param src              IPv4 source address string (e.g., "192.168.0.10").
 * @param dst              IPv4 destination address string (e.g., "192.168.0.1").
 * @param data             Packet payload string (currently unused in this snippet).
 * @param data_len         Packet paylaod string length.
 * @param seq              Sequence number (currently unused in this snippet).
 * @return                 Size of the packet
 */
size_t construct_packet(uint8_t *buffer, size_t capacity, const char *src, const char *dst, const char *data, int data_len, int seq)
{
    size_t required_size = sizeof(struct ipv4_header) + sizeof(struct icmp_header) + sizeof(struct icmp_echo) + data_len;
    if (required_size > capacity)
    {
        fprintf(stderr, "Error: Required size (%zu) exceeds buffer capacity (%zu)\n", required_size, capacity);
        return 0;
    }
    construct_ipv4_header(buffer, src, dst, data_len);
    construct_icmp_data(buffer, seq);
    construct_icmp_payload(buffer, data, data_len);
    construct_icmp_header(buffer, data_len);
    return sizeof(struct ipv4_header) + sizeof(struct icmp_header) + sizeof(struct icmp_echo) + data_len;
}

/**
 * create_raw_socket
 *
 * @brief Creates a raw socket
 *
 * @return A file descriptor (int) to use the socket
 */
int create_raw_socket()
{
    int raw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (raw_socket < 0)
    {
        fprintf(stderr, "Error: Raw socket creation failed. Are you running as root?\n");
        return -1;
    }
    int hdr_incl = 1;
    if (setsockopt(raw_socket, IPPROTO_IP, IP_HDRINCL, &hdr_incl, sizeof(hdr_incl)) < 0)
    {
        fprintf(stderr, ("Error: Failed to set IP_HDRINCL.\n"));
        return -1;
    }
    return raw_socket;
}

/**
 * send_packet
 *
 * @brief Takes a raw socket and an IPv4 packet buffer, and sends it out on the line
 *
 * @param socket The file descriptor (int) of the socket to use
 * @param buffer The packet buffer containing the headers and payload
 * @return The number of bytes (ssize_t) sent
 */
ssize_t send_packet(int socket, uint8_t *buffer)
{
    struct sockaddr_in dest_info = {0};
    dest_info.sin_family = AF_INET;
    dest_info.sin_port = htons(0);

    struct ipv4_header *iphdr = (struct ipv4_header *)buffer;
    dest_info.sin_addr.s_addr = iphdr->dst_addr;

    ssize_t bytes_sent = sendto(socket, buffer, ntohs(iphdr->total_length), 0, (struct sockaddr *)&dest_info, sizeof(dest_info));

    if (bytes_sent < 0)
    {
        fprintf(stderr, "Error: Failed to send packet.\n");
        close(socket);
        return -1;
    }

    return bytes_sent;
}

/***
 * @brief Generates (constructs) @p n packets.
 *
 * In this snippet, packets are constructed into a local buffer in a loop.
 * Sending/receiving is not implemented here.
 *
 * @param src       IPv4 source address string.
 * @param dst       IPv4 destination address string.
 * @param data      Packet payload string.
 * @param data_len  Packet payload string length.
 * @param n         Number of packets to generate.
 * @return          void
 */
void execute(const char *src, const char *dst, const char *data, int data_len, int n)
{
    uint8_t packet_buffer[PACKET_MAX_SIZE];
    int sock = create_raw_socket();
    if (sock < 0)
    {
        fprintf(stderr, "Error: Socket creation failed.\n");
        return;
    }

    for (int i = 0; i < n; ++i)
    {
        memset(packet_buffer, 0, sizeof(packet_buffer)); // TODO: Don't need to clear the entire buffer
        size_t packet_len = construct_packet(packet_buffer, sizeof(packet_buffer), src, dst, data, data_len, i);

        if (packet_len == 0)
        {
            fprintf(stderr, "Error: Packet could not be constructed\n");
            return;
        }

        // Debug Packet
        printf("\nRaw IPv4 + ICMP Datagram (Wire Format):\n");
        for (size_t i = 0; i < packet_len; ++i)
        {
            printf("%02X ", packet_buffer[i]);
            if ((i + 1) % 4 == 0)
                printf("\n");
        }
        printf("\n");

        ssize_t bytes_sent = send_packet(sock, packet_buffer);

        if (bytes_sent < 0)
        {
            fprintf(stderr, "Error: Failed to send packet.\n");
            return;
        }

        printf("Bytes sent in packet number %i: %li\n", i, bytes_sent);
    }

    close(sock);
    return;
}

/**
 * @brief Program entry point.
 *
 * Prints configuration info and invokes @ref execute to generate packets.
 *
 * @return Exit status code (0 on success).
 */
int main(void)
{
    printf("** ICMP Echo Sender **\n");

    char *src_addr = "192.168.50.128";
    // char *src_addr = "127.0.0.1";
    printf("Source Address: %s\n", src_addr);

    char *dst_addr = "192.168.50.230";
    // char *dst_addr = "127.0.0.1";
    printf("Destination Address: %s\n", dst_addr);

    char *payload = "HELLO";
    printf("Payload: %s\n", payload);

    int payload_len = 5; // TODO: dynamic payload sizing
    printf("Payload Length: %i\n", payload_len);

    // TODO: When implementing dynamic payload sizes from user input, it must
    //       be verified that the payload requested fits in the buffer.
    //       The below conditional will always result in false at the moment,
    //       but has been set as a reminder for future versions.
    if (payload_len > PAYLOAD_MAX_SIZE)
    {
        fprintf(stderr, "Payload exceeds maximum size of: %i\n", PAYLOAD_MAX_SIZE);
        return -1;
    }

    int num_to_send = 3;
    printf("Number of packets: %i\n", num_to_send);

    execute(src_addr, dst_addr, payload, payload_len, num_to_send);

    return 0;
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
/***
 * @file ip_icmp_stack_v2.c
 * @brief Minimal IPv4 + ICMP packet construction (learning exercise).
 *
 * IP / ICMP Stack V2
 *
 * This program is built using the lessons from V1, but completely refactored
 * to be more dynamic.
 *
 * @note This source is intended for educational purposes and currently only
 *       constructs headers (sending/receiving is not implemented here).
 */

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>

/**
 * @def PACKET_MAX_SIZE
 * @brief Maximum supported packet buffer size (bytes).
 */
#define PACKET_MAX_SIZE 65535

/**
 * @def PAYLOAD_MAX_SIZE
 * @brief Maximum supported payload size (bytes).
 *        PACKET_MAX_SIZE - (20 byte IP header + 4 byte ICMP header + 4 byte ICMP data)
 */
#define PAYLOAD_MAX_SIZE 65507

/**
 * @def IP_HEADER_LENGTH
 * @brief IPv4 IHL value in 32-bit words for a standard 20-byte header.
 */
#define IP_HEADER_LENGTH 5

/**
 * @def STANDARD_TTL
 * @brief Default IPv4 Time-To-Live used for constructed packets.
 */
#define STANDARD_TTL 64

/**
 * @enum ip_version
 * @brief IP protocol version identifiers used in the IPv4 header.
 */
enum ip_version
{

    IPV4 = 4, /** IPv4 */
    IPV6 = 6  /** IPv6 */
};

/**
 * @enum ip_protocol
 * @brief Layer-4 protocol identifiers for the IPv4 header "protocol" field.
 */
enum ip_protocol
{
    /** ICMP (Internet Control Message Protocol) */
    IP_PROTO_ICMP = 1
};

/**
 * @enum icmp_message_type
 * @brief ICMP message "type" field identifiers.
 */
enum icmp_message_type
{
    /** Echo Reply (ping response) */
    ECHO_REPLY = 0,
    /** Echo Request (ping request) */
    ECHO = 8
};

/**
 * @struct ipv4_header
 * @brief IPv4 header (RFC 791), packed wire layout.
 *
 * @note This struct models the 20-byte IPv4 header without options (IHL=5).
 *       Multi-byte fields are typically stored in network byte order when
 *       serialized on the wire.
 */
struct ipv4_header
{
    uint8_t version_ihl;        /** IP Version (4 bits) + Internet Header Length (4 bits). */
    uint8_t type_of_service;    /** Type of Service / DSCP+ECN (QoS). */
    uint16_t total_length;      /** Total length of the IPv4 packet (header + payload), in bytes. */
    uint16_t identification;    /** Identification value for fragmentation/reassembly. */
    uint16_t flags_frag_offset; /** Flags (3 bits) + Fragment Offset (13 bits). */
    uint8_t time_to_live;       /** Time To Live (TTL). */
    uint8_t protocol;           /** Encapsulated L4 protocol (e.g., ICMP=1). */
    uint16_t checksum;          /** IPv4 header checksum (ones' complement). */
    uint32_t src_addr;          /** Source IPv4 address (network order). */
    uint32_t dst_addr;          /** Destination IPv4 address (network order). */
} __attribute__((packed));

/**
 * @struct icmp_header
 * @brief ICMP header (type/code/checksum), packed wire layout. Required for all ICMP packets.
 *
 * @note For many ICMP messages, additional fields follow this header (e.g.,
 *       echo identifier/sequence).
 */
struct icmp_header
{

    uint8_t type;      /** ICMP message type (e.g., Echo=8, Echo Reply=0). */
    uint8_t code;      /** ICMP message code (meaning depends on @ref type). */
    uint16_t checksum; /** ICMP checksum over ICMP header + ICMP data (ones' complement). */
} __attribute__((packed));

/***
 * @struct icmp_echo
 * @brief ICMP Echo message fixed fields (identifier + sequence), packed.
 *
 * Required header information for an echo / echo reply request. Not payload data.
 *
 * @field identifier  Session ID, may be 0
 * @field sequence    Sequence number, may be 0
 */
struct icmp_echo
{
    uint16_t identifier; /** Session identifier (often used to match requests/replies). */
    uint16_t sequence;   /** Sequence number (often incremented per request). */
} __attribute__((packed));

/**
 * @brief Computes a ones' complement checksum over an arbitrary byte buffer.
 *
 * Important: Ensure checksum field is zeroed before computing new checksum
 * Important: htons is unnecessary with checksum, as algorithm is byte-order agnostic
 *
 * This computes the Internet checksum commonly used by IPv4 and ICMP:
 * - Interprets the buffer as 16-bit words
 * - Sums them using 32-bit accumulation
 * - Folds carries into 16 bits
 * - Returns the ones' complement of the final 16-bit sum
 *
 * @param data    Pointer to the data to checksum.
 * @param length  Number of bytes in @p data.
 * @return        16-bit ones' complement checksum.
 */
uint16_t compute_checksum(const void *data, size_t length)
{
    const uint16_t *word_ptr = (const uint16_t *)data;
    uint32_t sum = 0;

    while (length > 1)
    {
        sum += *word_ptr++;
        length -= 2;
    }

    if (length == 1)
    {
        uint16_t padded_word = 0;
        ((uint8_t *)&padded_word)[0] = *(const uint8_t *)word_ptr;
        sum += padded_word;
    }

    while (sum >> 16)
    {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }

    return (uint16_t)~sum;
}

/**
 * @brief Populates an IPv4 header in the provided packet buffer.
 *
 * This writes a minimal 20-byte IPv4 header (no options) at the start of the
 * packet buffer. It sets:
 * - version/IHL
 * - TOS
 * - total length (ipv4 hdr + icmp hdr + icmp echo data + payload)
 * - identification (currently hard-coded)
 * - flags/fragment offset
 * - TTL
 * - protocol (ICMP)
 * - checksum (computed over the header)
 * - source/destination addresses
 *
 * @param buffer           Pointer to packet buffer to populate.
 * @param buffer_capacity  Total size of packet buffer (currently unused).
 * @param src              IPv4 source address string.
 * @param dst              IPv4 destination address string.
 * @param data_len         Packet payload string length.
 * @return                 void
 */
void construct_ipv4_header(uint8_t *buffer, const char *src, const char *dst, int data_len)
{
    struct ipv4_header *iphdr = (struct ipv4_header *)buffer;
    uint8_t version = IPV4;
    iphdr->version_ihl = (version << 4) | IP_HEADER_LENGTH;
    iphdr->type_of_service = 0x00;
    iphdr->total_length = htons(sizeof(struct ipv4_header) +
                                sizeof(struct icmp_header) +
                                sizeof(struct icmp_echo) +
                                data_len);
    iphdr->identification = htons(0xBEEF); // BEEF == 48879
    iphdr->flags_frag_offset = htons(0x0000);
    iphdr->time_to_live = STANDARD_TTL;
    iphdr->protocol = IP_PROTO_ICMP;
    iphdr->checksum = htons(0x0000);
    inet_pton(AF_INET, src, &iphdr->src_addr);
    inet_pton(AF_INET, dst, &iphdr->dst_addr);
    iphdr->checksum = 0;
    iphdr->checksum = compute_checksum(iphdr, sizeof(struct ipv4_header));
    return;
}

void construct_icmp_header(uint8_t *buffer, int payload_len)
{
    struct icmp_header *icmphdr = (struct icmp_header *)(buffer + sizeof(struct ipv4_header));
    icmphdr->type = ECHO;
    icmphdr->code = 0;
    icmphdr->checksum = 0;
    icmphdr->checksum = compute_checksum(icmphdr, sizeof(struct icmp_header) + sizeof(struct icmp_echo) + payload_len);
    return;
}

void construct_icmp_data(uint8_t *buffer, int seq)
{
    struct icmp_echo *icmpdata = (struct icmp_echo *)(buffer + (sizeof(struct ipv4_header)) + sizeof(struct icmp_header));
    icmpdata->identifier = htons(0x1234);
    icmpdata->sequence = htons(seq);
    return;
}

void construct_icmp_payload(uint8_t *buffer, const char *data, int len)
{
    uint8_t *payload_ptr = buffer + sizeof(struct ipv4_header) + sizeof(struct icmp_header) + sizeof(struct icmp_echo);
    memcpy(payload_ptr, data, len);
    return;
}

/**
 * @brief Constructs a complete packet into the provided buffer.
 *
 * Currently this function constructs only the IPv4 header (no ICMP header/payload
 * are written yet in this snippet).
 *
 * @param buffer           Pointer to the packet buffer to populate.
 * @param capacity         TODO: Total size of the packet buffer in bytes.
 * @param src              IPv4 source address string (e.g., "192.168.0.10").
 * @param dst              IPv4 destination address string (e.g., "192.168.0.1").
 * @param data             Packet payload string (currently unused in this snippet).
 * @param data_len         Packet paylaod string length.
 * @param seq              Sequence number (currently unused in this snippet).
 * @return                 Size of the packet
 */
size_t construct_packet(uint8_t *buffer, size_t capacity, const char *src, const char *dst, const char *data, int data_len, int seq)
{
    size_t required_size = sizeof(struct ipv4_header) + sizeof(struct icmp_header) + sizeof(struct icmp_echo) + data_len;
    if (required_size > capacity)
    {
        fprintf(stderr, "Error: Required size (%zu) exceeds buffer capacity (%zu)\n", required_size, capacity);
        return 0;
    }
    construct_ipv4_header(buffer, src, dst, data_len);
    construct_icmp_data(buffer, seq);
    construct_icmp_payload(buffer, data, data_len);
    construct_icmp_header(buffer, data_len);
    return sizeof(struct ipv4_header) + sizeof(struct icmp_header) + sizeof(struct icmp_echo) + data_len;
}

/**
 * create_raw_socket
 *
 * @brief Creates a raw socket
 *
 * @return A file descriptor (int) to use the socket
 */
int create_raw_socket()
{
    int raw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (raw_socket < 0)
    {
        fprintf(stderr, "Error: Raw socket creation failed. Are you running as root?\n");
        return -1;
    }
    int hdr_incl = 1;
    if (setsockopt(raw_socket, IPPROTO_IP, IP_HDRINCL, &hdr_incl, sizeof(hdr_incl)) < 0)
    {
        fprintf(stderr, ("Error: Failed to set IP_HDRINCL.\n"));
        return -1;
    }
    return raw_socket;
}

/**
 * send_packet
 *
 * @brief Takes a raw socket and an IPv4 packet buffer, and sends it out on the line
 *
 * @param socket The file descriptor (int) of the socket to use
 * @param buffer The packet buffer containing the headers and payload
 * @return The number of bytes (ssize_t) sent
 */
ssize_t send_packet(int socket, uint8_t *buffer)
{
    struct sockaddr_in dest_info = {0};
    dest_info.sin_family = AF_INET;
    dest_info.sin_port = htons(0);

    struct ipv4_header *iphdr = (struct ipv4_header *)buffer;
    dest_info.sin_addr.s_addr = iphdr->dst_addr;

    ssize_t bytes_sent = sendto(socket, buffer, ntohs(iphdr->total_length), 0, (struct sockaddr *)&dest_info, sizeof(dest_info));

    if (bytes_sent < 0)
    {
        fprintf(stderr, "Error: Failed to send packet.\n");
        close(socket);
        return -1;
    }

    return bytes_sent;
}

/***
 * @brief Generates (constructs) @p n packets.
 *
 * In this snippet, packets are constructed into a local buffer in a loop.
 * Sending/receiving is not implemented here.
 *
 * @param src       IPv4 source address string.
 * @param dst       IPv4 destination address string.
 * @param data      Packet payload string.
 * @param data_len  Packet payload string length.
 * @param n         Number of packets to generate.
 * @return          void
 */
void execute(const char *src, const char *dst, const char *data, int data_len, int n)
{
    uint8_t packet_buffer[PACKET_MAX_SIZE];
    int sock = create_raw_socket();
    if (sock < 0)
    {
        fprintf(stderr, "Error: Socket creation failed.\n");
        return;
    }

    for (int i = 0; i < n; ++i)
    {
        memset(packet_buffer, 0, sizeof(packet_buffer)); // TODO: Don't need to clear the entire buffer
        size_t packet_len = construct_packet(packet_buffer, sizeof(packet_buffer), src, dst, data, data_len, i);

        if (packet_len == 0)
        {
            fprintf(stderr, "Error: Packet could not be constructed\n");
            return;
        }

        // Debug Packet
        printf("\nRaw IPv4 + ICMP Datagram (Wire Format):\n");
        for (size_t i = 0; i < packet_len; ++i)
        {
            printf("%02X ", packet_buffer[i]);
            if ((i + 1) % 4 == 0)
                printf("\n");
        }
        printf("\n");

        ssize_t bytes_sent = send_packet(sock, packet_buffer);

        if (bytes_sent < 0)
        {
            fprintf(stderr, "Error: Failed to send packet.\n");
            return;
        }

        printf("Bytes sent in packet number %i: %li\n", i, bytes_sent);
    }

    close(sock);
    return;
}

/**
 * @brief Program entry point.
 *
 * Prints configuration info and invokes @ref execute to generate packets.
 *
 * @return Exit status code (0 on success).
 */
int main(void)
{
    printf("** ICMP Echo Sender **\n");

    char *src_addr = "192.168.50.128";
    // char *src_addr = "127.0.0.1";
    printf("Source Address: %s\n", src_addr);

    char *dst_addr = "192.168.50.230";
    // char *dst_addr = "127.0.0.1";
    printf("Destination Address: %s\n", dst_addr);

    char *payload = "HELLO";
    printf("Payload: %s\n", payload);

    int payload_len = 5; // TODO: dynamic payload sizing
    printf("Payload Length: %i\n", payload_len);

    // TODO: When implementing dynamic payload sizes from user input, it must
    //       be verified that the payload requested fits in the buffer.
    //       The below conditional will always result in false at the moment,
    //       but has been set as a reminder for future versions.
    if (payload_len > PAYLOAD_MAX_SIZE)
    {
        fprintf(stderr, "Payload exceeds maximum size of: %i\n", PAYLOAD_MAX_SIZE);
        return -1;
    }

    int num_to_send = 3;
    printf("Number of packets: %i\n", num_to_send);

    execute(src_addr, dst_addr, payload, payload_len, num_to_send);

    return 0;
}
  • Output:
bash
sudo ./ip_stack_v2
** ICMP Echo Sender **
Source Address: 192.168.50.128
Destination Address: 192.168.50.230
Payload: HELLO
Payload Length: 5
Number of packets: 3

Raw IPv4 + ICMP Datagram (Wire Format):
45 00 00 21
BE EF 00 00
40 01 D5 35
C0 A8 32 80
C0 A8 32 E6
08 00 02 3A
12 34 00 00
48 45 4C 4C
4F
Bytes sent in packet number 0: 33

Raw IPv4 + ICMP Datagram (Wire Format):
45 00 00 21
BE EF 00 00
40 01 D5 35
C0 A8 32 80
C0 A8 32 E6
08 00 02 39
12 34 00 01
48 45 4C 4C
4F
Bytes sent in packet number 1: 33

Raw IPv4 + ICMP Datagram (Wire Format):
45 00 00 21
BE EF 00 00
40 01 D5 35
C0 A8 32 80
C0 A8 32 E6
08 00 02 38
12 34 00 02
48 45 4C 4C
4F
Bytes sent in packet number 2: 33
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
sudo ./ip_stack_v2
** ICMP Echo Sender **
Source Address: 192.168.50.128
Destination Address: 192.168.50.230
Payload: HELLO
Payload Length: 5
Number of packets: 3

Raw IPv4 + ICMP Datagram (Wire Format):
45 00 00 21
BE EF 00 00
40 01 D5 35
C0 A8 32 80
C0 A8 32 E6
08 00 02 3A
12 34 00 00
48 45 4C 4C
4F
Bytes sent in packet number 0: 33

Raw IPv4 + ICMP Datagram (Wire Format):
45 00 00 21
BE EF 00 00
40 01 D5 35
C0 A8 32 80
C0 A8 32 E6
08 00 02 39
12 34 00 01
48 45 4C 4C
4F
Bytes sent in packet number 1: 33

Raw IPv4 + ICMP Datagram (Wire Format):
45 00 00 21
BE EF 00 00
40 01 D5 35
C0 A8 32 80
C0 A8 32 E6
08 00 02 38
12 34 00 02
48 45 4C 4C
4F
Bytes sent in packet number 2: 33
  • tcpdump output:
bash
04:32:17.476585 IP (tos 0x0, ttl 64, id 48879, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.128 > 192.168.50.230: ICMP echo request, id 4660, seq 0, length 13
        0x0000:  4500 0021 beef 0000 4001 d535 c0a8 3280  E..!....@..5..2.
        0x0010:  c0a8 32e6 0800 023a 1234 0000 4845 4c4c  ..2....:.4..HELL
        0x0020:  4f                                       O
04:32:17.476606 IP (tos 0x0, ttl 64, id 48879, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.128 > 192.168.50.230: ICMP echo request, id 4660, seq 1, length 13
        0x0000:  4500 0021 beef 0000 4001 d535 c0a8 3280  E..!....@..5..2.
        0x0010:  c0a8 32e6 0800 0239 1234 0001 4845 4c4c  ..2....9.4..HELL
        0x0020:  4f                                       O
04:32:17.476608 IP (tos 0x0, ttl 64, id 48879, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.128 > 192.168.50.230: ICMP echo request, id 4660, seq 2, length 13
        0x0000:  4500 0021 beef 0000 4001 d535 c0a8 3280  E..!....@..5..2.
        0x0010:  c0a8 32e6 0800 0238 1234 0002 4845 4c4c  ..2....8.4..HELL
        0x0020:  4f                                       O
04:32:17.476860 IP (tos 0x0, ttl 64, id 35955, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.230 > 192.168.50.128: ICMP echo reply, id 4660, seq 0, length 13
        0x0000:  4500 0021 8c73 0000 4001 07b2 c0a8 32e6  E..!.s..@.....2.
        0x0010:  c0a8 3280 0000 0a3a 1234 0000 4845 4c4c  ..2....:.4..HELL
        0x0020:  4f                                       O
04:32:17.476861 IP (tos 0x0, ttl 64, id 35956, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.230 > 192.168.50.128: ICMP echo reply, id 4660, seq 1, length 13
        0x0000:  4500 0021 8c74 0000 4001 07b1 c0a8 32e6  E..!.t..@.....2.
        0x0010:  c0a8 3280 0000 0a39 1234 0001 4845 4c4c  ..2....9.4..HELL
        0x0020:  4f                                       O
04:32:17.476861 IP (tos 0x0, ttl 64, id 35957, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.230 > 192.168.50.128: ICMP echo reply, id 4660, seq 2, length 13
        0x0000:  4500 0021 8c75 0000 4001 07b0 c0a8 32e6  E..!.u..@.....2.
        0x0010:  c0a8 3280 0000 0a38 1234 0002 4845 4c4c  ..2....8.4..HELL
        0x0020:  4f
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
04:32:17.476585 IP (tos 0x0, ttl 64, id 48879, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.128 > 192.168.50.230: ICMP echo request, id 4660, seq 0, length 13
        0x0000:  4500 0021 beef 0000 4001 d535 c0a8 3280  E..!....@..5..2.
        0x0010:  c0a8 32e6 0800 023a 1234 0000 4845 4c4c  ..2....:.4..HELL
        0x0020:  4f                                       O
04:32:17.476606 IP (tos 0x0, ttl 64, id 48879, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.128 > 192.168.50.230: ICMP echo request, id 4660, seq 1, length 13
        0x0000:  4500 0021 beef 0000 4001 d535 c0a8 3280  E..!....@..5..2.
        0x0010:  c0a8 32e6 0800 0239 1234 0001 4845 4c4c  ..2....9.4..HELL
        0x0020:  4f                                       O
04:32:17.476608 IP (tos 0x0, ttl 64, id 48879, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.128 > 192.168.50.230: ICMP echo request, id 4660, seq 2, length 13
        0x0000:  4500 0021 beef 0000 4001 d535 c0a8 3280  E..!....@..5..2.
        0x0010:  c0a8 32e6 0800 0238 1234 0002 4845 4c4c  ..2....8.4..HELL
        0x0020:  4f                                       O
04:32:17.476860 IP (tos 0x0, ttl 64, id 35955, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.230 > 192.168.50.128: ICMP echo reply, id 4660, seq 0, length 13
        0x0000:  4500 0021 8c73 0000 4001 07b2 c0a8 32e6  E..!.s..@.....2.
        0x0010:  c0a8 3280 0000 0a3a 1234 0000 4845 4c4c  ..2....:.4..HELL
        0x0020:  4f                                       O
04:32:17.476861 IP (tos 0x0, ttl 64, id 35956, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.230 > 192.168.50.128: ICMP echo reply, id 4660, seq 1, length 13
        0x0000:  4500 0021 8c74 0000 4001 07b1 c0a8 32e6  E..!.t..@.....2.
        0x0010:  c0a8 3280 0000 0a39 1234 0001 4845 4c4c  ..2....9.4..HELL
        0x0020:  4f                                       O
04:32:17.476861 IP (tos 0x0, ttl 64, id 35957, offset 0, flags [none], proto ICMP (1), length 33)
    192.168.50.230 > 192.168.50.128: ICMP echo reply, id 4660, seq 2, length 13
        0x0000:  4500 0021 8c75 0000 4001 07b0 c0a8 32e6  E..!.u..@.....2.
        0x0010:  c0a8 3280 0000 0a38 1234 0002 4845 4c4c  ..2....8.4..HELL
        0x0020:  4f

Related content