PPP protocol workflow, combined with ppp-2.4.9 source code analysis

ppp-2.4.9 source code analysis

PPP agreement workflow

When a user dials in to an ISP, a physical connection is established from the user's personal computer to the ISP. At this time, the user's personal computer sends a series of LCP packets (encapsulated into multiple PPP frames) to the ISP to establish the LCP connection. These packets and their responses select some PPP parameters to be used. Then, configure the network layer. The network control protocol NCP assigns a temporary IP address to the newly accessed user's personal computer. In this way, the user's personal computer becomes a host with IP address on the Internet.

When the user communication is completed, NCP releases the network layer connection and takes back the originally allocated IP address. Next, the LCP releases the data link layer connection. Finally, the connection of the physical layer is released.

The start and end states of PPP links are always "link dead". At this time, there is no physical layer connection between the user's personal computer and the ISP's router.

When the user's personal computer calls the router through the modem (usually by clicking a connection button with the mouse on the screen), the router can detect the carrier signal sent by the modem. After both parties have established the physical layer connection, PPP enters the "link establishment" state, which aims to establish the LCP connection of the link layer.

At this time, LCP starts to negotiate some configuration options, that is, send LCP's configuration request frame. This is a PPP frame. Its protocol field is set to the code corresponding to LCP, and the information field contains specific configuration requests. The other end of the link can send one of the following responses:

  1. All options of configure ack are accepted.
  2. All options of configure NAK are understood but not accepted.
  3. Some of the configure reject options are unrecognized or unacceptable and need to be negotiated.

LCP configuration options include the maximum frame length on the link, the protocol of the authentication protocol used (if any), and not using the address and control fields in the PPP frame (because the values of these two fields are fixed and there is no amount of information, these two bytes can be omitted from the header of the PPP frame).

After the negotiation, both parties establish the LCP link, and then enter the "authenticate" state. In this state, only packets of LCP protocol, packets of authentication protocol and packets of monitoring link quality are allowed to be transmitted. If PAP (Password Authentication Protocol) is used, the party initiating the communication needs to send the identity identifier and password. The system allows the user to retry several times. If you need better security, you can use a more complex password handshake authentication protocol chap (Challenge Handshake Authentication Protocol). If the link authentication fails, go to the "terminate" status. If the authentication is successful, it will enter the "network layer protocol" state.

In the "network layer protocol" state, the network control protocol NCP at both ends of the PPP link exchanges network layer specific network control packets with each other according to different protocols of the network layer. This step is very important because today's routers can support multiple network layer protocols at the same time. In short, the network layers at both ends of the PPP protocol can run different network layer protocols, but the same PPP protocol can still be used for communication.

If the IP protocol is running on the PP link, the protocol supporting IP in NCP - IP control protocol (IPCP) should be used when configuring the IP protocol module (such as assigning IP address) at each end of the PPP link. The packet of 0xcp is also encapsulated in the PPP protocol 8021. At low speed link_ When running on, both parties can also negotiate to use compressed TCP and IP headers to reduce the number of bits sent on the link.

When the network layer is configured, the link will enter the "link open" state for data communication. The two PPP endpoints of the link can send packets to each other. The two PPP endpoints can also send echo request LCP packet and echo reply LCP packet to check the status of the link.

After the data transmission is completed, one end of the link can send a termination request LCP packet (terminate request) to request the termination of the link connection. After receiving the termination confirmation LCP packet (terminate ACK) sent by the other party, it will go to the "link termination" state. If the link fails, it will also change from "link open" state to "link end" state. When the carrier of the modem stops, it returns to the "link static" state.

The above process can be described by a state diagram.

Start with no link between devices, establish physical link first, and then establish link control protocol LCP link. After authentication, the network control protocol NCP link can be established, and then the data can be exchanged. It can be seen that PPP protocol is not a pure data link layer protocol, but also includes the contents of physical layer and network layer.

ppp-2.4.9 source code analysis

Description of global variables and structures

PPP protocol includes various link control protocols, such as LCP, PAP, CHAP, IPCP, etc. These control protocols have a lot in common. Therefore, PPPD represents each control protocol with the structure protein and puts it in the control protocol array protocols []. Generally, LCP, PAP, CHAP and IPCP are commonly used.

pppd/main.c

/*
 * PPP Data Link Layer "protocol" table.
 * One entry per supported protocol.
 * The last entry must be NULL.
 */
 // PPP data link layer "Protocol" table. One entry per supported protocol. The last entry must be empty.
struct protent *protocols[] = {
    &lcp_protent, // LCP protocol
    &pap_protent, // PAP protocol
    &chap_protent, // CHAP protocol
#ifdef CBCP_SUPPORT
    &cbcp_protent,
#endif
    &ipcp_protent, // IPCP protocol, IPv4
#ifdef INET6
    &ipv6cp_protent,  // IPCP protocol, IPv6
#endif
    &ccp_protent,
    &ecp_protent,
#ifdef IPX_CHANGE
    &ipxcp_protent,
#endif
#ifdef AT_CHANGE
    &atcp_protent,
#endif
    &eap_protent,
    NULL
};

Each control protocol is represented by the protent structure, which contains the function pointers used in the processing of each protocol.

/*
 * The following struct gives the addresses of procedures to call
 * for a particular protocol.
 */
struct protent {
    u_short protocol;		/* PPP protocol number */
    /* Initialization procedure */
    void (*init)(int unit); // Initialization pointer, which is called in main()
    /* Process a received packet */
    void (*input)(int unit, u_char *pkt, int len); // Received message processing
    /* Process a received protocol-reject */
    void (*protrej)(int unit);  // Protocol error handling
    /* Lower layer has come up */
    void (*lowerup)(int unit); // Processing after the lower layer protocol is UP
    /* Lower layer has gone down */
    void (*lowerdown)(int unit);  // Processing after lower layer protocol DOWN
    /* Open the protocol */
    void (*open)(int unit); // Open protocol
    /* Close the protocol */
    void (*close)(int unit, char *reason);  // Close agreement
    /* Print a packet in readable form */ //Print message information and try it out
    int  (*printpkt)(u_char *pkt, int len, printer_func printer, void *arg); 
    /* Process a received data packet */
    void (*datainput)(int unit, u_char *pkt, int len); //Process received packets
    bool enabled_flag;		/* 0 iff protocol is disabled */
    char *name;			/* Text name of protocol */
    char *data_name;		/* Text name of corresponding data protocol */
    option_t *options;		/* List of command-line options */
    /* Check requested options, assign defaults */
    void (*check_options)(void);  // Detect option parameters related to this protocol
    /* Configure interface for demand-dial */
    int  (*demand_conf)(int unit);  // Configure the interface to the actions required for on-demand dialing
    /* Say whether to bring up link for this pkt */
    int  (*active_pkt)(u_char *pkt, int len); // Determine the message type and activate the link 
};

Definition of pppd state machine

/*
 * Values for phase.
 */
#define PHASE_DEAD 		 0 / / static
#define PHASE_INITIALIZE 	 1 / / initialization
#define PHASE_SERIALCONN	2  
#define PHASE_DORMANT 		 3 / / sleep
#define PHASE_ESTABLISH 		 4 / / create
#define PHASE_AUTHENTICATE 	 5 / / identity authentication
#define PHASE_CALLBACK 		 6 / / callback
#define PHASE_NETWORK 		 7 / / connect
#define PHASE_RUNNING 		 8 / / running
#define PHASE_TERMINATE 		 9 / / termination
#define PHASE_DISCONNECT 	 10 / / disconnect
#define PHASE_HOLDOFF 		 11 / / delay
#define PHASE_MASTER 		 12 / / control

The main function program of pppd starts to analyze its workflow;

First stage initialization

pppd/main.c

int
main(int argc, char *argv[])
{
    int i, t;
    char *p;
    struct passwd *pw;
    struct protent *protp;
    char numbuf[16];

    strlcpy(path_ipup, _PATH_IPUP, sizeof(path_ipup));
    strlcpy(path_ipdown, _PATH_IPDOWN, sizeof(path_ipdown));

    link_stats_valid = 0;
    new_phase(PHASE_INITIALIZE);  
	// State machine in PPPD, initialization phase

    script_env = NULL;

    /* Initialize syslog facilities */
    reopen_log();

    if (gethostname(hostname, MAXNAMELEN) < 0 ) {
	option_error("Couldn't get hostname: %m");
	exit(1);
    }
    hostname[MAXNAMELEN-1] = 0;

    /* make sure we don't create world or group writable files. */
    umask(umask(0777) | 022);

    uid = getuid();
    privileged = uid == 0;
    slprintf(numbuf, sizeof(numbuf), "%d", uid);
    script_setenv("ORIG_UID", numbuf, 0);

    ngroups = getgroups(NGROUPS_MAX, groups);

    /*
     * Initialize magic number generator now so that protocols may
     * use magic numbers in initialization.
	 Now initialize the magic number generator so that the protocol can use magic numbers in initialization.
     */
    magic_init();

    /*
     * Initialize each protocol. Initialize each protocol
     */   
    for (i = 0; (protp = protocols[i]) != NULL; ++i)
        (*protp->init)(0); 
    // Protocols [] is the protocol array of global variables, which initializes all protocols in the protocol array

    /*
     * Initialize the default channel.
     */
    tty_init(); 
	//channel initialization is global TTY by default_ channel, which includes many TTY function pointers   

    progname = *argv;

    /*
     * Parse, in order, the system options file, the user's options file,
     * and the command line arguments.
     */  
	 //Parse system option files, user option files, and command line parameters in order.
    if (!options_from_file(_PATH_SYSOPTIONS, !privileged, 0, 1)  // Parse the parameter / PPP / etc in options
	|| !options_from_user()
	|| !parse_args(argc-1, argv+1)) // Parsing PPPD command line parameters
	exit(EXIT_OPTION_ERROR);
    devnam_fixed = 1;		/* can no longer change device name */

    /*
     * Work out the device name, if it hasn't already been specified,
     * and parse the tty's options file.
     */
	 //Calculate the device name (if it has not been specified) and parse tty's options file.
    if (the_channel->process_extra_options)
	(*the_channel->process_extra_options)();
	// It's actually calling tty_process_extra_options parse TTY parameters
	
    if (debug)
	setlogmask(LOG_UPTO(LOG_DEBUG));

    /*
     * Check that we are running as root.
     */
	 // geteuid gets the current running user ruid and checks whether it runs as the root user. 
    // This program must be run as root, otherwise an error will be reported
    if (geteuid() != 0) {
	option_error("must be root to run %s, since it is not setuid-root",
		     argv[0]);
	exit(EXIT_NOT_ROOT);
    }
	
	// Check whether the / dev/ppp device file is valid
    if (!ppp_available()) {
	option_error("%s", no_ppp_msg);
	exit(EXIT_NO_KERNEL_SUPPORT);
    }

In the main() function, the initialization function init() of all supported control protocols will be called, then TTY channel will be initialized, the configuration file or command line parameters will be parsed, and then whether the kernel supports PPP driver will be detected.

Function ppp_available will try to open the / dev/ppp device file to determine whether the PPP driver is loaded in the kernel. If the device file cannot be opened, judge the kernel version number through uname to distinguish whether the current kernel version supports PPP driver. If the kernel version is very old (below 2.3.x), open the PTY device file and set the PPP line protocol.

pppd/sys_linux.c

main() -> ppp_avaiable():

/********************************************************************
 *
 * ppp_available - check whether the system has any ppp interfaces
 * (in fact we check whether we can do an ioctl on ppp0).
 */
// ppp_available - check whether the system has a PPP interface (in fact, we check whether IOCTL can be executed on ppp0).
int ppp_available(void)
{
    int s, ok, fd;
    struct ifreq ifr;
    int    size;
    int    my_version, my_modification, my_patch;
    int osmaj, osmin, ospatch;

    /* get the kernel version now, since we are called before sys_init */
    // Now get the kernel version because we're in sys_init was called before
	uname(&utsname);
    osmaj = osmin = ospatch = 0;
    sscanf(utsname.release, "%d.%d.%d", &osmaj, &osmin, &ospatch);
    kernel_version = KVERSION(osmaj, osmin, ospatch);

    fd = open("/dev/ppp", O_RDWR); // Open / dev/ppp
    if (fd >= 0) {
	new_style_driver = 1; // Support PPPK
......
/*
 * Validate the version of the driver against the version that we used.
 */
// Verify the version of the driver according to the version we use.
	    decode_version(VERSION,
			   &my_version,
			   &my_modification,
			   &my_patch);

	    /* The version numbers must match */
	    if (driver_version != my_version) // Version number must match
		ok = 0;

	    /* The modification levels must be legal */
	    if (driver_modification < 3) { // The modification level must be legal
		if (driver_modification >= 2) {
		    /* we can cope with 2.2.0 and above */
		    driver_is_old = 1;
		} else {
		    ok = 0;
		}
	    }

	    if (!ok) {
		slprintf(route_buffer, sizeof(route_buffer),
			 "Sorry - PPP driver version %d.%d.%d is out of date\n",
			 driver_version, driver_modification, driver_patch);

		no_ppp_msg = route_buffer;
	    }
	}
    }
    close(s);
    return ok;
}

Next, the validity of the option parameters will be checked, including system parameters, authentication related parameters, etc. the parameter configuration and tty parameters of each control protocol will also be checked. The latter is to execute or keep pppd running in the foreground in the way of daemon, and set some environment variables and signal processing functions

pppd/main.c

    /*
     * Check that the options given are valid and consistent.
     */
	 // Check that the options given are valid and consistent.
    check_options(); // Check option parameters
    if (!sys_check_options()) // Test system parameters
	exit(EXIT_OPTION_ERROR);
    auth_check_options(); // Check the parameters related to certification
#ifdef HAVE_MULTILINK
    mp_check_options();
#endif
    for (i = 0; (protp = protocols[i]) != NULL; ++i)
	if (protp->check_options != NULL) // Check the parameter configuration of each control protocol 
	    (*protp->check_options)();
    if (the_channel->check_options) 
	(*the_channel->check_options)();  // Call tty_check_options detect TTY parameters


    if (dump_options || dryrun) {
	init_pr_log(NULL, LOG_INFO);
	print_options(pr_log, NULL);
	end_pr_log();
    }

    if (dryrun)
	die(0);

    /* Make sure fds 0, 1, 2 are open to somewhere. */
    fd_devnull = open(_PATH_DEVNULL, O_RDWR);
    if (fd_devnull < 0)
	fatal("Couldn't open %s: %m", _PATH_DEVNULL);
    while (fd_devnull <= 2) {
	i = dup(fd_devnull);
	if (i < 0)
	    fatal("Critical shortage of file descriptors: dup failed: %m");
	fd_devnull = i;
    }

    /*
     * Initialize system-dependent stuff. Something that initializes the system.
     */
    sys_init();

#ifdef USE_TDB
    pppdb = tdb_open(_PATH_PPPDB, 0, 0, O_RDWR|O_CREAT, 0644);
    if (pppdb != NULL) {
	slprintf(db_key, sizeof(db_key), "pppd%d", getpid());
	update_db_entry();
    } else {
	warn("Warning: couldn't open ppp database %s", _PATH_PPPDB);
	if (multilink) {
	    warn("Warning: disabling multilink");
	    multilink = 0;
	}
    }
#endif

    /*
     * Detach ourselves from the terminal, if required,
     * and identify who is running us.
     */
	 // If necessary, separate ourselves from the terminal and determine who is running us.
    if (!nodetach && !updetach)
	detach(); //By default, it is placed in the background and executed as daemon. You can also configure the nodetach parameter in / etc/ppp/option to be placed in the foreground for execution
    p = getlogin();
    if (p == NULL) {
	pw = getpwuid(uid);
	if (pw != NULL && pw->pw_name != NULL)
	    p = pw->pw_name;
	else
	    p = "(unknown)";
    }
	 // Ready to execute
    syslog(LOG_NOTICE, "pppd %s started by %s, uid %d", VERSION, p, uid);
	script_setenv("PPPLOGNAME", p, 0);

    if (devnam[0])
	script_setenv("DEVICE", devnam, 1);
    slprintf(numbuf, sizeof(numbuf), "%d", getpid());
    script_setenv("PPPD_PID", numbuf, 1);

    setup_signals(); //Set signal processing function

    create_linkpidfile(getpid()); //Create PID file

    waiting = 0;

    /*
     * If we're doing dial-on-demand, set up the interface now.
     */
	 // If we want to dial on demand, set up the interface now.
    if (demand) {
	/*
	 * Open the loopback channel and set it up to be the ppp interface.
	 */
	 // Open the loopback channel and set it as the ppp interface.
	fd_loop = open_ppp_loopback(); // Run in on-demand dialing mode, configurable
	set_ifunit(1);  // Set the IFNAME environment variable to the interface name, such as ppp0
	/*
	 * Configure the interface and mark it up, etc.
	 */
	 // Configure the interface and mark it, and so on.
	demand_conf();
    }

When the demand variable is 1, it means that PPPD operates in on-demand dialing mode, open the loopback channel and set it as the ppp interface.

The function of on-demand dialing is: if there is no data flow to the external network, the PPP link will not be established. When traffic is detected to access the external network, PPP will start dialing and establish a connection with the dial-up server of ISP. Billing will be generated only after dialing is successful. On the contrary, if there is no traffic accessing the external network within a certain period of time, PPP will disconnect, saving traffic costs for users.

To realize the on-demand dialing function of PPP, first call open_ppp_loopback:

pppd/sys-linux.c

main() -> open_ppp_loopback():

int
open_ppp_loopback(void)
{
    intflags;
    looped=1; //Set the global variable looped to 1, which will be used later
    if(new_style_driver){
       /* allocate ourselves a ppp unit */
       if(make_ppp_unit()<0) //Create PPP network interface
           die(1);
       modify_flags(ppp_dev_fd,0, SC_LOOP_TRAFFIC); //Set SC through ioctl_ LOOP_ TRAFFIC
       set_kdebugflag(kdebugflag);
       ppp_fd=-1;
       returnppp_dev_fd;
    }
......
}

Global variable new_style_driver, this variable is already in PPP_ The avaliable function is set to 1. Next, call make_ppp_unit opens the / dev/ppp device file and requests the creation of a new unit.

pppd/sys-linux.c

main() -> open_ppp_loopback() -> make_ppp_unit():

static int make_ppp_unit()
{
       intx,flags;
       if(ppp_dev_fd>=0){ //If it has been opened, close it first
              dbglog("in make_ppp_unit, already had /dev/ppp open?");
              close(ppp_dev_fd);
       }
       ppp_dev_fd=open("/dev/ppp", O_RDWR);  //Open / dev/ppp
       if(ppp_dev_fd<0)
              fatal("Couldn't open /dev/ppp: %m");
       flags=fcntl(ppp_dev_fd, F_GETFL);
       if(flags==-1
          ||fcntl(ppp_dev_fd, F_SETFL,flags| O_NONBLOCK)==-1) //Set to non blocking
              warn("Couldn't set /dev/ppp to nonblock: %m");
       ifunit=req_unit; //The unit number of the incoming request can be configured through / etc/ppp/options
       x=ioctl(ppp_dev_fd, PPPIOCNEWUNIT,&ifunit); //Create a new unit request
       if(x<0&&req_unit>=0&& errno == EEXIST){
              warn("Couldn't allocate PPP unit %d as it is already in use",req_unit);
              ifunit=-1;
              x=ioctl(ppp_dev_fd, PPPIOCNEWUNIT,&ifunit);
       }
       if(x<0)
              error("Couldn't create new ppp unit: %m");
       return x;
}

The unit here can be understood as a PPP interface. In Multilink PPP, a unit can be composed of multiple channels, that is, there can be multiple physical links under a PPP interface. The physical links here are not necessarily physical interfaces, but also multiple frequency bands (channels) on a physical interface, such as HDLC channel. In PPPK, channel is represented by structure channel and unit is represented by structure PPP.

ppp0 seen through ifconfig in Linux is established through ioctl (ppp_dev_fd, pppiocnewunit, & ifunit). Unit number can be configured, but it is generally not configured. Passing in - 1 will automatically allocate an unused unit number, which starts from 0 by default. This ioctl calls the PPP registered in PPPK_ ioctl, then ppp_ioctl calls ppp_unattached_ioctl, this function will call PPP again_ create_ Interface create a PPP network interface

linux-5.9/drivers/net/ppp/ppp_generic.c

main() -> open_ppp_loopback() -> make_ppp_unit() -> ioctl(ppp_dev_fd,PPPIOCNEWUNIT,&ifunit) -> ppp_ioctl() –> ppp_unattached_ioctl()-> ppp_create_interface():

/*
 * Stuff for handling the lists of ppp units and channels
 * and for initialization. Deal with ppp unit and channel list and initialization.
 */
/*
 * Create a new ppp interface unit.  Fails if it can't allocate memory
 * or if there is already a unit with the requested number.
 * unit == -1 means allocate a new number.
 */
// Create a new ppp interface unit. If it cannot allocate memory, or there is already a unit with the request number, it fails.
// unit == -1 means to assign a new number.
static int ppp_create_interface(struct net *net, struct file *file, int *unit)
{
	struct ppp_config conf = {
		.file = file,
		.unit = *unit,
		.ifname_is_set = false,
	};
	struct net_device *dev;
	struct ppp *ppp;
	int err;
	dev = alloc_netdev(sizeof(struct ppp), "", NET_NAME_ENUM, ppp_setup); 
    //Assign net_device, this structure represents a network interface
	if (!dev) {
		err = -ENOMEM;
		goto err;
	}
	dev_net_set(dev, net);
	dev->rtnl_link_ops = &ppp_link_ops;
	rtnl_lock(); // Lock
	err = ppp_dev_configure(net, dev, &conf); // Configure ppp; Initialization operation,
	if (err < 0)
		goto err_dev;
	ppp = netdev_priv(dev);
	*unit = ppp->file.index;
	rtnl_unlock(); // Mutually exclusive operation, unlocking
	return 0;
err_dev:
	rtnl_unlock(); // Unlock
	free_netdev(dev); 
err:
	return err;
}

Then call ppp_. dev_ Configure configuration interface:

linux-5.9/drivers/net/ppp/ppp_generic.c

main() -> open_ppp_loopback() -> make_ppp_unit() -> ioctl(ppp_dev_fd,PPPIOCNEWUNIT,&ifunit) -> ppp_ioctl() –> ppp_unattached_ioctl()-> ppp_create_interface() -> ppp_dev_configure():

static int ppp_dev_configure(struct net *src_net, struct net_device *dev,
			     const struct ppp_config *conf)
{
	struct ppp *ppp = netdev_priv(dev);
	int indx;
	int err;
	int cpu;
 
	ppp->dev = dev;  // Point to assigned net_device structure
	ppp->ppp_net = src_net;
	ppp->mru = PPP_MRU; // Initialize MRU (maximum receiving unit)
	ppp->owner = conf->file;
 
	init_ppp_file(&ppp->file, INTERFACE); //Initialize ppp_file structure, type INTERFACE
	ppp->file.hdrlen = PPP_HDRLEN - 2; /* don't count proto bytes */
 
	for (indx = 0; indx < NUM_NP; ++indx)
		ppp->npmode[indx] = NPMODE_PASS;
	INIT_LIST_HEAD(&ppp->channels); //channel linked list in PPP interface
	spin_lock_init(&ppp->rlock); //Receive queue lock
	spin_lock_init(&ppp->wlock); //Send queue lock
 ......
	err = ppp_unit_register(ppp, conf->unit, conf->ifname_is_set);
    // Register the ppp network interface. Only then can ifconfig see this interface
	if (err < 0)
		goto err2; 
	conf->file->private_data = &ppp->file;
	return 0;
err2:
	free_percpu(ppp->xmit_recursion);
err1:
	return err;
}

Now the PPP network interface has been created. For example, the established interface is called ppp0. Here ppp0 is just a "false interface". In fact, the whole dialing process of PPP has not started at all. The reason why this interface is established is to enable data messages to be sent through this interface, so as to trigger PPP dialing.

Next, go back to the opening of PPPD_ ppp_ loopback,make_ ppp_ After unit returns successfully, modify will be called_ Flags function to set the flag bit SC_LOOP_TRAFFIC, this function actually calls ioctl() - > PPP_ Ioctl() to set the flag.

Flag bit SC_LOOP_TRAFFIC is very important. When sending data through the ppp0 interface, PPPK will wake up the PPPD process to establish a real PPP connection. Previously, when the PPP interface was created in the kernel, an interface packet sending function PPP was registered_ start_ Xmit, when the network program sends data through the ppp0 interface, the TCP/IP protocol stack will eventually call this function.

linux-5.9/drivers/net/ppp_generic.c

ppp_start_xmit() -> ppp_xmit_process()-> ppp_send_frame():

/*
 * Compress and send a frame.
 * The caller should have locked the xmit path,
 * and xmit_pending should be 0.
 */
//Compress and send a frame. The caller should have locked the xmit path and xmit_pending should be 0.
static void
ppp_send_frame(struct ppp *ppp, struct sk_buff *skb)
{
 ......
	/*
	 * If we are waiting for traffic (demand dialling),
	 * queue it up for pppd to receive.
	 */
	if (ppp->flags & SC_LOOP_TRAFFIC) {
		if (ppp->file.rq.qlen > PPP_MAX_RQLEN)
			goto drop;
		skb_queue_tail(&ppp->file.rq, skb); //The sent packets are placed in the rq receive pair column instead of the send queue
		wake_up_interruptible(&ppp->file.rwait); //Wake up PPPD process
		return;
	}
 ......
}

Obviously, as long as the SC in PPP - > flags_ LOOP_ When the traffic is set, we need to do some special processing: put the sent data packet in the receiving queue PPP - > file RQ instead of the usual send queue. Wakes up the PPPD process for processing and does not send data.

Return to the main function main(), when open_ ppp_ After the loopback call returns, its return value is assigned to FD at the same time_ Loop represents the file descriptor of / dev/ppp. At this time, the network interface ppp0 has been created and registered in the TCP/IP protocol stack. Of course, only the ppp0 interface is not enough. We also need to configure the ppp0 interface, and then call demand_conf:

pppd/demand.c

main() -> demand_conf():

/*
 * demand_conf - configure the interface for doing dial-on-demand.
 */
// Configure the interface for on-demand dialing.
void
demand_conf(void)
{
 ......
    netif_set_mtu(0, MIN(lcp_allowoptions[0].mru, PPP_MRU)); // Set MTU of ppp0 interface
    if (ppp_send_config(0, PPP_MRU, (u_int32_t) 0, 0, 0) < 0
	|| ppp_recv_config(0, PPP_MRU, (u_int32_t) 0, 0, 0) < 0)
	    fatal("Couldn't set up demand-dialled PPP interface: %m");

#ifdef PPP_FILTER
    set_filters(&pass_filter, &active_filter);
#endif

    /*
     * Call the demand_conf procedure for each protocol that's got one.
     */
    // Call demand for each protocol that has a protocol_ Conf procedure.
    for (i = 0; (protp = protocols[i]) != NULL; ++i)
	if (protp->enabled_flag && protp->demand_conf != NULL)
	    if (!((*protp->demand_conf)(0)))
            // Call the demand of each control protocol_ Conf function
		die(1);
}

This function sets the MTU and MRU of ppp0, and then calls demand_ of each control protocol. Conf function. For LCP, PAP and CHAP protocols, protocol - > demand_conf is null. Only the IPCP protocol has the function pointer to initialize:

pppd/ipcp.c

main() -> demand_conf()-> ip_demand_conf():

/*
 * ip_demand_conf - configure the interface as though
 * IPCP were up, for use with dial-on-demand.
 */
// ip_demand_conf - configure the interface to the IPCP startup state to use on-demand dialing.
static int
ip_demand_conf(int u)
{
    ipcp_options *wo = &ipcp_wantoptions[u];

    if (wo->hisaddr == 0 && !noremoteip) {
	/* make up an arbitrary address for the peer */
	wo->hisaddr = htonl(0x0a707070 + ifunit); //Peer address
	wo->accept_remote = 1;
    }
    if (wo->ouraddr == 0) {
	/* make up an arbitrary address for us */
	wo->ouraddr = htonl(0x0a404040 + ifunit);  //Local address
	wo->accept_local = 1;
	ask_for_local = 0;	/* don't tell the peer this address */
    }
    if (!sifaddr(u, wo->ouraddr, wo->hisaddr, GetMask(wo->ouraddr)))
	return 0;
    ipcp_script(_PATH_IPPREUP, 1);
    if (!sifup(u))
	return 0;
    if (!sifnpmode(u, PPP_IP, NPMODE_QUEUE))
        // Configure local address, peer address and subnet mask on ppp0 interface
	return 0;
    if (wo->default_route)
	if (sifdefaultroute(u, wo->ouraddr, wo->hisaddr, wo->replace_default_route))
	    // Set ppp0 as the default gateway interface
        default_route_set[u] = 1;
    if (wo->proxy_arp)
	if (sifproxyarp(u, wo->hisaddr))
	    proxy_arp_set[u] = 1;

    notice("local  IP address %I", wo->ouraddr);
    if (wo->hisaddr)
	notice("remote IP address %I", wo->hisaddr);

    return 1;
}

As mentioned above, in the on-demand dial-up mode, the establishment of PPP connection will be triggered only when the data message is sent through the ppp0 interface. So here, the IP provided by the IPCP protocol block_ demand_ The conf function configures two fake IP addresses for ppp0: the local IP address is 10.64.64.64 and the opposite IP address is 10.112.112.112, and sets the opposite IP as the default gateway. In this way, when the user accesses the external network, the Linux routing subsystem will select the ppp0 interface to send data packets, thus triggering the establishment of PPP link.

The second stage starts the link

Return to the main function main()

pppd/main.c

/* The first stage is link initialization */
......
    do_callback = 0;
    for (;;) {

	bundle_eof = 0;
	bundle_terminating = 0;
	listen_time = 0;
	need_holdoff = 1;
	devfd = -1;
	status = EXIT_OK;
	++unsuccess;
	doing_callback = do_callback;
	do_callback = 0;

	if (demand && !doing_callback) {  // On demand dialing
	    /*
	     * Don't do anything until we see some activity.
	     */
	    new_phase(PHASE_DORMANT); // PPPD, state machine, delay state
	    demand_unblock();
	    add_fd(fd_loop); // 1. Add fd_loop, the file descriptor of / dev/ppp, is added to the fds of select
	    for (;;) {
		handle_events();  // 2. select event handling
		if (asked_to_quit)
		    break;
		if (get_loop_output())  // If the sending data is valid, it will jump out of the loop
		    break;
	    }
	    remove_fd(fd_loop); 
        //Note: remove the / dev/ppp file descriptor from fds and add it later
	    if (asked_to_quit)
		break;

	    /*
	     * Now we want to bring up the link.
	     */
	    demand_block();
	    info("Starting link"); // Start link
	}

If it is in the demand dial-up mode, the PPPD state machine enters PHASE_DORMANT mainly consists of two parts:

  1. Call add_fd the file descriptor of / dev/ppp to fd_loop join in_ In FDS:

pppd/sys-linux.c:

main() -> add_fd():

/*
 * add_fd - add an fd to the set that wait_input waits for.
 */
// add_fd - wait_ Add an fd to the input waiting set.
void add_fd(int fd)
{
    if (fd >= FD_SETSIZE)
	fatal("internal error: file descriptor too large (%d)", fd);
    FD_SET(fd, &in_fds);
    if (fd > max_in_fd)
	max_in_fd = fd;
}
  1. In nested for(;) Call handle in an endless loop_ The events function handles events.

pppd/main.c:

main() -> handle_events():

/*
 * handle_events - wait for something to happen and respond to it.
 */
// Wait for things to happen and react.
static void
handle_events(void)
{
    struct timeval timo;
    unsigned char buf[16];

    kill_link = open_ccp_flag = 0;

    /* alert via signal pipe */
    waiting = 1;
    /* flush signal pipe */
    for (; read(sigpipe[0], buf, sizeof(buf)) > 0; );
    add_fd(sigpipe[0]);
    /* wait if necessary */
    if (!(got_sighup || got_sigterm || got_sigusr2 || got_sigchld))
	wait_input(timeleft(&timo)); // Call select for I/O multiplexing
    waiting = 0;
    remove_fd(sigpipe[0]);

    calltimeout(); //Call the registered timer function
    // The following is signal processing
    if (got_sighup) {
	info("Hangup (SIGHUP)");
	kill_link = 1;
	got_sighup = 0;
	if (status != EXIT_HANGUP)
	    status = EXIT_USER_REQUEST;
    }
    if (got_sigterm) {  // Exit when SIGTERM signal is received
	info("Terminating on signal %d", got_sigterm);
	kill_link = 1;
	asked_to_quit = 1;
	persist = 0;
	status = EXIT_USER_REQUEST;
	got_sigterm = 0;
    }
......
}

The key point of this function is to call wait_input calls select on the / dev/ppp file descriptor added earlier to listen for events

pppd/sys-linux.c:

main() -> handle_events()-> wait_input():

/********************************************************************
 *
 * wait_input - wait until there is data available,
 * for the length of time specified by *timo (indefinite
 * if timo is NULL).
 */
//wait_input - wait for data until data is available, and the length of time is specified by * timo (infinite if timo is empty).
void wait_input(struct timeval *timo)
{
    fd_set ready, exc;
    int n;

    ready = in_fds; //in_fds contains the file descriptor of / dev/ppp
    exc = in_fds;
    n = select(max_in_fd + 1, &ready, NULL, &exc, timo); //Call select to listen for events
    if (n < 0 && errno != EINTR)
	fatal("select: %m");
}

/dev/ppp in front of make_ ppp_ The unit function has been set to non blocking, so when no event occurs, the select call will not be blocked all the time. When the timeout expires, wait_input will be returned soon, and the calltimeout function will be called to process the registered timer function. These timer functions are needed by each control protocol and its fsm state machine. From here, we can see the necessity of setting / dev/ppp to non blocking mode.

This nested for(;) When can the loop jump out? There are two possibilities:

  1. variable asked_to_quit Set to 1. reference resources handle_events Signal processing in, when received SIGTERM Indicates that the user wants to actively exit PPPD. 
    
  2. function get_loop_output The call returns 1. Let's analyze this function:
    

pppd/sys-linux.c:

main() -> get_loop_output():

/********************************************************************
 *
 * get_loop_output - get outgoing packets from the ppp device,
 * and detect when we want to bring the real link up.
 * Return value is 1 if we need to bring up the link, 0 otherwise.
 */
// get_loop_output - get the outgoing packet from the ppp device and detect when we want to start the real link.
// If the link needs to be opened, the return value is 1; otherwise, the return value is 0.
int
get_loop_output(void)
{
    int rv = 0;
    int n;

    if (new_style_driver) {
	while ((n = read_packet(inpacket_buf)) > 0)  // Data sent via ppp0
	    if (loop_frame(inpacket_buf, n)) // True when sending legal data
		rv = 1; // Returns 1, resulting in nested for(;) Loop exit
	return rv;
    }
......
}

First call read_packet read data to inpacket_ In buf: read_packet - get a PPP packet from the serial device. This function is very simple. In fact, it calls the standard file reading function read() to read the / dev/ppp device file. In fact, it calls PPP in PPPK_ read:

linux-5.9/drivers/net/ppp_generic.c

main() -> get_loop_output() -> read_packet() -> ppp_read():

static ssize_t ppp_read(struct file *file, char __user *buf,
			size_t count, loff_t *ppos)
{
	struct ppp_file *pf = file->private_data;
	DECLARE_WAITQUEUE(wait, current);
	ssize_t ret;
	struct sk_buff *skb = NULL;
	struct iovec iov;
	struct iov_iter to;
 
	ret = count;
 
	if (!pf)
		return -ENXIO;
	add_wait_queue(&pf->rwait, &wait); //Note: adding to the waiting queue will be ppp_send_frame() wake up
	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE); //Set the status of the current process to interruptible sleep
		skb = skb_dequeue(&pf->rq); //Queue a packet from the receiving queue
		if (skb)  //If there is a data packet, it indicates that there is data readable
			break;
		ret = 0;
		if (pf->dead) //unit or channel no longer exists
			break;
		if (pf->kind == INTERFACE) {
			/*
			 * Return 0 (EOF) on an interface that has no
			 * channels connected, unless it is looping
			 * network traffic (demand mode).
			 */
			struct ppp *ppp = PF_TO_PPP(pf);
 
			ppp_recv_lock(ppp);
			if (ppp->n_channels == 0 &&
			    (ppp->flags & SC_LOOP_TRAFFIC) == 0) {
				ppp_recv_unlock(ppp);
				break;
			}
			ppp_recv_unlock(ppp);
		}
		ret = -EAGAIN;
		if (file->f_flags & O_NONBLOCK) //If fd is O_NONBLOCK, you don't sleep and jump out of the cycle directly
			break;
		ret = -ERESTARTSYS;
		if (signal_pending(current)) // After receiving the signal, you don't sleep and jump out of the loop directly
			break;
		schedule(); // Process scheduler to make the current process sleep
	}
	set_current_state(TASK_RUNNING);
	remove_wait_queue(&pf->rwait, &wait);
 
	if (!skb) // Being able to go here indicates that fd is O_NONBLOCK or received signal
		goto out;
  
	ret = -EOVERFLOW;
	if (skb->len > count)
		goto outf;
	ret = -EFAULT;
	iov.iov_base = buf;
	iov.iov_len = count;
	iov_iter_init(&to, READ, &iov, 1, count);
	if (skb_copy_datagram_iter(skb, 0, &to, skb->len)) // Copy data to user buffer
		goto outf;
	ret = skb->len; // The return value is the data length
 
 outf:
	kfree_skb(skb);
 out:
	return ret;
}

This function adds the PPPD process to the waiting queue. If pf - > RQ queue is not empty, it reads the first packet in the queue and returns immediately. As mentioned above, when the network program sends data through the ppp0 interface, it will eventually call the kernel function ppp_send_frame, and the sent data is placed in PPP - > file In the RQ queue, this queue is the PF - > RQ queue here, which means PPP_ The data read by read is actually the data just sent by the network program.

Consider sending data through ppp0, read_packet returns the length of the read data, and loop_frame will be called. In fact, the last step here is to call the IP address of the IPCP protocol block_ active_ Pkt function is used to check the validity of data packets, which will not be analyzed in detail here. If the sent data is a legal IP message, these packets will be saved later and temporarily placed in pen_ In the qtail queue, resend after the PPP link is established.

The third stage is to establish PPP link

If it is in the demand dial-up mode, and assuming that data is sent through ppp0 and is a legal IP message, the nested for(;) in the second stage The loop will jump out, and the next code is the same as the normal dialing mode.

Go back to the main function main() again and start to establish a real PPP link:

pppd/main.c

/* Phase II */
......
	get_time(&start_time);
	script_unsetenv("CONNECT_TIME");
	script_unsetenv("BYTES_SENT");
	script_unsetenv("BYTES_RCVD");

	lcp_open(0);		/* Start protocol */
	// Step 1: open PPPK interface to send LCP frame
	start_link(0);
	while (phase != PHASE_DEAD) { // Step 2: pppd state machine circulates event processing; pppd non static state
	    handle_events();  //select event handling
	    get_input(); // Processing of received messages
	    if (kill_link) // Kill_link is a global variable, initialized to 0. Once it is set to 1, it indicates that the user requests to disconnect
		lcp_close(0, "User request"); // break link
	    if (asked_to_quit) {
		bundle_terminating = 1;
		if (phase == PHASE_MASTER) // pppd is under control
		    mp_bundle_terminated();
	    }
	    if (open_ccp_flag) {
		if (phase == PHASE_NETWORK || phase == PHASE_RUNNING) { // pppd is connected or static
		    ccp_fsm[0].flags = OPT_RESTART; /* clears OPT_SILENT Clear OPT_SILENT */ 
		    (*ccp_protent.open)(0);// Protocol processing function pointer, link open
		}
	    }
	}

Step 1: call lcp_open(0) establish LCP link.

pppd/lcp.c

Main() -> lcp_open():

/*
 * lcp_open - LCP is allowed to come up.
 */
void
lcp_open(int unit)
{
    fsm *f = &lcp_fsm[unit]; // LCP state machine
    lcp_options *wo = &lcp_wantoptions[unit];

    f->flags &= ~(OPT_PASSIVE | OPT_SILENT);
    if (wo->passive)
	f->flags |= OPT_PASSIVE;
    if (wo->silent)
	f->flags |= OPT_SILENT;
    fsm_open(f);
}

Call FSM_ The open state machine function opens the LCP state machine:

pppd/fsm.c

main() -> lcp_open() -> fsm_open():

/*
 * fsm_open - Link is allowed to come up.
 */
void
fsm_open(fsm *f)
{
    switch( f->state ){
    case INITIAL:
	f->state = STARTING;
	if( f->callbacks->starting )
	    (*f->callbacks->starting)(f); // Start link establishment at initialization
	break;

    case CLOSED: // If closed
	if( f->flags & OPT_SILENT )
	    f->state = STOPPED;
	else {
	    /* Send an initial configure-request Send an initial configuration request */
	    fsm_sconfreq(f, 0);
	    f->state = REQSENT;
	}
	break;

    case CLOSING:
	f->state = STOPPING;
	/* fall through */
    case STOPPED:
    case OPENED:
	if( f->flags & OPT_RESTART ){
	    fsm_lowerdown(f);
	    fsm_lowerup(f);
	}
	break;
    }
}

Initialization status, actual call LCP_ starting()-> link_ required():

pppd/auth.c

main() -> lcp_open() -> fsm_open() -> lcp_starting() -> link_required():

/*
 * An Open on LCP has requested a change from Dead to Establish phase.
 */
// An open requirement of LCP changes from static phase to establishment phase.
void
link_required(int unit)
{
}

/*
 * Bring the link up to the point of being able to do ppp.
 */
void start_link(int unit)
{
    status = EXIT_CONNECT_FAILED;
    new_phase(PHASE_SERIALCONN); // The PPPD state machine is in the "serial port connection" stage

    hungup = 0;
    devfd = the_channel->connect();  //1. Call connect_tty connection TTY driver
    if (devfd < 0)
	goto fail;

    /* set up the serial device as a ppp interface */
    /*
     * N.B. we used to do tdb_writelock/tdb_writeunlock around this
     * (from establish_ppp to set_ifunit).  However, we won't be
     * doing the set_ifunit in multilink mode, which is the only time
     * we need the atomicity that the tdb_writelock/tdb_writeunlock
     * gives us.  Thus we don't need the tdb_writelock/tdb_writeunlock.
     */
    fd_ppp = the_channel->establish_ppp(devfd); //2. Call tty_establish_ppp
    if (fd_ppp < 0) {
	status = EXIT_FATAL_ERROR;
	goto disconnect;
    }

    if (!demand && ifunit >= 0) //If it is in demand mode or not, you need to set the IFNAME environment variable
	set_ifunit(1);

    /*
     * Start opening the connection and wait for
     * incoming events (reply, timeout, etc.).
     */
    if (ifunit >= 0)
	notice("Connect: %s <--> %s", ifname, ppp_devnam);
    else
	notice("Starting negotiation on %s", ppp_devnam);
    add_fd(fd_ppp);
	//Add the description of / dev/ppp file to fds. If it is the demand mode, it needs to be added again because it has been remove d in main(). For the non demand mode, it is the first time
    status = EXIT_NEGOTIATION_FAILED;
    new_phase(PHASE_ESTABLISH); // The PPPD state machine enters the "link establishment" stage

    lcp_lowerup(0); //3. Send LCP Configure Request message and request the other party to establish LCP link
    return;

 disconnect:
    new_phase(PHASE_DISCONNECT);
    if (the_channel->disconnect)
	the_channel->disconnect();

 fail:
    new_phase(PHASE_DEAD);
    if (the_channel->cleanup)
	(*the_channel->cleanup)();
}

The main function of this function can be seen from the function name, which is to bring all the required physical links. Now the PPPD state machine enters PHASE_SERIALCONN phase.

  1. call connect_tty Open serial port TTY Drive and configure TTY Parameter, variable ppp_devnam Is a serial port driven device file, such as/dev/ttyS0,/dev/ttyUSB0,/dev/ttyHDLC0 For details, please refer to the relevant serial port TTY Drive, no specific analysis here.
    
  2. Then call tty_establish_ppp: 
    

pppd/sys-linux.c

main() -> lcp_open() -> fsm_open() -> lcp_starting() -> link_required() -> tty_establish_ppp():

/********************************************************************
 *
 * tty_establish_ppp - Turn the serial port into a ppp interface.
 */
// tty_establish_ppp - change the serial port into a ppp interface.
int tty_establish_ppp (int tty_fd)
{
    int ret_fd;

/*
 * Ensure that the tty device is in exclusive mode.Make sure the tty device is in exclusive mode.
 */
    if (ioctl(tty_fd, TIOCEXCL, 0) < 0) {
	if ( ! ok_error ( errno ))
	    warn("Couldn't make tty exclusive: %m");
    }
/*
 * Demand mode - prime the old ppp device to relinquish the unit.
 Demand mode - start the old ppp device and abandon the unit.
 */
    if (!new_style_driver && looped
	&& ioctl(slave_fd, PPPIOCXFERUNIT, 0) < 0) {
	error("ioctl(transfer ppp unit): %m, line %d", __LINE__);
	return -1;
    }
/*
 * Set the current tty to the PPP discpline Set current tty as PPP rule
 */

#ifndef N_SYNC_PPP
#define N_SYNC_PPP 14
#endif
    ppp_disc = (new_style_driver && sync_serial)? N_SYNC_PPP: N_PPP; // Synchronous or asynchronous PPP
    if (ioctl(tty_fd, TIOCSETD, &ppp_disc) < 0) { // 2.1 procedures for setting PPP lines
	if ( ! ok_error (errno) ) {
	    error("Couldn't set tty to PPP discipline: %m");
	    return -1;
	}
    }
    ret_fd = generic_establish_ppp(tty_fd);  // 2.2 create PPP interface
......
}

It is divided into two parts for specific in-depth analysis:

2.1 first call IOCTL (tty_fd, tiocsetd, & ppp_disc) to bind the TTY driver to the PPP line procedure. IOCTL here is an operation on the TTY file descriptor, which actually calls TTY in the kernel_ ioctl()-> tiocsetd():

linux-5.9/drivers/tty/tty_io.c

main() -> lcp_open() -> fsm_open() -> lcp_starting() -> link_required() -> tty_establish_ppp() -> tty_ioctl() -> tiocsetd():

/**
 *	tiocsetd	-	set line discipline
 *	@tty: tty device
 *	@p: pointer to user data
 *
 *	Set the line discipline according to user request.
 *
 *	Locking: see tty_set_ldisc, this function is just a helper
 */
static int tiocsetd(struct tty_struct *tty, int __user *p)
{
	int disc;
	int ret;
	if (get_user(disc, p))
		return -EFAULT;
	ret = tty_set_ldisc(tty, disc);//Set the line protocol, i.e. asynchronous PPP or synchronous PPP
	return ret;
}

This tiocsetd function just puts the int parameter passed in from the user mode into the kernel mode ldisc, and then calls tty_set_ldist sets the line procedure,

linux-5.9/drivers/tty/tty_ldisc.c

main() -> lcp_open() -> fsm_open() -> lcp_starting() -> link_required() -> tty_establish_ppp() -> tty_ioctl() -> tiocsetd() -> tty_set_ldisc():

/**
 *	tty_set_ldisc		-	set line discipline
 *	@tty: the terminal to set
 *	@ldisc: the line discipline
 *
 *	Set the discipline of a tty line. Must be called from a process
 *	context. The ldisc change logic has to protect itself against any
 *	overlapping ldisc change (including on the other end of pty pairs),
 *	the close of one side of a tty/pty pair, and eventually hangup.
 */
// Set the discipline of tty line. It must be invoked from the context of the process. The ldisc change logic must protect itself from any overlapping ldisc changes (including at the other end of the pty pair), tty/pty closing on one side, and final hang up.
int tty_set_ldisc(struct tty_struct *tty, int disc)
{
......
	new_ldisc = tty_ldisc_get(tty, disc);  //From tty_ldiscs global array to find the registered PPP line protocol
	if (IS_ERR(new_ldisc))
		return PTR_ERR(new_ldisc);
......
}

This function binds n for TTY driver_ PPP line rules, after binding, call the open() function of the line rules, for N_PPP is actually calling ppp_asynctty_open, this function allocates and initializes the struct asyncppp structure to represent an asynchronous PPP, and the disc of TTY structure_ Data points to the structure. Also call ppp_register_net_channel registers an asynchronous PPP channel:

linux-5.9/drivers/net/ppp_generic.c

main() -> lcp_open() -> fsm_open() -> lcp_starting() -> link_required() -> tty_establish_ppp() -> tty_ioctl() -> tiocsetd() -> tty_set_ldisc() -> ppp_asynctty_open() -> ppp_register_channel() -> ppp_register_net_channel():

int ppp_register_channel(struct ppp_channel *chan)
{
	return ppp_register_net_channel(current->nsproxy->net_ns, chan);
}
/* Create a new, unattached ppp channel for specified net. */
int ppp_register_net_channel(struct net *net, struct ppp_channel *chan)
{
	struct channel *pch;
	struct ppp_net *pn;
	pch = kzalloc(sizeof(struct channel), GFP_KERNEL);
	if (!pch)
		return -ENOMEM;
	pn = ppp_pernet(net);
	pch->ppp = NULL;  // The channel does not belong to any PPP unit yet and is initialized to NULL
	pch->chan = chan; // Point to PPP in channel_ Pointer to channel
	pch->chan_net = get_net(net);
	chan->ppp = pch;  // ppp_ Pointer to channel in channel
	init_ppp_file(&pch->file, CHANNEL); // Initialize ppp_file, type CHANNEL
	pch->file.hdrlen = chan->hdrlen;
#ifdef CONFIG_PPP_MULTILINK
	pch->lastseq = -1;
#endif /* CONFIG_PPP_MULTILINK */
	init_rwsem(&pch->chan_sem);
	spin_lock_init(&pch->downl);
	rwlock_init(&pch->upl);
	spin_lock_bh(&pn->all_channels_lock);
	pch->file.index = ++pn->last_channel_index; // channel index value, which will be used later
	list_add(&pch->list, &pn->new_channels); // Register to new_ Global channels
	atomic_inc(&channel_count);
	spin_unlock_bh(&pn->all_channels_lock);
	return 0;
}

At this point, the implementation of IOCTL (tty_fd, tiocsetd, &ppp_disc) in the kernel is analyzed.

2.2 return tty_establish_ppp, continue calling generic_establish_ppp create PPP interface:

pppd/sys-linux.c

main() -> lcp_open() -> fsm_open() -> lcp_starting() -> link_required() -> tty_establish_ppp() –> generic_establish_ppp():

/********************************************************************
 *
 * generic_establish_ppp - Turn the fd into a ppp interface.
 */
int generic_establish_ppp (int fd)
{
......
	/* Open an instance of /dev/ppp and connect the channel to it */
	if (ioctl(fd, PPPIOCGCHAN, &chindex) == -1) {  // 1) Get channel number
	    error("Couldn't get channel number: %m");
	    goto err;
	}
	dbglog("using channel %d", chindex);
	fd = open("/dev/ppp", O_RDWR);  // Open / dev/ppp
	if (fd < 0) {
	    error("Couldn't reopen /dev/ppp: %m");
	    goto err;
	}
	(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
	if (ioctl(fd, PPPIOCATTCHAN, &chindex) < 0) {  // 2) Bind channel to / dev/ppp
	    error("Couldn't attach to channel %d: %m", chindex);
	    goto err_close;
	}
	flags = fcntl(fd, F_GETFL);
	if (flags == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) // Set to non blocking fd
	    warn("Couldn't set /dev/ppp (channel) to nonblock: %m");
	set_ppp_fd(fd); // Save this FD to the variable ppp_fd

	if (!looped)
	    ifunit = -1;
	if (!looped && !multilink) {  // Open in demand mode_ ppp_ Loopback sets looped to 1
	    /*
	     * Create a new PPP unit.
	     */
	    if (make_ppp_unit() < 0)  // 3) Make has been called in the demand mode_ ppp_ Unit, which is used for normal dialing
		goto err_close;
	}

	if (looped)
	    modify_flags(ppp_dev_fd, SC_LOOP_TRAFFIC, 0); // For demand mode, clear

	if (!multilink) {
	    add_fd(ppp_dev_fd); // PPP_ dev_ Add FD to fds of select
	    if (ioctl(fd, PPPIOCCONNECT, &ifunit) < 0) { // 4) Connect channel to unit
		error("Couldn't attach to PPP unit %d: %m", ifunit);
		goto err_close;
	    }
	}
......
}

This function can be divided into four main parts:

  1. Gets the index value of the registered channel in TTY.

  2. Bind the registered channel to the / dev/ppp file descriptor and save it to ppp_fd.

  3. For normal dialing, call make_ppp_unit creates a ppp0 network interface and binds it. The bound / dev/ppp file descriptor is saved in PPP_ dev_ fd. (ppp_dev_fd file descriptor represents a unit, and ppp_fd file descriptor represents a channel)

  4. ppp_dev_fd join the fds of select and connect the channe l to PPP unit.

3 . Function link_ The link interface has been configured in the first two steps of required. Now it's time to get down to business: PPPD state machine enters PHASE_ESTABLISH phase, and then use lcp_lowerup(0) sends LCP message to establish connection.

pppd/sys-linux.c

main() -> lcp_open() -> fsm_open() -> lcp_starting() -> link_required() -> lcp_lowerup() -> fsm_lowerup() -> fsm_sconfreq() -> fsm_sdata() -> output():

/********************************************************************
 *
 * output - Output PPP packet.
 */
void output (int unit, unsigned char *p, int len)
{
    int fd = ppp_fd;
    int proto;

    dump_packet("sent", p, len);
    if (snoop_send_hook) snoop_send_hook(p, len);

    if (len < PPP_HDRLEN)
	return;
    if (new_style_driver) {
	p += 2;
	len -= 2;
	proto = (p[0] << 8) + p[1];
	if (ppp_dev_fd >= 0 && !(proto >= 0xc000 || proto == PPP_CCPFRAG))
	    fd = ppp_dev_fd; // Note: PPP is used for data frames_ dev_ FD transmission, PPP for LCP control frame_ FD transmission
    }
    if (write(fd, p, len) < 0) { // Call kernel function ppp_write send data
	if (errno == EWOULDBLOCK || errno == EAGAIN || errno == ENOBUFS
	    || errno == ENXIO || errno == EIO || errno == EINTR)
	    warn("write: warning: %m (%d)", errno);
	else
	    error("write: %m (%d)", errno);
    }
}

There are two situations for data transmission:

  1. PPP for LCP control frame_ FD send.

  2. PPP for data frame_ dev_ FD send.

Whether it's ppp_fd or PPP_ dev_ The device files opened by FD are all / dev/ppp, so the same function PPP is called_ write:

linux-5.9/drivers/net/ppp_generic.c

main() -> lcp_open() -> fsm_open() -> lcp_starting() -> link_required() -> lcp_lowerup() -> fsm_lowerup() -> fsm_sconfreq() -> fsm_sdata() -> output() -> ppp_write() :

static ssize_t ppp_write(struct file *file, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	struct ppp_file *pf = file->private_data;
	struct sk_buff *skb;
	ssize_t ret;
	if (!pf)
		return -ENXIO;
	ret = -ENOMEM;
	skb = alloc_skb(count + pf->hdrlen, GFP_KERNEL);  // Sk for Linux kernel_ Buff stores network packets
	if (!skb)
		goto out;
	skb_reserve(skb, pf->hdrlen);
	ret = -EFAULT;
	if (copy_from_user(skb_put(skb, count), buf, count)) { // Copy sent data to skb
		kfree_skb(skb);
		goto out;
	}
	switch (pf->kind) {  //Note: through PPP_ The kind field of file determines whether / dev/ppp is bound to unit or channel
	case INTERFACE:  // Interface is unit
		ppp_xmit_process(PF_TO_PPP(pf), skb);
		break;
	case CHANNEL: // channel
		skb_queue_tail(&pf->xq, skb);
		ppp_channel_push(PF_TO_CHANNEL(pf)); // Call ppp_channel_push
		break;
	}
	ret = count;
 out:
	return ret;
}

Continue to analyze this function. Now you need to send the LCP frame to establish the connection, so call ppp_channel_push to send:

linux-2.6.18/drivers/net/ppp_generic.c

main() -> lcp_open() -> fsm_open() -> lcp_starting() -> link_required() -> lcp_lowerup() -> fsm_lowerup() -> fsm_sconfreq() -> fsm_sdata() -> output() -> ppp_write() -> ppp_channel_push() -> _ppp_channel_push:

static void ppp_channel_push(struct channel *pch)
{
	read_lock_bh(&pch->upl);
	if (pch->ppp) {
		(*this_cpu_ptr(pch->ppp->xmit_recursion))++;
		__ppp_channel_push(pch);
		(*this_cpu_ptr(pch->ppp->xmit_recursion))--;
	} else {
		__ppp_channel_push(pch);
	}
	read_unlock_bh(&pch->upl);
}

/* Try to send data out on a channel */
static void __ppp_channel_push(struct channel *pch)
{
	struct sk_buff *skb;
	struct ppp *ppp;
	spin_lock(&pch->downl);
	if (pch->chan) { // PPP already in front_ register_ The channel is initialized. It is not empty here
		while (!skb_queue_empty(&pch->file.xq)) { // skb has been placed in the send queue xq
			skb = skb_dequeue(&pch->file.xq);
        // PPP in front_ asynctty_ Open has been initialized. ops is async_ops, so PPP is actually called here_ async_ send
			if (!pch->chan->ops->start_xmit(pch->chan, skb)) {
				/* put the packet back and try again later */
				skb_queue_head(&pch->file.xq, skb);
				break;
			}
		}
	} else {
		/* channel got deregistered */
		skb_queue_purge(&pch->file.xq);
	}
	spin_unlock(&pch->downl);
	/* see if there is anything from the attached unit to be sent */
	if (skb_queue_empty(&pch->file.xq)) {
		ppp = pch->ppp;
		if (ppp) // If the channel has been connected to the unit, it is not empty
			__ppp_xmit_process(ppp, NULL); // This function is used to send data frames
	}
}

Actual call ppp_async_send sends the LCP frame. At this point, the LCP Configure Request frame is sent.

Now, return to the main function main() in PPPD again:

**Step 2: * * PPPD state machine cycle for event processing

pppd/main.c -> main():

	lcp_open(0);		/* Start protocol */
	// Step 1: open PPPK interface to send LCP frame
	start_link(0);
	while (phase != PHASE_DEAD) { // Step 2: PPPD state machine cycle for event processing
	    handle_events();  // select event handling
	    get_input(); // Processing of received messages
	    if (kill_link)
		lcp_close(0, "User request");
	    if (asked_to_quit) {
		bundle_terminating = 1;
		if (phase == PHASE_MASTER)
		    mp_bundle_terminated();
	    }

Call handle_events handles events. See the analysis of this function in demand mode. Note: the fds waiting for event processing here contains ppp_dev_fd. Next, call get_input processing received message:

pppd/main.c

main() -> get_input():

/*
 * get_input - called when incoming data is available.
 */
static void
get_input(void)
{
......
    p = inpacket_buf;	/* point to beginning of packet buffer */

    len = read_packet(inpacket_buf); //Read received message to inpacket_buf buffer
    if (len < 0)
	return;
......
    p += 2;				/* Skip address and control */
    GETSHORT(protocol, p); // Get the protocol number in the message
    len -= PPP_HDRLEN; // Effective data length
......
    /*
     * Upcall the proper protocol input routine.
     */
    for (i = 0; (protp = protocols[i]) != NULL; ++i) {
	if (protp->protocol == protocol && protp->enabled_flag) {
	    (*protp->input)(0, p, len);  // Call the input function of each protocol block to process the received message
	    return;
	}
        if (protocol == (protp->protocol & ~0x8000) && protp->enabled_flag
	    && protp->datainput != NULL) {
	    (*protp->datainput)(0, p, len);
	    return;
	}
    }
......
}

This function calls read_packet read received message to inpacket_buf buffer, extract the protocol number of the received message (LCP is 0xC021), and then call the input and datainput functions of the corresponding protocol block according to the protocol number matching.

So far, the basic process of data sending and receiving required by PPPD to establish a connection has been outlined.

Phase 4: link termination

pppd/main.c

main()

/* Phase III */
......
	/* restore FSMs to original state */
	lcp_close(0, ""); // Close agreement

	if (!persist || asked_to_quit || (maxfail > 0 && unsuccess >= maxfail))
	    break; // Link failure or request shutdown

	if (demand)
	    demand_discard(); // Set each network protocol to discard packets with errors.
	t = need_holdoff? holdoff: 0;
	if (holdoff_hook)
	    t = (*holdoff_hook)();
	if (t > 0) {
	    new_phase(PHASE_HOLDOFF); // pppd state machine. pppd is in delayed state
	    TIMEOUT(holdoff_end, NULL, t);
	    do {
		handle_events();
		if (kill_link)  // User requested shutdown
		    new_phase(PHASE_DORMANT); /* allow signal to end holdoff */
             // pppd state machine. pppd enters sleep state
	    } while (phase == PHASE_HOLDOFF);
	    if (!persist)
		break;
	}
    }

Call LCP directly_ Close close the LCP protocol. In case of link failure or user request for shutdown, directly jump out of the loop and recycle the sub process. Otherwise, exit normally and call demand_discard function:

pppd/demand.c

main() -> demand_discard()

/*
 * demand_discard - set each network protocol to discard packets
 * with an error. Set each network protocol to discard packets with errors.
 */
void
demand_discard(void)
{
    struct packet *pkt, *nextpkt;
    int i;
    struct protent *protp;

    for (i = 0; (protp = protocols[i]) != NULL; ++i) // Traverse all protocols
	if (protp->enabled_flag && protp->demand_conf != NULL)
	    sifnpmode(0, protp->protocol & ~0x8000, NPMODE_ERROR);
    get_loop_output(); // This function is used again to obtain outgoing packets from ppp devices

    /* discard all saved packets Discard all saved packages */
    for (pkt = pend_q; pkt != NULL; pkt = nextpkt) {
	nextpkt = pkt->next;
	free(pkt); // release
    }
    pend_q = NULL;
    framelen = 0;
    flush_flag = 0;
    escape_flag = 0;
    fcs = PPP_INITFCS;
}

This function traverses all current and configured protocols, and calls get for the US protocol_ loop_ The output function obtains the outgoing packets and releases them in turn.

pppd/main.c

main()

......
    /* Wait for scripts to finish Wait for the script to complete */
    reap_kids(); // Get the status from any dead child process and record the message of abnormal termination.
    if (n_children > 0) {
	if (child_wait > 0)
	    TIMEOUT(childwait_end, NULL, child_wait); // Timeout, recycling child process
	if (debug) {
	    struct subprocess *chp;
	    dbglog("Waiting for %d child processes...", n_children);
	    for (chp = children; chp != NULL; chp = chp->next)
		dbglog("  script %s, pid %d", chp->prog, chp->pid);
	}
	while (n_children > 0 && !childwait_done) {
	    handle_events(); // Processing function
	    if (kill_link && !childwait_done)
		childwait_end(NULL);
	}
    }

    die(status); // end
    return 0;
}

After that, the main function sets the state of pppd to the delayed state. After receiving the user's shutdown request, it enters the sleep state and the link is closed. Finally, the subprocess is recycled and the main function ends.

reference material

The workflow of ppp protocol comes from Xie Xiren, the 7th edition of computer network

Analysis of ppp source code of Linux https://blog.csdn.net/osnetdev/article/details/8958058 Analysis of pppd android 2.3 source package

The Linux source code is from https://code.woboq.org/linux/linux/

Keywords: network computer networks

Added by fpbaum on Tue, 08 Mar 2022 16:13:27 +0200