<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bhargav </title>
    <description>The latest articles on DEV Community by Bhargav  (@bharqav).</description>
    <link>https://dev.to/bharqav</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4090971%2F357ff43e-9933-4d14-8ccd-f350ea1ef50d.png</url>
      <title>DEV Community: Bhargav </title>
      <link>https://dev.to/bharqav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bharqav"/>
    <language>en</language>
    <item>
      <title>I Built a Mesh Network That Works When the Internet Doesn't</title>
      <dc:creator>Bhargav </dc:creator>
      <pubDate>Sun, 23 Aug 2026 15:49:10 +0000</pubDate>
      <link>https://dev.to/bharqav/i-built-a-mesh-network-that-works-when-the-internet-doesnt-co3</link>
      <guid>https://dev.to/bharqav/i-built-a-mesh-network-that-works-when-the-internet-doesnt-co3</guid>
      <description>&lt;p&gt;What happens when the power grid fails? When cellular towers collapse? When the internet simply disappears?&lt;/p&gt;

&lt;p&gt;You get silence. Isolation. No way to call for help.&lt;/p&gt;

&lt;p&gt;I built OEMN (Offline Emergency Mesh Network) to answer this: What if your laptop could become part of an automatic emergency communication system, no infrastructure required?&lt;/p&gt;

&lt;p&gt;THE PROBLEM: CONNECTIVITY DOESN'T SCALE&lt;/p&gt;

&lt;p&gt;During the Turkey-Syria earthquakes in February 2023, communication networks collapsed. The Indian Ocean tsunami. Wildfire zones. Disaster areas where people are trapped but can't reach anyone.&lt;/p&gt;

&lt;p&gt;Why? Because infrastructure is fragile.&lt;/p&gt;

&lt;p&gt;Cell networks are centralized. One point of failure cascades. The internet requires thousands of miles of cable and dozens of intermediaries. As long as even one link breaks, you're isolated.&lt;/p&gt;

&lt;p&gt;But what if you didn't need infrastructure at all?&lt;/p&gt;

&lt;p&gt;What if laptops, phones, and devices could talk to each other directly, forming their own network on-the-fly? Even if cellular fails. Even if the internet is gone.&lt;/p&gt;

&lt;p&gt;That's what OEMN does.&lt;/p&gt;

&lt;p&gt;WHY IT'S ACTUALLY HARD&lt;/p&gt;

&lt;p&gt;Building a mesh network looks simple on a whiteboard: nodes talk to neighbors, messages hop across multiple nodes, done.&lt;/p&gt;

&lt;p&gt;The reality is three layers of hard problems:&lt;/p&gt;

&lt;p&gt;PROBLEM 1: HOW DO NODES FIND EACH OTHER?&lt;/p&gt;

&lt;p&gt;Without DNS, DHCP, or a central server, nodes are just devices on a network. They don't know who exists, where they are, or how to reach them. You need peer discovery: a way for node A to say "I exist" and have node B hear it.&lt;/p&gt;

&lt;p&gt;PROBLEM 2: HOW DO YOU ROUTE MESSAGES EFFICIENTLY?&lt;/p&gt;

&lt;p&gt;In a mesh of 50 nodes, there could be hundreds of paths from A to Z. Which one is fastest? Which one still works after node failures? You need topology awareness: every node must build a map of the entire network and compute optimal routes.&lt;/p&gt;

&lt;p&gt;This is the classic shortest-path problem. But it needs to recompute in real-time as nodes join and leave.&lt;/p&gt;

&lt;p&gt;PROBLEM 3: HOW DO YOU GUARANTEE DELIVERY?&lt;/p&gt;

&lt;p&gt;UDP (the transport layer) doesn't guarantee messages arrive. They can be lost, duplicated, reordered, or corrupted. You need reliability: ACKs, retries, deduplication. But you can't do what TCP does (overhead is too high). You need something in between.&lt;/p&gt;

&lt;p&gt;Add encryption. Add replay protection (so attackers can't resend old messages). Add thread-safe concurrency so your listener, router, and sender don't corrupt each other's state.&lt;/p&gt;

&lt;p&gt;Now you have a mesh network.&lt;/p&gt;

&lt;p&gt;THE ARCHITECTURE: FOUR THREADS, ONE SHARED QUEUE&lt;/p&gt;

&lt;p&gt;OEMN's elegance comes from its simplicity.&lt;/p&gt;

&lt;p&gt;FOUR THREADS, each with a single responsibility:&lt;/p&gt;

&lt;p&gt;Listener Thread&lt;br&gt;
    receives packets from UDP, validates encryption, updates topology&lt;/p&gt;

&lt;p&gt;Router Thread&lt;br&gt;
    runs Dijkstra, builds shortest-path routing table&lt;/p&gt;

&lt;p&gt;Send Queue (mutex-protected, condition variable signaled)&lt;/p&gt;

&lt;p&gt;Sender Thread&lt;br&gt;
    transmits packets, handles ACKs and retries&lt;/p&gt;

&lt;p&gt;The listener doesn't transmit. The sender doesn't route. The router doesn't listen. Each thread owns its data. They communicate through one shared send queue.&lt;/p&gt;

&lt;p&gt;This is THREAD SAFETY BY DESIGN, not by accident.&lt;/p&gt;

&lt;p&gt;Here's what happens when a packet arrives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Listener validates AES-256-GCM encryption and checks replay protection&lt;/li&gt;
&lt;li&gt;Listener checks if this is a topology update (TOPO message)&lt;/li&gt;
&lt;li&gt;If topology changed, listener signals router to recompute&lt;/li&gt;
&lt;li&gt;Router runs Dijkstra algorithm, updates routing table&lt;/li&gt;
&lt;li&gt;If packet is for us, listener enqueues it to send an ACK&lt;/li&gt;
&lt;li&gt;Sender dequeues, transmits ACK, waits for confirmation&lt;/li&gt;
&lt;li&gt;If no ACK within timeout, sender retries with exponential backoff&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire system is coordinated by a single atomic shutdown flag. When you hit Ctrl+C, all threads gracefully drain their queues and exit.&lt;/p&gt;

&lt;p&gt;DIJKSTRA SHORTEST-PATH ROUTING&lt;/p&gt;

&lt;p&gt;Here's where topology matters.&lt;/p&gt;

&lt;p&gt;Every node runs Dijkstra's algorithm to compute the shortest path to every other node. A naive implementation is O(V²). That's too slow for real-time rerouting.&lt;/p&gt;

&lt;p&gt;OEMN uses a BINARY MIN-HEAP, reducing complexity to O((V + E) log V). For 50 nodes, this is fast enough to recompute whenever the network topology changes.&lt;/p&gt;

&lt;p&gt;Here's the actual implementation from router.c:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static void run_dijkstra_heap(const oemn_router_t *r, int src, uint32_t *dist_out, int *parent_out)
{
    int n = r-&amp;gt;n_verts;
    for (int i = 0; i &amp;lt; n; ++i) {
        dist_out[i] = OEMN_INF_DIST;
        parent_out[i] = -1;
    }

    heap_ent_t *heap = calloc(OEMN_HEAP_CAP, sizeof(*heap));
    int hs = 0;

    dist_out[src] = 0;
    heap_push(heap, &amp;amp;hs, (heap_ent_t){0u, src});

    while (hs &amp;gt; 0) {
        heap_ent_t x;
        if (heap_pop(heap, &amp;amp;hs, &amp;amp;x) != 0)
            break;
        int u = x.v;
        if (x.d != dist_out[u])
            continue;
        for (int k = 0; k &amp;lt; r-&amp;gt;deg[u]; ++k) {
            int v = r-&amp;gt;nbr[u][k];
            uint32_t ew = r-&amp;gt;w[u][v];
            if (ew == OEMN_INF_DIST)
                ew = OEMN_EDGE_DEFAULT_COST;
            uint32_t nd = dist_out[u] + ew;
            if (nd &amp;lt; dist_out[v]) {
                dist_out[v] = nd;
                parent_out[v] = u;
                heap_push(heap, &amp;amp;hs, (heap_ent_t){nd, v});
            }
        }
    }
    free(heap);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The routing table maps: destination → next hop → distance in hops&lt;/p&gt;

&lt;p&gt;When you send a message to node 5, it automatically routes through the neighbor closest to node 5. If that neighbor fails, the next topology update (every 2-4 seconds) recomputes and reroutes through an alternate path.&lt;/p&gt;

&lt;p&gt;This is AUTOMATIC FAILOVER. No manual intervention. No central coordinator.&lt;/p&gt;

&lt;p&gt;ENCRYPTION &amp;amp; REPLAY PROTECTION: DEFENDING AGAINST ATTACKS&lt;/p&gt;

&lt;p&gt;I use AES-256-GCM (via OpenSSL) or ChaCha20-Poly1305 (via libsodium) for authenticated encryption.&lt;/p&gt;

&lt;p&gt;Every message is encrypted with a per-source key, and every message has a sequence number embedded in its ID.&lt;/p&gt;

&lt;p&gt;But encryption alone isn't enough. I had to prevent replay attacks:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attacker sees: "Node A sends $100 to Node B"
Attacker captures the encrypted packet
Attacker replays the same encrypted packet
Node B doesn't know it's a duplicate
Attacker just transferred $100 twice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is trivial to execute on UDP because there's no session state.&lt;/p&gt;

&lt;p&gt;OEMN solves this with a PER-SOURCE SLIDING WINDOW:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For each source, track the last 64 received sequence numbers
If we see sequence N where N &amp;lt;= (current_seq - 64), reject as duplicate
If we see N where (current_seq - 64) &amp;lt; N &amp;lt; current_seq, reject as duplicate
Only accept N &amp;gt; current_seq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here's the actual code from replay.c:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int oemn_replay_check_and_update(oemn_replay_t *r, uint32_t src_id, const uint8_t msg_id[16])
{
    uint64_t seq = msg_seq_from_id(msg_id);

    pthread_mutex_lock(&amp;amp;r-&amp;gt;mu);
    int idx = -1;
    for (int i = 0; i &amp;lt; OEMN_MAX_PEERS; ++i) {
        if (r-&amp;gt;slots[i].used &amp;amp;&amp;amp; r-&amp;gt;slots[i].src_id == src_id) {
            idx = i;
            break;
        }
    }

    oemn_replay_slot_t *s = &amp;amp;r-&amp;gt;slots[idx];

    if (seq &amp;gt; s-&amp;gt;max_seq) {
        uint64_t gap = seq - s-&amp;gt;max_seq;
        if (gap &amp;gt;= 64)
            s-&amp;gt;window = 1ULL;
        else
            s-&amp;gt;window = (s-&amp;gt;window &amp;lt;&amp;lt; gap) | 1ULL;
        s-&amp;gt;max_seq = seq;
        pthread_mutex_unlock(&amp;amp;r-&amp;gt;mu);
        return 0;  // Accept: fresh sequence
    }

    uint64_t delta = s-&amp;gt;max_seq - seq;
    if (delta &amp;gt;= 64) {
        pthread_mutex_unlock(&amp;amp;r-&amp;gt;mu);
        return 1;  // Reject: too old
    }

    uint64_t mask = 1ULL &amp;lt;&amp;lt; delta;
    if (s-&amp;gt;window &amp;amp; mask) {
        pthread_mutex_unlock(&amp;amp;r-&amp;gt;mu);
        return 1;  // Reject: duplicate
    }

    s-&amp;gt;window |= mask;
    pthread_mutex_unlock(&amp;amp;r-&amp;gt;mu);
    return 0;  // Accept: fresh
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This costs 64 bits of memory per source. At 50 nodes, that's 400 bytes total. Negligible.&lt;/p&gt;

&lt;p&gt;The attacker can't replay old messages. They can only forge new ones (which requires the encryption key, which they don't have).&lt;/p&gt;

&lt;p&gt;BUFFER POOLING: FROM MALLOC THRASHING TO SLAB ALLOCATION&lt;/p&gt;

&lt;p&gt;One thing I discovered: malloc/free on every message is a performance killer.&lt;/p&gt;

&lt;p&gt;OEMN uses pre-allocated SLAB BUFFERS:&lt;/p&gt;

&lt;p&gt;From fwd_pool.h:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#define OEMN_FWD_POOL_SLOTS 48
#define OEMN_FWD_BUFSZ (OEMN_HDR_SIZE + OEMN_MAX_PAYLOAD)

typedef struct {
    pthread_mutex_t mu;
    uint8_t slab[OEMN_FWD_POOL_SLOTS][OEMN_FWD_BUFSZ];
    int free_stack[OEMN_FWD_POOL_SLOTS];
    int nfree;
} oemn_fwd_pool_t;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Instead of malloc-ing on every message, OEMN allocates one giant slab upfront. Each thread acquires a buffer from the pool, uses it, then returns it. No heap fragmentation. No allocation overhead.&lt;/p&gt;

&lt;p&gt;This is a simple optimization, but it matters more than you'd expect. Systems that malloc constantly spend more CPU managing memory than doing useful work.&lt;/p&gt;

&lt;p&gt;LESSONS LEARNED&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;THREAD SAFETY IS HARD BUT WORTH GETTING RIGHT&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I spent weeks hunting data races using ThreadSanitizer. The payoff: zero race conditions under stress testing. This is how production systems should be built. Most open-source projects skip this. Don't.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;TOPOLOGY CHANGES ARE YOUR PERFORMANCE BOTTLENECK&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dijkstra is O((V + E) log V). For 50 nodes, it's still fast. But if you rerun it on every message (amateur mistake), you're doing wasteful computation.&lt;/p&gt;

&lt;p&gt;OEMN only recomputes on topology changes. This is the right optimization target.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;UDP ISN'T UNRELIABLE, IT'S JUST HONEST&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TCP hides failures through retries and buffering. UDP shows them immediately.&lt;/p&gt;

&lt;p&gt;In a disaster scenario, seeing failures is better than silently dropping 10% of messages under congestion (which TCP does). You need visibility into what's happening.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;BUFFER POOLING WINS OVER MALLOC/FREE&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pre-allocated slabs eliminate allocation overhead. The threading model means each buffer acquisition is an O(1) stack pop. No freelist searching. No fragmentation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;WRITING THINGS IN C MAKES YOU UNDERSTAND HARDWARE&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Python and Rust abstract away memory. C forces you to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache line locality&lt;/li&gt;
&lt;li&gt;Malloc overhead&lt;/li&gt;
&lt;li&gt;Mutex contention&lt;/li&gt;
&lt;li&gt;Thread synchronization primitives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, you understand networking differently.&lt;/p&gt;

&lt;p&gt;WHAT'S NEXT&lt;/p&gt;

&lt;p&gt;The current roadmap has three priorities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;LATENCY-WEIGHTED ROUTING: Currently, OEMN finds the shortest path (fewest hops). But 10 hops of low-latency LAN is better than 3 hops of high-latency satellite. Adding EWMA-based RTT measurement per edge is next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DELTA TOPOLOGY UPDATES: Right now, nodes send full topology advertisements. With 100+ nodes, that's bandwidth-heavy. Delta updates (send only changes) could cut bandwidth by 10x.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FUZZING: I want to throw 10,000 random packet corruptions at the protocol and watch it survive. libFuzzer + AddressSanitizer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;WHY THIS MATTERS&lt;/p&gt;

&lt;p&gt;Most software engineers will never think about resilience. You build on Kubernetes, AWS, and 99.99% uptime guarantees.&lt;/p&gt;

&lt;p&gt;But when infrastructure fails, when the easy assumptions break, mesh networks become critical infrastructure.&lt;/p&gt;

&lt;p&gt;Understanding how to build one teaches you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to program without central authority (coordination, consensus)&lt;/li&gt;
&lt;li&gt;How to handle adversarial networks (encryption, replay protection)&lt;/li&gt;
&lt;li&gt;How to design concurrent systems that don't have race conditions&lt;/li&gt;
&lt;li&gt;How to measure and optimize for real-world constraints (packet loss, node failures)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OEMN isn't just a project. It's a masterclass in systems engineering.&lt;/p&gt;

&lt;p&gt;But more importantly: it works. No dependencies. No cloud. Just peers talking to peers.&lt;/p&gt;

&lt;p&gt;TRY IT&lt;/p&gt;

&lt;p&gt;OEMN is on GitHub: github.com/bharqav/oemn&lt;/p&gt;

&lt;p&gt;To run the demo locally:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make
./oemn --id 1 --port 7777
./oemn --id 2 --port 7778
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then connect them (in the CLI):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# on node 1: peer add 2 127.0.0.1 7778
# on node 2: peer add 1 127.0.0.1 7777
# on node 1: send 2 hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Watch the message travel. Watch the ACK come back. Watch the routing table update in real-time.&lt;/p&gt;

&lt;p&gt;The code is production-grade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;48-slot buffer pool&lt;/li&gt;
&lt;li&gt;Thread-safe by design&lt;/li&gt;
&lt;li&gt;Dijkstra shortest-path routing&lt;/li&gt;
&lt;li&gt;AES-256-GCM encryption&lt;/li&gt;
&lt;li&gt;Per-source replay protection&lt;/li&gt;
&lt;li&gt;Graceful shutdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read the docs in the repo. They're detailed. Run the benchmarks yourself. See what performance you get on your hardware.&lt;/p&gt;




&lt;p&gt;If you're curious about how systems really work, build your own. Build a mesh network. Build an autograd engine. Build a shell.&lt;/p&gt;

&lt;p&gt;Don't just use other people's abstractions. Understand the layer beneath.&lt;/p&gt;

&lt;p&gt;That's where the real learning happens.&lt;/p&gt;




&lt;p&gt;OEMN is open-source (MIT License). Contributions welcome: github.com/bharqav/oemn&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>iot</category>
      <category>networking</category>
    </item>
  </channel>
</rss>
