<?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: Douxx</title>
    <description>The latest articles on DEV Community by Douxx (@douxxtech).</description>
    <link>https://dev.to/douxxtech</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3758859%2Fad212daa-edd8-4191-b6ef-8959803c14c8.jpeg</url>
      <title>DEV Community: Douxx</title>
      <link>https://dev.to/douxxtech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/douxxtech"/>
    <language>en</language>
    <item>
      <title>Your Shell Is Just a Loop</title>
      <dc:creator>Douxx</dc:creator>
      <pubDate>Sun, 03 May 2026 15:26:27 +0000</pubDate>
      <link>https://dev.to/douxxtech/your-shell-is-just-a-loop-2h1m</link>
      <guid>https://dev.to/douxxtech/your-shell-is-just-a-loop-2h1m</guid>
      <description>&lt;p&gt;Every developer uses a &lt;a href="https://en.wikipedia.org/wiki/Shell_(computing)" rel="noopener noreferrer"&gt;shell&lt;/a&gt; daily. Most people assume it's some complex, arcane piece of software. It's not. At its core, a shell is just a loop that reads a command, runs it, and waits for the next one. That's it.&lt;br&gt;
So let's build one.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Prompt That Reads Commands
&lt;/h2&gt;

&lt;p&gt;Every shell starts the same way: show a prompt, wait for input, chop it into pieces. Let's build that first.&lt;/p&gt;

&lt;p&gt;What we want to do is read a string from &lt;code&gt;stdin&lt;/code&gt; (the terminal input), and then split it into multiple args.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_INPUT&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;^shell&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;fflush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// used to be sure that the stdout buffer is printed&lt;/span&gt;

        &lt;span class="n"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX_INPUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// capture stdin&lt;/span&gt;

        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// parse input&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MAX_ARGS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s, "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// free the allocated memory for args&lt;/span&gt;
        &lt;span class="n"&gt;fflush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have our basic command reader, right now it only prints back the parsed args.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^shell&amp;gt; yo
yo, 
^shell&amp;gt; hello world
hello, world, 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As for the &lt;code&gt;parse(input)&lt;/code&gt;, it's also quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;MAX_ARGS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// allocate the array on the heap so it stays alive after the function returns&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strtok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\t\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MAX_ARGS&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strtok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\t\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;strtok&lt;/code&gt; splits the string by spaces, tabs and newlines, and we store each chunk as a pointer in our args array. The final NULL is required for the next part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running The Commands
&lt;/h2&gt;

&lt;p&gt;Right now, we just print them, what we want to do is &lt;em&gt;run&lt;/em&gt; them. &lt;/p&gt;

&lt;p&gt;The first thing we'll do is edit the main loop: instead of printing the args, we'll call a new &lt;code&gt;execute(args)&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// parse input&lt;/span&gt;

&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will actually do the work, and here it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pid_t&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// pid = 0, we're in the child&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;resolved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_in_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;resolved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"^shell: command not found: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// launches the program&lt;/span&gt;
        &lt;span class="n"&gt;execve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// wait for the child to die&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fork"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// something went wrong&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the two important parts is the &lt;code&gt;fork()&lt;/code&gt; call. This one is directly a request to the &lt;a href="https://en.wikipedia.org/wiki/Linux_kernel" rel="noopener noreferrer"&gt;Linux Kernel&lt;/a&gt; telling it to duplicate the current process into a new one, with the exact same memory values, and current execution point.&lt;/p&gt;

&lt;p&gt;The created process is a &lt;em&gt;child&lt;/em&gt;, it inherits from the &lt;em&gt;parent&lt;/em&gt; (our shell), and becomes orphan if its parent dies.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fork()&lt;/code&gt; returns a process id (pid), if it is equal to 0, it means that we're the child. if it's a positive value, we're in the parent.&lt;/p&gt;

&lt;p&gt;If we're the parent, it's easy, another syscall: &lt;code&gt;wait(NULL)&lt;/code&gt;, and we're blocked until the child exits.&lt;/p&gt;

&lt;p&gt;On the other hand, we got a bit more work if we're the child.&lt;/p&gt;

&lt;p&gt;The first thing we need to do is find the program to execute, the &lt;a href="https://gist.github.com/douxxtech/77169672f96cd8bd04565b90b730b862#file-shell-c-L40" rel="noopener noreferrer"&gt;&lt;code&gt;find_in_path()&lt;/code&gt;&lt;/a&gt; function will do the job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It fetches the &lt;code&gt;PATH&lt;/code&gt; environment variable, formatted like this: &lt;code&gt;/first/path:/second/path:/etc/bin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It'll then look in each one of the colon-separated directories for an executable with the same name as &lt;code&gt;args[0]&lt;/code&gt; – the program name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once it found the program (if it did), it runs one last syscall: &lt;code&gt;execve&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This one is a bit special, since it completely replaces the current program, including its &lt;a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)" rel="noopener noreferrer"&gt;stack&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Heap_(data_structure)" rel="noopener noreferrer"&gt;heap&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Code_segment" rel="noopener noreferrer"&gt;code segments&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Data_segment" rel="noopener noreferrer"&gt;data segments&lt;/a&gt; with the new program ones.&lt;/p&gt;

&lt;p&gt;Once done, the child is no longer our shell process. Once it exits, so does the child process, and the shell can loop again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^shell&amp;gt; ls
blog.md  main  main.c  pbp  pbp.c

^shell&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Builtins, And Why We Can't Call cd
&lt;/h2&gt;

&lt;p&gt;Another important piece of what constitutes a shell is builtins – commands that aren't executed, but used to directly talk to the shell application itself.&lt;/p&gt;

&lt;p&gt;A good example to understand why we need them is the &lt;code&gt;cd&lt;/code&gt; (change directory) command. It allows the user to navigate through its file system easily.&lt;/p&gt;

&lt;p&gt;See, processes carry kernel-managed state beyond memory – like their current working directory (cwd).&lt;br&gt;
As you probably guessed it, it indicates which directory the program is currently in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We can see the cwd of programs using &lt;code&gt;ls -l /proc/&amp;lt;PID&amp;gt;/cwd&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F49351358" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F49351358" alt="cwd seeking in bash" width="962" height="263"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If &lt;code&gt;cd&lt;/code&gt; was called like any other program, here is what would happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The current process gets duplicated, the child and parent now have separate states&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;cd&lt;/code&gt; process would be called, and change its own directory to the destination&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;cd&lt;/code&gt; process would exit&lt;/li&gt;
&lt;li&gt;We come back to our shell, but without its directory changed – the main program and its child don't share the same attributes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is what builtins are for: executing actions on the current program.&lt;/p&gt;

&lt;p&gt;Here's how I handle builtins in the main loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// parse input&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"exit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"cd"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"myshell: cd: missing argument&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// change dir syscall failed&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"myshell: cd"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cd&lt;/code&gt; builtin uses the &lt;code&gt;chdir&lt;/code&gt; syscall to change its own cwd, and the &lt;code&gt;exit&lt;/code&gt; one exits the loop, and therefore the program.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^shell&amp;gt; pwd
/home/local/c/shell

^shell&amp;gt; cd ..

^shell&amp;gt; pwd
/home/local/c

^shell&amp;gt; exit

[local@DouLen] ~/c/shell › 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Command Prompt
&lt;/h2&gt;

&lt;p&gt;Currently, the command prompt is just &lt;code&gt;^shell&amp;gt;&lt;/code&gt;. It's not bad, but it could be better.&lt;/p&gt;

&lt;p&gt;Let's improve it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Show the current user&lt;/li&gt;
&lt;li&gt;Show the machine hostname&lt;/li&gt;
&lt;li&gt;Show the working directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do this, I'll make a new function, &lt;code&gt;spawn_prompt()&lt;/code&gt; that will handle it cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;spawn_prompt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_curr_dir&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_prompt_char&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_hostname&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;passwd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getpwuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getuid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;%s@%s %s %c "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pw_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;fflush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll leave out the &lt;code&gt;get_*&lt;/code&gt; functions for brevity, but you'll be able to find the full code in &lt;a href="https://gist.github.com/douxxtech/77169672f96cd8bd04565b90b730b862" rel="noopener noreferrer"&gt;this gist&lt;/a&gt; :)&lt;/p&gt;

&lt;p&gt;After replacing the current implementation in &lt;code&gt;main()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;spawn_prompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have a nice prompt, let's escalate our privileges with &lt;a href="https://copy.fail/" rel="noopener noreferrer"&gt;this new exploit&lt;/a&gt; &lt;em&gt;(update your systems, folks!)&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fe5f4758d" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fe5f4758d" alt="new prompt" width="530" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, it updates correctly the prompt, and when root, &lt;a href="https://www.baeldung.com/linux/dollar-sign-vs-pound-prompts" rel="noopener noreferrer"&gt;the prompt character switches from &lt;code&gt;$&lt;/code&gt; to &lt;code&gt;#&lt;/code&gt;&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Surviving Ctrl-C
&lt;/h2&gt;

&lt;p&gt;Right now, if we &lt;code&gt;Ctrl-C&lt;/code&gt; inside our shell, it simply exits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local@DouLen ~/c/shell $ ^C

[local@DouLen] ~/c/shell › 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a bit annoying, so let's fix it.&lt;/p&gt;

&lt;p&gt;To do so, we need to setup signal handlers. Signals in Linux are a mechanism for communicating directly with a process, there are quite a few of them; the one that interests us is the &lt;code&gt;SIGINT&lt;/code&gt; (signal interrupt), that pressing &lt;code&gt;Ctrl-C&lt;/code&gt; sends.&lt;/p&gt;

&lt;p&gt;The idea is simple: when we catch a signal, spawn a new clean prompt instead of exiting.&lt;/p&gt;

&lt;p&gt;The implementation isn't complicated either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spawn_prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// call spawn_prompt on ^C&lt;/span&gt;

    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_INPUT&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's run &lt;code&gt;^C&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local@DouLen ~/c/shell $ ^C
local@DouLen ~/c/shell $ hello^C
local@DouLen ~/c/shell $ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice, it works! However, let's run a program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local@DouLen ~/c/shell $ nasmserver
Started the NASMServer static files HTTP server.

16:01:25 [INFO] Listening on 0.0.0.0:8080
^C16:01:26 [INFO] Stopping... (signal received)

local@DouLen ~/c/shell $ 
local@DouLen ~/c/shell $ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The double prompt happens because ^C doesn't just signal the child – it signals the entire foreground process group, meaning both the child and our shell receive the SIGINT at the same time. So &lt;code&gt;spawn_prompt()&lt;/code&gt; fires in our shell while the child is still running. Then, once the child exits, the main loop iterates and calls &lt;code&gt;spawn_prompt()&lt;/code&gt; again – giving us two prompts.&lt;/p&gt;

&lt;p&gt;Easy fix – just check tell that we got a &lt;code&gt;SIGINT&lt;/code&gt; to the loop, so it skips the prompt the next iteration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;spawn_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;got_sigint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// [...]&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;got_sigint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;spawn_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 0 = not a real signal, just drawing the prompt&lt;/span&gt;
    &lt;span class="n"&gt;got_sigint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;I'll probably keep hacking on this – pipes (&lt;code&gt;|&lt;/code&gt;) and output redirection (&lt;code&gt;&amp;gt;&lt;/code&gt;) are the obvious next steps, and I'd love to add command history at some point. There's also a bunch of smaller things, like proper quote handling, or &lt;code&gt;$VAR&lt;/code&gt; expansion, that would make it feel a lot more like a real shell.&lt;/p&gt;

&lt;p&gt;It's been a fun little project honestly. Writing your own shell really makes you appreciate how much work goes into the ones we use every day – bash and zsh are doing a &lt;em&gt;lot&lt;/em&gt; under the hood (especially since they also handle job control, complex signal management, scripting with control flow, and still manage to feel snappy and responsive).&lt;/p&gt;

&lt;p&gt;Again, full source with &lt;code&gt;~&lt;/code&gt; and &lt;code&gt;*&lt;/code&gt; expansion is available on &lt;a href="https://gist.github.com/douxxtech/77169672f96cd8bd04565b90b730b862" rel="noopener noreferrer"&gt;this gist&lt;/a&gt;, if you're interested in it :^D&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F7cccbd22" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F7cccbd22" alt="end meme" width="610" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>c</category>
      <category>linux</category>
      <category>shell</category>
      <category>programming</category>
    </item>
    <item>
      <title>How To "Gaslight" A Binary</title>
      <dc:creator>Douxx</dc:creator>
      <pubDate>Wed, 29 Apr 2026 21:01:23 +0000</pubDate>
      <link>https://dev.to/douxxtech/how-to-gaslight-a-binary-16ae</link>
      <guid>https://dev.to/douxxtech/how-to-gaslight-a-binary-16ae</guid>
      <description>&lt;p&gt;Here is a very simple C code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;uid_t&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getuid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;passwd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getpwuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uid: %d, user: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pw_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It simply prints your identity in the current session. Let's run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;@DouLen] ~ › ./whoami
uid: 0, user: root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program says I'm root, but look at the prompt: I'm logged in as &lt;code&gt;local&lt;/code&gt;. There isn't any &lt;a href="https://en.wikipedia.org/wiki/Privilege_escalation" rel="noopener noreferrer"&gt;privilege escalation&lt;/a&gt; in the code, and the program isn't run with &lt;a href="https://en.wikipedia.org/wiki/Sudo" rel="noopener noreferrer"&gt;sudo&lt;/a&gt; or similar.&lt;/p&gt;

&lt;p&gt;But if so, why is the program telling me that it is running as root? Well, it just got lied to. It genuinely thinks that it is root, and it is because it blindly trusts &lt;a href="https://man7.org/linux/man-pages/man2/geteuid.2.html" rel="noopener noreferrer"&gt;&lt;code&gt;getuid&lt;/code&gt;&lt;/a&gt; function from libc, which we've overridden. &lt;/p&gt;

&lt;p&gt;See, I lied to you. I didn't "just" run &lt;code&gt;./whoami&lt;/code&gt;. Before this, I ran this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LD_PRELOAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./fake_uid.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it: a single environment variable just compromised my program. What it actually does, is tell the dynamic linker: "before the program runs, load this library".&lt;/p&gt;

&lt;p&gt;You probably already guessed what this library does, but here is its code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;int getuid&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It overrides &lt;code&gt;getuid&lt;/code&gt; to always return zero (=root). This is how you gaslight a binary.&lt;br&gt;&lt;br&gt;
And obviously, it's the least dangerous thing that a malicious library could do.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;At a high level, this works because of how dynamically linked programs are executed on Linux.&lt;/p&gt;

&lt;p&gt;Most binaries don't contain all the code they need. Instead, they rely on shared libraries (like libc), which are loaded at runtime by the dynamic linker (usually &lt;code&gt;ld-linux.so&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;When a program calls a function like &lt;code&gt;getuid&lt;/code&gt;, it doesn't jump directly to a fixed address. Instead, the dynamic linker resolves that symbol at runtime and decides which implementation to use.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LD_PRELOAD&lt;/code&gt; takes advantage of this mechanism by injecting a library &lt;em&gt;before&lt;/em&gt; all others. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your library defines a function (e.g., &lt;code&gt;getuid&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;That definition is used &lt;em&gt;instead&lt;/em&gt; of the one in libc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, you're not modifying the program itself, you're &lt;strong&gt;changing what its function calls resolve to at runtime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This technique is often referred to as &lt;a href="https://www.geeksforgeeks.org/cpp/function-interposition-in-c-with-an-example-of-user-defined-malloc/" rel="noopener noreferrer"&gt;function interposition&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another example, commonly used in &lt;a href="https://en.wikipedia.org/wiki/Rootkit" rel="noopener noreferrer"&gt;rootkits&lt;/a&gt;, is hiding a process.&lt;/p&gt;

&lt;p&gt;Here is &lt;code&gt;evil.c&lt;/code&gt;, an extremely evil code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Haha I'm so evil &amp;gt;:)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It just loops and prints forever, nothing special.  &lt;/p&gt;

&lt;p&gt;And now, let's switch to a sysadmin that wants to search for an &lt;code&gt;evil&lt;/code&gt; process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;admin@DouLen] ~ › ps a | &lt;span class="nb"&gt;grep &lt;/span&gt;evil
   7050 pts/2    S+     0:00 ./evil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And they immediately find it. Now, let's hide it a bit more.&lt;/p&gt;

&lt;p&gt;See, processes in Linux are listed in &lt;a href="https://medium.com/@kisanthr22/mastering-proc-a-practical-guide-to-linux-process-and-system-monitoring-b5a24c1734e4" rel="noopener noreferrer"&gt;&lt;code&gt;/proc&lt;/code&gt;&lt;/a&gt; along other information. To list those processes, most programs enumerate &lt;code&gt;/proc&lt;/code&gt; by reading directory entries (similar to &lt;code&gt;ls /proc&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;So all we have to do is overwrite &lt;code&gt;readdir&lt;/code&gt;, trickier than it sounds, because we still need the real readdir to work underneath us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;dirent&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;DIR&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dirp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;dirent&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;real_readdir&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="kt"&gt;DIR&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;real_readdir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dlsym&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RTLD_NEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"readdir"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// get the *real* readdir, since we need to use it&lt;/span&gt;

    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;dirent&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;real_readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// probe each entry of the real readdir call&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_pid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;d_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;d_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;   &lt;span class="c1"&gt;// if it is our target process, skip it&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// hide this process&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(This part only is the "main" logic, full codes can be found &lt;a href="https://github.com/douxxtech/ld-preload-interceptors" rel="noopener noreferrer"&gt;here&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, let's use &lt;code&gt;ps&lt;/code&gt; again, with the library attached, this time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;admin@DouLen] ~ › &lt;span class="nv"&gt;LD_PRELOAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./ps_hide.so ps a | &lt;span class="nb"&gt;grep &lt;/span&gt;evil
   7214 pts/0    S+     0:00 &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;--color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto evil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that, even if our evil program is still running, it isn't listed anymore. This is the way most rootkits operate to hide themselves (well, they use way more intensive techniques, but you got it).&lt;/p&gt;

&lt;p&gt;Obviously, this is some light work. Actual malware does &lt;em&gt;way&lt;/em&gt; more than this. A good example would be this simple library, that purely keylogs everything inputted into stdin:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Feacf1017" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Feacf1017" alt="keylog lib gif" width="720" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works the same way: hook &lt;code&gt;fgets&lt;/code&gt; from libc, and everything typed into the program flows through your code first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables Are Impractical
&lt;/h2&gt;

&lt;p&gt;Let's be real, ain't no user will voluntarily prepend &lt;code&gt;LD_PRELOAD=./safe_lib.so&lt;/code&gt; to all of their commands.&lt;/p&gt;

&lt;p&gt;However, there are obviously other ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Hooking New Shells
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/etc/profile.d/&lt;/code&gt; contains scripts that are automatically loaded on terminal init, so an attacker could create a legitimate-looking file, like &lt;code&gt;/etc/profile.d/who.sh&lt;/code&gt; with &lt;code&gt;export LD_PRELOAD=/usr/local/lib/systemd-compat.so&lt;/code&gt; as a content, and every new shell created from there will preload systemd-compat.so, thus it being the malicious script.&lt;/p&gt;

&lt;p&gt;This is a nice way, but not the most reliable one, since it only works for interactive shells.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. System-Wide Persistence
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/etc/ld.so.preload&lt;/code&gt; is literally the file meant to do that. Every entry in it is preloaded by the dynamic linker for all dynamically linked binaries.&lt;/p&gt;

&lt;p&gt;So the attacker could simply append &lt;code&gt;/usr/local/lib/systemd-compat.so&lt;/code&gt; to it, and the whole system would be compromised, even programs not being launched from an interactive shell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting Malicious Libraries
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Obvious Check
&lt;/h3&gt;

&lt;p&gt;The first thing to do is check both persistence mechanisms directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/ld.so.preload
&lt;span class="nb"&gt;ls&lt;/span&gt; /etc/profile.d/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything unexpected in either of those is a red flag. Look for files with innocent-sounding names like &lt;code&gt;systemd-compat.so&lt;/code&gt;, &lt;code&gt;libgcc-utils.so&lt;/code&gt;, anything trying to blend in. If you aren't sure about something, the internet is your best friend here.&lt;/p&gt;

&lt;h3&gt;
  
  
  strace
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;strace&lt;/code&gt; operates at the syscall level, below libc entirely, so LD_PRELOAD can't hide things from it. You can use it to see what a program is &lt;em&gt;actually&lt;/em&gt; doing regardless of what any hooked library tells you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strace &lt;span class="nt"&gt;-e&lt;/span&gt; openat ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;ps&lt;/code&gt; is opening files it shouldn't, or skipping &lt;code&gt;/proc&lt;/code&gt; entries, you'll see it here.&lt;/p&gt;

&lt;p&gt;Another approach: &lt;code&gt;LD_PRELOAD&lt;/code&gt; doesn't disappear once a process starts, it stays visible in &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/environ&lt;/code&gt;. So even if a hooked &lt;code&gt;ps&lt;/code&gt; hides the process, the preload trail is still sitting in procfs, readable by anything that looks directly at &lt;code&gt;/proc&lt;/code&gt; instead of going through libc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Binaries
&lt;/h2&gt;

&lt;p&gt;The best solution is to use a statically compiled binary, which doesn't use the dynamic linker at all, so preloaded libraries are completely ignored. This is why forensic tools are often distributed as static binaries: on a compromised system, &lt;strong&gt;they're the only tools you can actually trust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can check if a binary is static with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ldd &lt;span class="si"&gt;$(&lt;/span&gt;which ps&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# if it says "not a dynamic executable", LD_PRELOAD won't touch it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the end of the day, nothing here is "breaking" Linux so much as bending trust. The program still believes it is calling libc. The system still believes it is listing processes. It’s only the answers that change. And that’s the uncomfortable part: in &lt;a href="https://en.wikipedia.org/wiki/User_space_and_kernel_space" rel="noopener noreferrer"&gt;userspace&lt;/a&gt;, reality can be altered.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F2274c818" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F2274c818" alt="a small meme hahaha" width="604" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>devops</category>
      <category>c</category>
    </item>
    <item>
      <title>23 Strangers Standing Between You and This Article</title>
      <dc:creator>Douxx</dc:creator>
      <pubDate>Fri, 24 Apr 2026 09:15:46 +0000</pubDate>
      <link>https://dev.to/douxxtech/23-strangers-standing-between-you-and-this-article-1afk</link>
      <guid>https://dev.to/douxxtech/23-strangers-standing-between-you-and-this-article-1afk</guid>
      <description>&lt;p&gt;You clicked this link. Quite simple, right? But before these words appeared in your browser, they went on a little journey, hopping through routers, data centers, and cables you'll never see, operated by people you'll never meet.&lt;/p&gt;

&lt;p&gt;And you know what? You can see &lt;em&gt;every&lt;/em&gt; one of those stops, and even trace the full path from your machine all the way to mine, or any server, really.&lt;/p&gt;

&lt;p&gt;The tool that lets you observe your route through the constellation of routers called &lt;em&gt;the internet&lt;/em&gt; is &lt;code&gt;traceroute&lt;/code&gt; (or &lt;code&gt;tracert&lt;/code&gt; on Windows, I guess they wanted to be original).&lt;/p&gt;

&lt;p&gt;Let's run a traceroute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;› tracert 152.53.236.228

Tracing route to theserver.life [152.53.236.228]
over a maximum of 30 hops:

  1     3 ms     1 ms     1 ms  192.168.1.1
  2    20 ms    18 ms    11 ms  77-56-216-1.dclient.hispeed.ch [77.56.216.1]
  3    15 ms    14 ms    13 ms  217-168-61-145.static.cablecom.ch [217.168.61.145]
  4    48 ms    41 ms    27 ms  carbsm101-be-2.aorta.net [84.116.211.21]
  5    15 ms    15 ms    13 ms  ch-otf01b-rc2-ae-54-0.aorta.net [84.116.202.225]
  6    25 ms    13 ms    18 ms  zur01lsr01.ae1.bb.sunrise.net [212.161.150.164]
  7     *        *        *     Request timed out.
  8    39 ms    54 ms    14 ms  213.46.171.182
  9    23 ms    20 ms    20 ms  ae2-2015.nbg60.core-backbone.com [80.255.15.250]
 10    23 ms    22 ms    24 ms  ae12-500.nbg40.core-backbone.com [80.255.9.21]
 11    36 ms    24 ms    45 ms  theserver.life [152.53.236.228]

Trace complete.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Looks like gibberish, right?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But actually, it's easy to decipher. Let's analyze our data's journey:&lt;/p&gt;

&lt;p&gt;Each line is a stop. Let's walk through the journey.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first one, &lt;code&gt;192.168.1.1&lt;/code&gt;, is my own router, still in my living room. &lt;em&gt;The first stranger is actually myself.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Hops &lt;code&gt;2&lt;/code&gt; and &lt;code&gt;3&lt;/code&gt; are my &lt;a href="https://en.wikipedia.org/wiki/Internet_service_provider" rel="noopener noreferrer"&gt;ISP&lt;/a&gt;: the entrance to the highway. You can even see it in the hostnames: &lt;code&gt;cablecom.ch&lt;/code&gt;, a Swiss internet provider, handing my data off to the wider world.&lt;/li&gt;
&lt;li&gt;Hops &lt;code&gt;4&lt;/code&gt; through &lt;code&gt;6&lt;/code&gt; are that highway. &lt;code&gt;aorta.net&lt;/code&gt;, &lt;code&gt;sunrise.net&lt;/code&gt; are transit backbones you've probably never heard of, but your data uses constantly, every single day.&lt;/li&gt;
&lt;li&gt;Hop &lt;code&gt;7&lt;/code&gt; is a ghost. Three &lt;code&gt;*&lt;/code&gt; instead of a response. Looks like someone doesn't want to be seen. We'll come back to that.&lt;/li&gt;
&lt;li&gt;Hop &lt;code&gt;8&lt;/code&gt; is another silent one, no hostname, just a raw IP. Not hiding, but not introducing itself either.&lt;/li&gt;
&lt;li&gt;And then &lt;code&gt;9&lt;/code&gt; and &lt;code&gt;10&lt;/code&gt; are another backbone, &lt;code&gt;core-backbone.com&lt;/code&gt;, and if you squint at the hostname you can see &lt;code&gt;nbg&lt;/code&gt;: Nuremberg, Germany. My data just crossed a border.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;11&lt;/code&gt; is home, well, &lt;em&gt;my&lt;/em&gt; home. The server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your output will look different: different ISP, different city, maybe even different countries in between. But the story is the same: &lt;em&gt;a chain of strangers, passing your data along&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So in 11 hops, my request crossed my living room, my ISP, some of the thousand of internet backbones, crossed Germany, and landed on my server. All this in about 40 milliseconds.&lt;/p&gt;

&lt;p&gt;But &lt;em&gt;how&lt;/em&gt; does traceroute even know all this? Well, it shouldn't. It's exploiting a small feature built into every router on the internet, originally designed to prevent flooding the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internet's Structure
&lt;/h2&gt;

&lt;p&gt;The internet works a lot like the postal system. When you send a letter abroad, your local postman doesn't know the way to Germany, he just drops it at the sorting center. The sorting center sends it to the national hub. The national hub hands it to an international carrier. Nobody has the full map. Everyone just knows their next step.&lt;/p&gt;

&lt;p&gt;Your request leaves your home, climbs through your ISP, then through bigger and bigger backbone networks, like &lt;code&gt;aorta.net&lt;/code&gt; or &lt;code&gt;core-backbone.com&lt;/code&gt; from our traceroute, until it reaches the destination network, and works its way down to the target server. Each of those networks is owned by a different company, and they all just agreed to hand traffic to each other.&lt;/p&gt;

&lt;p&gt;All of this is coordinated by &lt;a href="https://en.wikipedia.org/wiki/Border_Gateway_Protocol" rel="noopener noreferrer"&gt;Border Gateway Protocol&lt;/a&gt;, a fascinating rabbit hole for another day. &lt;em&gt;(Finish this article first.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Traceroute Exploits This Network
&lt;/h2&gt;

&lt;p&gt;What traceroute actually does is map every step your packet takes through the internet, and it does it by exploiting a feature that was never meant for this.&lt;/p&gt;

&lt;p&gt;To do that, it diverts the original purpose of &lt;code&gt;TTL&lt;/code&gt; (Time To Live): as defined in the first IP spec (&lt;a href="https://www.rfc-editor.org/rfc/rfc791.html" rel="noopener noreferrer"&gt;RFC 791&lt;/a&gt;), its intent was to "kill stale packets before they clog the network forever".&lt;/p&gt;

&lt;p&gt;Every IP packet has a &lt;code&gt;TTL&lt;/code&gt; field that gets decremented each time it crosses a new router (transit point). When it reaches 0, the packet gets destroyed and the router that killed it warns the sender about it.&lt;/p&gt;

&lt;p&gt;You might already see the trick coming: by setting the &lt;code&gt;TTL&lt;/code&gt; to 1, we can get the first router to drop the packet, and tell us it did. That gives us the first router's IP. Then we just repeat, incrementing the TTL each time to peel back one more hop.&lt;/p&gt;

&lt;p&gt;We keep going until the destination itself replies with either &lt;code&gt;ICMP_ECHOREPLY&lt;/code&gt; or &lt;code&gt;ICMP_DEST_UNREACH&lt;/code&gt; depending on the implementation, and that's our signal to stop.&lt;/p&gt;

&lt;p&gt;Here's the core loop in C, if you're curious:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;MAX_HOPS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Set the TTL to the current iter ttl&lt;/span&gt;
        &lt;span class="n"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send_sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IPPROTO_IP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IP_TTL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BUF_SIZE&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sockaddr_in&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;socklen_t&lt;/span&gt; &lt;span class="n"&gt;from_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Ping the target router 3 times to get the 3 delays&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;PROBE_COUNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send_sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;from_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="cm"&gt;/* print stats
           Skipped it for this example
        */&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icmp_hdr&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ICMP_ECHOREPLY&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin_addr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s_addr&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin_addr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s_addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full C code can be found on &lt;a href="https://gist.github.com/douxxtech/3a1ffdf1acf6b3c1dcf81aad55a4bfe6" rel="noopener noreferrer"&gt;this gist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ghosts, and Other Lies
&lt;/h2&gt;

&lt;p&gt;Remember hop &lt;code&gt;7&lt;/code&gt;, the one that went silent? And hop &lt;code&gt;8&lt;/code&gt;, the no-name one?&lt;/p&gt;

&lt;p&gt;They're not broken, they just don't want to be seen.&lt;/p&gt;

&lt;p&gt;Some routers are configured to drop ICMP packets (the ones &lt;code&gt;traceroute&lt;/code&gt; uses to probe the network). They still forward the traffic just fine, but they don't want to play traceroute's little &lt;em&gt;spy game&lt;/em&gt;. Others, like hop &lt;code&gt;8&lt;/code&gt;, simply don't have a &lt;a href="https://en.wikipedia.org/wiki/Reverse_DNS_lookup" rel="noopener noreferrer"&gt;reverse DNS&lt;/a&gt; record.&lt;/p&gt;

&lt;p&gt;And it gets worse. The route we just saw? It might not exist anymore. Run two traceroutes with a one-hour difference and you might see completely different routes, maybe even different countries. The internet reroutes itself constantly, reacting to &lt;a href="https://en.wikipedia.org/wiki/Metrics_(networking)" rel="noopener noreferrer"&gt;routing metrics&lt;/a&gt;, failures, and countless other factors. There's no fixed path, but at least there will always be a path.&lt;/p&gt;

&lt;p&gt;Even the timings can lie. See how hop &lt;code&gt;4&lt;/code&gt; shows &lt;code&gt;48 ms&lt;/code&gt; while hop &lt;code&gt;9&lt;/code&gt; shows only &lt;code&gt;23 ms&lt;/code&gt;? A further hop that is faster than a closer one?! That's because routers prioritize "real" traffic over responding to ICMP probes. The latency numbers tell you something, but never &lt;em&gt;everything&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Remember that traceroute is a window into the internet, but not a clear one.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>linux</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>An Attempt to Ban Bad Bots Crawling My Sites</title>
      <dc:creator>Douxx</dc:creator>
      <pubDate>Tue, 31 Mar 2026 17:23:16 +0000</pubDate>
      <link>https://dev.to/douxxtech/an-attempt-to-ban-bad-bots-crawling-my-sites-2lhg</link>
      <guid>https://dev.to/douxxtech/an-attempt-to-ban-bad-bots-crawling-my-sites-2lhg</guid>
      <description>&lt;p&gt;I don't really like &lt;strong&gt;bad bots&lt;/strong&gt;, and by that I mean crawlers that don't care about &lt;a href="https://www.robotstxt.org/" rel="noopener noreferrer"&gt;robots.txt&lt;/a&gt;. The reason is simple: I don't want my data fed into obscure systems, and also just by principle, &lt;em&gt;if we give you rules, follow them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Credit where it's due: the idea came from &lt;a href="https://caolan.uk/" rel="noopener noreferrer"&gt;Caolan's website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The idea is simple: make the bad bots click a link they aren't supposed to, then ban them. To do that, I added a &lt;code&gt;robots.txt&lt;/code&gt; at the root of my site, explicitly disallowing robots from a specific page (I went with &lt;code&gt;/roboty/&lt;/code&gt;, because why not):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-agent: *
Disallow: /roboty/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I slipped a link to that page somewhere on the root page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Ff2419bf9" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Ff2419bf9" alt="link" width="484" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since I don't want curious humans getting instantly banned, the page itself just explains what's going on and links to &lt;code&gt;article.php&lt;/code&gt;, the actual dangerous script. I named it like that to bypass possible keyword blacklists like &lt;code&gt;ban&lt;/code&gt; or &lt;code&gt;ban-ip&lt;/code&gt;. &lt;code&gt;¯\_(ツ)_/¯&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Talking about the script, here it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$cf_api_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$zone_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Auto banned by dtech/roboty at '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"H:i d/m/y"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REMOTE_ADDR'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'mode'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'block'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'configuration'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'target'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'ip'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'value'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'notes'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://api.cloudflare.com/client/v4/zones/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$zone_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/firewall/access_rules/rules"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;CURLOPT_POST&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;CURLOPT_POSTFIELDS&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;CURLOPT_IPRESOLVE&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;CURL_IPRESOLVE_V4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$cf_api_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Location: /?blehhhhh"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// redirect to '/', should be blocked&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Bye ;)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right now it only bans the bot's IP on &lt;code&gt;douxx.tech&lt;/code&gt; (proxied through Cloudflare), but I plan to eventually implement it into an internal API to block across every domain I own, and maybe throw in some &lt;code&gt;iptables&lt;/code&gt; rules too.&lt;/p&gt;

&lt;p&gt;So yeah, I'll keep it running for a bit and see how many IPs we get.&lt;br&gt;&lt;br&gt;
For the record, the first one to be banned is an IP from &lt;a href="https://en.wikipedia.org/wiki/Tencent" rel="noopener noreferrer"&gt;Tencent&lt;/a&gt; datacenters 🤡&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F2e254199" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F2e254199" alt="tencent ipban" width="512" height="122"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F28a91d76" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F28a91d76" alt="ip info screenshot" width="652" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>web</category>
      <category>development</category>
      <category>php</category>
      <category>crawlers</category>
    </item>
    <item>
      <title>Building a Web Server from Scratch (No, Actually)</title>
      <dc:creator>Douxx</dc:creator>
      <pubDate>Mon, 16 Mar 2026 00:20:44 +0000</pubDate>
      <link>https://dev.to/douxxtech/building-a-web-server-from-scratch-no-actually-2o38</link>
      <guid>https://dev.to/douxxtech/building-a-web-server-from-scratch-no-actually-2o38</guid>
      <description>&lt;p&gt;When I say from scratch, I &lt;em&gt;mean&lt;/em&gt; it. No frameworks, no &lt;code&gt;node_modules&lt;/code&gt; taking 500MB of disk space, no runtime. Just you, and your Linux kernel.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bit of Context
&lt;/h2&gt;

&lt;p&gt;Exactly one week ago, I was in my NoSQL class, and got bored, like, really. And what does a sane person do when they're bored? Certainly not learn assembly. But that's what I did.&lt;/p&gt;

&lt;p&gt;To be honest, the idea had been running on my mind for some time already. So I said to myself that it could be more interesting than reading papers about MongoDB, and looked for a guide.&lt;/p&gt;

&lt;p&gt;I directly found &lt;a href="https://github.com/0xAX/asm" rel="noopener noreferrer"&gt;this guide from Alex Kuleshov&lt;/a&gt;, and started reading. That afternoon, I read about 3 posts instead of listening to my teacher, and then went home.&lt;/p&gt;

&lt;p&gt;Since I didn't want to digest more theory that day, I decided to do some practice. You learn more from random &lt;a href="https://en.wikipedia.org/wiki/Segmentation_fault" rel="noopener noreferrer"&gt;segfaults&lt;/a&gt; than from pages of theory.&lt;/p&gt;

&lt;p&gt;The guide didn't cover exercises + answers, so I decided to use the thing that will probably steal my job in a few years: &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;Claude&lt;/a&gt;. Even if it can't &lt;em&gt;(yet)&lt;/em&gt; write good assembly code, it can create "good" exercises and correct them. So I spent the evening doing that.&lt;/p&gt;

&lt;p&gt;The next day, I continued the course and read the final chapters. After that, I felt like I knew enough things but had clearly not enough practice.&lt;/p&gt;

&lt;p&gt;And damn, I was so right.&lt;/p&gt;

&lt;p&gt;I decided to create an HTTP client to train. Basically curl, but with no other feature than &lt;code&gt;get&lt;/code&gt;-ing pages. It was a horror. Every time I took a step forward, I took three steps back due to code that stopped working, mostly because of those damn &lt;a href="https://en.wikipedia.org/wiki/Processor_register" rel="noopener noreferrer"&gt;CPU registers&lt;/a&gt; &lt;code&gt;&amp;gt;:[&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Well, after a bit of practice, I got something working:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fc1012703%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fc1012703%2F" alt="asmclient" width="683" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One day passes, we're now Tuesday, 10AM. My next project was pretty obvious: a web server. What's the point of having a web client without one?&lt;/p&gt;

&lt;p&gt;So in the rest of this article, I'll explain how I built &lt;a href="https://nasmserver.douxx.tech/" rel="noopener noreferrer"&gt;NASMServer&lt;/a&gt;, the 95% &lt;a href="https://nasm.us" rel="noopener noreferrer"&gt;NetWide assembly&lt;/a&gt; web server that runs &lt;a href="https://douxx.tech" rel="noopener noreferrer"&gt;douxx.tech&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quick note: I won't talk &lt;em&gt;assembly&lt;/em&gt; in this article. It would require &lt;em&gt;you, the reader&lt;/em&gt;, to have knowledge about it, and it isn't needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ok, let's start!&lt;/p&gt;
&lt;h2&gt;
  
  
  The Basics
&lt;/h2&gt;

&lt;p&gt;This article covers &lt;strong&gt;only&lt;/strong&gt; x86_64 Linux. Any other OS or architecture would have different instructions.&lt;/p&gt;

&lt;p&gt;I'll try to avoid talking directly in assembly, but I'll regularly add links to the relevant parts on the GitHub repo. You don't need assembly knowledge, but you might need some about Linux and programming in general.&lt;/p&gt;

&lt;p&gt;Two things to keep in mind before we continue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Linux, &lt;a href="https://en.wikipedia.org/wiki/Everything_is_a_file" rel="noopener noreferrer"&gt;everything is a file&lt;/a&gt; (&lt;a href="https://dev.to/eteimz/everything-is-a-file-explained-g2a"&gt;Dev.to article&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;We talk to the Linux kernel using &lt;a href="https://en.wikipedia.org/wiki/System_call" rel="noopener noreferrer"&gt;System Calls&lt;/a&gt;, the &lt;em&gt;bridges&lt;/em&gt; between our application and the hardware.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a system call in &lt;a href="https://en.wikipedia.org/wiki/C_(programming_language)" rel="noopener noreferrer"&gt;C&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/syscall.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, world!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SYS_write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// fd=1, buffer, length&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's one in NASM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_start:
    mov rax, 1    ; syscall number for write
    mov rdi, 1    ; fd = 1 (stdout)
    mov rsi, msg  ; buffer
    mov rdx, len  ; length
    syscall       ; call kernel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System calls will be the only thing we use for I/O, so make sure you're comfortable with them. Here's the &lt;a href="https://filippo.io/linux-syscall-table/" rel="noopener noreferrer"&gt;full Linux x86_64 syscalls table&lt;/a&gt; for reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Logic
&lt;/h2&gt;

&lt;p&gt;Before writing a single line, you need to plan what the program will do and leave the &lt;em&gt;how&lt;/em&gt; to your future self. Here's what I planned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☐ Listen to a port&lt;/li&gt;
&lt;li&gt;☐ Wait for requests, and accept them&lt;/li&gt;
&lt;li&gt;☐ Read the content&lt;/li&gt;
&lt;li&gt;☐ Parse the HTTP request&lt;/li&gt;
&lt;li&gt;☐ Read the requested file&lt;/li&gt;
&lt;li&gt;☐ Send a HTTP response back, with the file content&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Listen To a Port
&lt;/h2&gt;

&lt;p&gt;The first thing we need is something clients can connect to and "talk with us": a &lt;a href="https://en.wikipedia.org/wiki/Network_socket" rel="noopener noreferrer"&gt;TCP Socket&lt;/a&gt;. It's, well, a &lt;em&gt;file&lt;/em&gt;, and it's basically the way the client says "I'm here, and I want to talk to X application".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/program.asm#L82-L93" rel="noopener noreferrer"&gt;[-&amp;gt; program.asm]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating the socket alone isn't enough though. It exists, it &lt;em&gt;can&lt;/em&gt; do its job, but it isn't accessible to anyone yet. We need to bind it to a port and an interface.&lt;/p&gt;

&lt;p&gt;The interface is one of the IP addresses available to the system: &lt;code&gt;127.0.0.1&lt;/code&gt;, &lt;code&gt;192.168.x.x&lt;/code&gt;, etc. To simplify our lives, we'll use &lt;code&gt;0.0.0.0&lt;/code&gt;, "listen on every interface". The port is a value between 1 and 65535, and HTTP usually lives on &lt;code&gt;80&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We give the kernel the socket &lt;a href="https://en.wikipedia.org/wiki/File_descriptor" rel="noopener noreferrer"&gt;file descriptor&lt;/a&gt; and the interface + port to bind to. It either returns &lt;code&gt;0&lt;/code&gt; (done), or a negative error code, usually meaning the port is already in use on the given interface, or we don't have enough permissions (&lt;code&gt;&amp;lt; 1024&lt;/code&gt; ports require root).&lt;/p&gt;

&lt;p&gt;Finally, we tell the kernel we're ready to listen with the &lt;a href="https://manpages.debian.org/unstable/manpages-dev/listen.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;listen&lt;/code&gt;&lt;/a&gt; syscall.&lt;br&gt;
&lt;a href="https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/program.asm#L109-L127" rel="noopener noreferrer"&gt;[-&amp;gt; program.asm]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To summarize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a socket: &lt;a href="https://manpages.debian.org/unstable/manpages-dev/socket.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;socket&lt;/code&gt;&lt;/a&gt; syscall&lt;/li&gt;
&lt;li&gt;Bind it: &lt;a href="https://manpages.debian.org/unstable/manpages-dev/bind.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;bind&lt;/code&gt;&lt;/a&gt; syscall&lt;/li&gt;
&lt;li&gt;Start listening: &lt;a href="https://manpages.debian.org/unstable/manpages-dev/listen.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;listen&lt;/code&gt;&lt;/a&gt; syscall&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And just like that, we're reachable on &lt;code&gt;0.0.0.0:80&lt;/code&gt;!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Listen to a port&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Wait For Requests, And Accept Them
&lt;/h2&gt;

&lt;p&gt;This is where the main loop lives:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```plain text&lt;br&gt;
[Wait for a request] --&amp;gt; [Accept it] --&amp;gt; [Handle it (explained later)] --&amp;gt; |&lt;br&gt;
        ^------------------------------------------------------------------+&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The [`accept`](https://manpages.debian.org/unstable/manpages-dev/accept.2.en.html) syscall handles both waiting (blocking) and accepting in one shot. And guess what it returns? A file!!
[[-&amp;gt; program.asm]](https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/program.asm#L142-L156)

That file is the private space where we and the client will talk to each other.

- ☑ ~~Wait for requests, and accept them~~

## Read The Client Request

The "private space" file contains the request the client wrote. Reading it is easy: use the [`read`](https://manpages.debian.org/unstable/manpages-dev/read.2.en.html) syscall and dump it into a buffer.

[[-&amp;gt; program.asm]](https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/program.asm#L222)
[[-&amp;gt; fileutils.asm]](https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/macros/fileutils.asm#L113-L121)

Then we check if it's a valid HTTP request. If not, we send back a [400 Bad Request](https://developer.mozilla.org/de/docs/Web/HTTP/Reference/Status/400). A very minimal valid request looks like:



```plaintext
GET / HTTP/1.0
\r\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Which breaks down to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;METHOD&amp;gt; &amp;lt;path&amp;gt; &amp;lt;HTTP_VERSION&amp;gt;
&amp;lt;crlf&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a static server, we only handle &lt;code&gt;GET&lt;/code&gt;, and anything else gets a &lt;a href="https://developer.mozilla.org/de/docs/Web/HTTP/Reference/Status/405" rel="noopener noreferrer"&gt;405 Method Not Allowed&lt;/a&gt;. If the method is valid, we parse the path and append it to the &lt;strong&gt;document root&lt;/strong&gt; (e.g. &lt;code&gt;/var/www/html&lt;/code&gt;), which is the directory we'll be serving files from.&lt;/p&gt;

&lt;p&gt;One important thing: path traversal prevention. In Linux, &lt;code&gt;..&lt;/code&gt; means "go to the previous directory", so a path like &lt;code&gt;/../../../opt/sensitive/passwords.txt&lt;/code&gt; appended to &lt;code&gt;/var/www/html&lt;/code&gt; would resolve to &lt;code&gt;/opt/sensitive/passwords.txt&lt;/code&gt;. Not great. We simply check for any &lt;code&gt;..&lt;/code&gt; in the path and drop the request with a &lt;a href="https://developer.mozilla.org/de/docs/Web/HTTP/Reference/Status/403" rel="noopener noreferrer"&gt;403 Forbidden&lt;/a&gt; if we find one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/program.asm#L221-L278" rel="noopener noreferrer"&gt;[-&amp;gt; program.asm]&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/macros/httputils.asm" rel="noopener noreferrer"&gt;[-&amp;gt; httputils.asm]&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Read the content&lt;/del&gt;
&lt;/li&gt;
&lt;li&gt;☑ &lt;del&gt;Parse the HTTP request&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Read The Requested File
&lt;/h2&gt;

&lt;p&gt;We have a safe path, now let's actually get the file. A couple of things to handle first.&lt;/p&gt;

&lt;p&gt;If the client requested &lt;code&gt;/&lt;/code&gt;, we'd end up with &lt;code&gt;/var/www/html/&lt;/code&gt;, figure out it's a directory, and go crazy. So we &lt;em&gt;internally&lt;/em&gt; append an index file (e.g. &lt;code&gt;/index.html&lt;/code&gt;) to the path (no redirecting the client, I see you bad programs). This works for subdirectories too: &lt;code&gt;/home/&lt;/code&gt; becomes &lt;code&gt;/home/index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;"But what about directories that don't end with &lt;code&gt;/&lt;/code&gt;?". Fair point, and we'll get there. For now, let's move on.&lt;/p&gt;

&lt;p&gt;We use the &lt;a href="https://manpages.debian.org/unstable/manpages-dev/stat.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;stat&lt;/code&gt;&lt;/a&gt; syscall to check if the file exists and what type it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Doesn't exist → &lt;a href="https://developer.mozilla.org/de/docs/Web/HTTP/Reference/Status/404" rel="noopener noreferrer"&gt;404 Not Found&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;It's a directory → the trailing slash was missing, add it and loop back to the index-appending step&lt;/li&gt;
&lt;li&gt;It's a regular file but not readable → &lt;a href="https://developer.mozilla.org/de/docs/Web/HTTP/Reference/Status/403" rel="noopener noreferrer"&gt;403 Forbidden&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Otherwise → continue!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/program.asm#L280-L326" rel="noopener noreferrer"&gt;[-&amp;gt; program.asm]&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/macros/fileutils.asm" rel="noopener noreferrer"&gt;[-&amp;gt; fileutils.asm]&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Read the requested file&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Send The Response
&lt;/h2&gt;

&lt;p&gt;All edge cases handled, time to actually send something. We write to the "private space" file, starting with the HTTP header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.0 200 OK
Server: NASMServer/1.0
Content-Type: text/html
Content-Length: 1442
Connection: close

[file content]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;HTTP/1.0 200 OK&lt;/code&gt;: static string, HTTP version + status code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Server: NASMServer/1.0&lt;/code&gt;: not required, but nice to have&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Content-Type: text/html&lt;/code&gt;: tells the client what it's receiving, must follow &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types" rel="noopener noreferrer"&gt;Media Types&lt;/a&gt; format&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Content-Length: 1442&lt;/code&gt;: byte count of the response, grabbed from &lt;a href="https://manpages.debian.org/unstable/manpages-dev/stat.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;stat&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Connection: close&lt;/code&gt;: we won't keep the connection alive after sending&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\r\n&lt;/code&gt;: blank line separating header from body. HTTP uses &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/CRLF" rel="noopener noreferrer"&gt;&lt;code&gt;CRLF&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We write the header with &lt;a href="https://manpages.debian.org/unstable/manpages-dev/write.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;write&lt;/code&gt;&lt;/a&gt;, send the file content with &lt;a href="https://manpages.debian.org/unstable/manpages-dev/sendfile.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;sendfile&lt;/code&gt;&lt;/a&gt; (no manual copying needed), then close up with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://manpages.debian.org/unstable/manpages-dev/shutdown.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;shutdown&lt;/code&gt;&lt;/a&gt;: tell the client we're done&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://manpages.debian.org/unstable/manpages-dev/close.2.en.html" rel="noopener noreferrer"&gt;&lt;code&gt;close&lt;/code&gt;&lt;/a&gt;: close the connection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then jump back to waiting. &lt;code&gt;:D&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/douxxtech/nasmserver/blob/0f7cab0cbe27963e078fb7257371919416c107b9/program.asm#L496-L562" rel="noopener noreferrer"&gt;[-&amp;gt; program.asm]&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Send a HTTP response back, with the file content&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And just like that, we have a &lt;strong&gt;working HTTP 1.0 static file server&lt;/strong&gt;!!&lt;/p&gt;

&lt;h2&gt;
  
  
  And Now?
&lt;/h2&gt;

&lt;p&gt;I lied, but not entirely. This works, but it wouldn't survive being spammed. There's no proper per-request handling, so a request coming in while another is being processed will either be queued or dropped.&lt;/p&gt;

&lt;p&gt;The fix is to &lt;a href="https://manpages.debian.org/unstable/manpages-dev/fork.2.en.html" rel="noopener noreferrer"&gt;fork&lt;/a&gt; the process on each request, and the main process immediately goes back to waiting while the clone handles it. I won't go into detail here, but the code is there if you want to look!&lt;/p&gt;

&lt;p&gt;Other improvements are possible too, but this post only covers the basics. If you're interested, consider reading, starring, or contributing!&lt;br&gt;
&lt;a href="https://git.douxx.tech/nasmserver/" rel="noopener noreferrer"&gt;Github:douxxtech/nasmserver&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The logic explanation ends here, feel free to leave now. Otherwise, let's talk numbers.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Fast Is It?
&lt;/h2&gt;

&lt;p&gt;Three servers, three environments, same file, no TLS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NASMServer&lt;/strong&gt;: fully built in assembly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BusyBox HTTPD&lt;/strong&gt;: a really small HTTP server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apache2&lt;/strong&gt;: one of the most used web servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Speed measured with cURL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"
DNS: %{time_namelookup}s
Connect: %{time_connect}s
TLS: %{time_appconnect}s
Start Transfer: %{time_starttransfer}s
Total: %{time_total}s
&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; http://localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Each command is run 10 times, results are averaged.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Environments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;localhost&lt;/code&gt;: staying on the machine&lt;/li&gt;
&lt;li&gt;Windows &amp;lt;&amp;gt; WSL: servers running in Fedora WSL, testing the virtual interface&lt;/li&gt;
&lt;li&gt;Local network: fetching over LAN&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Localhost&lt;/th&gt;
&lt;th&gt;Windows Host&lt;/th&gt;
&lt;th&gt;Network&lt;/th&gt;
&lt;th&gt;Average&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BusyBox HTTPD&lt;/td&gt;
&lt;td&gt;0.0004677s&lt;/td&gt;
&lt;td&gt;0.0075919s&lt;/td&gt;
&lt;td&gt;0.0038408s&lt;/td&gt;
&lt;td&gt;0.0039668s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NASMServer&lt;/td&gt;
&lt;td&gt;0.0005997s&lt;/td&gt;
&lt;td&gt;0.0082924s&lt;/td&gt;
&lt;td&gt;0.0076072s&lt;/td&gt;
&lt;td&gt;0.0054998s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache2&lt;/td&gt;
&lt;td&gt;0.0004769s&lt;/td&gt;
&lt;td&gt;0.0102861s&lt;/td&gt;
&lt;td&gt;0.0062916s&lt;/td&gt;
&lt;td&gt;0.0056849s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;BusyBox HTTPD&lt;/strong&gt; wins across the board. NASMServer holds its own on localhost but falls behind on the network. Apache2 is the slowest on the Windows host by a noticeable margin, which makes sense given its heavier feature set.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NASMServer and Apache2 being slower over WSL than over LAN is likely due to WSL's virtual network interface adding overhead that a direct LAN connection doesn't have. Not 100% sure on that though.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Final Words
&lt;/h2&gt;

&lt;p&gt;I really loved building this project, writing this article, and learning assembly. I'll keep updating the server, so if you have feature ideas, bug reports, etc. feel free to reach out via &lt;a href="https://git.douxx.tech/nasmserver" rel="noopener noreferrer"&gt;GitHub issues&lt;/a&gt;, the &lt;a href="https://dev.to/douxxtech/building-a-web-server-from-scratch-no-actually-2o38"&gt;dev.to&lt;/a&gt; comments, or &lt;a href="mailto:douxx@douxx.tech"&gt;mail&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Would I recommend NASMServer in production? For god's sake, &lt;strong&gt;NO!&lt;/strong&gt;&lt;br&gt;
Did I do it? &lt;em&gt;Maybe.&lt;/em&gt;&lt;br&gt;
Will I regret it? &lt;em&gt;Surely.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But remember, I started this because I was bored in a NoSQL class.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>assembly</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bringing Web Radios Back to FM</title>
      <dc:creator>Douxx</dc:creator>
      <pubDate>Sat, 28 Feb 2026 12:00:29 +0000</pubDate>
      <link>https://dev.to/douxxtech/bringing-web-radios-back-to-fm-eai</link>
      <guid>https://dev.to/douxxtech/bringing-web-radios-back-to-fm-eai</guid>
      <description>&lt;p&gt;When going on vacation, I listen to local radios stations using a small portable radio that I bring with me. I absolutely love doing this as the music often changes of what I'm used to, and it makes me discover new things.&lt;/p&gt;

&lt;p&gt;One of those radios I love listening to is the &lt;a href="https://rtl.it" rel="noopener noreferrer"&gt;RTL 102.5&lt;/a&gt;, an italian radio. I always listen to it when I go on a trip in Italy. However, it only is a national station and doesn't broadcast in other countries such as Switzerland.&lt;/p&gt;

&lt;p&gt;A good way of continuing to listen to programs that aren't broadcasted on FM are &lt;a href="https://en.wikipedia.org/wiki/Internet_radio" rel="noopener noreferrer"&gt;web radios&lt;/a&gt;, and you'll ask me, why don't I want to listen to them ? They're near perfection, they're live, have a good audio quality, and much more !&lt;br&gt;&lt;br&gt;
And the answer is in the question. &lt;em&gt;They're perfect&lt;/em&gt;. I find that this perfection breaks the charm of FM. Having lossless 96kHz in your headset doesn't have the same &lt;em&gt;vibe&lt;/em&gt; at all than getting a signal from a tower being at hundreds of kilometers from your tiny 15 bucks portable radio.&lt;/p&gt;

&lt;p&gt;So in this article, I'll try to &lt;strong&gt;take a live stream from the RTL 102.5 web radio, and broadcast it in my house, on FM&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As always, here is an overview of what I'll be doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☐ Finding a way of broadcasting FM on a short range&lt;/li&gt;
&lt;li&gt;☐ Getting the audio source for the web radio stream&lt;/li&gt;
&lt;li&gt;☐ Putting both together&lt;/li&gt;
&lt;li&gt;☐ Automate everything&lt;/li&gt;
&lt;li&gt;☐ Enjoy !&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Figuring Out What I Even Need
&lt;/h2&gt;

&lt;p&gt;As I just said, I only need two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A device being able to broadcast FM radio&lt;/li&gt;
&lt;li&gt;A stream from where I can get the live radio feed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And great news, I already have an idea on how to get them !&lt;/p&gt;
&lt;h3&gt;
  
  
  1: The broadcaster
&lt;/h3&gt;

&lt;p&gt;This is the easiest part of this article, since I literally made a software being able to do that, and I won't hesitate to use it !&lt;/p&gt;

&lt;p&gt;For those who don't know it, it's &lt;a href="https://git.douxx.tech/botwave/" rel="noopener noreferrer"&gt;BotWave&lt;/a&gt;, a software that lets you easily play files and live feeds on FM using a &lt;a href="https://raspberrypi.com" rel="noopener noreferrer"&gt;Raspberry Pi&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  2: The source
&lt;/h3&gt;

&lt;p&gt;What I initially wanted to do was simple: Go to &lt;a href="https://radio-browser.info" rel="noopener noreferrer"&gt;radio-browser.info&lt;/a&gt;, a library of almost every "big" web radios that documents a lot of information about them, but more importantly, the stream url.&lt;/p&gt;

&lt;p&gt;So I went on the website, searched for RTL 102.5, found it, and got this stream url:&lt;br&gt;&lt;br&gt;
&lt;code&gt;https://dd782ed59e2a4e86aabf6fc508674b59.msvdn.net/live/S97044836/tbbP8T1ZRPBL/playlist_audio.m3u8&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Sadly, once I opened it up in my browser, I saw that it was a dead link and that nothing was served anymore :/&lt;/p&gt;

&lt;p&gt;So it's time for the fallback plan ! Find the stream url directly on the website !&lt;br&gt;&lt;br&gt;
It sounds like an epic thing, but it's really not much, I just went on the website player, and then checked for any &lt;code&gt;m3u&lt;/code&gt; files in the network tab of the devtools. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fa025e208%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fa025e208%2F" alt="The network tab" width="1330" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And I found it:&lt;br&gt;&lt;br&gt;
&lt;code&gt;https://streamcdnb1-dd782ed59e2a4e86aabf6fc508674b59.msvdn.net/live/S97044836/WjpMtPyNjHwj/playlist_audio.m3u8&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And this one &lt;em&gt;actually&lt;/em&gt; works ! &lt;/p&gt;

&lt;p&gt;Ok so just like that, we got the first two points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Finding a way of broadcasting FM on a short range&lt;/del&gt;
&lt;/li&gt;
&lt;li&gt;☑ &lt;del&gt;Getting the audio source for the web radio stream&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Getting That Stream on FM
&lt;/h2&gt;

&lt;p&gt;I already had an idea on how to do that, but I'll have to check if it works. I'm planning on using &lt;a href="https://ffmpeg.org" rel="noopener noreferrer"&gt;FFmpeg&lt;/a&gt;, basically the swiss-knife of audio, video, and image editing. &lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Test it
&lt;/h3&gt;

&lt;p&gt;I'll start by trying to record 5 seconds of the stream, and put them in a &lt;code&gt;.wav&lt;/code&gt; file.&lt;br&gt;&lt;br&gt;
And, surprisingly, I succeeded first try, which is pretty unusual :]&lt;/p&gt;

&lt;p&gt;Here is the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"https://streamcdnb1-dd782ed59e2a4e86aabf6fc508674b59.msvdn.net/live/S97044836/WjpMtPyNjHwj/playlist_audio.m3u8"&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 5 test.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes the stream in input, and converts 5 seconds of it in the wave format to save it !&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Put it Into Practice
&lt;/h3&gt;

&lt;p&gt;BotWave exposes a sound card in which we can input audio, and it will play it live. So all we have to do is, instead of outputting the audio into a wave file, we output it directly in the sound card !&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"https://streamcdnb1-dd782ed59e2a4e86aabf6fc508674b59.msvdn.net/live/S97044836/WjpMtPyNjHwj/playlist_audio.m3u8"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; alsa plughw:BotWave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I removed the time limit so it plays indefinitely, and the other stuff redirects the sound to the sound card.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Broadcasting it
&lt;/h3&gt;

&lt;p&gt;The last step is actually telling the software to take the card output and broadcast it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bw-local
botwave&amp;gt; live 102.5 &lt;span class="s2"&gt;"RTL 102.5"&lt;/span&gt; &lt;span class="s2"&gt;"aka.dbo.one/webtofm"&lt;/span&gt;
botwave&amp;gt; &lt;span class="c"&gt;# This plays at 102.5 FM, with the name and desc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's check if we got anything on radio !&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F18503ffc%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F18503ffc%2F" width="446" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And yes ! We can see the broadcast on the spectrum, and if we stop the broadcast, it disappears:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fff7d526f%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fff7d526f%2F" width="391" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Putting both together&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Automating Everything
&lt;/h2&gt;

&lt;p&gt;It works, but currently it's kinda painful to setup, we need to get into the pi shell, two times actually, run ffmpeg and then BotWave, and leave it open.&lt;/p&gt;

&lt;p&gt;So what I'll do is automating it, and it's surprisingly simple !&lt;/p&gt;

&lt;p&gt;First, I'll create a file that will execute at the moment where BotWave starts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bw-nandl l_onready_webtofm.hdl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F0a8e1649%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F0a8e1649%2F" alt="nano editor" width="931" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside, I put the ffmpeg command and the live instruction, so this will run ffmpeg in the background, and then start the broadcast automatically.&lt;/p&gt;

&lt;p&gt;Now, when we run &lt;code&gt;bw-local&lt;/code&gt;, the broadcast will automatically start. This removed one step of the process, but we still have to open a shell and run the command. So let's also automate this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bw-autorun &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;--ws&lt;/span&gt; 9939
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make a systemd service that automatically starts BotWave on boot. It also opens a remote connection on port &lt;code&gt;9939&lt;/code&gt; so I can still manually send commands if needed.&lt;/p&gt;

&lt;p&gt;And we're done !&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Automate everything&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now, I'm able again to take my 15 bucks radio, tune it to 102.5MHz, and listen to it with a less perfect, but more charming audio quality, where and when I want :D&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F9577090e%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F9577090e%2F" alt="picture of the Raspberry Pi and the radio" width="688" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Picture taken with my &lt;a href="https://douxx.blog/?p=9-circuit-bending-a-camera" rel="noopener noreferrer"&gt;circuit bent camera&lt;/a&gt;, btw&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Enjoy !&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>radio</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How I Built a Random Number Generator (Sort Of)</title>
      <dc:creator>Douxx</dc:creator>
      <pubDate>Sat, 07 Feb 2026 22:26:59 +0000</pubDate>
      <link>https://dev.to/douxxtech/how-i-built-a-random-number-generator-sort-of-37i</link>
      <guid>https://dev.to/douxxtech/how-i-built-a-random-number-generator-sort-of-37i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: I made an Hybrid Hardware Random Number Generator (HHRNG) using radio noise. You can find the full source code on &lt;a href="https://git.douxx.tech/rfdom/" rel="noopener noreferrer"&gt;my GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Generating randomness is fascinating, and I always wanted to go deeper than just importing some library into my python project and calling &lt;code&gt;.random()&lt;/code&gt;. So I set out to build my own library with its own &lt;code&gt;.random()&lt;/code&gt; function. Revolutionary, I know.&lt;/p&gt;

&lt;p&gt;But I didn't want to just cobble together pseudo-random values. I wanted to build a &lt;strong&gt;hardware random number generator&lt;/strong&gt; (HRNG): one that uses actual physical processes to generate entropy. Think Linux's &lt;code&gt;/dev/random&lt;/code&gt;, which harvests entropy from environmental noise: keyboard timings, mouse movements, disk I/O patterns. Windows also have a component named &lt;strong&gt;CNG&lt;/strong&gt; (Cryptography Next Generation) that uses similar inputs.   &lt;/p&gt;

&lt;p&gt;When I hear &lt;em&gt;noise&lt;/em&gt;, the first thing that comes to mind is this:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F43bb815d%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F43bb815d%2F" alt="Radio noise"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
For the uninitiated, this is an &lt;strong&gt;SDR&lt;/strong&gt; (Software Defined Radio), a real-time visualization of the radio spectrum around me. Notice the constant flickering at the bottom? That's noise, pure and simple. &lt;em&gt;Nothing&lt;/em&gt; is transmitting there, yet the intensity is constantly dancing. It's the same static you hear when tuning to an empty FM frequency. If we could capture and measure that movement, we'd have a source of &lt;strong&gt;&lt;em&gt;true&lt;/em&gt;&lt;/strong&gt; randomness, unpredictable and nearly impossible to manipulate.&lt;/p&gt;

&lt;p&gt;So, based on that, I decided to get to work. Note that I will be using a &lt;a href="https://www.SDR.com/v4/" rel="noopener noreferrer"&gt;SDR blog v4&lt;/a&gt; to capture radio signals and compute them.  &lt;/p&gt;

&lt;p&gt;Here is a global overview of what I needed to do to get this project done:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☐ Find a way of getting the radio signals on my PC&lt;/li&gt;
&lt;li&gt;☐ Process them to generate randomness&lt;/li&gt;
&lt;li&gt;☐ Create the core functions&lt;/li&gt;
&lt;li&gt;☐ Add other functions on top of that&lt;/li&gt;
&lt;li&gt;☐ Check that everything works and isn't easily affected by external events&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Setting Up the Foundation
&lt;/h2&gt;

&lt;p&gt;So, first of all, I needed to find a way to programmatically access my radio data, served as samples. I'd tried this on Windows before with no luck, so this time I went straight to a &lt;a href="https://gist.github.com/douxxtech/793fe37022d9551df3114f6d72498b94" rel="noopener noreferrer"&gt;template&lt;/a&gt; that connects &lt;strong&gt;over the network&lt;/strong&gt; on an &lt;code&gt;rtl_tcp&lt;/code&gt; server running on one of my Raspberry Pis.&lt;/p&gt;

&lt;p&gt;Once everything setup, I was able to receive samples. The code is quite simple, it uses a socket to connect to the rtl_tcp server, and then reads samples from it. Here is a part of the &lt;code&gt;read_samples&lt;/code&gt; function, that reads and processes the signals:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F386dbaa4%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F386dbaa4%2F" alt="codesnap"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;As we can see, it computes IQ samples. I and Q refer to two components of a &lt;strong&gt;complex baseband signal&lt;/strong&gt;. They capture both amplitude and phase of the RF signal.  &lt;/p&gt;

&lt;p&gt;Well, anyways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Find a way of getting the radio signals on my PC&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Creating Randomness
&lt;/h2&gt;

&lt;p&gt;The next step is building the "randomizer". It is basically a function that will take, as an input, samples from our SDR, and output a seed we will base our calculations on later.&lt;br&gt;&lt;br&gt;
To do this, I've tried a couple of ways  (2, actually), but the most efficient method was using &lt;strong&gt;Phase difference&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The phase is the &lt;strong&gt;angle&lt;/strong&gt; of the signal at a given moment. It can be easily calculated using both values of an IQ sample using this formula:&lt;br&gt;
&lt;code&gt;angle = atan2(Q, I)&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
We'll do this for both the current iteration value (&lt;code&gt;n&lt;/code&gt;), and the previous one (&lt;code&gt;n-1&lt;/code&gt;).  &lt;/p&gt;

&lt;p&gt;After getting both angles, we will retrieve the phase difference, that will tell us in which direction the signal rotated since the previous sample. It can be done like this: &lt;code&gt;delta = (current_angle - previous_angle + π) % 2π - π&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The wrapping in &lt;code&gt;π&lt;/code&gt; is to ensure that the result stays between -π and +π, and doesn't make big jumps, like going from 2° to -359° just by rotating 3°.  &lt;/p&gt;

&lt;p&gt;We got the rotation angle, but that value is still too complex to be properly processed. We will reduce it to a simple &lt;strong&gt;bit&lt;/strong&gt;. The easiest way to do this is by checking its direction:&lt;br&gt;
&lt;code&gt;bit = delta &amp;gt; 0 ? 1 : 0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now that we got a bit, we're simply repeating this &lt;code&gt;n&lt;/code&gt; samples times, to get a randomly generated bits array.&lt;/p&gt;

&lt;p&gt;But there's a problem. After running the program, and logging some statistics, &lt;strong&gt;I observed more 1s than 0s&lt;/strong&gt;, which isn't great, since the program would tend to go on the 1 side more than the 0 one.&lt;/p&gt;

&lt;p&gt;Fixing that is easy, and there are plenty of methods available. I used a very simple one: XOR-ing all the values together, to spread the 1s and 0s. For those who don't know what it implies, it is a simple bit operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 &amp;amp; 0 -&amp;gt; 0
0 &amp;amp; 1 -&amp;gt; 1
1 &amp;amp; 0 -&amp;gt; 1
1 &amp;amp; 1 -&amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used that to compare both bits each others, and the results were perfect: we went from a near 20% difference to a max of around 2%.&lt;/p&gt;

&lt;p&gt;Finally, for convenience, I'm hashing the results to get an uniform seed to continue with (using SHA256).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Process the signals to generate randomness&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  Code explained in this part
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Ff6a02ec2%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Ff6a02ec2%2F" alt="code snippet"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Core Functionalities
&lt;/h2&gt;

&lt;p&gt;We now have our random seed source, but now we have to let the user actually &lt;em&gt;use&lt;/em&gt; it.&lt;br&gt;&lt;br&gt;
My first plan: every time someone calls &lt;code&gt;.random()&lt;/code&gt;, grab fresh samples from the SDR, generate a seed, and convert it to a float.&lt;/p&gt;

&lt;p&gt;This was &lt;strong&gt;painfully slow&lt;/strong&gt;: we were hitting the hardware for every single random number.&lt;/p&gt;

&lt;p&gt;Solution: make it &lt;strong&gt;hybrid&lt;/strong&gt;. Use a fast Pseudo-Random Number Generator (PRNG) that gets periodically reseeded with fresh radio entropy, instead of hitting the SDR every time.&lt;/p&gt;

&lt;p&gt;This was fairly straightforward to achieve: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We first update our seeder to update its seed each X milliseconds, using a runner thread.&lt;/li&gt;
&lt;li&gt;Then, we create a Pseudo-Random Number generator. I used a simple &lt;strong&gt;Linear Congruential Generator&lt;/strong&gt; (LCG), made with the help of &lt;a href="https://www.youtube.com/watch?v=mXBGXU0zJnw" rel="noopener noreferrer"&gt;this very good video&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;We change our code to constantly feed the generator with our new seed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the code uses the LCG as a base, but it is always reseeded with our seeds taken from our radio samples.&lt;/p&gt;

&lt;p&gt;Now, let's build our core random class, that we will call &lt;code&gt;RFDom&lt;/code&gt;. We will optionally pass arguments to it, such as the rtl_tcp host, frequency ranges to scan, gain, and other settings.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.random()&lt;/code&gt; (returns &lt;code&gt;[0.0, 1.0)&lt;/code&gt;) was pretty simple to do, as it used the same code as the youtube tutorial, same for &lt;code&gt;.randint(a: int, b: int)&lt;/code&gt;, that returns &lt;code&gt;[a, b]&lt;/code&gt;. I've just had to implement a &lt;code&gt;include: bool&lt;/code&gt; parameter to the LCG API, but that was quite easy.&lt;/p&gt;

&lt;p&gt;
  LCG .get_float()
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fdda6ace7%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fdda6ace7%2F" alt="code snippet"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;

&lt;p&gt;
  RFDom .random()
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fa1096e26%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2Fa1096e26%2F" alt="code snippet"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;

&lt;p&gt;And done !&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Create the core functions&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding Other Stuff
&lt;/h2&gt;

&lt;p&gt;After adding &lt;code&gt;.random()&lt;/code&gt; -&amp;gt; &lt;code&gt;[0.0, 1.0)&lt;/code&gt;, &lt;code&gt;.uniform(a: float, b: float)&lt;/code&gt; -&amp;gt; &lt;code&gt;[a, b]&lt;/code&gt;, and &lt;code&gt;.randint(a: int, b: int)&lt;/code&gt; -&amp;gt; &lt;code&gt;[a, b]&lt;/code&gt; I've looked to the core functions of the python &lt;code&gt;random&lt;/code&gt; library, and tried to replicate them. Some of them were really easy, some were quite a bit harder to do. I'm not going to explain all of them, since it's just a bunch of formulas, but here are two that I found quite interesting:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Choices
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;.choices()&lt;/code&gt; func takes at max 4 arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;population: a sequence of objects&lt;/li&gt;
&lt;li&gt;weight (optional): the probability weight of each object to get picked&lt;/li&gt;
&lt;li&gt;cum_weights (optional): preprocessed cumulative weights&lt;/li&gt;
&lt;li&gt;k (optional): the number of objects to pick&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The way it works is pretty simple, even tho it was a bit hard to implement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each population object has a weight, either the same for every object, or a given one for each object.&lt;/li&gt;
&lt;li&gt;If no weight (or cum_weights) has been provided, simply take a random object in the &lt;code&gt;population&lt;/code&gt; array &lt;code&gt;k&lt;/code&gt; times.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F94137cba%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F94137cba%2F" alt="code snippet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If a weight array is provided, we will need to build a cum_weight list, if it isn't already provided.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The cumulative weights represent the range where a number falls into an object, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;objects -&amp;gt; ["apple", "banana", "orange"]
weights -&amp;gt; [4,        1,       6]

cum_weights = [4,     5,       11]

=&amp;gt; 
apple -&amp;gt; [0, 4)
banana -&amp;gt; [4, 5)
orange -&amp;gt; [5, 11)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Choose a random object &lt;code&gt;k&lt;/code&gt; times, based on the weights:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;we use &lt;code&gt;.uniform(0, max_weight)&lt;/code&gt; to get a float value, get the object falling in that range, add it to the list&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Return the list: we return a list of &lt;code&gt;k&lt;/code&gt; elements chosen randomly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F36594512%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F36594512%2F" alt="code snippet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. shuffle
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;.shuffle()&lt;/code&gt; function is much simpler, but I wanted to talk about the algorithm.&lt;br&gt;
It takes a list as the only argument and edits it in-place (doesn't return it, but changes the original).&lt;/p&gt;

&lt;p&gt;To do this, we're using the Fisher-Yates algorithm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starting from the beginning of the list, for each index &lt;code&gt;i&lt;/code&gt;, a random index &lt;code&gt;j&lt;/code&gt; is chosen from the range &lt;code&gt;[i, n - 1]&lt;/code&gt;, and the elements at positions &lt;code&gt;i&lt;/code&gt; and &lt;code&gt;j&lt;/code&gt; are swapped.&lt;/li&gt;
&lt;li&gt;Repeat until everything has been shuffled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is represented by this code:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F71da375e%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F71da375e%2F" alt="code snippet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Add other functions on top of that&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trying to break the randomizer
&lt;/h2&gt;

&lt;p&gt;My first idea (before even starting the project) was to try to break it out by overflowing the bandwidth using &lt;a href="https://git.douxx.tech/fmjam/" rel="noopener noreferrer"&gt;this tool&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;I then made this simple code to have an eye on the values&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F432fe4b2%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F432fe4b2%2F" alt="code snippet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ran it, first without any special radio broadcast or whatsoever.&lt;/p&gt;

&lt;p&gt;Then, I ran the program, and I observed. No changes at all, but that was predictable. Even with the bandwidth saturated, oscillations in the signal are still there, and the phase difference still works. I also later had an AI analyze the results, which confirmed there weren't any major changes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ &lt;del&gt;Check that everything works and isn't easily affected by external events&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  For the End
&lt;/h2&gt;

&lt;p&gt;This project began with a simple question: &lt;em&gt;where does randomness actually come from ?&lt;/em&gt;, and turned into a deep dive into radio physics, signal processing, entropy extraction, and practical limits.&lt;/p&gt;

&lt;p&gt;Building this taught me that randomness isn't magic: it's &lt;strong&gt;trade-offs&lt;/strong&gt;. Hardware entropy is slow but truly random. PRNGs are fast but predictable. Combining both? That's where it gets interesting.&lt;/p&gt;

&lt;p&gt;Is this generator cryptographically secure ? Probably not. Is it faster than Python's built-in &lt;code&gt;random&lt;/code&gt; ? Definitely not. Would I recommend using this in production (or in general) ? Absolutely not.&lt;/p&gt;

&lt;p&gt;But that &lt;strong&gt;was never the point&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the end, I won't replace the casual &lt;code&gt;import random&lt;/code&gt; when I need randomness, but I learned how this works behind the scenes, even if it's only a fraction of the modern random generators.&lt;/p&gt;

&lt;p&gt;If you're interested into seeing the full code, I uploaded it to &lt;a href="https://git.douxx.tech/RFDom" rel="noopener noreferrer"&gt;my GitHub&lt;/a&gt;. Usage examples can be found in the &lt;code&gt;/examples/&lt;/code&gt; dir, and installation instructions on the README.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions? Ideas?
&lt;/h2&gt;

&lt;p&gt;If you have any questions about the implementation, suggestions for improvements, or you've experimented with similar approaches, feel free to drop a comment below. I'm curious to hear what you think!&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;p&gt;Here are some links of videos / websites that I used to understand the subject better (not in any specific order). I might've forgotten some.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Random number generation - &lt;a href="https://en.wikipedia.org/wiki/Random_number_generation" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pseudo-Random Number Generator From Scratch in Python - &lt;a href="https://www.youtube.com/watch?v=mXBGXU0zJnw" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Entropy (information theory) - &lt;a href="https://en.wikipedia.org/wiki/Entropy_(information_theory)" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;XOR (Exclusive or) - &lt;a href="https://en.wikipedia.org/wiki/Exclusive_or" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Fisher–Yates shuffle - &lt;a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Linear congruential generator - &lt;a href="https://en.wikipedia.org/wiki/Linear_congruential_generator" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SHA256 - &lt;a href="http://en.wikipedia.org/wiki/SHA-2" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Actual Generator
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F19367830%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.dbo.one%2F19367830%2F" alt="A SDR connected to a raspberry pi"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>radio</category>
      <category>python</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
