Point to Point Protocol
References:
rfc (something around 1600)
Documentation/networking/ppp_generic.txt
The support of the Point-to-Point Protocol (ppp) in the kernel
consists essentially of four obejcts,
- ppp units (struct ppp)
- ppp file (struct ppp_file), for the file operations (read, write, etc.)
- ppp interface
- ppp channel (struct ppp_channel) which describes a transport channel.
The ppp unit contains a ppp_file, various locks, "flags", "stats",
and a pointer to the list of "channels" attached to this unit.
It has also a pointer to a net_device ("dev").
The ppp_file can be of two "kind"s: PPP_INTERFACE or PPP_CHANNEL.
It has a transmit ("xq") and a receive ("rq") queue.
The ppp unit does not point directly to a list of ppp_channels but
to a list of channels. The channels are organized in a global list
(headed by "all_channels", and with link "list") and in lists of channels
per unit (link "clist"). The channel struct has a pointer to the ppp_file,
("file"), one to the "ppp" unit, and one to the ppp_channel ("chan").
The ppp_channel has a pointer to the channel ("ppp") and contains
the ppp_channel_ops. These are just two operations,
- start_xmit(), return 1 if the socket_buffer "skb" has been accepted,
and 0 otherwise;
- ioctl().
The net_device contains several data ("hard_header_len", "mtu",
"addr_len", "tx_queue_len", "type", "flags"), and the network
functions ppp_start_xmit(), ppp_net_stats(), ppp_net_ioctl().
PPP functions
The ppp core is implemented in the file drivers/net/ppp_generic.c
file operations
- ppp_read(file,buf,count,pos)
take a socket buffer skb from the receive queue rq;
copy it to user-space;
free the skb
- ppp_write(file,buf,count,pos)
allocate a socket buffer with extra header space;
copy the user buffer into it;
append it to the transmit queue xq;
call ppp_xmit_process or ppp_channel_push (depending on the kind)
- ppp_poll(file,wait)
call poll_wait (with rwait);
peek if receive queue is not empty
and check if the ppp_file is dead
- ppp_open(inode,file)
(does nothing)
- ppp_release(inode,file)
if the ppp_file is of kinf PPP_INTERFACE do a ppp_shutdown_interface;
decrement the refcnt
and if it is zero call ppp_destroy_interface/channel (according to the kind)
- ppp_ioctl(inode,file,cmd,arg)
if the file is not attached (no private_data)
call ppp_unattached_ioctl() and return;
if the ppp_file (ie, the file's private_data) is of kind CHANNEL
call ppp_channel_ioctl();
otherwise do the requested control function:
[1] ppp_connect_channel(ppp_channel, unit)
[2] ppp_disconnect_channel(ppp_channel)
[3] or various set/get
- ppp_unattached_ioclt(ppp_file,file,cmd,arg)
either create a ppp unit (interface) and attach it to the file;
or attach the file to an existing ppp unit;
or attach the file to an existing channel
network functions
- ppp_start_xmit(skb,dev)
make sure the skb has 2 byte headerspace and put the proto in it;
get the ppp unit from the net_device (ie, dev->priv);
call netif_stop_queue(dev);
put the skb on the transmit queue xq of the ppp_file;
call ppp_xmit_process(ppp)
- ppp_xmit_process(ppp)
call ppp_push();
while there is no xmit_pending
try to get a skb from xq and ppp_send_frame(ppp,skb);
if there is not xmit_pending and there is no skb in the queue
netif_wake_queue(dev)
- ppp_send_frame(ppp,skb)
if the proto is PPP_IP try to compress tcp header;
if it is PPP_CCP do ppp_ccp_peek(ppp,skb,0);
if the proto is not PPP_LCP nor PPP_CCP try to do packet compression;
if looping back add the skb to the ppp_file rq and wake up sleepers (rwait)
otherwise the skb becomes the xmit_pending
and call ppp_push(ppp)
- ppp_push(ppp)
if there is a channel in the channels list {
tell it to start_xmit(ppp_channel,skb) the socket buffer
and if succeeds clear xmit_pending;
}
otherwise drop the socket buffer and clear xmit_pending
- ppp_net_stats(dev)
return the ppp unit (dev->priv) stats
- ppp_net_ioctl(dev,ifreq,cdm)
...
input functions
- ppp_input(ppp_channel,skb)
get the channel from the ppp_channel
if the channel has no ppp unit, or the proto is 0xC000, or ... {
add the skb to the rq of the ppp_file
and wake up sleepers
}
otherwise call ppp_do_recv(...)
- ppp_do_recv(ppp,skb,ch)
if the ppp unit has a net_device dev
do ppp_receive_frame(ppp,skb,ch)
- ppp_receive_frame(ppp,skb,ch)
if skb has length >=2 call ppp_receive_(non)mp_frame(...)
otherwise it is a ppp_receive_error()
- ppp_receive_nonmp_frame()
if necessary do ppp_decompress_frame(...);
if it is a control frame (proto_index < 0)
pass the frame to pppd (put it on the ppp_file rq and wake sleepers rwait)
else (it is a network frame)
give it to the network interface: netif_rx(skb)
- ppp_output_wakeup(ppp_channel)
call ppp_channel_push() on the channel of the ppp_channel
- ppp_input_error(ppp_channel,code)
get the channel from the ppp_channel;
if it has a ppp unit {
allocate a skb, write the code into it, and call ppp_do_recv(...)
}
- ppp_receive_error(...)
adjust the stats
channel functions
- ppp_channel_register(ppp_channel)
allocate a channel and connect it to the ppp_channel;
init_ppp_file() of kind CHANNEL;
other initializations;
add the channel to the new_channel global list
- ppp_channel_unregister(ppp_channel)
unlink the channel from the ppp_channel;
call ppp_disconnect_channel(...);
remove it from the global list;
decrement the ppp_file refcnt, and if zero ppp_destroy_channel(...)
- ppp_channel_index(ppp_channel)
return the ppp_file index
- ppp_unit_index(ppp_channel)
return the ppp_file index of the channel's ppp unit
- ppp_channel_push(ch)
if (ch has a ppp_channel) {
for each skb on the ppp_file transmit queue xq
if start_xmit(ppp_channel,skb) fails
put the skb back and break
}
otherwise purge the ppp_file xq;
if (xq is not empty) try with ppp_xmit_process(ppp)
interface functions
- ppp_create_interface(unit,*ret)
allocate a ppp unit and its net_device;
do some initialization (index, mru, ...);
init_ppp_file() of kind INTERFACE;
register the net_device;
put the unit in the cardmap all_ppp_units
- init_ppp_file(ppp_file,kind)
initialize the ppp_file fields
- ppp_shutdown_interface(ppp)
takes down a ppp interface unit: {
close and unregister the net_device
remove the unit from the cardmap
mark its ppp_file as "dead" and wake up sleepers
}
- ppp_destroy_interface(ppp)
check that the ppp_file is "dead" and the unit has no channels;
call ppp_ccp_closed(ppp);
purge the ppp_file queues (rq, xq);
release the ppp unit
- ppp_find_unit(unit)
locate the ppp unit in the cardmap
- ppp_find_channel(index)
locate the existing channel with the given index
- ppp_connect_channel(ch,unit)
add the channel to the ppp unit' channels list
- ppp_disconnect_channel(ch)
remove the channel from the ppp unit's channels list
- ppp_destroy_channel(ch)
check that the ppp_file is "dead";
purge the ppp_file queues (rq, xq);
release the channel
PPP channels
The first sample channel in the kernel is ppp_asynctty.c which
implements a ppp channel over a dedicated line controlled by a
terminal (tty) set with a ppp line discipline.
asyncppp
Exercise
[TO DO]
Although the ppp already has a traffic loop back facility,
write a loopback ppp channel.
See ppp_asynctty and ppp_synctty for examples.
PPP line discipline
There are two ppp line disciplines, ppp_asynctty and ppp_synctty,
implemented in the files ppp_asynctty.c and ppp_synctty.c,
respectively.
The two files are very similar, and many of the function are
actually equal.
Both define a data structure, asyncppp and syncppp, respectively
which contains a pointer to the tty, pointers for the packets (socket
buffers), state and flags fields, stats, and so on.
The line disciplines are named ppp for the async, and
pppsync for the sync.
PPP async/sync functions
The "XXXtty_open()" function is called when the tty is put into
the ppp line discipline.
line discipline operations
- ppp_asynctty_open(tty)
allocate an asyncppp structure;
initialize it and its ppp_channel;
register the ppp_channel
- ppp_asynctty_close(tty)
unlink asyncppp and tty;
decrease the refcnt, and if zero down the dead_sem;
unregister the ppp_channel;
release receive and transmit packets, rpkt and tpkt;
release the asyncppp
- ppp_asynctty_read(tty,file,buf,count)
return -EAGAIN
- ppp_asynctty_write(tty,file,buf,count)
return -EAGAIN
- ppp_asynctty_ioctl(tty,file,buf,count)
some ioctl: flush output, get unit or channel number
- ppp_asynctty_poll(tty,file,wait)
return 0
- ppp_asynctty_room(tty)
return 65535
- ppp_asynctty_receive(tty,buf,flags,count)
get the asyncppp from the tty and call ppp_async_input();
put the async_ppp;
if the case unthrottle the tty
- ppp_asynctty_wakeup(tty)
get the asyncppp from the tty;
clear the WAKEUP bit on the tty flags;
if ppp_async_push() return 1 then call ppp_output_wakeup();
put the asyncppp
PPP channel functions
- ppp_asyn_ioctl(ppp_channel,cmd,arg)
get/set flags, asyncmap, mru, ...
I/O functions
- PUT_BYTE(ap,buf,c,islcp)
macro that puts the byte 'c' an buf
- ppp_async_encode(ap)
get the proto (first two bytes of skb data);
islcp is set if proto is PPP_LCP and the third byte is between 1 and 7;
if ( tpkt_pos is 0 ) then {
if islcp call async_lcp_peek();
if islcvp and need flagtime put PPP_FLAG on buf;
if islcp or SC_COMP_AC put 0xff 0x03 on buf;
}
put the data;
if we are not finished, remember where we are and return 0
otherwise add FCS (frame checksum), close packet, and release skb
- ppp_async_send(ppp_channel,skb)
call ppp_async_push();
if full return 0 (the packet has not been accepted)
otherwise {
put the skb on tpkt and call ppp_async_push()
and return 1
}
- ppp_async_push(ap)
if busy return 0;
loop: {
test and clean WAKEUP is was set and tty is not stuffed;
use tty to write as much data as possible;
if CD is lost, flush everything and return;
if partial write, the tty is stuffed;
if have finished with the data (and tpkt is not 0) call ppp_async_encode;
break if there is no more work;
}
- ppp_async_flush_output(ap)
release tpkt and call ppp_output_wakeup()
- process_input_packet(ap)
if the state is TOSS or ESCAPE,
{ clear it, release skb, and return };
check FCS (packet checksum);
trim skb to len-2;
if first two bytes are ALLSTATION and UI, chop address/control (skb_pull);
if proto is compressed, skb_push(1)[0] = 0
else if proto is PPP_LCP call async_lcp_peek();
call ppp_input(ppp_channel,skb)
- ppp_async_input(ap,buf,flags,count)
skipping flagged bytes, check is there is a bit-7 or odd parity;
let n be the number of ordinary bytes;
if any byte had an error call input_error(ap,code)
otherwise {
use rpkt, or allocate a new skb
and copy n bytes from buf to rpkt
}
if the n-th byte is PPP_FLAG process_input_packet()
else if it is PPP_ESCAPE set ESCAPE bit in the state
- async_lcp_peek(ap,data,len,inbound)
skip the two proto bytes;
return if the next byte is neither CONFACK nor CONFREQ;
length is bytes 2 and 3; return if it exceed the packet length;
compute FCS;
if outbound CONFREQ remember lcp_fcs for later and return;
if received CONFACK check fcs: if not 0 return;
neglect inbound CONFREQ and return;
process options in CONFACK;
Marco Corvi - 2003