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:
- All options of configure ack are accepted.
- All options of configure NAK are understood but not accepted.
- 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:
- 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; }
- 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:
-
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.
-
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.
-
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.
-
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:
-
Gets the index value of the registered channel in TTY.
-
Bind the registered channel to the / dev/ppp file descriptor and save it to ppp_fd.
-
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)
-
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:
-
PPP for LCP control frame_ FD send.
-
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/