We are now through discussing char and block drivers and are ready to move on to the fascinating world of networking. Network interfaces are the third standard class of Linux devices, and this chapter describes how they interact with the rest of the kernel.
The role of a network interface within the system is similar to that
of a mounted block device. A block device registers its features in
the blk_dev
array and other kernel structures, and
it then “transmits” and “receives” blocks on request, by means of
its request function. Similarly, a network
interface must register itself in specific data structures in order to
be invoked when packets are exchanged with the outside world.
There are a few important differences between mounted disks and
packet-delivery interfaces. To begin with, a disk exists as a special
file in the /dev
directory, whereas a network
interface has no such entry point. The normal file operations (read,
write, and so on) do not make sense when applied to network
interfaces, so it is not possible to apply the Unix
“everything is a file” approach to them. Thus, network interfaces
exist in their own namespace and export a different set of operations.
Although you may object that applications use the read and write system calls when using sockets, those calls act on a software object that is distinct from the interface. Several hundred sockets can be multiplexed on the same physical interface.
But the most important difference between the two is that block drivers operate only in response to requests from the kernel, whereas network drivers receive packets asynchronously from the outside. Thus, while a block driver is asked to send a buffer toward the kernel, the network device asks to push incoming packets toward the kernel. The kernel interface for network drivers is designed for this different mode of operation.
Network drivers also have to be prepared to support a number of administrative tasks, such as setting addresses, modifying transmission parameters, and maintaining traffic and error statistics. The API for network drivers reflects this need, and thus looks somewhat different from the interfaces we have seen so far.
The network subsystem of the Linux kernel is designed to be completely protocol independent. This applies to both networking protocols (IP versus IPX or other protocols) and hardware protocols (Ethernet versus token ring, etc.). Interaction between a network driver and the kernel proper deals with one network packet at a time; this allows protocol issues to be hidden neatly from the driver and the physical transmission to be hidden from the protocol.
This chapter describes how the network interfaces fit in with the rest of the Linux kernel and shows a memory-based modularized network interface, which is called (you guessed it) snull. To simplify the discussion, the interface uses the Ethernet hardware protocol and transmits IP packets. The knowledge you acquire from examining snull can be readily applied to protocols other than IP, and writing a non-Ethernet driver is only different in tiny details related to the actual network protocol.
This chapter doesn’t talk about IP numbering schemes, network protocols, or other general networking concepts. Such topics are not (usually) of concern to the driver writer, and it’s impossible to offer a satisfactory overview of networking technology in less than a few hundred pages. The interested reader is urged to refer to other books describing networking issues.
The networking subsystem has seen many changes over the years as the kernel developers have striven to provide the best performance possible. The bulk of this chapter describes network drivers as they are implemented in the 2.4 kernel. Once again, the sample code works on the 2.0 and 2.2 kernels as well, and we cover the differences between those kernels and 2.4 at the end of the chapter.
One note on terminology is called for before getting into network devices. The networking world uses the term octet to refer to a group of eight bits, which is generally the smallest unit understood by networking devices and protocols. The term byte is almost never encountered in this context. In keeping with standard usage, we will use octet when talking about networking devices.
This section discusses the design concepts that led to the snull network interface. Although this information might appear to be of marginal use, failing to understand this driver might lead to problems while playing with the sample code.
The first, and most important, design decision was that the sample
interfaces should remain independent of real hardware, just like most
of the sample code used in this book. This constraint led to
something that resembles the loopback interface.
snull is not a loopback interface, however;
it simulates conversations with real remote hosts in order to better
demonstrate the task of writing a network driver. The Linux loopback
driver is actually quite simple; it can be found in
drivers/net/loopback.c
.
Another feature of snull is that it supports only IP traffic. This is a consequence of the internal workings of the interface—snull has to look inside and interpret the packets to properly emulate a pair of hardware interfaces. Real interfaces don’t depend on the protocol being transmitted, and this limitation of snull doesn’t affect the fragments of code that are shown in this chapter.
The snull module creates two interfaces. These interfaces are different from a simple loopback in that whatever you transmit through one of the interfaces loops back to the other one, not to itself. It looks like you have two external links, but actually your computer is replying to itself.
Unfortunately, this effect can’t be accomplished through IP-number assignment alone, because the kernel wouldn’t send out a packet through interface A that was directed to its own interface B. Instead, it would use the loopback channel without passing through snull. To be able to establish a communication through the snull interfaces, the source and destination addresses need to be modified during data transmission. In other words, packets sent through one of the interfaces should be received by the other, but the receiver of the outgoing packet shouldn’t be recognized as the local host. The same applies to the source address of received packets.
To achieve this kind of “hidden loopback,” the
snull interface toggles the least
significant bit of the third octet of both the source and destination
addresses; that is, it changes both the network number and the host
number of class C IP numbers. The net effect is that packets sent to
network A (connected to sn0
, the first interface)
appear on the sn1
interface as packets belonging to
network B.
To avoid dealing with too many numbers, let’s assign symbolic names to the IP numbers involved:
snullnet0
is the class C network that is connected to thesn0
interface. Similarly,snullnet1
is the network connected tosn1
. The addresses of these networks should differ only in the least significant bit of the third octet.local0
is the IP address assigned to thesn0
interface; it belongs tosnullnet0
. The address associated withsn1
islocal1
.local0
andlocal1
must differ in the least significant bit of their third octet and in the fourth octet.remote0
is a host insnullnet0
, and its fourth octet is the same as that oflocal1
. Any packet sent toremote0
will reachlocal1
after its class C address has been modified by the interface code. The hostremote1
belongs tosnullnet1
, and its fourth octet is the same as that oflocal0
.
The operation of the snull interfaces is depicted in Figure 14-1, in which the hostname associated with each interface is printed near the interface name.
Here are possible values for the network numbers. Once you put these
lines in /etc/networks
, you can call your
networks by name. The values shown were chosen from the range of
numbers reserved for private use.
snullnet0 192.168.0.0 snullnet1 192.168.1.0
The following are possible host numbers to put into
/etc/hosts
:
192.168.0.1 local0 192.168.0.2 remote0 192.168.1.2 local1 192.168.1.1 remote1
The important feature of these numbers is that the host portion of
local0
is the same as that of
remote1
, and the host portion of
local1
is the same as that of
remote0
. You can use completely different numbers
as long as this relationship applies.
Be careful, however, if your computer is already connected to a network. The numbers you choose might be real Internet or intranet numbers, and assigning them to your interfaces will prevent communication with the real hosts. For example, although the numbers just shown are not routable Internet numbers, they could already be used by your private network if it lives behind a firewall.
Whatever numbers you choose, you can correctly set up the interfaces for operation by issuing the following commands:
ifconfig sn0 local0 ifconfig sn1 local1 case "`uname -r`" in 2.0.*) route add -net snullnet0 dev sn0 route add -net snullnet1 dev sn1 esac
There is no need to invoke route with 2.2
and later kernels because the route is automatically added. Also, you
may need to add the netmask 255.255.255.0
parameter
if the address range chosen is not a class C range.
At this point, the “remote” end of the interface can be reached.
The following screendump shows how a host reaches
remote0
and remote1
through the
snull interface.
morgana% ping -c 2 remote0 64 bytes from 192.168.0.99: icmp_seq=0 ttl=64 time=1.6 ms 64 bytes from 192.168.0.99: icmp_seq=1 ttl=64 time=0.9 ms 2 packets transmitted, 2 packets received, 0% packet loss morgana% ping -c 2 remote1 64 bytes from 192.168.1.88: icmp_seq=0 ttl=64 time=1.8 ms 64 bytes from 192.168.1.88: icmp_seq=1 ttl=64 time=0.9 ms 2 packets transmitted, 2 packets received, 0% packet loss
Note that you won’t be able to reach any other “host” belonging to
the two networks because the packets are discarded by your computer
after the address has been modified and the packet has been
received. For example, a packet aimed at 192.168.0.32 will leave
through sn0
and reappear at sn1
with a destination address of
192.168.1.32, which is not a local address for the host computer.
As far as data transport is concerned, the snull interfaces belong to the Ethernet class.
snull emulates Ethernet because the vast majority of existing networks—at least the segments that a workstation connects to—are based on Ethernet technology, be it 10baseT, 100baseT, or gigabit. Additionally, the kernel offers some generalized support for Ethernet devices, and there’s no reason not to use it. The advantage of being an Ethernet device is so strong that even the plip interface (the interface that uses the printer ports) declares itself as an Ethernet device.
The last advantage of using the Ethernet setup for
snull is that you can run
tcpdump on the interface to see the packets
go by. Watching the interfaces with
tcpdump can be a useful way to see how the
two interfaces work. (Note that on 2.0 kernels,
tcpdump will not work properly unless
snull’s interfaces show up as
eth
x. Load the driver with the
eth=1
option to use the regular Ethernet names,
rather than the default snx
names.)
As was mentioned previously, snull only works with IP packets. This limitation is a result of the fact that snull snoops in the packets and even modifies them, in order for the code to work. The code modifies the source, destination, and checksum in the IP header of each packet without checking whether it actually conveys IP information. This quick-and-dirty data modification destroys non-IP packets. If you want to deliver other protocols through snull, you must modify the module’s source code.
Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.