<?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: Jaime Lopez</title>
    <description>The latest articles on DEV Community by Jaime Lopez (@jailop).</description>
    <link>https://dev.to/jailop</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%2F1276374%2F9ce7c62a-025c-4cb9-b5ec-630d40d113e0.jpeg</url>
      <title>DEV Community: Jaime Lopez</title>
      <link>https://dev.to/jailop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jailop"/>
    <language>en</language>
    <item>
      <title>Using Zig Functions from Python</title>
      <dc:creator>Jaime Lopez</dc:creator>
      <pubDate>Tue, 17 Feb 2026 05:17:13 +0000</pubDate>
      <link>https://dev.to/jailop/using-zig-functions-from-python-3ki6</link>
      <guid>https://dev.to/jailop/using-zig-functions-from-python-3ki6</guid>
      <description>&lt;p&gt;Python proves excellent for rapid prototyping and its ecosystem is&lt;br&gt;
extensive; however, when real speed is required, compiled languages offer&lt;br&gt;
significant advantages. Python provides excellent interoperability with&lt;br&gt;
C, and Zig can share the same Application Binary Interface (ABI) as C,&lt;br&gt;
which allows establishing this connection.&lt;/p&gt;

&lt;p&gt;This compatibility enables writing performance-critical parts in Zig and&lt;br&gt;
exposing them as if they were normal Python functions. Zig offers&lt;br&gt;
features that C doesn't provide natively: more explicit memory&lt;br&gt;
management, compile-time execution, and error handling that forces&lt;br&gt;
consideration of all failure cases.&lt;/p&gt;

&lt;p&gt;It's important to clarify from the outset: what's presented here is not&lt;br&gt;
the only way to connect Python with Zig. There are different approaches,&lt;br&gt;
each with advantages and disadvantages. This article shows a&lt;br&gt;
straightforward method using C's FFI and Python's &lt;code&gt;ctypes&lt;/code&gt; library, which&lt;br&gt;
has the advantage of being clear and requiring no additional&lt;br&gt;
dependencies. What matters is understanding the concept; once understood,&lt;br&gt;
you can apply the method with other variants and to different problems.&lt;/p&gt;

&lt;p&gt;A simple HTTP client will be built to illustrate the process. It's not&lt;br&gt;
trivial. There are several steps and details that require attention. But&lt;br&gt;
following this same procedure you can integrate virtually any Zig&lt;br&gt;
functionality into Python. We'll see how to create Zig functions, wrap&lt;br&gt;
them so C (and Python) can understand them, and handle details like&lt;br&gt;
memory management, character strings, and errors that transit between&lt;br&gt;
languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The request Function in Zig
&lt;/h2&gt;

&lt;p&gt;The straightforward implementation is presented. A function is required&lt;br&gt;
that performs HTTP GET requests and returns the response in a format that&lt;br&gt;
C (and thus Python) can understand. The function signature is revealing:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn request(allocator: std.mem.Allocator, url: [:0]const u8) ![:0]u8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Two parameters: first &lt;code&gt;allocator&lt;/code&gt;, which represents Zig's way of handling&lt;br&gt;
memory allocation. There are no hidden malloc() calls—everything is&lt;br&gt;
explicit. Second, &lt;code&gt;url&lt;/code&gt;, which is a null-terminated pointer&lt;br&gt;
(&lt;code&gt;[:0]const u8&lt;/code&gt;). Zig has its own way of representing this, but it's&lt;br&gt;
equivalent to &lt;code&gt;const char*&lt;/code&gt; in C.&lt;/p&gt;

&lt;p&gt;The return type &lt;code&gt;![:0]u8&lt;/code&gt; deserves attention. The initial &lt;code&gt;!&lt;/code&gt; indicates&lt;br&gt;
this can fail. You can get a pointer with the HTTP response, or you can&lt;br&gt;
get an error. The &lt;code&gt;[:0]u8&lt;/code&gt; represents the successful result: bytes with a&lt;br&gt;
zero at the end, exactly what C expects.&lt;/p&gt;

&lt;p&gt;The implementation uses Zig 0.15's HTTP API and an &lt;code&gt;ArrayListUnmanaged&lt;/code&gt;&lt;br&gt;
to accumulate the response byte by byte:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const std = @import("std");

fn request(allocator: std.mem.Allocator, url: [:0]const u8) ![:0]u8 {
    const len = std.mem.len(url);
    const url_slice = url[0..len];
    const uri = try std.Uri.parse(url_slice);
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    var req = try client.request(.GET, uri, .{});
    defer req.deinit();

    try req.sendBodiless();
    var redirect_buffer: [4096]u8 = undefined;
    var response = try req.receiveHead(&amp;amp;redirect_buffer);

    var list = std.ArrayListUnmanaged(u8){};
    errdefer list.deinit(allocator);

    var reader = response.reader(&amp;amp;.{});
    try reader.appendRemainingUnlimited(allocator, &amp;amp;list);

    try list.append(allocator, 0);
    const owned = try list.toOwnedSlice(allocator);
    return @ptrCast(owned.ptr);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This implementation avoids unnecessary data copies. Note that it accepts&lt;br&gt;
&lt;code&gt;[:0]const u8&lt;/code&gt; directly—the same format that strings come in from C. The&lt;br&gt;
process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Converts the pointer to a normal slice to interpret the URL&lt;/li&gt;
&lt;li&gt;URL parsing: &lt;code&gt;std.Uri.parse&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Creates the HTTP client with the provided allocator&lt;/li&gt;
&lt;li&gt;Constructs a GET request with &lt;code&gt;client.request&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;To the server: &lt;code&gt;sendBodiless()&lt;/code&gt; sends it without a body&lt;/li&gt;
&lt;li&gt;Response: &lt;code&gt;receiveHead()&lt;/code&gt; reads the headers (with space for redirects)&lt;/li&gt;
&lt;li&gt;Accumulating data: Reads the entire body with
&lt;code&gt;ArrayListUnmanaged&lt;/code&gt;—efficient because it grows as needed&lt;/li&gt;
&lt;li&gt;The null terminator: Adds a zero at the end (C requirement)&lt;/li&gt;
&lt;li&gt;Returns a pointer to the result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A notable aspect of Zig: &lt;code&gt;defer&lt;/code&gt; and &lt;code&gt;errdefer&lt;/code&gt;. The first indicates&lt;br&gt;
"when exiting this function, regardless of how, clean this up". The&lt;br&gt;
second indicates "if an error occurs, clean this up". Each &lt;code&gt;try&lt;/code&gt;&lt;br&gt;
represents a point where something can fail; if it fails, the error&lt;br&gt;
propagates immediately. It certainly looks like a lot of code, but it's&lt;br&gt;
clear what can fail and where.&lt;/p&gt;

&lt;p&gt;Functionality can be verified with Zig's testing system:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test "Request" {
    const allocator = std.testing.allocator;
    const url = "http://localhost";
    const response = try request(allocator, url.ptr);
    defer {
        const len = std.mem.len(response) + 1;
        allocator.free(response[0..len]);
    }
    try std.testing.expect(std.mem.len(response) &amp;gt; 0);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Observe how memory is freed: first the length is calculated (including&lt;br&gt;
the final zero), then it's converted to a slice, and finally passed to&lt;br&gt;
&lt;code&gt;allocator.free()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Saving this in &lt;code&gt;request.zig&lt;/code&gt; allows execution:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ zig test request.zig
All 1 tests passed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note: an HTTP server running on localhost is required for this test to&lt;br&gt;
work. The &lt;code&gt;testing.allocator&lt;/code&gt; is special; it detects when memory isn't&lt;br&gt;
freed. It's very useful for catching memory leaks early.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wrappers: Speaking C's Language
&lt;/h2&gt;

&lt;p&gt;The part where Zig is made to speak C is now presented. Zig functions are&lt;br&gt;
powerful, but Python cannot call them directly. A translator is&lt;br&gt;
required—wrapper functions that C (and thus Python) can understand.&lt;/p&gt;

&lt;p&gt;In this case &lt;code&gt;request()&lt;/code&gt; already returns &lt;code&gt;[:0]u8&lt;/code&gt;, which is exactly what&lt;br&gt;
C expects. However, some adjustments are needed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Memory management: Provide Python an explicit way to free what's
allocated&lt;/li&gt;
&lt;li&gt;Errors: Convert Zig's error unions to something C understands
(basically, in this context NULL means "it failed")&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The signatures turn out like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export fn request_wrapper(url: [:0]const u8) ?[:0]u8
export fn request_deallocate(result: [:0]u8) void
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;[:0]const u8&lt;/code&gt; is a null-terminated pointer to bytes (equivalent to&lt;br&gt;
&lt;code&gt;const char*&lt;/code&gt;). The &lt;code&gt;?&lt;/code&gt; before the return type means "optional"—it can be&lt;br&gt;
a valid pointer or &lt;code&gt;null&lt;/code&gt;. The &lt;code&gt;export&lt;/code&gt; keyword instructs the compiler to&lt;br&gt;
generate symbols that C can link.&lt;/p&gt;

&lt;p&gt;The implementation of &lt;code&gt;request_wrapper&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export fn request_wrapper(url: [:0]const u8) ?[:0]u8 {
    const allocator = std.heap.page_allocator;
    return request(allocator, url) catch return null;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Observe how straightforward the approach is. Since &lt;code&gt;request()&lt;/code&gt; already&lt;br&gt;
accepts and returns the correct format, complex conversions aren't&lt;br&gt;
required:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Zero extra conversions: &lt;code&gt;request()&lt;/code&gt; works with &lt;code&gt;[:0]const u8&lt;/code&gt; directly&lt;/li&gt;
&lt;li&gt;Zero copies: Direct call, without intermediate buffers&lt;/li&gt;
&lt;li&gt;Single allocation: The one &lt;code&gt;request()&lt;/code&gt; performs internally&lt;/li&gt;
&lt;li&gt;Simple errors: &lt;code&gt;catch return null&lt;/code&gt; converts any error to NULL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The function to free memory is critical:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export fn request_deallocate(result: [:0]u8) void {
    const allocator = std.heap.page_allocator;
    const len = std.mem.len(result) + 1;
    allocator.free(result[0..len]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It must use the same &lt;code&gt;page_allocator&lt;/code&gt; used for allocation (this is&lt;br&gt;
important—you cannot mix allocators). It calculates the total length&lt;br&gt;
(including the zero), converts the pointer to a slice, and finally frees.&lt;br&gt;
This provides a means and control over when to free memory, which is&lt;br&gt;
essential when crossing language boundaries.&lt;/p&gt;

&lt;p&gt;A test for the wrappers is shown:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test "Wrappers" {
    const url = "http://localhost";
    const body = request_wrapper(url.ptr);
    try std.testing.expect(std.mem.len(body.?) &amp;gt; 0);
    request_deallocate(body.?);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If everything works correctly:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ zig test request.zig 
All 2 tests passed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Testing from C First
&lt;/h2&gt;

&lt;p&gt;Before integrating with Python, it's advisable to verify everything works&lt;br&gt;
from C. It's simpler to debug here than when three layers are involved. A&lt;br&gt;
header that C understands is created:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#ifndef _REQUEST_H
#define _REQUEST_H 0

char *request_wrapper(const char *url);
void request_deallocate(char *content);

#endif // _REQUEST_H
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Simple: Zig's &lt;code&gt;[:0]const u8&lt;/code&gt; translates to &lt;code&gt;const char*&lt;/code&gt; in C, &lt;code&gt;[:0]u8&lt;/code&gt;&lt;br&gt;
translates to &lt;code&gt;char*&lt;/code&gt;. Zig's optional types disappear. In C only the&lt;br&gt;
pointer exists and you must check NULL manually.&lt;/p&gt;

&lt;p&gt;A test program in C:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include "request.h"

int main(int argc, char *argv[]) {
    if (argc &amp;lt; 2) {
        fprintf(stderr, "Usage: %s URL\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    const char *url = argv[1];
    char *content = request_wrapper(url);
    if (!content) {
        printf("Failed\n");
        exit(EXIT_FAILURE);
    }
    printf("%s\n", content);
    request_deallocate(content);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The classic C pattern: &lt;code&gt;request_wrapper()&lt;/code&gt; is called, verification that&lt;br&gt;
it's not NULL, the result is used, and then &lt;code&gt;request_deallocate()&lt;/code&gt; is&lt;br&gt;
called to free memory. If this last step is omitted, there will be memory&lt;br&gt;
leaks.&lt;/p&gt;

&lt;p&gt;Compile and run:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ zig build-lib -dynamic request.zig
$ gcc example.c -L. -lrequest -o example
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
$ ./example http://localhost
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Zig generates a shared library (&lt;code&gt;.so&lt;/code&gt; on Linux, &lt;code&gt;.dylib&lt;/code&gt; on macOS, &lt;code&gt;.dll&lt;/code&gt;&lt;br&gt;
on Windows). The &lt;code&gt;-dynamic&lt;/code&gt; flag indicates that a shared library is&lt;br&gt;
desired rather than static. Then gcc links the C program against the&lt;br&gt;
newly created Zig library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finally: Python
&lt;/h2&gt;

&lt;p&gt;Python integration is now presented. The Zig library will be wrapped to&lt;br&gt;
appear as any Python module. &lt;code&gt;ctypes&lt;/code&gt; is used, which comes included with&lt;br&gt;
Python and allows loading shared libraries and calling their functions.&lt;br&gt;
The challenge lies in correctly declaring types and not forgetting to&lt;br&gt;
free memory.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;Request&lt;/code&gt; class that encapsulates all the complexity:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ctypes

class Request:
    def __init__(self):
        self.lib = ctypes.CDLL("./librequest.so")
        self.lib.request_wrapper.argtypes = [ctypes.c_char_p]
        self.lib.request_wrapper.restype = ctypes.POINTER(ctypes.c_char)

    def get(self, url: str) -&amp;gt; str:
        result = self.lib.request_wrapper(url.encode())
        if not result:
            raise RuntimeError("Request failed")
        i = 0
        while result[i] != b'\0':
            i += 1
        content = result[:i].decode()
        self.lib.request_deallocate(result)
        return content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Analysis of each part:&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;__init__&lt;/code&gt;: The library is loaded with &lt;code&gt;CDLL&lt;/code&gt; (for functions that&lt;br&gt;
follow C calling conventions). Then the signatures are declared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;argtypes = [ctypes.c_char_p]&lt;/code&gt;: the function expects a C string&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;restype = ctypes.POINTER(ctypes.c_char)&lt;/code&gt;: returns a pointer to
characters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;code&gt;get&lt;/code&gt;: The entire cycle is handled:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Python string is converted to bytes (&lt;code&gt;encode()&lt;/code&gt;)—ctypes knows how
to pass this as a C string&lt;/li&gt;
&lt;li&gt;The Zig function is called and NULL is checked&lt;/li&gt;
&lt;li&gt;Manual search for where the string ends (the &lt;code&gt;\0&lt;/code&gt; byte)&lt;/li&gt;
&lt;li&gt;The bytes are extracted and decoded to a Python string&lt;/li&gt;
&lt;li&gt;Critical: &lt;code&gt;request_deallocate()&lt;/code&gt; is called to free memory&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The payoff—using this from Python:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import request

req = request.Request()
body = req.get("http://localhost")
print(body)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;All the FFI complexity is encapsulated. For whoever uses it, it's simply&lt;br&gt;
another Python library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Python has been connected with Zig using C as a bridge. It's not magical,&lt;br&gt;
and it's definitely not trivial—there are several steps, types to&lt;br&gt;
convert, memory to handle carefully. But once the process is understood,&lt;br&gt;
it's replicable. The same steps followed here apply to any function you&lt;br&gt;
wish to expose: image processing, ML algorithms, binary protocol&lt;br&gt;
handling, etc.&lt;/p&gt;

&lt;p&gt;It's worth remembering: this is one path, not the path. Other ways exist&lt;br&gt;
to make Python and Zig communicate, each with its trade-offs. This one&lt;br&gt;
has the advantage of being relatively straightforward and not depending&lt;br&gt;
on external dependencies.&lt;/p&gt;

&lt;p&gt;If you wish to extend this example, you could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more HTTP methods (POST, PUT, DELETE)—the pattern is the same&lt;/li&gt;
&lt;li&gt;Expose configuration options (timeouts, headers, auth)&lt;/li&gt;
&lt;li&gt;Integrate computationally intensive algorithms written in Zig&lt;/li&gt;
&lt;li&gt;Wrap Zig libraries that interact with the operating system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key aspects to remember:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Memory is your responsibility: When crossing between languages, you
must know who allocated what and who should free it. Zig forces you to
be explicit, which helps avoid leaks, but also requires discipline.&lt;/li&gt;
&lt;li&gt;Types matter: Each language represents strings and arrays in its own
way. Conversions must be exact or everything fails.&lt;/li&gt;
&lt;li&gt;Errors in translation: Zig's errors aren't C's errors. You must
translate between systems—here NULL was used, but other approaches
exist.&lt;/li&gt;
&lt;li&gt;Test in layers: First Zig alone, then from C, finally from Python.
This way you identify where the problem is when something fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This can be applied with HTTP, but also with any other functionality. Zig&lt;br&gt;
is maturing, and it's likely we'll see more libraries that combine the&lt;br&gt;
best of both worlds: Python's expressiveness with Zig's speed and&lt;br&gt;
low-level control.&lt;/p&gt;

&lt;p&gt;Note: This article has originally been written in&lt;br&gt;
&lt;a href="//./zig-through-python-es.html"&gt;Spanish&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>zig</category>
      <category>python</category>
      <category>c</category>
      <category>bindings</category>
    </item>
    <item>
      <title>n2n: build a private network over the Internet</title>
      <dc:creator>Jaime Lopez</dc:creator>
      <pubDate>Wed, 21 Feb 2024 22:01:49 +0000</pubDate>
      <link>https://dev.to/jailop/n2n-build-a-private-network-over-the-internet-1b44</link>
      <guid>https://dev.to/jailop/n2n-build-a-private-network-over-the-internet-1b44</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;n2n&lt;/code&gt; is a peer-to-peer application that emulates a local network connection over the Internet. That means that two computers can ping each other and use any other local area network service, even if they are on different private networks. As a practical example, you can connect your laptop to your home computer from anywhere using a &lt;code&gt;SSH&lt;/code&gt; session, among other options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshorm5ajzggbeuhnmtud.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshorm5ajzggbeuhnmtud.jpg" alt="Image description" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To establish the connection, &lt;code&gt;n2n&lt;/code&gt; requires a supernode that maintains a record of the connected devices in the virtual network, so that they can be located. Once the connection has been established, communication takes place directly device to device, that is, in a point-to-point scheme. Eventually, in the event that the security of the private network in which the devices are located prevents the point-to-point connection, the supernode can also mediate the transport of the data.&lt;/p&gt;

&lt;p&gt;Data travels encrypted. Only devices at each point can decrypt them. This ensures that no intermediate point, not even the supernode that helps to establish the connection, can decrypt the content of the messages. The security scheme implemented by &lt;code&gt;n2n&lt;/code&gt; is through a password, which for basic cases is sufficient.&lt;/p&gt;

&lt;p&gt;As an example, a use case is presented with three computers with Linux operating system. One of them is located on a public network and will act as a supernode. The other two are behind different private networks and will act as nodes. &lt;code&gt;n2n&lt;/code&gt; is available as an installable package on most GNU/Linux distributions. It is assumed that the example distribution makes use of &lt;code&gt;systemd&lt;/code&gt; services, such as it is in Debian, Ubuntu, CentOS, Rocky Linux, and ArchLinux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supernode configuration
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;n2n&lt;/code&gt; configuration files are located in &lt;code&gt;/etc/n2n&lt;/code&gt;. Supernode configuration is in the file &lt;code&gt;supernode.conf&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;-p=37777
-c=community.list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Option &lt;code&gt;-p&lt;/code&gt; indicates the port on which the supernode will be listening for requests from nodes that need to connect. Option &lt;code&gt;-c&lt;/code&gt; is optional and indicates the list of communities in which nodes will be able to establish connections. A community is a representation of a virtual local area network identified by a name. In this example, the content of the file &lt;code&gt;community.list&lt;/code&gt; is any identifier for a virtual network, like &lt;code&gt;k2t9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once the configuration is complete, you can start the service, enable it to automatically load when the computer is restarted, and verify that the service is running correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;supernode $ sudo systemctl start supernode

supernode $ sudo systemctl enable supernode
Created symlink /etc/systemd/system/multi-user.target.wants/supernode.service → /usr/lib/systemd/system/supernode.service.

supernode $ sudo systemctl status supernode
● supernode.service - n2n supernode process
     Loaded: loaded (/usr/lib/systemd/system/supernode.service; enabled; preset: disabled)
     Active: active (running) since Wed 2024-02-21 15:48:30 UTC; 12s ago
   Main PID: 3396157 (supernode)
      Tasks: 2 (limit: 1139)
     Memory: 316.0K (peak: 584.0K)
        CPU: 2ms
     CGroup: /system.slice/supernode.service
              └─3396157 /usr/bin/supernode /etc/n2n/supernode.conf -f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Node configuration
&lt;/h2&gt;

&lt;p&gt;Assuming that the supernode has the public IP 140.40.40.1, on each node the connection can be established using the command &lt;code&gt;edge&lt;/code&gt; provided by &lt;code&gt;n2n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the first node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node1 $ sudo edge -c k2t9 -k 1234 -a 192.168.100.1 -f -l 140.40.40.1:37777
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the second node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node2 $ sudo edge -c k2t9 -k 1234 -a 192.168.100.2 -f -l 140.40.40.1:37777
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the previous examples, argument &lt;code&gt;-c&lt;/code&gt; is the community identifier. Argument &lt;code&gt;-k&lt;/code&gt;is the password. All nodes must use the same community identifier and the same password. The argument &lt;code&gt;-a&lt;/code&gt; allows you to specify the IP of the node. Option &lt;code&gt;-f&lt;/code&gt; is to tell the edge not to run as a service. The last argument is &lt;code&gt;-l&lt;/code&gt;, which indicates the address and port of the supernode.&lt;/p&gt;

&lt;p&gt;When the command &lt;code&gt;edge&lt;/code&gt; is executed, virtual network interfaces will be created on each node. For example, querying the network interfaces with &lt;code&gt;ip addr&lt;/code&gt;, the first node will display something similar to the following output. In this case, the name of the network interface is &lt;code&gt;n2n0&lt;/code&gt; and it is assigned IP 192.168.100.1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4: n2n0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1290 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 2e:9b:e3:14:88:7e brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.1/24 brd 192.168.100.255 scope global n2n0
      valid_lft forever preferred_lft forever
    inet6 fe80::2c9b:e3ff:fe14:887e/64 scope link proto kernel_ll 
      valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, nodes 1 and 2 shown in this example are able to communicate, for example, by pinging  one each other or establishing an SSH session.&lt;/p&gt;

&lt;p&gt;The command &lt;code&gt;edge&lt;/code&gt; can also be run as a service. In this way, when the computer is booted, the connection with the virtual local network created through &lt;code&gt;n2n&lt;/code&gt; will be automatically enabled. For this purpose, it is necessary to create the configuration file &lt;code&gt;/etc/n2n/edge.conf&lt;/code&gt;. Continuing with the example, the configuration file for node 1 is shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-d=n2n0
-c=k2t9
-k=1234
-a=192.168.100.1
-p=50001
-l=140.40.40.1:37777
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional arguments in this configuration are &lt;code&gt;-d&lt;/code&gt;, which allows you to specify the name of the network interface, and &lt;code&gt;-p&lt;/code&gt; to indicate the port that will be used for connection to the virtual local network.&lt;/p&gt;

&lt;p&gt;Once the node configuration is complete, you can start the service, enable it to boot, and check its status, as shown in the following screenshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node1 $ sudo systemctl start edge

node1 $ sudo systemctl enable edge

node1 $ sudo systemctl status edge
  edge.service - n2n edge process
     Loaded: loaded (/usr/lib/systemd/system/edge.service; enabled; preset: disabled)
     Active: active (running) since Wed 2024-02-21 11:10:04 EST; 9s ago
   Main PID: 5333 (edge)
      Tasks: 3 (limit: 76743)
     Memory: 5.1M (peak: 6.6M)
        CPU: 14ms
     CGroup: /system.slice/edge.service
             └─5333 /usr/bin/edge /etc/n2n/edge.conf -f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Discussion
&lt;/h2&gt;

&lt;p&gt;Use cases for a virtual local network over the Internet are diverse. They include the connection between distant points, the linking of cloud services, collaboration between peers, remote access and telecommunication, and redundancy and disaster recovery. Point-to-point networks offer the additional advantage of not requiring, once the connection between nodes has been established, intermediary servers to maintain communication.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;n2n&lt;/code&gt; is a simple alternative for a point-to-point virtual local network over the Internet, as shown in this article. It has easy-to-define configuration and security options that do not require expert support. It can be sufficient for basic applications. &lt;code&gt;n2n&lt;/code&gt; is an open source project from the team at &lt;code&gt;ntop&lt;/code&gt;, which also maintains other projects for network monitoring and security.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Documentation: &lt;a href="https://www.ntop.org/products/n2n/"&gt;n2n, a Layer Two Peer-to-Peer VPN&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repository in Github: &lt;a href="https://github.com/ntop/n2n/"&gt;n2n&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Paper: Deri, L. y Andrews, R. (sf). &lt;a href="http://luca.ntop.org/n2n.pdf"&gt;N2N: A Layer Two Peer-to-Peer VPN&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ntop.org/about/about-us-2/"&gt;About ntop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>n2n</category>
      <category>vpn</category>
      <category>vlan</category>
    </item>
    <item>
      <title>n2n: construya una red privada sobre Internet</title>
      <dc:creator>Jaime Lopez</dc:creator>
      <pubDate>Wed, 21 Feb 2024 21:10:52 +0000</pubDate>
      <link>https://dev.to/jailop/n2n-construya-una-red-privada-sobre-internet-7gd</link>
      <guid>https://dev.to/jailop/n2n-construya-una-red-privada-sobre-internet-7gd</guid>
      <description>&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;n2n&lt;/code&gt;  es una aplicación punto a punto que emula una conexión de red local sobre Internet. Eso significa que dos computadoras pueden entre ellas hacer ping y usar cualquier otro servicio de red de área local, aún cuando se encuentren en diferentes redes privadas. Como ejemplo práctico, usted puede conectar desde cualquier lugar su computadora portátil con su computadora en casa mediante una sesión &lt;code&gt;SSH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslyr8fm6x0z2rn4h2i4o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslyr8fm6x0z2rn4h2i4o.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para establecer la conexión, &lt;code&gt;n2n&lt;/code&gt;  requiere de un supernodo que mantiene el registro de los dispositivos conectados en la red virtual, de tal forma que estos se puedan ubicar. Una vez que la conexión ha sido establecida, la comunicación transcurre directamente entre dispositivo y dispositivo, es decir, en un esquema punto a punto. Eventualmente, en caso de que la seguridad de la red privada en la que se encuentra los dispositivos impida la conexión punto a punto, el supernodo también puede intermediar en el transporte de los datos.&lt;/p&gt;

&lt;p&gt;Los datos viajan encriptados. Solo los dispositivos que están en cada punto pueden desencriptarlos. Esto garantiza que ningún punto intermedio, ni siquiera el supernodo que ayuda a establecer la conexión, pueda descifrar el contenido de los mensajes. El esquema de seguridad implementado por &lt;code&gt;n2n&lt;/code&gt;  es mediante contraseña, que para casos básicos puede ser suficiente.&lt;/p&gt;

&lt;p&gt;Como ejemplo, se presenta un escenario con tres computadoras con sistema operativo Linux. Una de ellas está ubicada en una red pública y actuará como supernodo. Las otras dos se encuentran detrás de diferentes redes privadas y actuarán como nodos. &lt;code&gt;n2n&lt;/code&gt;  está disponible como paquete instalable en la mayoría de distribuciones GNU/Linux. Se asume que la distribución del ejemplo hace uso de servicios &lt;code&gt;systemd&lt;/code&gt;, como Debian, Ubuntu, CentOS, Rocky Linux y ArchLinux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuración del supernodo
&lt;/h2&gt;

&lt;p&gt;Los archivos de configuración de &lt;code&gt;n2n&lt;/code&gt;  están ubicados en &lt;code&gt;/etc/n2n&lt;/code&gt;. La configuración del supernodo está en el archivo &lt;code&gt;supernode.conf&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

-p=37777
-c=community.list


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;La opción &lt;code&gt;-p&lt;/code&gt;  indica el puerto en el cual el supernodo estará escuchando peticiones de los nodos que necesiten conectarse. La opción &lt;code&gt;-c&lt;/code&gt;  es opcional e indica la lista de comunidades que podrán establecer conexiones. Una comunidad es la representación de una red de área local virtual identificada mediante un nombre. En este ejemplo, el contenido del archivo &lt;code&gt;community.list&lt;/code&gt;  es solo el identificador de una red virtual, &lt;code&gt;k2t9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Una vez la configuración está completa, se puede iniciar el servicio, habilitar su carga automática cuando la computadora sea reiniciada, y verificar que el servicio está corriendo correctamente.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

supernode $ sudo systemctl start supernode

supernode $ sudo systemctl enable supernode
Created symlink /etc/systemd/system/multi-user.target.wants/supernode.service → /usr/lib/systemd/system/supernode.service.

supernode $ sudo systemctl status supernode
● supernode.service - n2n supernode process
     Loaded: loaded (/usr/lib/systemd/system/supernode.service; enabled; preset: disabled)
     Active: active (running) since Wed 2024-02-21 15:48:30 UTC; 12s ago
   Main PID: 3396157 (supernode)
      Tasks: 2 (limit: 1139)
     Memory: 316.0K (peak: 584.0K)
        CPU: 2ms
     CGroup: /system.slice/supernode.service
              └─3396157 /usr/bin/supernode /etc/n2n/supernode.conf -f


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Configuración de los nodos
&lt;/h2&gt;

&lt;p&gt;Asumiendo que el supernodo tiene la IP pública &lt;code&gt;140.40.40.1&lt;/code&gt;, en cada nodo se puede establecer la conexión usando el comando &lt;code&gt;edge&lt;/code&gt; provisto por &lt;code&gt;n2n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;En el primer nodo:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

node1 $ sudo edge -c k2t9 -k 1234 -a 192.168.100.1 -f -l 140.40.40.1:37777


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;En el segundo nodo:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

node2 $ sudo edge -c k2t9 -k 1234 -a 192.168.100.2 -f -l 140.40.40.1:37777


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;En los ejemplos previos, el argumento &lt;code&gt;-c&lt;/code&gt;  es el identificador de la comunidad. El argumento &lt;code&gt;-k&lt;/code&gt;  es la contraseña. Todos los nodos deben hacer uso del mismo identificador de comunidad de la misma contraseña. El argumento &lt;code&gt;-a&lt;/code&gt;  permite especificar la IP del nodo. La opción &lt;code&gt;-f&lt;/code&gt;  es para indicarle a &lt;code&gt;edge&lt;/code&gt;  que no se ejecute como servicio. El último argumento es &lt;code&gt;-l&lt;/code&gt;, que indica la dirección y el puerto del supernodo.&lt;/p&gt;

&lt;p&gt;Cuando se ejecuta el comando &lt;code&gt;edge&lt;/code&gt;, en cada nodo se creará una interfaz virtual de red. Por ejemplo, consultando las interfaces de red con &lt;code&gt;ip addr&lt;/code&gt;, en el primer nodo aparecerá algo similar a la siguiente captura de pantalla. En este caso, el nombre de la interfaz de red es &lt;code&gt;n2n0&lt;/code&gt;  y tiene asignada la IP 192.168.100.1.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

4: n2n0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1290 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 2e:9b:e3:14:88:7e brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.1/24 brd 192.168.100.255 scope global n2n0
      valid_lft forever preferred_lft forever
    inet6 fe80::2c9b:e3ff:fe14:887e/64 scope link proto kernel_ll 
      valid_lft forever preferred_lft forever


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;En adelante, los nodos 1 y 2 mostrados en este ejemplo podrán comunicarse, por ejemplo, haciendo ping o estableciendo una sessión SSH.&lt;/p&gt;

&lt;p&gt;El comando &lt;code&gt;edge&lt;/code&gt;  también puede ser ejecutado como servicio. En esa forma, cuando se encienda la computadora, la conexión con la red local virtual creada mediante &lt;code&gt;n2n&lt;/code&gt;  será automáticamente habilitada. Para tal efecto, es necesario crear el archivo de configuración &lt;code&gt;/etc/n2n/edge.conf&lt;/code&gt;. Siguiendo con el ejemplo, el archivo de configuración para nodo 1 se muestra a continuación.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

-d=n2n0
-c=k2t9
-k=1234
-a=192.168.100.1
-p=50001
-l=140.40.40.1:37777


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Los argumentos adicionales en esta configuración son &lt;code&gt;-d&lt;/code&gt;, que permite especificar el nombre de la interfaz de red, y &lt;code&gt;-p&lt;/code&gt;  para indicar el puerto que será utilizado para la conexión a la red local virtual.&lt;/p&gt;

&lt;p&gt;Completada la configuración del nodo, se puede iniciar el servicio, habilitarlo para el arranque y verificar su estado, tal como se muestra en la siguiente captura de pantalla:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;node1 $ sudo systemctl start edge&lt;/p&gt;

&lt;p&gt;node1 $ sudo systemctl enable edge&lt;/p&gt;

&lt;p&gt;node1 $ sudo systemctl status edge&lt;br&gt;
  edge.service - n2n edge process&lt;br&gt;
     Loaded: loaded (/usr/lib/systemd/system/edge.service; enabled; preset: disabled)&lt;br&gt;
     Active: active (running) since Wed 2024-02-21 11:10:04 EST; 9s ago&lt;br&gt;
   Main PID: 5333 (edge)&lt;br&gt;
      Tasks: 3 (limit: 76743)&lt;br&gt;
     Memory: 5.1M (peak: 6.6M)&lt;br&gt;
        CPU: 14ms&lt;br&gt;
     CGroup: /system.slice/edge.service&lt;br&gt;
             └─5333 /usr/bin/edge /etc/n2n/edge.conf -f&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusión&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Los casos de uso de una red local virtual sobre Internet son diversos. Incluyen la conexión entre distantes puntos, el enlace de servicios en la nube, la colaboración entre pares, acceso y telecomunicación remota y redundancia y recuperación ante desastres. Las redes punto a punto ofrecen la ventaja adicional de no requerir,  una vez que la conexión entre nodos ha sido establecida, servidores intermediarios para mantener la comunicación.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;n2n&lt;/code&gt;  es una alternativa sencilla de red local virtual punto a punto sobre Internet, como se ha mostrado en este artículo. Tiene opciones de configuración y de seguridad fáciles de definir, que no requieren de  soporte experto, y que pueden ser suficientes para aplicaciones básicas. &lt;code&gt;n2n&lt;/code&gt;  es un proyecto de código abierto del equipo de &lt;code&gt;ntop&lt;/code&gt;, que también mantiene otros proyectos para monitoreo y seguridad de redes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Documentación: &lt;a href="https://www.ntop.org/products/n2n/" rel="noopener noreferrer"&gt;n2n, a Layer Two Peer-to-Peer VPN&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repositorio en Github: &lt;a href="https://github.com/ntop/n2n/" rel="noopener noreferrer"&gt;n2n&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Paper: Deri, L. y Andrews, R. (sf). &lt;a href="http://luca.ntop.org/n2n.pdf" rel="noopener noreferrer"&gt;N2N: A Layer Two Peer-to-Peer VPN&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ntop.org/about/about-us-2/" rel="noopener noreferrer"&gt;About ntop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>n2n</category>
      <category>vpn</category>
      <category>vlan</category>
    </item>
    <item>
      <title>Automatic light/dark theme with CSS</title>
      <dc:creator>Jaime Lopez</dc:creator>
      <pubDate>Thu, 08 Feb 2024 20:10:23 +0000</pubDate>
      <link>https://dev.to/jailop/automatic-lightdark-theme-with-css-phj</link>
      <guid>https://dev.to/jailop/automatic-lightdark-theme-with-css-phj</guid>
      <description>&lt;p&gt;Nowadays, it is expected that web pages change automatically between a light and a dark theme based on the user's desktop theme. Therefore, we, as developers, should provide at least two basic color configurations, a default light set and another for a dark theme.&lt;/p&gt;

&lt;p&gt;One way to accomplish that expectation is by setting the style sheet with variables to define the basic colors used in the web page, and a media selector to detect the user's default desktop theme.  Below, an example of implementing this solution is shown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Color variables for the light theme:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:root {
    --body-bgcolor: white;
    --body-fgcolor: #333;
    --link-color: #4169E1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Alternate color variables for the dark theme:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This ones are defined inside a media selector.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@media (prefers-color-scheme: dark) {
    :root {
        --body-bgcolor: #111;
        --body-fgcolor: #ccc;
        --link-color: #B0E0E6;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Using variable definitions in element and class selectors:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;body {
    background-color: var(--body-bgcolor);
    color: var(--body-fgcolor);
}

a { 
    color: var(--link-color); 
} 

input {
    background-color: var(--body-bgcolor);
    color: var(--body-fgcolor);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the previous example, the light color set has three variables: for the background, the text color, and the link color. The media selector uses the feature &lt;code&gt;prefers-color-scheme&lt;/code&gt; to detect if the user is using a dark theme. If that is the case, variables are redefined. Finally, element and class selectors are configured using these variables.&lt;/p&gt;

&lt;p&gt;Resource:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MDN Web Docs: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme"&gt;prefers-color-scheme  &lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
    </item>
  </channel>
</rss>
