8.3 PPP Implementation in the Linux Kernel

   


As mentioned before, the PPP implementation in Linux is divided into four different tasks: three kernel modules and the pppd user space daemon. During design of this division, care was taken to move as little functionality as possible into the Linux kernel. For this reason, the kernel modules are rather simple. pppd includes 13,000 lines of code (2,100 lines alone in main.c), which means that it is four times the size of the three kernel modules (ppp_generic.c, ppp_synctty.c, and ppp_async.c) together. In the following sections, we will first discuss the generic PPP driver and then the driver for the asynchronous PPP TTY line discipline. The driver for the synchronous PPP line discipline is relatively simple, so we will not discuss it here.

8.3.1 Functions and Data Structures of the Generic PPP Driver

Figure 8-4 shows the most important data structures of the generic PPP driver. There is a separate ppp structure with general management information for each PPP device. Some important entries, particularly the transmit and receive queues, xq and rq, are in a substructure of the type ppp_file. This substructure is also found in the channel structure, which is used to manage single channels in multilink PPP, which will not be discussed here, for the sake of simplicity.

Figure 8-4. Important data structures of the generic PPP driver.

graphics/08fig04.gif


There is a PPP device for each network device, the net_device structure of which refers to the related ppp structure in the field priv. In addition, the PPP daemon can send and receive control packets of subprotocols (see Section 8.1.1) over the device /dev/ppp. For this purpose, it must first bind the device /dev/ppp to a specific PPP device by use of an ioctl() call. This binding means that a pointer to the ppp structure is entered into the field private_data of the relevant file structure.

ppp_init()

drivers/net/ppp_generic.c


This function is invoked by init_module() whenever the PPP module is loaded: It uses the function devs_register_chrdev() to register the character-oriented device /dev/ppp (see Section 8.1.2) with the Linux kernel.

ppp_cleanup()

drivers/net/ppp_generic.c


This function is invoked whenever the PPP module is removed. It frees all data structures used and deregisters the device /dev/ppp.

ppp_open()

drivers/net/ppp_generic.c


This function is invoked by the function pointer open() in the file_operations structure as soon as the PPP daemon opens the device /dev/ppp.

ppp_release()

drivers/net/ppp_generic.c


This function is invoked by the function pointer release() in the file_operations structure as soon as the device /dev/ppp is closed again.

ppp_write()

drivers/net/ppp_generic.c


This function is invoked by the function pointer write() in the file_operations structure when the PPP daemon sends a PPP control packet over the device /dev/ppp. For this, a matching ppp_file structure is determined and passed to the function ppp_file_write() as the pf parameter. First, an sk_buff structure with the data to be sent is created in this function and appended to the transmit queue pf->xq by skb_queue_tail(); then it is output to the underlying network device by ppp_xmit_process().

ppp_read()

drivers/net/ppp_generic.c


This function is invoked by the function pointer read() in the file_operations structure when the PPP daemon wants to receive PPP control packets over the device /dev/ppp. As with ppp_write(), a matching ppp_file structure first is located and passed to the function ppp_file_read() as the pf parameter. In this function, add_wait_queue() first waits for packets to arrive in the receive queue pf->rq; then the incoming packets are read by skb_dequeue().

ppp_ioctl()

drivers/net/ppp_generic.c


This function is invoked by the function pointer ioctl() in the file_operations structure when the PPP daemon uses an ioctl() call for the device /dev/ppp to change various parameters of the PPP drivers in the Linux kernel.

ppp_unattached_ioctl()

drivers/net/ppp_generic.c


This function is invoked by ppp_ioctl() when the device /dev/ppp has not yet been bound to a PPP device and so (the private_data field of the related file structure has the value 0). Its tasks include the ioctl() call PPPIOCNEWUNIT, which creates a new PPP device and writes a pointer to the relevant ppp structure in the field file->private_data.

ppp_net_init()

drivers/net/ppp_generic.c


This function is invoked by the Linux kernel whenever a new PPP network device is registered. It initializes the net_device structure; in particular, the function pointers described below are set.

ppp_start_xmit()

drivers/net/ppp_generic.c


This function is invoked by the function pointer hard_start_xmit() in the net_device structure of the IP layer to output an IP packet over the PPP network device. First, the required PPP header is added, then skb_queue_tail() adds the complete packet to the transmit queue (similarly to the function ppp_write()), and finally ppp_xmit_process() outputs the packet to the underlying network device.

ppp_xmit_process()

drivers/net/ppp_generic.c


This function is responsible for outputting all packets waiting in the transmit queue ppp->file.xq to the underlying device. The auxiliary function ppp_send_frame(), which can optionally compress the PPP packets, is used for the actual output.

ppp_input()

drivers/net/ppp_generic.c


This function is invoked by the driver of the underlying TTY line discipline (asynchronous or synchronous) as soon as a PPP packet has been received. After a defragmenting of the packets, if necessary, the function ppp_do_recv() is invoked for further processing; then this function forwards the packet to ppp_receive_frame().

ppp_receive_frame()

drivers/net/ppp_generic.c


This function checks for whether multilink PPP is activated and forwards an incoming PPP packet to either the function ppp_receive_mp_frame() (with multilink PPP) or ppp_receive_nonmp_frame() (without multilink PPP).

ppp_receive_nonmp_frame()

drivers/net/ppp_generic.c


When a PPP packet arrives, this function first undoes the compression, if applicable, and then checks for whether it is a data packet or a control packet of a subprotocol. (See Section 8.1.1.) If it is a control packet, then skb_queue_tail() adds the packet to the receive queue, where it can be read by the PPP daemon over the device /dev/ppp. If it is a data packet, then the payload is packed in an sk_buff structure with the correct protocol identifier and passed to the network layer by calling netif_rx().

8.3.2 Functions and Data Structures of the Asynchronous PPP Driver

The asynchronous PPP module essentially supplies a new TTY line discipline (see Section 7.2.1), by the name of N_PPP, and representing an intermediate layer between the generic PPP driver and the driver of the underlying TTY device.

Figure 8-5 gives an overview of the most important data structures. The driver's state information is maintained in an asynctty structure. As in the SLIP implementation (see Section 7.2.3), there is a reference to the tty_struct structure of the underlying TTY device, which contains a tty_ldisc structure for the PPP TTY line discipline.

Figure 8-5. Important data structures of the asynchronous PPP driver.

graphics/08fig05.gif


A ppp_channel structure (which will not be discussed in detail here) is used to reach both the ppp structure of the relevant generic PPP driver and a structure of the type ppp_channel_ops, which includes function pointers to, among others, the function ppp_async_send() described further below. Inversely, the ppp structure of the generic PPP driver can be used to reach the relevant ppp_channel structure (and thus the function pointers in ppp_channel_ops) over several detours (which will not be described here, to keep things simple). This is important for being able to pass outgoing packets to the function ppp_async_send() in case of asynchronous PPP.

ppp_async_init()

drivers/net/ppp_async.c


This function is invoked whenever the ppp_async.o module loads. It uses tty_register_ldisc() to register the PPP TTY line discipline with the Linux kernel.

ppp_async_cleanup()

drivers/net/ppp_async.c


This function is invoked whenever the ppp_async.o module is removed. It calls tty_register_ldisc(N_PPP, NULL) to deregister the PPP TTY line discipline.

ppp_async_send()

drivers/net/ppp_async.c


This function is invoked by the function ppp_push() of the generic PPP driver over the function pointer start_xmit() in the ppp_channel_ops structure (see Figure 8-5) as soon as a PPP packet is ready to be sent. It forwards the packet to the function ppp_async_push().

ppp_async_push()

drivers/net/ppp_async.c


This function is invoked by the function ppp_async_send() to transmit a PPP packet. The function uses the auxiliary function ppp_async_encode() to prepare the packet for asynchronous transmission and then sends it to the driver of the underlying TTY device by repeatedly calling tty->driver.write().

ppp_async_encode()

drivers/net/ppp_async.c


This function uses the character stuffing described in Section 7.1.1 to transmit a PPP packet over an asynchronous device.

ppp_async_input()

drivers/net/ppp_async.c


This function is invoked by ppp_asynctty_receive() as soon as new data was supplied by the underlying TTY device. As with the SLIP functionality (see Section 7.1.1), it undoes character stuffing and detects the beginning and end of PPP packets. As soon as a packet has been read completely, it is forwarded to the function process_input_packet().

process_input_packet()

drivers/net/ppp_async.c


This function is invoked by ppp_async_input() as soon as a PPP packet has been read completely. First, the packet checksum (Frame Check Sequence FCS) is checked. After a number of additional checks, ppp_input() is called eventually, to forward the packet to the generic PPP driver.

ppp_asynctty_open(), ppp_asynctty_close()

drivers/net/ppp_async.c


These functions are invoked by the function pointers open() and close() of the tty_ldisc structure as soon as a user program switches a TTY device to the PPP line discipline or resets it to another line discipline. Essentially, an asynctty structure is created in tty_asynctty_open() for the state data of the TTY line discipline, then initialized, and finally released in tty_asynctty_close().

ppp_asynctty_read(), ppp_asynctty_write()

drivers/net/ppp_async.c


These functions are invoked by the function pointers read() and write() of the tty_ldisc structure whenever a program attempts to send data to a TTY device in PPP line discipline or read from it. Its only functionality is that it returns an error message, because all inputs and outputs of the asynchronous PPP driver run over the device /dev/ppp.

ppp_asynctty_room(), ppp_asynctty_receive(),

drivers/net/ppp_async.c

ppp_asynctty_wakeup()


These functions correspond largely to the functions slip_receive_room(), slip_receive_buf(), and slip_write_wakeup() of the SLIP implementation. (See Section 7.2.3). When data arrives, then ppp_asynctty_receive() invokes the function ppp_async_input() (as described earlier).

8.3.3 Initialization

When the PPP module ppp_generic.o is loaded, then ppp_init() first registers a character-oriented device with the major number 108, which is normally embedded under /dev/ppp into the system. In the next step, the module uses ppp_async_init() to register a new TTY line discipline (see Section 7.2.1) by the name "ppp" for the asynchronous PPP driver (ppp_async.o). This TTY line discipline is an intermediate layer between the device driver of the underlying device and the ppp_async.o module, which facilitates access to all incoming data packets. Once the /dev/tty device and the PPP TTY line discipline have been registered, the first initialization phase is completed.

The second phase is initiated by the calling of pppd. After a brief test of whether its version number matches the kernel driver version, it opens the device /dev/ppp. It then obtains a file descriptor, which is required later to communicate with the generic PPP driver; at first, however, only the reference pointer USAGE_COUNT of the PPP device is incremented.

Next, a user process has to establish a physical connection (e.g., the chat program could dial the number of a dialup server). If this action was successful, then pppd uses the system call ioctl(tty_fd, TIOCSETD, N_PPP) to change the TTY line discipline to N_PPP. It then uses ioctl(ppp_dev_fd,PPPIOCNEWUNIT) to request the generic PPP driver to create a new network device and then uses ioctl(fd,_PPPIOCATTACH) to bind the new network device to the underlying TTY device.

These steps complete the establishment of the actual PPP connection; now the PPP subprotocols, such as LCP, can start authenticating the user and configure higher layers. (See Section 8.4.)

8.3.4 Transmitting IP Packets

The generic PPP driver accepts packets ready for transmission over two different routes: The network layer sends payload packets over the matching network device (pppX), and the PPP daemon sends control packets over the character-oriented device /dev/ppp.

Each data packet to be sent is passed to the function ppp_start_xmit() by the network layer in an sk_buff structure. (See Section 4.1.) This function appends a 2-byte PPP header (see Figure 8-2) to the beginning of the packet and stores the packet in the transmit queue ppp->xq. Virtually the same thing happens in ppp_write(), the function that accepts packets from pppd.

Finally, the function ppp_xmit_process() is invoked in each case. It takes packets from the transmit queue and forwards them to the function ppp_send_frame() for further processing. Depending on the setting, the packet headers might be compressed by the Van Jacobson method and the deflate or BSD-Compress method might be used for payload compression. After a forwarding to the function ppp_async_send()_ of the asynchronous PPP driver (or to the corresponding function of the synchronous PPP driver), the generic PPP driver has completed its processing.

8.3.5 Detecting Frame Boundaries

Frame synchronization (framing) is implemented as a TTY line discipline in the asynchronous PPP driver (drivers/net/ppp_async.c) and follows the standard specified in [Simp94b]. Basically, this is an easily modified and streamlined HDLC (High Level Data Link Control; see [ISO93]).

Section 7.1.1 briefly explained why framing is necessary: An asynchronous TTY device (e.g., a modem connection) can process only unstructured byte streams and not full packets, so it is necessary to mark the beginning and end of a packet specially. In PPP, this is done by use of the special control character PPP_FLAG with the binary representation 01111110.

Of course, the remaining data stream should not inadvertently contain such special characters. To prevent special characters from occurring, we use character stuffing. This means that all payload bytes corresponding to a control character, such as PPP_FLAG, are prefixed by the character PPP_ESCAPE (binary 01111101). There are more control characters; see include/linux/ppp.defs.h for a complete list.

Framing and character stuffing are largely implemented in the function ppp_async_encode(). A bit vector in the field xaccm in the struct asyncppp structure is used to detect the characters that should have a PPP_ESCAPE prefix. Each of the 32 X 8 bits in this vector corresponds to one of the 256 available 8-bit characters.

The following program dump from drivers/net/ppp async.c shows how you can convert payload into a data stream:

 #define PUT_BYTE(ap, buf, c, islcp)               do {    \       if ((islcp && c < 0x20) || (ap->xaccm[c >> 5] & (1 << (c & 0x1f)))) {\                *buf++ = PPP_ESCAPE;                       \                *buf++ = C ^ 0x20;                         \       } else                                              \                *buf++ = c;                                \ } while (0) 

In this code, islcp is a flag set only for special LCP commands, which have to work even when the bit vector ap->xaccm has not yet been initialized or has been wrongly initialized.

To protect against transmission errors, a 2-byte CRC checksum (Frame Check Sequence FCS) is appended to the PPP packet before the closing end character (PPP_FLAG). If the packet was fully converted into a data stream, then the driver of the underlying TTY device, which is called by tty->driver.write(), assumes the remaining work.

8.3.6 Receiving IP Packets

Receiving PPP packets over the asynchronous PPP driver works much as does sending packets, just in opposite direction: Incoming data is first sent to the function ppp_asynctty_receive() of the asynchronous PPP driver by the driver of the underlying TTY device. Then the function ppp_async_input()_searches for frame boundaries and undoes character stuffing. The function process_input_packet() tests for whether the checksum (FCS; see Section 8.3.5) is correct. Finally, the fully restored packet is passed to the function ppp_input() of the generic PPP driver.

Next, if the packet was compressed, it is now unpacked in the function ppp_receive_nonmp_frame() (or, in the case of multilink PPP, ppp_receive_mp_frame()). On the basis of the protocol identifier in the first two bytes of the packet (see Figure 8-2), a decision is made about whether the packet should be passed to the network layer in an sk_buff structure or added to the receive queue ppp->rq, from which it can be read by pppd over the device /dev/ppp.


       


    Linux Network Architecture
    Linux Network Architecture
    ISBN: 131777203
    EAN: N/A
    Year: 2004
    Pages: 187

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net