<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Gapple</title>
        <link>https://paragraph.com/@gapple</link>
        <description>Learning Smart Contract Security 
@42born2code Security Track | Full-Stack Dev
Building in Web3 🚀</description>
        <lastBuildDate>Wed, 27 May 2026 04:59:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Gapple</title>
            <url>https://storage.googleapis.com/papyrus_images/98267e0d4ff77a61bd15c9b367628c7638f263ae9e8781fd8225788f5f4ec365.jpg</url>
            <link>https://paragraph.com/@gapple</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[From Zero to Ping: How I Rebuilt the Classic Network Tool in C]]></title>
            <link>https://paragraph.com/@gapple/from-zero-to-ping-how-i-rebuilt-the-classic-network-tool-in-c</link>
            <guid>lPnH7wK5Q1lZoVEFs1KB</guid>
            <pubDate>Sat, 26 Jul 2025 13:49:12 GMT</pubDate>
            <description><![CDATA[A deep dive into network programming, ICMP packets, and the challenges of recreating a fundamental Unix utility from scratch.The ChallengeWhen I first saw the ft_ping project specification, I thought: "How hard could it be? It&apos;s just sending packets and measuring response time." Spoiler alert: I was very wrong. Recreating the ping command meant diving deep into:Raw socket programmingICMP protocol implementationNetwork packet parsingSignal handling and timeoutsCross-platform networking qu...]]></description>
            <content:encoded><![CDATA[<p><em>A deep dive into network programming, ICMP packets, and the challenges of recreating a fundamental Unix utility from scratch.</em></p><hr><h2 id="h-the-challenge" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Challenge</h2><p>When I first saw the <code>ft_ping</code> project specification, I thought: &quot;How hard could it be? It&apos;s just sending packets and measuring response time.&quot;</p><p><strong>Spoiler alert</strong>: I was very wrong.</p><p>Recreating the <code>ping</code> command meant diving deep into:</p><ul><li><p>Raw socket programming</p></li><li><p>ICMP protocol implementation</p></li><li><p>Network packet parsing</p></li><li><p>Signal handling and timeouts</p></li><li><p>Cross-platform networking quirks</p></li></ul><h2 id="h-what-is-ping-really" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is Ping, Really?</h2><p>Before jumping into code, I had to understand what <code>ping</code> actually does under the hood:</p><ol><li><p><strong>Creates a raw ICMP socket</strong> (requires root privileges)</p></li><li><p><strong>Builds ICMP Echo Request packets</strong> with sequence numbers and timestamps</p></li><li><p><strong>Sends packets</strong> to the target host at regular intervals</p></li><li><p><strong>Listens for responses</strong> (Echo Reply, Destination Unreachable, etc.)</p></li><li><p><strong>Calculates round-trip time</strong> and maintains statistics</p></li><li><p><strong>Handles edge cases</strong> like timeouts, localhost, and packet loss</p></li></ol><p>Sounds simple? Let me show you why it&apos;s not.</p><h2 id="h-the-technical-deep-dive" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Technical Deep Dive</h2><h3 id="h-challenge-1-raw-sockets-and-packet-construction" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Challenge 1: Raw Sockets and Packet Construction</h3><pre data-type="codeBlock" text="// Creating a raw ICMP socket (Linux/Unix only)
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd &lt; 0) {
    perror(&quot;socket&quot;); // Needs root privileges!
    exit(1);
}
"><code><span class="hljs-comment">// Creating a raw ICMP socket (Linux/Unix only)</span>
int sockfd = <span class="hljs-built_in">socket</span>(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd &#x3C; <span class="hljs-number">0</span>) {
    <span class="hljs-built_in">perror</span>("socket"); <span class="hljs-comment">// Needs root privileges!</span>
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">1</span>);
}
</code></pre><p>Raw sockets require root access because they bypass the OS network stack. This means I had to manually construct every part of the ICMP packet:</p><pre data-type="codeBlock" text="struct icmphdr *icmp = (struct icmphdr *)buffer;
icmp-&gt;type = ICMP_ECHO;           // Echo Request
icmp-&gt;code = 0;                   // Always 0 for ping
icmp-&gt;un.echo.id = getpid();      // Process ID
icmp-&gt;un.echo.sequence = seq++;   // Sequence number
icmp-&gt;checksum = calculate_checksum(icmp, packet_size);
"><code>struct icmphdr *<span class="hljs-attr">icmp</span> = (struct icmphdr *)buffer<span class="hljs-comment">;</span>
icmp-><span class="hljs-attr">type</span> = ICMP_ECHO<span class="hljs-comment">;           // Echo Request</span>
icmp-><span class="hljs-attr">code</span> = <span class="hljs-number">0</span><span class="hljs-comment">;                   // Always 0 for ping</span>
icmp-><span class="hljs-attr">un.echo.id</span> = getpid()<span class="hljs-comment">;      // Process ID</span>
icmp-><span class="hljs-attr">un.echo.sequence</span> = seq++<span class="hljs-comment">;   // Sequence number</span>
icmp-><span class="hljs-attr">checksum</span> = calculate_checksum(icmp, packet_size)<span class="hljs-comment">;</span>
</code></pre><h3 id="h-challenge-2-the-checksum-algorithm" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Challenge 2: The Checksum Algorithm</h3><p>ICMP packets require a valid checksum or they get dropped. The algorithm is deceptively simple but tricky to implement correctly:</p><pre data-type="codeBlock" text="uint16_t checksum(void *data, int len) {
    uint32_t sum = 0;
    uint16_t *ptr = data;
    
    // Sum all 16-bit words
    while (len &gt; 1) {
        sum += *ptr++;
        len -= 2;
    }
    
    // Handle odd byte
    if (len == 1)
        sum += *(uint8_t *)ptr;
    
    // Fold 32-bit sum to 16 bits
    sum = (sum &gt;&gt; 16) + (sum &amp; 0xFFFF);
    sum += (sum &gt;&gt; 16);
    
    return ~sum;  // One&apos;s complement
}
"><code>uint16_t checksum(void <span class="hljs-operator">*</span>data, <span class="hljs-keyword">int</span> len) {
    uint32_t sum <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
    uint16_t <span class="hljs-operator">*</span>ptr <span class="hljs-operator">=</span> data;
    
    <span class="hljs-comment">// Sum all 16-bit words</span>
    <span class="hljs-keyword">while</span> (len <span class="hljs-operator">></span> <span class="hljs-number">1</span>) {
        sum <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-operator">*</span>ptr<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
        len <span class="hljs-operator">-</span><span class="hljs-operator">=</span> <span class="hljs-number">2</span>;
    }
    
    <span class="hljs-comment">// Handle odd byte</span>
    <span class="hljs-keyword">if</span> (len <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>)
        sum <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-operator">*</span>(uint8_t <span class="hljs-operator">*</span>)ptr;
    
    <span class="hljs-comment">// Fold 32-bit sum to 16 bits</span>
    sum <span class="hljs-operator">=</span> (sum <span class="hljs-operator">></span><span class="hljs-operator">></span> <span class="hljs-number">16</span>) <span class="hljs-operator">+</span> (sum <span class="hljs-operator">&#x26;</span> <span class="hljs-number">0xFFFF</span>);
    sum <span class="hljs-operator">+</span><span class="hljs-operator">=</span> (sum <span class="hljs-operator">></span><span class="hljs-operator">></span> <span class="hljs-number">16</span>);
    
    <span class="hljs-keyword">return</span> <span class="hljs-operator">~</span>sum;  <span class="hljs-comment">// One's complement</span>
}
</code></pre><p>One wrong bit and your packets disappear into the void.</p><h3 id="h-challenge-3-parsing-raw-network-data" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Challenge 3: Parsing Raw Network Data</h3><p>When you receive data from a raw socket, you get the <strong>entire IP packet</strong>, not just the ICMP payload:</p><pre data-type="codeBlock" text="// Raw packet = IP Header + ICMP Header + Data
struct ip *ip_header = (struct ip *)buffer;
int ip_len = ip_header-&gt;ip_hl * 4;  // Header length in bytes

// Skip to ICMP part
struct icmphdr *icmp = (struct icmphdr *)(buffer + ip_len);
"><code>// Raw <span class="hljs-attr">packet</span> = IP Header + ICMP Header + Data
struct ip *<span class="hljs-attr">ip_header</span> = (struct ip *)buffer<span class="hljs-comment">;</span>
int <span class="hljs-attr">ip_len</span> = ip_header->ip_hl * <span class="hljs-number">4</span><span class="hljs-comment">;  // Header length in bytes</span>

// Skip to ICMP part
struct icmphdr *<span class="hljs-attr">icmp</span> = (struct icmphdr *)(buffer + ip_len)<span class="hljs-comment">;</span>
</code></pre><p>The IP header length is variable (20-60 bytes), so you have to parse it correctly to find your ICMP data.</p><h3 id="h-challenge-4-the-localhost-problem" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Challenge 4: The Localhost Problem</h3><p>Here&apos;s something that stumped me for hours: when pinging <code>localhost</code>, you receive <strong>your own Echo Request packets</strong> in addition to the Echo Replies.</p><p>Why? Because localhost routes through the loopback interface, and raw sockets see <em>everything</em>.</p><p>The solution:</p><pre data-type="codeBlock" text="// Filter out our own Echo Requests
if (icmp-&gt;type == ICMP_ECHO &amp;&amp; ntohs(icmp-&gt;un.echo.id) == our_pid) {
    continue; // Ignore and wait for the next packet
}
"><code><span class="hljs-comment">// Filter out our own Echo Requests</span>
<span class="hljs-keyword">if</span> (icmp<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-keyword">type</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> ICMP_ECHO <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> ntohs(icmp<span class="hljs-operator">-</span><span class="hljs-operator">></span>un.echo.id) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> our_pid) {
    <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// Ignore and wait for the next packet</span>
}
</code></pre><h3 id="h-challenge-5-timeout-management" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Challenge 5: Timeout Management</h3><p>Implementing proper timeouts was trickier than expected. The naive approach using <code>setsockopt(SO_RCVTIMEO)</code> works, but error handling becomes messy:</p><pre data-type="codeBlock" text="// Set socket timeout
struct timeval timeout = {timeout_sec, 0};
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &amp;timeout, sizeof(timeout));

ssize_t bytes = recvmsg(sockfd, &amp;msg, 0);
if (bytes &lt; 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
        // Timeout - this is expected, not an error
        return -1;
    }
    perror(&quot;recvmsg&quot;); // Real error
    return -1;
}
"><code><span class="hljs-comment">// Set socket timeout</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">timeval</span> <span class="hljs-title">timeout</span> = {timeout_sec, <span class="hljs-number">0</span>};
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, <span class="hljs-operator">&#x26;</span>timeout, sizeof(timeout));

ssize_t <span class="hljs-keyword">bytes</span> <span class="hljs-operator">=</span> recvmsg(sockfd, <span class="hljs-operator">&#x26;</span><span class="hljs-built_in">msg</span>, <span class="hljs-number">0</span>);
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">bytes</span> <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">if</span> (errno <span class="hljs-operator">=</span><span class="hljs-operator">=</span> EAGAIN <span class="hljs-operator">|</span><span class="hljs-operator">|</span> errno <span class="hljs-operator">=</span><span class="hljs-operator">=</span> EWOULDBLOCK) {
        <span class="hljs-comment">// Timeout - this is expected, not an error</span>
        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
    perror(<span class="hljs-string">"recvmsg"</span>); <span class="hljs-comment">// Real error</span>
    <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
}
</code></pre><h2 id="h-the-statistics-challenge" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Statistics Challenge</h2><p>Getting the statistics right required careful RTT (Round-Trip Time) tracking:</p><pre data-type="codeBlock" text="// Calculate RTT in milliseconds
double rtt = (recv_time.tv_sec - send_time.tv_sec) * 1000.0 + 
             (recv_time.tv_usec - send_time.tv_usec) / 1000.0;

// Track min/max/average
if (rtt_min &lt; 0 || rtt &lt; rtt_min) rtt_min = rtt;
if (rtt &gt; rtt_max) rtt_max = rtt;
rtt_sum += rtt;

// Standard deviation calculation
rtt_sum_squares += rtt * rtt;
double variance = (rtt_sum_squares / count) - (average * average);
double mdev = sqrt(variance);
"><code><span class="hljs-comment">// Calculate RTT in milliseconds</span>
double rtt <span class="hljs-operator">=</span> (recv_time.tv_sec <span class="hljs-operator">-</span> send_time.tv_sec) <span class="hljs-operator">*</span> <span class="hljs-number">1000.0</span> <span class="hljs-operator">+</span> 
             (recv_time.tv_usec <span class="hljs-operator">-</span> send_time.tv_usec) <span class="hljs-operator">/</span> <span class="hljs-number">1000.0</span>;

<span class="hljs-comment">// Track min/max/average</span>
<span class="hljs-keyword">if</span> (rtt_min <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> rtt <span class="hljs-operator">&#x3C;</span> rtt_min) rtt_min <span class="hljs-operator">=</span> rtt;
<span class="hljs-keyword">if</span> (rtt <span class="hljs-operator">></span> rtt_max) rtt_max <span class="hljs-operator">=</span> rtt;
rtt_sum <span class="hljs-operator">+</span><span class="hljs-operator">=</span> rtt;

<span class="hljs-comment">// Standard deviation calculation</span>
rtt_sum_squares <span class="hljs-operator">+</span><span class="hljs-operator">=</span> rtt <span class="hljs-operator">*</span> rtt;
double variance <span class="hljs-operator">=</span> (rtt_sum_squares <span class="hljs-operator">/</span> count) <span class="hljs-operator">-</span> (average <span class="hljs-operator">*</span> average);
double mdev <span class="hljs-operator">=</span> sqrt(variance);
</code></pre><h2 id="h-what-i-learned" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What I Learned</h2><ol><li><p><strong>Network programming is humbling</strong> - So many edge cases you never think about</p></li><li><p><strong>Raw sockets are powerful but dangerous</strong> - One wrong packet can crash things</p></li><li><p><strong>Testing is everything</strong> - Localhost, remote hosts, timeouts, errors - each behaves differently</p></li><li><p><strong>The devil is in the details</strong> - Byte order, padding, alignment all matter</p></li><li><p><strong>Root privileges exist for a reason</strong> - Raw socket access is serious business</p></li></ol><h2 id="h-the-result" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Result</h2><p>After debugging timeout issues, fixing localhost packet filtering, and ensuring byte-perfect compatibility with the original <code>ping</code>, my <code>ft_ping</code> now:</p><ul><li><p>✅ Handles IPv4 addresses and hostnames</p></li><li><p>✅ Implements all major ping options (-c, -i, -W, -s, -v, -n)</p></li><li><p>✅ Provides accurate RTT measurements (±30ms tolerance)</p></li><li><p>✅ Matches original ping output format</p></li><li><p>✅ Gracefully handles errors and edge cases</p></li><li><p>✅ Works with localhost, remote hosts, and unreachable destinations</p></li></ul><h2 id="h-key-takeaways-for-fellow-developers" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Key Takeaways for Fellow Developers</h2><ol><li><p><strong>Start simple</strong> - Get basic packet sending/receiving working first</p></li><li><p><strong>Test extensively</strong> - Network code has infinite edge cases</p></li><li><p><strong>Read the RFCs</strong> - ICMP specification is your friend</p></li><li><p><strong>Use packet capture tools</strong> - Wireshark saved me countless times</p></li><li><p><strong>Handle privileges carefully</strong> - Raw sockets are not a toy</p></li></ol><h2 id="h-the-code" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Code</h2><p>The complete implementation is available on my GitHub, featuring:</p><ul><li><p>Clean, modular C code</p></li><li><p>Comprehensive error handling</p></li><li><p>Detailed comments explaining the tricky parts</p></li><li><p>Full test suite covering edge cases</p></li></ul><p>Building a fundamental network tool from scratch taught me more about networking in a few weeks than years of high-level development. Sometimes the best way to understand something is to rebuild it yourself.</p><hr><p><em>Have you ever rebuilt a classic Unix tool? What did you learn? Share your experiences in the comments!</em></p><p><strong>Tags</strong>: #NetworkProgramming #C #Unix #SystemsProgramming #ICMP #Sockets</p>]]></content:encoded>
            <author>gapple@newsletter.paragraph.com (Gapple)</author>
        </item>
    </channel>
</rss>