This is documentation for the source code of Minix networking. It describes in
detail the basic parts of how the networking system works. The Minix networking
source code is in the inet/ directory. The documentation assumes that the reader
has a basic understanding of the ip, tcp, and udp protocols. It also assumes the
reader has an understanding of how Minix works and basic understanding of the C
programming language. We shall begin with the main function at inet/inet.c.
Minix networking runs as a process called 'inet.' Inet works just like every
other operating system process - like the file system process (fs) and the memory
management process (mm). The basic structure of a operating system process can be
described as follows in the following c-like program:
#include <minix/type.h>
#define TRUE 1
int main()
{
init();
while (TRUE)
{
message m;
receive(&m);
processmessage(&m);
}
}
init() initializes the inet process. After inet is initialized inet goes into an
endless loop as indicated by the 'while (TRUE)' statement. Inet does 2 things while
in the endless loop. First it waits for a message to be sent to it (usually by the
file system process fs) in receive(&m). Second, after it receives the message, it
processes the message in processmessage(&m).
The main function (which starts the inet process) is at inet/inet.c.
User processes do not directly call the process inet when it wants to do something.
Instead it calls the file system process fs. An example of calling fs in order to
user minix networking is given in the ping program for Minix (commands/simple/ping.c):
int fd, i;
int result, result1;
nwio_ipopt_t ipopt;
fd= open ("/dev/ip", O_RDWR);
ipopt.nwio_flags= NWIO_COPY | NWIO_PROTOSPEC;
ipopt.nwio_proto= 1;
result= ioctl (fd, NWIOSIPOPT, &ipopt);
First, ping calls the system function open in order to get a file descriptor fd (ie an
ip channel) to the ip protocol in the inet process. Afterwards ping calls the system
function ioctl to configure the device referred to by the file descriptor fd. ioctl
is a standard unix system call which one uses on a file descriptor. The specifics of
how Minix implements ioctl in inet shall be described later.
The file "/dev/ip" is a device file. The file type of a file is specified in the
i_mode field of its inode structure. The inode structure is defined in fs/inode.h.
Specifically it's a character file. In Minix opening a device file is implemented as
follows.
1) Every device file has a major number and a minor number.
2) The major number is an index in to the array dmap defined in fs/table.c.
For the inet process in standard Minix 2.0.0, the major device number for inet is
7 = /dev/ip (look at fs/table.c).
3) The minor number is passed as a parameter in the function dmap.dmap_open.
The dmap structure is defined in the file fs/dev.h.
extern struct dmap {
dmap_t dmap_open;
dmap_t dmap_rw;
dmap_t dmap_close;
int dmap_task;
} dmap[];
For a major device number which corresponds to a driver for a real hardware device
(say a hard disk drive) the minor device number typically corresponds to the specific
device. This is because the same driver can be the driver of more than one hardware
device (say more than one hard disk). For example a hard disk driver can handle more
than one hard disk drive ie hard disk drive 0 and hard disk drive 1.
The major device number for inet does not correspond to a real hardware device driver.
This is because even though it's typical to do networking via an Ethernet card, one
doesn't have to. One could do networking through, say, a modem. Or, say, an internal
cable modem. The inet process was written to be versatile enough so that one doesn't
have to change the inet source code in order to use the ip protocol if one decides to
use a modem instead of a network card ie inet can send datagrams on different network
interfaces.
Since the major device number does not correspond to a real device driver, neither
does the minor device number generally correspond to real device. Instead (generally)
it corresponds to a layer in a networking protocol.
The minor device numbers for standard Minix 2.0.0 are given in inet/generic/sr.h
#define ETH_DEV ETH_DEV0
#define IP_DEV IP_DEV0
#define ETH_DEV0 0x01
#define IP_DEV0 0x02
#define TCP_DEV0 0x03
#define UDP_DEV0 0x04
#define ETH_DEV1 0x11
#define IP_DEV1 0x12
#define TCP_DEV1 0x13
#define UDP_DEV1 0x14
#define PSIP_DEV0 0x21
#define IP_DEV2 0x22
#define TCP_DEV2 0x23
#define UDP_DEV2 0x24
#define PSIP_DEV1 0x31
#define IP_DEV3 0x32
#define TCP_DEV3 0x33
#define UDP_DEV3 0x34
The defines in inet/generic/sr.h which begin with ETH correspond to the Ethernet layer
ie a network card. For example ETH_DEV0 is the minor device number for a network card.
The 3 other minor devices numbers which are defined immediately afterwards correspond
to protocols which use the network card. Hence, writing to the inet device with the
minor number IP_DEV0 would mean one is sending an ip datagram through the network card
referred to with minor device number ETH_DEV0. Similarly, writing to the inet device
with the minor number TCP_DEV0 would mean one is sending an tcp/ip datagram through
the network card referred to with minor device number ETH_DEV0. Similarly, writing to
the inet device with the minor number UDP_DEV0 would mean one is sending an udp/ip
datagram through the network card referred to with minor device number ETH_DEV0.
Similar comments are also true concerning the minor device number ETH_DEV1.
The minor device number can therefore be understood as uniquely identifying an ordered pair: (network interface, protocol).
PSIP_DEV0 is the minor device number for a pseudo-ip device. A pseudo-ip device can be
understood in terms of another well known pseudo device in unix (and minix)
ie pseudo-terminals.
In minix a terminal device can be called /dev/ttyp0 while the corresponding pseudo terminal
device would be called /dev/ptyp0. The pseudo-terminal device and the corresponding terminal
device form a bi-directional (2 way) pipe. Hence when one writes to ttyp0, one can read what
was just written by reading ptyp0. Similarly if one were to write to ptyp0, one can read
what was just written by reading ttyp0.
PSIP_DEV0, the pseudo-ip device, plays a similar role to the pseudo-terminal device while
IP_DEV2 plays a similar role to the terminal device. When one writes to IP_DEV2, one can read
what was just written by reading PSIP_DEV0. Similarly if one were to write to PSIP_DEV0, one
can read what was just written by reading IP_DEV2.
The pseudo-ip devices however offer more options than the pseudo-terminal devices. If one
wants to read from the ip device IP_DEV2 without reading the ip header one can call
ioctl(fd, NWIOSIPOPT, &struct nwio_ipopt)
with the option NWIO_RWDATONLY set so that one can read to and write from the ip device
IP_DEV2 without adding or retrieving the ip header. For details see the ip.4 man page.
Using pseudo-ip devices one can easily write a user process program which would allow
one to connect to the internet using a serial modem even though there is no serial modem
driver in the inet process. More generally, one can send ip messages using different
network interfaces.
The outline for using a modem on com1 and ppp as a network interface would be as follows.
int main
{
int fd;
char buffer[512];
pid_t pid;
fd= open ("/dev/psip", O_RDWR);
if ((pid = fork()) == -1) {
exit(0); /*handle error*/
}
if pid != 0 { /*parent process*/
while(1)
{
ReadCom1(buffer);
RemovePPPHeaders(buffer);
write(fd, buff, sizeof(buff));
}
} else { /*child process*/
while(1)
{
read(fd, buff, sizeof(buff));
AddPPPHeaders(buffer);
WriteCom1(buffer);
}
}
}
We make the assumption that the ppp protocol is being used as network interface.
The open statement returns a file descriptor to the psip (pseudo-ip) device. The
program then executes the fork system command so that can be a pseudo-ip reader
(which shall write to the modem) and a pseudo-ip writer (which shall read from
the modem).
The parent process is the pseudo-ip reader (which shall write to the modem). The
parent process goes in an endless loop. First it reads from the modem. Next it
removes the ppp protocol header. Finally it writes to the pseudo-ip device.
The child process is the pseudo-ip writer (which shall read from the modem). The
child process goes in an endless loop. First it reads from the pseudo-ip device.
Next it adds the ppp protocol header. Finally it writes to the modem.
Every protocol interface (tcp,ip, udp) has a fd table. The fd table holds the protocol interface
specific information for the file descriptor and also information specific to the
file descriptor.
Every protocol (tcp,ip, udp) interface has a port table. The port table corresponds to the
minor device numbers for the protocol interface. There's a 1-1 correspondence between the
elements of the port table and the minor device numbers for the protocol interface and hence
the minor device numbers for the network interfaces.
A port table relates a lower level protocol interface(eg UDP) to higher level protocol interface(eg IP).
Every protocol which has data encapsulated in a higher level protocol (eg UDP data is
encapsulated with a IP header) has a port table. The reason why is as follows.
A protocol encapsulates data from a lower level protocol if there is a field in the protocol header which
declares what the lower level protocol of the encapsulated data is. The Ethernet protocol
has a field which declares what lower level protocol of the encapsulated data is (eg IP).
Similarly the IP procotol has a header which declares what lower level protocol of the encapsulated
data is (eg UDP). The higher level protocol (eg IP) can not look at the header in the lower
level protocol (eg UDP) eg the IP interface can not look at the UDP header and determine what local
or remote port the packet is being sent from or to. The port table is therefore used to interface
a lower level protocol interface with a higher level protocol interface. More specifically, the port
table is used to interface a lower level protocol interface with the fd table of a higher
level protocol interface. Every used entry in the fd table of a higher level protocol interface (eg IP)
corresponds to an entry in the port table of a lower level protocol interface (eg UDP).
For the IP protocol interface, a used entry in the fd table can be uniquely
identified by the following pair: 1) the protocol of the corresponding entry in the port table
and 2) the network interface which the port table entry correponds to. Accordingly, when the IP
interface receives a packet, it can look for the correct port
table to send the packet to by looking at its own (IP) fd table and looking for the entry with the
with the same network interface and lower level protocol.
(The above paragraph is not totally true as one can check by looking at the code for the ip interface
in the ip_port_arrive function in ip_read.c. It is possible to share an fd entry. It is also possible
to specify a remote ip address for the IP interface to check, but for the code
as currently written it is true as applied to the relationship between the UDP and TCP
interface with the IP interface).
When a lower level protocol interface
sends a packet out, it must sent the packet out through its port table because the lower level protocol interface
interfaces with the higher level protocol interface only through the port table.
This documentation is written by Charlie Tsz-Hong Wong and is copyrighted to him.
Do not use or copy this without permission from him. In general though since I
wish this documentation to be useful for educational purposes I would gladly
consider any requests to be used for such purposes.