This article is currently an experimental machine translation and may contain errors. If anything is unclear, please refer to the original Chinese version. I am continuously working to improve the translation.
The goal of this blog series is to clearly and simply explain computer networking concepts by building a TCP/IP stack from scratch. For more details, see Ep.0 of this series. The implementation is open-sourced at https://github.com/lyc8503/DNet-core.
So, where should we begin when implementing a network stack? Clearly, we should start with the physical connection to the network. Our computer needs to exchange information with the outside world through some kind of medium.
Today, there are many such media—like Ethernet cables connected to an RJ45 port, Wi-Fi, or cellular networks. Binary bits in our computer are encoded into electrical, optical, or other types of signals, then transmitted through different kinds of cables (or even through the air). Since this series focuses more on software implementation, we won’t dive deep into the physical details. For further reading, refer to standard textbooks (such as the classic this one).
In Ep.0, we set a big—perhaps overly ambitious—goal: to implement a full TCP/IP stack. Let’s clarify that a bit: we aim to build a network stack in C++, running in user space on Linux. That means our final program will run as an ordinary application (not a kernel module) within the system.
And here comes the problem: since our software runs in user space, it has no direct access to physical hardware. We can’t interact with network devices directly—we must rely on operating system APIs to do the work for us.
So we’re forced to start dealing with Linux APIs…
Linux provides a type of virtual network device called TAP. It acts like a virtual network interface card (NIC), behaving almost identically to a real one—except it has no physical hardware behind it.
Following the instructions in the linked documentation, we create a virtual network interface and use our program to read from or write data to it. Here’s the code:
1 | struct ifreq ifr{}; |
After creating and opening the virtual device, we obtain a corresponding file descriptor, which allows us to perform read and write operations.
However, the network interface isn’t active yet. We need to bring it up (and also set up the routing table—more on that later).
1 | ip link set dev dnet0 up |
Once done, try ping 10.0.0.1—you’ll find that each ping generates readable input in our program.
After wrapping the above code into a clean structure, we turned it into a class: https://github.com/lyc8503/DNet-core/blob/main/driver/driver.cpp
We’ve also added multi-threaded and callback-based methods for reading data, in preparation for future needs.
Right now, the data we read is just meaningless binary—next, we’ll start parsing it.
What we’ve achieved so far corresponds to simulating the Physical Layer in the OSI seven-layer model, or part of the Network Access Layer in the TCP/IP protocol suite.
This article is licensed under the CC BY-NC-SA 4.0 license.
Author: lyc8503, Article link: https://blog.lyc8503.net/en/post/tcpip-1-linux-driver/
If this article was helpful or interesting to you, consider buy me a coffee¬_¬
Feel free to comment in English below o/