Broadcom方案PPPoE实现分析

来源:互联网 发布:sql主键怎么设置 编辑:程序博客网 时间:2024/05/16 19:36
一、用户程序PPPD初始化main//如果设置了相关回调,则调用回调告知当前程序运行的阶段进展new_phase(PHASE_INITIALIZE);phase = p;//如果有new_phase_hook回调,则调用new_phase_hook回调告知当前处理//到了哪个阶段。if (new_phase_hook)(*new_phase_hook)(p);//如果phasechange链有回调,则调用该链中所有回调,通知当前进展到了哪个//阶段。notify(phasechange, p);//如果文件句柄0、1、2都没有打开,将文件句柄0、1、2都指向/dev/nullif ((i = open("/dev/null", O_RDWR)) >= 0)while (0 <= i && i <= 2)i = dup(i);if (i >= 0)close(i);script_env = NULL;//打开syslog日志reopen_log();openlog("pppd", LOG_PID | LOG_NDELAY, LOG_PPP);setlogmask(LOG_UPTO(LOG_INFO));//获取主机名gethostname(hostname, MAXNAMELEN)hostname[MAXNAMELEN-1] = 0;//设置权限掩码,在默认掩码的基础上增加禁止同组及其它组创建的权限umask(umask(0777) | 022);uid = getuid();privileged = uid == 0;//构建程序自身的环境变量,格式为name=val,存储在script_env指针数组中slprintf(numbuf, sizeof(numbuf), "%d", uid);script_setenv("ORIG_UID", numbuf, 0);//获取当前用户所在的组ngroups = getgroups(NGROUPS_MAX, groups);//生成随机种子magic_init();//各相关子协议初始化,常用的有LCP、PAP、CHAP、IPCPfor (i = 0; (protp = protocols[i]) != NULL; ++i)(*protp->init)(0);//lcp子协议初始化lcp_initfsm *f = &lcp_fsm[unit];lcp_options *wo = &lcp_wantoptions[unit];lcp_options *ao = &lcp_allowoptions[unit];f->unit = unit;//0f->protocol = PPP_LCP;f->callbacks = &lcp_callbacks;fsm_init(f);f->state = INITIAL;f->flags = 0;f->id = 0;f->timeouttime = DEFTIMEOUT;//3秒f->maxconfreqtransmits = DEFMAXCONFREQS;//10f->maxtermtransmits = DEFMAXTERMREQS;//2f->maxnakloops = DEFMAXNAKLOOPS;//5f->term_reason_len = 0;BZERO(wo, sizeof(*wo));wo->neg_mru = 1;wo->mru = DEFMRU;//1500wo->neg_asyncmap = 1;wo->chap_mdtype = CHAP_DIGEST_MD5;wo->neg_magicnumber = 1;wo->neg_pcompression = 1;wo->neg_accompression = 1;BZERO(ao, sizeof(*ao));ao->neg_mru = 1;ao->mru = MAXMRU;//16384ao->neg_asyncmap = 1;ao->neg_chap = 1;ao->chap_mdtype = CHAP_DIGEST_MD5;ao->neg_upap = 1;ao->neg_magicnumber = 1;ao->neg_pcompression = 1;ao->neg_accompression = 1;ao->neg_endpoint = 1;//upap_init子协议初始化upap_initupap_state *u = &upap[unit];u->us_unit = unit;u->us_user = NULL;u->us_userlen = 0;u->us_userlen = 0;u->us_passwdlen = 0;u->us_clientstate = UPAPCS_INITIAL;u->us_serverstate = UPAPSS_INITIAL;u->us_id = 0;u->us_timeouttime = UPAP_DEFTIMEOUT;//3u->us_maxtransmits = 10;u->us_reqtimeout = UPAP_DEFREQTIME;//30//chap子协议初始化ChapInitchap_state *cstate = &chap[unit];BZERO(cstate, sizeof(*cstate));cstate->unit = unit;cstate->clientstate = CHAPCS_INITIAL;cstate->serverstate = CHAPSS_INITIAL;cstate->timeouttime = CHAP_DEFTIMEOUT;//3cstate->max_transmits = CHAP_DEFTRANSMITS;//10//ipcp子协议初始化ipcp_initfsm *f = &ipcp_fsm[unit];ipcp_options *wo = &ipcp_wantoptions[unit];ipcp_options *ao = &ipcp_allowoptions[unit];f->unit = unit;f->protocol = PPP_IPCP;f->callbacks = &ipcp_callbacks;fsm_init(&ipcp_fsm[unit]);f->state = INITIAL;f->flags = 0;f->id = 0;f->timeouttime = DEFTIMEOUT;//3f->maxconfreqtransmits = DEFMAXCONFREQS;//10f->maxtermtransmits = DEFMAXTERMREQS;//2f->maxnakloops = DEFMAXNAKLOOPS;//5f->term_reason_len = 0;memset(wo, 0, sizeof(*wo));memset(ao, 0, sizeof(*ao));wo->neg_addr = 1;wo->neg_vj = 1;wo->vj_protocol = IPCP_VJ_COMP;wo->maxslotindex = MAX_STATES - 1;wo->cflag = 1;ao->neg_addr = 1;ao->neg_vj = 1;ao->maxslotindex = MAX_STATES - 1;ao->cflag = 1;ao->proxy_arp = 1;ao->default_route = 1;tty_init();//在pidchange中添加通知链回调add_notifier(&pidchange, maybe_relock, 0);np = malloc(sizeof(struct notifier));np->next = *notif;np->func = func;//maybe_relocknp->arg = arg;//0*notif = np;//初始化默认通道the_channel = &tty_channel;//设置传送异步通讯字符映射码xmit_accm[3] = 0x60000000;progname = *argv;options_from_file(_PATH_SYSOPTIONS, !privileged, 0, 1)f = fopen(filename, "r");oldpriv = privileged_option;privileged_option = priv;option_source = strdup(filename);ret = 0;while (getword(f, cmd, &newline, filename))//从通用选项表、鉴权选项表、扩展选项表、通道选项表、子协议选项表中//查找与配置文件匹配的选项控制块。opt = find_option(cmd);//如果对应选项控制块标记含有参数,则分别将参数存储到argv数组中n = n_arguments(opt);for (i = 0; i < n; ++i)getword(f, args[i], &newline, filename)argv[i] = args[i];//根据当前选项类型处理选项,获取选项值process_option(opt, cmd, argv)ret = 1;fclose(f);privileged_option = oldpriv;option_source = oldsource;return ret;//从命令行中获取配置参数,假设当前为命令行如下//pppd -c ppp1.2 -r serviceName -i epon0.2 -u test -p test -f authProtocol // -o idleDisconnectTimeparse_args(argc, argv)while ((opt = getopt(argc, argv, "s:xda:i:ku:p:o:lc:m:f:r:RA:t:T:C")) != -1)switch (opt)//逻辑接口名case 'c':strncpy(req_name, optarg, MAXPATHLEN);//pppoe服务端名称case 'r':strncpy(pppoe_srv_name, optarg, MAXSRVNAMELEN);//关联的二层接口case 'i':setdevname_pppoe(optarg);get_sockaddr_ll(cp,NULL);//创建PF_PACKET的套接口,用于接收二层数据包disc_sock = socket(PF_PACKET, SOCK_DGRAM, 0);strncpy(devnam, cp, sizeof(devnam));if( ret == 1 && the_channel != &pppoe_channel )//在tty_init初始化时,默认通道为tty类型,当前使用pppoe//,所以默认通道替换为pppoe_channelthe_channel = &pppoe_channel;//将不适用于当前插件的扩展选项移除for (a = bad_options; *a != NULL; a++)remove_option(*a);ipcp_allowoptions[0].default_route = 0 ;ipcp_wantoptions[0].default_route = 0 ;lcp_allowoptions[0].neg_accompression = 0;lcp_wantoptions[0].neg_accompression = 0;lcp_allowoptions[0].neg_asyncmap = 0;lcp_wantoptions[0].neg_asyncmap = 0;lcp_allowoptions[0].neg_pcompression = 0;lcp_wantoptions[0].neg_pcompression = 0;ipcp_allowoptions[0].neg_vj=0;ipcp_wantoptions[0].neg_vj=0;init_device_pppoe();//为会话控制块分配内存ses=(void *)malloc(sizeof(struct session));memset(ses,0,sizeof(struct session));ses->filt=malloc(sizeof(struct filter))filt=ses->filt;memset(filt,0,sizeof(struct filter));//将配置的服务端名字做为一个过滤项if (strlen(pppoe_srv_name))ses->filt->stag = make_filter_tag(PTT_SRV_NAME,strlen(pppoe_srv_name),pppoe_srv_name);                     memcpy( ses->name, devnam, IFNAMSIZ);ses->opt_debug=1;//拨号帐号case 'u':strncpy(user, optarg, MAXNAMELEN);strncpy(our_name, optarg, MAXNAMELEN);//拨号密码case 'p':strncpy(passwd, optarg, MAXSECRETLEN);//使用哪种鉴权模式case 'f':opflag = atoi(optarg);//按需拨号的空闲断开时间case 'o':idle_time_limit = atoi(optarg);demand=1;//标识设备名已经固定,不能在修改devnam_fixed = 1;//当前非串口模式if (!console)//初始化MDM消息cmsMsg_initWithFlags(EID_PPP, EIF_MULTIPLE_INSTANCES, &msgHandle)//向SMD定阅CMS_MSG_WAN_LINK_UP和CMS_MSG_WAN_LINK_DOWN//这两个消息。registerInterestInWanLinkStatus();//当前通道对象已经变为pppoe_channel,则对应的回调函数为pppoe_extra_optionsif (the_channel->process_extra_options)(*the_channel->process_extra_options)();//如果存在/etc/ppp/options.设备名,则从该文件中设置配置选项pppoe_extra_options//buf = /etc/ppp/options.epon0snprintf(buf, 256, _PATH_ETHOPT "%s",devnam);options_from_file(buf, 0, 0, 1)//当前pppoe_channel的回调为NULLif (the_channel->check_options)(*the_channel->check_options)();ppp_available()//获取当前内核版本uname(&utsname);osmaj = osmin = ospatch = 0;sscanf(utsname.release, "%d.%d.%d", &osmaj, &osmin, &ospatch);kernel_version = KVERSION(osmaj, osmin, ospatch);//如果打开ppp设备模块正常,则表明当前是新的驱动类型,返回检测有效。fd = open("/dev/ppp", O_RDWR);if (fd >= 0)new_style_driver = 1;driver_version = 2;driver_modification = 4;driver_patch = 0;close(fd);return 1;//老的ppp驱动类型不再进行分析。......sys_init();//打开PPP设备驱动,同时设置为非阻塞模式// /dev/ppp设备描述符是一个字符型设备,主设备号为108,该模块的内核源码//为drviers/net/ppp_generic.c。if (new_style_driver)//对应内核代码为ppp_open,ppp_open没有太多的处理,主要检测当前用户//是否有CAP_NET_ADMIN网络管理的权限。ppp_dev_fd = open("/dev/ppp", O_RDWR);//设置非阻塞标记flags = fcntl(ppp_dev_fd, F_GETFL);fcntl(ppp_dev_fd, F_SETFL, flags | O_NONBLOCK);//创建用于pppoe使用的套接口对象sock_fd = socket(AF_INET, SOCK_DGRAM, 0);FD_ZERO(&in_fds);FD_ZERO(&in_fds_cp);max_in_fd = 0;pppdb = tdb_open(_PATH_PPPDB, 0, 0, O_RDWR|O_CREAT, 0644);memset(&tdb, 0, sizeof(tdb));tdb.fd = -1;tdb.name = NULL;tdb.map_ptr = NULL;hash_size = DEFAULT_HASH_SIZE;tdb.read_only = ((open_flags & O_ACCMODE) == O_RDONLY);//0tdb.fd = open(name, open_flags, mode);//设置独占写锁,防止多个进程同时处理tdb_brlock(&tdb, GLOBAL_LOCK, LOCK_SET, F_WRLCK, F_SETLKW);//设置共享读锁tdb_brlock(&tdb, ACTIVE_LOCK, LOCK_SET, F_RDLCK, F_SETLKW);//检测数据库文件是否有效,如果无效,当入参含有O_CREAT标记时,则创//建新的数据库文件。if (read(tdb.fd, &tdb.header, sizeof(tdb.header)) != sizeof(tdb.header) ||strcmp(tdb.header.magic_food, TDB_MAGIC_FOOD) != 0 ||tdb.header.version != TDB_VERSION)if (!(open_flags & O_CREAT))goto fail;//创建新的数据库文件,没有细分析。tdb_new_database(&tdb, hash_size)//设备数据库名及文件大小fstat(tdb.fd, &st);tdb.name = (char *)strdup(name);tdb.map_size = st.st_size;//为锁控制块分配内存资源tdb.locked = (int *)calloc(tdb.header.hash_size+1,sizeof(tdb.locked[0]));//将数据库文件进行内存映射tdb.map_ptr = (void *)mmap(NULL, st.st_size,tdb.read_only? PROT_READ : PROT_READ|PROT_WRITE,MAP_SHARED | MAP_FILE, tdb.fd, 0);//为数据库控制块分配内存资源,并将上面临时tdb复制到新创建的数据库控制块ret = (TDB_CONTEXT *)malloc(sizeof(tdb));*ret = tdb;//去除独占写锁,后续其它进程可以进入该函数tdb_brlock(&tdb, GLOBAL_LOCK, LOCK_CLEAR, F_WRLCK, F_SETLKW);return ret;slprintf(db_key, sizeof(db_key), "pppd%d", getpid());//当前BCM版本该函数为空update_db_entry();//如果当前标记需要进行终端分离,同时不是标记通过链路UP以后再分离,则进行//终端分离处理。if (!nodetach && !updetach)detach();pid = fork()//父进程退出if (pid != 0)notify(pidchange, pid);exit(0);//子进程创建新的会话,同时做为会话首领,更新当前目录为/,关闭标准输入//、标准输出、标准错误句柄。setsid();chdir("/");close(0);close(1);close(2);detached = 1;//构建程序自身的环境变量slprintf(numbuf, sizeof(numbuf), "%d", getpid());script_setenv("PPPD_PID", numbuf, 1);//获取当前登录用户名,如果getlogin获取失败,则使用密码文件中获取p = getlogin();if (p == NULL)pw = getpwuid(uid);p = pw->pw_name;//设置自身环境变量,记载登录名script_setenv("PPPLOGNAME", p, 0);//设置自身环境变量,记载关联的设备名if (devnam[0])script_setenv("DEVICE", devnam, 1);//设置自身环境变量,记载进程IDslprintf(numbuf, sizeof(numbuf), "%d", getpid());script_setenv("PPPD_PID", numbuf, 1);//信号相关处理,这里要注意的是BCM方案对pppd进程的退出处理是给该进程发送//SIGWINCH信号。setup_signals();waiting = 0;//如果配置了自动扫描选项,则标记按需拨号已经开始,不进行一些链路检测if (autoscan)demandBegin=1;//配置了按需拨号if (demand)//数据库文件加锁tdb_writelock(pppdb);//当按需拨号时,需要能获取触发拨号的数据流fd_loop = open_ppp_loopback();looped = 1;//假设当前支持新的PPP驱动,老的PPP驱动实现不再分析。if (new_style_driver)//创建PPP单元,也可以理解为创建一个PPP接口make_ppp_unit()unsigned num[3]={0, 0, 0};//当前请求名字为ppp1.2if ((p = strchr(req_name, '.')) != NULL)//num[0] = 1//num[2] = 2sscanf(&(req_name[3]), "%d.%d", num, num+2);num[1] = 1;//根据请求名字生成PPP单元号req_unit =  num[0]<<(FIELD1+FIELD2) | num[1]<<FIELD2 | num[2];//向设备/dev/ppp触发PPPIOCNEWUNIT ioctl,对应的内核代码//为ppp_ioctlx = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);//内核代码ppp_ioctlpf = file->private_data;//当前还没有关联ppp私有文件信息if (pf == 0)ppp_unattached_ioctl(pf, file, cmd, arg);switch (cmd)case PPPIOCNEWUNIT://将用户侧传入的单元ID存储到pget_user(unit, p)ppp = ppp_create_interface(unit, &err);ppp = kzalloc(sizeof(struct ppp), GFP_KERNEL);//分配一个接口设备对象,同时调用传入//的回调函数ppp_setup进行ppp特定属//性设置。//dev->hard_header_len = PPP_HDRLEN;//dev->mtu = PPP_MTU;//dev->addr_len = 0;//dev->tx_queue_len = 3;//dev->type = ARPHRD_PPP;//dev->flags = IFF_POINTOPOINT | //IFF_NOARP | IFF_MULTICAST;dev = alloc_netdev(0, "", ppp_setup);//最大接收单元ppp->mru = PPP_MRU;//初始化ppp对象中的file控制块,当前//linux机制是通过文件的调用来完成//PPPD用户程序与内核PPP驱动的通讯。init_ppp_file(&ppp->file, INTERFACE);pf->kind = kind;//INTERFACEskb_queue_head_init(&pf->xq);skb_queue_head_init(&pf->rq);atomic_set(&pf->refcnt, 1);init_waitqueue_head(&pf->rwait);//不含2个字节的协议字段长度ppp->file.hdrlen = PPP_HDRLEN - 2;//初始化时,针对PPP下的每种网络//子协议都允许通过for (i = 0; i < NUM_NP; ++i)ppp->npmode[i] = NPMODE_PASS;INIT_LIST_HEAD(&ppp->channels);spin_lock_init(&ppp->rlock);spin_lock_init(&ppp->wlock);ppp->minseq = -1;skb_queue_head_init(&ppp->mrq);//将接口设备控制块与PPP单元控制块关//联ppp->dev = dev;dev->priv = ppp;//为新建的pppoe接口设备设置发送、//获取统计、执行ioctl的回调函数。dev->hard_start_xmit = ppp_start_xmit;dev->get_stats = ppp_net_stats;dev->do_ioctl = ppp_net_ioctl;//如果用户传入单元ID为负数,则自动//分配一个,否则检测用户传入的单元//ID是否已经存在。if (unit < 0)unit = cardmap_find_first_free(all_ppp_unit)else if (cardmap_get(all_ppp_units, unit) != NULL)goto out2;//将单元索引关联到文件对象,同时设置//PPP的接口设备名也基于单元IDppp->file.index = unit;sprintf(dev->name, "ppp%d", unit);//向内核注册接口设备register_netdev(dev);//向all_ppp_units中添加当前单元ID//的条目。atomic_inc(&ppp_unit_count);cardmap_set(&all_ppp_units, unit, ppp);*retp = 0;return ppp;//将文件对象的私有数据指向PPP驱动对象的//file中。file->private_data = &ppp->file;//同时将PPP驱动对象关联文件描述符对象。ppp->owner = file;//将最终确定的单元ID传回给用户。put_user(ppp->file.index, p)//设置SC_LOOP_TRAFFIC标记,对应内核代码为ppp_ioctl,设置该//标记后如果有发向pppoe接口设备的报文,则将该报文放入PPP驱动//对象的file接收队列中,当应用层pppd程序使用read读取/dev/ppp设备//文件时,就可以获取到该报文。set_flags(ppp_dev_fd, SC_LOOP_TRAFFIC);ioctl(fd, PPPIOCSFLAGS, (caddr_t) &flags)//内核代码ppp_ioctl//获取ppp驱动控制块pf = file->private_data;ppp = PF_TO_PPP(pf);//将SC_LOOP_TRAFFIC设置到PPP驱动控制块的flagsswitch (cmd)case PPPIOCSFLAGS:get_user(val, p)ppp_lock(ppp);cflags = ppp->flags & ~val;ppp->flags = val & SC_FLAG_BITS;ppp_unlock(ppp);//设置内核调试等级,对应的内核函数为ppp_ioctlset_kdebugflag(kdebugflag);ioctl(ppp_dev_fd, PPPIOCSDEBUG, &requested_level)//内核函数ppp_ioctlppp = PF_TO_PPP(pf);switch (cmd)case PPPIOCSDEBUG:get_user(val, p)ppp->debug = val;ppp_fd = -1;return ppp_dev_fd;//将传入的pppoe接口名设置到程序自身环境变量中set_ifunit(1);slprintf(ifname, sizeof(ifname), "%s", req_name);script_setenv("IFNAME", ifname, iskey);//数据库文件解锁tdb_writeunlock(pppdb);//当前按需拨号需要对上面已创建的pppoe接口进行一些相关配置demand_conf();framemax = PPP_MRU;framemax += PPP_HDRLEN + PPP_FCSLEN;frame = malloc(framemax);framelen = 0;pend_q = NULL;escape_flag = 0;flush_flag = 0;fcs = PPP_INITFCS;//设置当前pppoe接口的最大传输单元netif_set_mtu(0, MIN(lcp_allowoptions[0].mru, PPP_MRU));memset (&ifr, '\0', sizeof (ifr));strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name));ifr.ifr_mtu = mtu;//对pppoe接口进行MTU设置ioctl(sock_fd, SIOCSIFMTU, (caddr_t) &ifr)ppp_send_config(0, PPP_MRU, (u_int32_t) 0, 0, 0);//当前默认通道已经为pppoe_channel,对应回调为send_config_pppoethe_channel->send_config((mtu), (accm), (pc), (acc));//设置MTUsend_config_pppoesock = socket(AF_INET, SOCK_DGRAM, 0);strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));ifr.ifr_mtu = mtu;ioctl(sock, SIOCSIFMTU, (caddr_t) &ifr)close (sock);ppp_recv_config(0, PPP_MRU, (u_int32_t) 0, 0, 0);//当前默认通道已经为pppoe_channel,对应回调为recv_config_pppoethe_channel->recv_config((mtu), (accm), (pc), (acc))//函数没有代码处理recv_config_pppoe//检查各子协议模块是否有对应的按需拨号配置回调,当前常用的LCP、//PAP、CHAP、IPCP四个子协议只有IPCP含有该回调函数,IPCP的回调//函数为ip_demand_conffor (i = 0; (protp = protocols[i]) != NULL; ++i)if (protp->enabled_flag && protp->demand_conf != NULL)protp->demand_conf(0)//IPCP的回调函数ip_demand_confwo = &ipcp_wantoptions[u];//当没有远端PPP服务器地址时,创建一个临时的if (wo->hisaddr == 0)wo->hisaddr = htonl(0x0a707070 + ifunit);wo->accept_remote = 1;//当没有本地地址时,创建一个临时的if (wo->ouraddr == 0)wo->ouraddr = htonl(0x0a404040 + ifunit);wo->accept_local = 1;ask_for_local = 0;//设置接口地址及掩码sifaddr(u, wo->ouraddr, wo->hisaddr, GetMask(wo->ouraddr))SET_SA_FAMILY (ifr.ifr_addr,    AF_INET);SET_SA_FAMILY (ifr.ifr_dstaddr, AF_INET);SET_SA_FAMILY (ifr.ifr_netmask, AF_INET);strlcpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));//设置pppoe接口本地地址SIN_ADDR(ifr.ifr_addr) = our_adr;ioctl(sock_fd, SIOCSIFADDR, (caddr_t) &ifr)//设备pppoe接口远端地址SIN_ADDR(ifr.ifr_dstaddr) = his_adr;ioctl(sock_fd, SIOCSIFDSTADDR, (caddr_t) &ifr)//内核版本大于2.1.16,则设置pppoe接口的子网掩//码为255.255.255.255if (kernel_version >= KVERSION(2,1,16))net_mask = ~0L;SIN_ADDR(ifr.ifr_netmask) = net_mask;ioctl(sock_fd, SIOCSIFNETMASK, (caddr_t) &ifr)our_old_addr = 0;//设置pppoe接口upsifup(u)//设置当前pppoe接口的flags为UPstrlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name));ioctl(sock_fd, SIOCGIFFLAGS, (caddr_t) &ifr)ifr.ifr_flags |= (IFF_UP | IFF_POINTOPOINT);ioctl(sock_fd, SIOCSIFFLAGS, (caddr_t) &ifr)if_is_up++;sifnpmode(u, PPP_IP, NPMODE_QUEUE)npi.protocol = proto;//PPP_IPnpi.mode     = mode;//NPMODE_QUEUEioctl(ppp_dev_fd, PPPIOCSNPMODE, (caddr_t) &npi)//内核代码ppp_ioctlppp = PF_TO_PPP(pf);switch (cmd)case PPPIOCSNPMODE://设置PPP_IP协议的模式为QUEUE//,此时如果有IPv4协议报文想通过//pppoe接口发送时都将丢弃。copy_from_user(&npi, argp, sizeof(npi))i = proto_to_npindex(npi.protocol);ppp->npmode[i] = npi.mode;//触发发出软中断的调度netif_wake_queue(ppp->dev);if (wo->default_route)//将远端PPP服务器地址做为网关sifdefaultroute(u, wo->ouraddr, wo->hisaddr)SET_SA_FAMILY (rt.rt_dst,     AF_INET);SET_SA_FAMILY (rt.rt_gateway, AF_INET);SET_SA_FAMILY (rt.rt_genmask, AF_INET);SIN_ADDR(rt.rt_genmask) = 0L;SIN_ADDR(rt.rt_gateway) = gateway;rt.rt_flags = RTF_UP | RTF_GATEWAY;ioctl(sock_fd, SIOCADDRT, &rt)default_route_gateway = gateway;default_route_set[u] = 1;if (wo->proxy_arp)sifproxyarp(u, wo->hisaddr)//设置ARP条目的协议地址为远端地址,同时设置//永久、发布该邻居的标记。SET_SA_FAMILY(arpreq.arp_pa, AF_INET);SIN_ADDR(arpreq.arp_pa) = his_adr;arpreq.arp_flags = ATF_PERM | ATF_PUBL;//获取同远端地址同网段的本地接口MAC地址//做为ARP条目的硬件地址。get_ether_addr(his_adr, &arpreq.arp_ha, proxy_arp_dev,sizeof(proxy_arp_dev))//接口设备strlcpy(arpreq.arp_dev, proxy_arp_dev, sizeof(arpreq.arp_dev));//向ARP缓存中添加该ARP条目ioctl(sock_fd, SIOCSARP, (caddr_t)&arpreq)proxy_arp_addr = his_adr;has_proxy_arp = 1;proxy_arp_set[u] = 1;do_callback = 0;for (;;)listen_time = 0;need_holdoff = 1;devfd = -1;status = EXIT_OK;++unsuccess;doing_callback = do_callback;do_callback = 0;proxy_loop:if (!autoscan)//从SSK获取WAN口的链路状态,如果没有UP,则每1秒再重新检测while(!link_up(devnam))sleep(1);//如果设置了‘x’选项,同时没有设置自动扫描,还则需要检测lan侧是否UPif (ipext && !demandBegin)while (!lan_link_up())sleep(1);//当设置自动扫描配置项,则在链路失败后,不停留等待一段时间。if (autoscan)holdoff=0;ses_retries = 3;//如果设置了代理模式则进行PPP代理初始化,暂不分析if (proxy_mode)ppp_proxy_init();//如果设置了代理模式,或者设置了需要按需拨号,则需要进行按需拨号处理if (proxy_mode || (demand && !doing_callback && !demandBegin))//主状态迁为PHASE_DORMANTnew_phase(PHASE_DORMANT);//暂时允许所有子协议报文通过demand_unblock();//当前常用的LCP、PAP、CHAP、IPCP四个子协议中,只有IPCP//含有demand_conf回调,在上面流程中该子协议已经调用了这个//回调,使得如果ipv4报文发向pppoe接口将被丢弃。这里暂时//放开。for (i = 0; (protp = protocols[i]) != NULL; ++i)if (protp->enabled_flag && protp->demand_conf != NULL)sifnpmode(0, protp->protocol & ~0x8000, NPMODE_PASS);//将fd_loop加入到pollfds中,这里fd_loop即为/dev/ppp的设备文件描述符add_fd(fd_loop);for (n = 0; n < n_pollfds; ++n)if (n_pollfds < MAX_POLLFDS)pollfds[n_pollfds].fd = fd;pollfds[n_pollfds].events = POLLIN | POLLPRI | POLLHUP;++n_pollfds;//代理相关暂不分析if(proxy_mode)ppp_proxy_check(NULL);//进入按需拨号所需要的主循环,主要用来等待Lan用户使用pppoe接口//进行数据发送,只有等到了Lan用户有数据要发出,才进行后续的拨号//处理。for (;;)handle_events();kill_link = open_ccp_flag = 0;//设置信号跳转函数,在等待期间如果有信号发生,则跳出等待//处理。信号跳转函数的使用可以参考《UNIX环境高级编程》,//主要需要明白sigsetjmp返回值为0和非0分别是什么意思就可//以了。if (sigsetjmp(sigjmp, 1) == 0)//暂时阻塞这几个关键信号sigemptyset(&mask);sigaddset(&mask, SIGHUP);sigaddset(&mask, SIGTERM);sigaddset(&mask, SIGUSR2);sigaddset(&mask, SIGCHLD);sigprocmask(SIG_BLOCK, &mask, NULL);//如果在阻塞之前已经收到这几个信号,则恢复这些信号的阻塞if (got_sighup || got_sigterm || got_sigusr2 || got_sigchld)sigprocmask(SIG_UNBLOCK, &mask, NULL);else//恢复这些信号的阻塞waiting = 1;sigprocmask(SIG_UNBLOCK, &mask, NULL);//timeleft用于获取定时器链表callout最近超时的时间值,//如果callout链表为空,则返回为NULL。//这里等待/dev/ppp设备是否有数据可读,当前linux的机//制如下,当用户从Lan侧发出数据后,路由到pppoe接口//,pppoe接口的发送回调函数ppp_start_xmit就会将数据包//存储到PPP驱动控制块的发送队列中,同时如果检测到//PPP驱动控制块含有SC_LOOP_TRAFFIC标记,就知道//当前正在按需拨号,将报文放入PPP驱动控制块的接收//队列,同时唤醒等待队列。当上层PPPD程序调用select//时(即这里的select),如果没有数据处理将当前进程加//入到这个等待队列,如果有数据处理或者被唤醒,则这//里wait_input退出等待。详见“按需拨号接收到LAN侧//数据”小节的分析。wait_input(timeleft(&timo));in_fds_cp = in_fds;exc = in_fds;if (timo && (timo->tv_usec < 0))timo->tv_usec=0;select(max_in_fd + 1, &in_fds_cp, NULL, &exc, timo);waiting = 0;//定时器机制处理calltimeout();//遍历定时器链表所有条目,如果有超时的定时器则触发对应//的回调,否则没有任何超时的定时器则退出。while (callout != NULL)p = callout;gettimeofday(&timenow, NULL)if (!(p->c_time.tv_sec < timenow.tv_sec|| (p->c_time.tv_sec == timenow.tv_sec&& p->c_time.tv_usec <= timenow.tv_usec)))break;callout = p->c_next;(*p->c_func)(p->c_arg);free((char *) p);//收到SIGHUP信号,标记需要退出,同时修正主程序状态if (got_sighup)kill_link = 1;got_sighup = 0;if (status != EXIT_HANGUP)status = EXIT_USER_REQUEST;//给SSK发送CMS_MSG_PPPOE_STATE_CHANGED消息,//通知SSK当前PPPOE链接错误。create_msg(BCM_PPPOE_REPORT_LASTCONNECTERROR, MDMVS_ERROR_USER_DISCONNECT);sendPppEventMessage(lognumber, NULL, NULL, NULL, NULL, lastConnectionError);//收到SIGTERM信号if (got_sigterm)//标记需要退出kill_link = 1;//标记不需要再循环处理,直接退出PPPD进程persist = 0;//修正主程序状态status = EXIT_USER_REQUEST;//给SSK发送CMS_MSG_PPPOE_STATE_CHANGED消息,//通知SSK当前PPPOE链接错误。create_msg(BCM_PPPOE_REPORT_LASTCONNECTERROR, MDMVS_ERROR_USER_DISCONNECT);got_sigterm = 0;//收到SIGCHLD信号if (got_sigchld)//进行孩子回收处理,如果子进程条目含有done回调,//则执行done回调处理。reap_kids(0);got_sigchld = 0;//收到SIGUSR2信号if (got_sigusr2)//设置重新打开压缩控制协议标记,当前BCM版本好像没有//处理此标记变量。open_ccp_flag = 1;got_sigusr2 = 0;//如果当前收到相关退出信号,同时没有设置循环处理的配置,则退出//按需拨号的循环。if (kill_link && !persist)break;//环回输出处理ret = get_loop_output()//假设当前内核使用的新的驱动类型,老的驱动类型暂不分析。if (new_style_driver)//从PPP驱动的接收队列中读取报文while ((n = read_packet(inpacket_buf)) > 0)read_packetlen = PPP_MRU + PPP_HDRLEN;//新的驱动类型,前两个字节域填充为固定值。if (new_style_driver)//地址域 = 0xff*buf++ = PPP_ALLSTATIONS;//控制域 = 0x03*buf++ = PPP_UI;len -= 2;nr = -1;//当前使用新的PPP驱动类型,则ppp_fd为负值,//不会走此流程,这里是为了兼容老的PPP驱动类//型,暂不分析。if (ppp_fd >= 0)if (!(nr = read(ppp_fd, buf, len)))nr = -1;if (nr < 0 && errno != EWOULDBLOCK && errno != EIO && errno != EINTR)error("read: %m");if (nr < 0 && errno == ENXIO)return 0;//当前使用新的PPP驱动类型,则从/dev/ppp设备//描述符中读取报文。if (nr < 0 && new_style_driver && ifunit >= 0)if (!(nr = read(ppp_dev_fd, buf, len)))nr = -1;if (nr < 0 && errno != EWOULDBLOCK && errno != EIO && errno != EINTR)error("read /dev/ppp: %m");if (nr < 0 && errno == ENXIO)return 0;//新PPP驱动类型则返回BUF值多加2,就是开头//手动填充的两个字节域。return (new_style_driver && nr > 0)? nr+2: nr;//在按需拨号的阶段,将从LAN侧收到的报文暂时,//存储到pend_qtail链表中,待后面进行PPP连接,完成//IPCP阶段后,再将这些待发送的报文发出。r = loop_frame(inpacket_buf, n)//协议域的值高16位是1时,表明是控制相关子协议,//不进行存储。if ((PPP_PROTOCOL(frame) & 0x8000) != 0)return 0;//进行子协议检测,不支持或者没有使能等情况,则丢//弃。if (!active_packet(frame, len))return 0;//报文暂时存储到pend_qtail链表pkt = (struct packet *) malloc(sizeof(struct packet) + len);pkt->length = len;pkt->next = NULL;memcpy(pkt->data, frame, len);if (pend_q == NULL)pend_q = pkt;elsepend_qtail->next = pkt;pend_qtail = pkt;if( r )rv = 1;return rv;if ( ret )break;//代理相关暂不分析if (proxy_mode && CLIENT_CONNECTED)break;//将fd_loop从pollfds中移除remove_fd(fd_loop);//如果当前收到相关退出信号,同时没有设置循环处理的配置,则退出主循//环。if (kill_link && !persist)break;demand_block();//对/dev/ppp驱动模块执行PPPIOCSNPMODE IOCTL调用,对所有//高16位为0的子协议重新设置模式为NPMODE_QUEUE,后续在//没有完成连接建立情况下,出现这些子协议报文都将丢弃。for (i = 0; (protp = protocols[i]) != NULL; ++i)if (protp->enabled_flag && protp->demand_conf != NULL)sifnpmode(0, protp->protocol & ~0x8000, NPMODE_QUEUE);//上面已经分析过,在按需拨号阶段,从/dev/ppp驱动文件描述符中读//取待发送的报文,存储到pend_qtail链表get_loop_output();demandBegin=0;//更新阶段状态到PHASE_SERIALCONNnew_phase(PHASE_SERIALCONN);//触发通道对象的connect回调,当前通道对象为pppoe_channel,对应回调为//connect_pppoe_sesdevfd = the_channel->connect();//建立PPPOE会话connect_pppoe_ses//初始化会话客户端client_init_ses(ses,devnam);//创建PF_PACKET协议族的套接口,准备在应用层PPPD处理//ETH_P_PPP_DISC协议类型的报文。disc_sock = socket(PF_PACKET, SOCK_DGRAM, 0);//获取本地接口信息//sll->sll_ifindex = ifr.ifr_ifindex;//sll->sll_family= AF_PACKET;//sll->sll_protocol= ntohs(ETH_P_PPP_DISC);//sll->sll_hatype= ARPHRD_ETHER;//sll->sll_pkttype = PACKET_BROADCAST;//sll->sll_hatype= ETH_ALEN;//sll->sll_addr    = ifr.ifr_hwaddr.sa_dataget_sockaddr_ll(devnam,&ses->local);//指示当前PPPOE会话状态在PPPoE Active Discovery Offer阶段ses->state = PADO_CODE;//给SSK发送CMS_MSG_PPPOE_STATE_CHANGED消息create_msg(BCM_PPPOE_CLIENT_STATE_PADO, MDMVS_ERROR_NONE);//将远端MAC地址设置为全1。memcpy(&ses->remote, &ses->local, sizeof(struct sockaddr_ll) );memset( ses->remote.sll_addr, 0xff, ETH_ALEN);bind( disc_sock ,&ses->local,sizeof(struct sockaddr_ll));//创建AF_PPPOX协议族类型的套接口,用于后续创建通道对象、//删除通道对象,以及由应用层PPPD触发PADT处理。ses->fd = socket(AF_PPPOX,SOCK_STREAM,PX_PROTO_OE);//初始化PPPOE会话客户端相关回调ses->init_disc = std_init_disc;ses->rcv_pado  = std_rcv_pado;ses->rcv_pads  = std_rcv_pads;ses->rcv_padt  = std_rcv_padt;ses->retries = ses_retries;//8return ses->fd;strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam));//临时增大接收BUFrcvbuf=5000;setsockopt(disc_sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));//建立PPPoE会话session_connect ( ses );//当前回调函数为std_init_disc(*ses->init_disc)(ses, NULL, &p_out);std_init_discmemset(&ses->curr_pkt,0, sizeof(struct pppoe_packet));ses->curr_pkt.hdr = (struct pppoe_hdr*) ses->curr_pkt.buf;ses->curr_pkt.hdr->ver  = 1;ses->curr_pkt.hdr->type = 1;ses->curr_pkt.hdr->code = PADI_CODE;//此时是全1广播地址memcpy( &ses->curr_pkt.addr, &ses->remote , sizeof(struct sockaddr_ll));ses->retransmits = 0 ;//如果当前含有访问控制器参数,则填充AC_NAME的//TAGif(ses->filt->ntag)ses->curr_pkt.tags[TAG_AC_NAME]=ses->filt->ntag;//如果当前含有服务名参数,则填充SRV_NAME的tagif(ses->filt->stag)ses->curr_pkt.tags[TAG_SRV_NAME]=ses->filt->stag;//如果含有主机标签参数,则填充HOST_UNIQ的tagif(ses->filt->htag)ses->curr_pkt.tags[TAG_HOST_UNIQ]=ses->filt->htag;//发送PPPoE的PADI报文,进行PPPoE的发现。send_disc(ses, &ses->curr_pkt);//检查当前是否报文是否有需要填充的TAG,如果有//则进行标记,同时修正长度for (i = 0; i < MAX_TAGS; i++)if (!p->tags[i])continue;got_host_uniq |= (p->tags[i]->tag_type == PTT_HOST_UNIQ);got_host_uniq |= (p->tags[i]->tag_type == PTT_RELAY_SID);got_srv_name |= (p->tags[i]->tag_type == PTT_SRV_NAME);got_ac_name  |= (p->tags[i]->tag_type == PTT_AC_NAME);data_len += (ntohs(p->tags[i]->tag_len) +sizeof(struct pppoe_tag));ph = (struct pppoe_hdr *) buf;memcpy(ph, p->hdr, sizeof(struct pppoe_hdr));ph->length = __constant_htons(0);//当没有设置主机标签时,使用会话指针地址做为该值//,并向当前发现报文增加HOST_UNIQ tag字段。if (!got_host_uniq)data_len += (sizeof(struct pppoe_tag) +sizeof(struct session *));tag = next_tag(ph);tag->tag_type = PTT_HOST_UNIQ;tag->tag_len = htons(sizeof(struct session *));memcpy(tag->tag_data,&ses,sizeof(struct session*));add_tag(ph, tag);//当没有设置服务名,则增加一个空值的SRV_NAME//tag字段,表明接收任何服务端处理。if( !got_srv_name )data_len += sizeof(struct pppoe_tag);tag = next_tag(ph);tag->tag_type = PTT_SRV_NAME;tag->tag_len = 0;add_tag(ph, tag);//当没有设置访问控制器参数,同时当前是初始阶段//则增加一个空值的AC_NAME tag字段。if(!got_ac_name && ph->code==PADO_CODE)data_len += sizeof(struct pppoe_tag);tag = next_tag(ph);tag->tag_type = PTT_AC_NAME;tag->tag_len = 0;add_tag(ph, tag);//把其它tag都进行复制for (i = 0; i < MAX_TAGS; i++)if (!p->tags[i])continue;tag = next_tag(ph);memcpy(tag, p->tags[i],sizeof(struct pppoe_tag) + ntohs(p->tags[i]->tag_len));add_tag(ph, tag);//重新更正当前待发送的包头memcpy( p->hdr , ph, data_len );//把当前包头中的tag都提取到当前tags中,后继报//文发送时就不用每次再重新初始化了。extract_tags( p->hdr, p->tags);//将报文发出。sendto(disc_sock, buf, data_len, 0,&p->addr,sizeof(struct sockaddr_ll));(*p_out)= &ses->curr_pkt;//当会话发现重传次数未到达上限,则一直尝试处理。while(ses->retransmits < ses->retries || ses->retries==-1 )FD_ZERO(&in);FD_SET(disc_sock,&in);if(ses->retransmits>=0)++ses->retransmits;//每进行一次重传,超时周期加倍tv.tv_sec = 1 << ses->retransmits;//最大超时周期时间上限为3秒if (tv.tv_sec > 3)tv.tv_sec = 3;tv.tv_usec = 0;ret = select(disc_sock+1, &in, NULL, NULL, &tv);else//没有重传设置,则一直阻塞ret = select(disc_sock+1, &in, NULL, NULL, NULL);//指定时间内,没有检测到disc_sock原始报文套接口收到任何//报文。if( ret == 0 )//如果会话控制块设置了timeout回调,则执行该回调处理。//当前方案没有设置该回调。if( ses->timeout )ret = (*ses->timeout)(ses, NULL, &p_out);if( ret != 0 )return ret;//进行发现报文重传else if(p_out)send_disc(ses,p_out);continue;//接收PPPoE服务端的发现报文的响应,同时将报文中的tag//字段提取到rcv_packet.tags中。ret = recv_disc(ses, &rcv_packet);p->hdr = (struct pppoe_hdr*)p->buf;recvfrom( disc_sock, p->buf, sizeof(p->buf), 0,&p->addr, &from_len);extract_tags(p->hdr,p->tags);switch (rcv_packet.hdr->code)//收到PADI报文,当前是PPPoE客户端,不处理此报文case PADI_CODE:if(ses->rcv_padi)(*ses->rcv_padi)(ses,&rcv_packet,&p_out);//收到PADO,PPPoE服务端已经回应,当前回调函数为//std_rcv_padocase PADO_CODE:(*ses->rcv_pado)(ses,&rcv_packet,&p_out);std_rcv_pado//进行HOST_UNIQ、AC_NAME、SRV_NAME//tag字段的合法判断。if( verify_packet(ses, p_in) < 0)return -1;//如果服务端填冲了服务名,则保存到全局变量//servicename中。if (p_in->tags[0]->tag_type == PTT_SRV_NAME)memset(sName, 0, p_in->tags[0]->tag_len+1);strncpy(sName, p_in->tags[0]->tag_data, p_in->tags[0]->tag_len);strncpy(servicename,  sName, sizeof(servicename));//记录远端服务器的MAC地址,同时后继发包的//目的地址也不再使用广播地址,则修改为服务器//的MAC地址。memcpy(&ses->remote, &p_in->addr, sizeof(struct sockaddr_ll));memcpy( &ses->curr_pkt.addr, &ses->remote , sizeof(struct sockaddr_ll));//收到服务器的PADO后,我们下一步发送//PADR进行发现请求。ses->curr_pkt.hdr->code = PADR_CODE;//设置HOST_UNIQ tag字段copy_tag(&ses->curr_pkt,get_tag(p_in->hdr,PTT_HOST_UNIQ));//不再发送AC_NAME tag字段if (ses->filt->ntag)ses->curr_pkt.tags[TAG_AC_NAME]=NULL;//设置SRV_NAME 、AC_COOKIE、RELAY_SID//tag字段copy_tag(&ses->curr_pkt, ses->filt->stag);copy_tag(&ses->curr_pkt,get_tag(p_in->hdr,PTT_AC_COOKIE));copy_tag(&ses->curr_pkt,get_tag(p_in->hdr,PTT_RELAY_SID));//会话控制块状态迁为PADS_CODE,准备接收//服务器的发现确认ses->state = PADS_CODE;//将发现PADR报文发送出去。send_disc(ses, &ses->curr_pkt);(*p_out) = &ses->curr_pkt;//收到PADR报文,当前是PPPoE客户端,不处理此报文case PADR_CODE:if(ses->rcv_padr)(*ses->rcv_padr)(ses,&rcv_packet,&p_out);//收到PADS报文,服务器对发现请求进行了确认。case PADS_CODE:(*ses->rcv_pads)(ses,&rcv_packet,&p_out);std_rcv_pads//进行合法tag的校验if( verify_packet(ses, p_in) < 0)return -1;//填充pppoe类型的套接口地址,这里最重要//的是从服务器得到了分配的会话IDses->sp.sa_family = AF_PPPOX;ses->sp.sa_protocol = PX_PROTO_OE;ses->sp.sa_addr.pppoe.sid = p_in->hdr->sid;memcpy(ses->sp.sa_addr.pppoe.dev,ses->name, IFNAMSIZ);memcpy(ses->sp.sa_addr.pppoe.remote, p_in->addr.sll_addr, ETH_ALEN);//给SSK发送//CMS_MSG_PPPOE_STATE_CHANGED消息create_msg(BCM_PPPOE_CLIENT_STATE_CONFIRMED, MDMVS_ERROR_NONE);//将远端地址及会话ID保存到FLASH的PSP区save_session_info(ses->sp.sa_addr.pppoe.remote, ses->sp.sa_addr.pppoe.sid);sprintf(oldsession, "%02x%02x%02x%02x%02x%02x/%04x", remote_addr[0], remote_addr[1], remote_addr[2],remote_addr[3], remote_addr[4], remote_addr[5], sid);cmsPsp_set(req_name, oldsession, IFC_PPP_SESSION_LEN)memcpy(&old_ses, ses, sizeof(struct session));//PPPoE会话建立完成,退出session_connect函数return ret;//如果在PPPoE会话没有建立完成阶段收到PADT,则在//此中止会话。case PADT_CODE://如果PADT中会话ID与当前已经获取的会话ID不同,//则忽略此PADT报文。if( rcv_packet.hdr->sid != ses->sp.sa_addr.pppoe.sid )--ses->retransmits;continue;(*ses->rcv_padt)(ses,&rcv_packet,&p_out);std_rcv_padt//会话控制块状态机复位ses->state = PADO_CODE;//给SSK发送//CMS_MSG_PPPOE_STATE_CHANGED消息。create_msg(BCM_PPPOE_CLIENT_STATE_PADO, MDMVS_ERROR_NONE);//恢复接收BUFrcvbuf=256;setsockopt(disc_sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));//调用系统接connect接口,对应内核函数为pppoe_connectconnect(ses->fd, (struct sockaddr*)&ses->sp,sizeof(struct sockaddr_pppox));//内核接口pppoe_connectstruct sock *sk = sock->sk;struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr;struct pppox_sock *po = pppox_sk(sk);//如果当前已经连接,并且会话已经建立,则返回EBUSY错误error = -EBUSY;if ((sk->sk_state & PPPOX_CONNECTED) && sp->sa_addr.pppoe.sid)goto end;//如果当前套接口已经关闭,则返回EALREADY错误error = -EALREADY;if ((sk->sk_state & PPPOX_DEAD) && !sp->sa_addr.pppoe.sid )goto end;error = 0;//如果之前已经将通道对象与PPP驱动绑定,则解除相关绑定if (po->pppoe_pa.sid)//解除通道对象与PPP驱动绑定pppox_unbind_sock(sk);//item_hash_table哈希表中删除当前通道相关条目delete_item(po->pppoe_pa.sid,po->pppoe_pa.remote);//释放套接口对pppoe真实设备的引用if(po->pppoe_dev)dev_put(po->pppoe_dev);//套接口相关信息复位memset(sk_pppox(po) + 1, 0,sizeof(struct pppox_sock) - sizeof(struct sock));sk->sk_state = PPPOX_NONE;//在PPPoE会话阶段已经从服务器获取到会话IDif (sp->sa_addr.pppoe.sid != 0)//获PPPoE关联的真实设备,比如当前为epon0.2dev = dev_get_by_name(sp->sa_addr.pppoe.dev);error = -ENODEV;//将PPPoE套接口与真实设备关联po->pppoe_dev = dev;//真实设备没有启动,返回错误if (!(dev->flags & IFF_UP))goto err_put;//从用户侧获取到PPPoE相关地址信息,存储到PPPoE//套接口中。memcpy(&po->pppoe_pa,&sp->sa_addr.pppoe,sizeof(struct pppoe_addr));//根据会话ID及服务端地址做为关键点,将当前PPPoE//套接口对象插入到item_hash_table中。set_item(po);__set_item(po);int hash = hash_item(po->pppoe_pa.sid, po->pppoe_pa.remote);ret = item_hash_table[hash];po->next = item_hash_table[hash];item_hash_table[hash] = po;po->chan.hdrlen = (sizeof(struct pppoe_hdr) +dev->hard_header_len);po->chan.mtu = dev->mtu - sizeof(struct pppoe_hdr);po->chan.private = sk;//通道与套接口关联po->chan.ops = &pppoe_chan_ops;//通道相关回调//创建一个PPP通道对象ppp_register_channel(&po->chan);pch = kzalloc(sizeof(struct channel), GFP_KERNEL);pch->ppp = NULL;//套接口对象与通道对象互相关联pch->chan = chan;chan->ppp = pch;init_ppp_file(&pch->file, CHANNEL);pf->kind = kind;//CHANNELskb_queue_head_init(&pf->xq);skb_queue_head_init(&pf->rq);atomic_set(&pf->refcnt, 1);init_waitqueue_head(&pf->rwait);pch->file.hdrlen = chan->hdrlen;pch->lastseq = -1;init_rwsem(&pch->chan_sem);spin_lock_init(&pch->downl);rwlock_init(&pch->upl);pch->file.index = ++last_channel_index;//将新建的通道对象加入到new_channels链表中list_add(&pch->list, &new_channels);atomic_inc(&channel_count);//套接口状态变迁sk->sk_state = PPPOX_CONNECTED;//后续发送报文填充pppoe头的会话ID都直接从po->num中//获取po->num = sp->sa_addr.pppoe.sid;end:release_sock(sk);return error;return ses->fd;//触发通道对象的establish_ppp回调,当前通道对象为pppoe_channel,对应回调为//generic_establish_pppfd_ppp = the_channel->establish_ppp(devfd);generic_establish_ppp//当前假设使用新的PPP驱动类型,则new_style_driver为true,暂不//分析老的PPP驱动类型相关流程。//获取刚才创建的通道索引,对应内核代码为pppox_ioctlioctl(fd, PPPIOCGCHAN, &chindex)//内核代码pppox_ioctlstruct sock *sk = sock->sk;struct pppox_sock *po = pppox_sk(sk);switch (cmd)case PPPIOCGCHAN:index = ppp_channel_index(&po->chan);struct channel *pch = chan->ppp;return pch->file.index;//返回给应用层通道索引put_user(index , (int __user *) arg)rc = 0;//当前通道对象关联的套接口状态增加PPPOX_BOUND//标记。sk->sk_state |= PPPOX_BOUND;//打开一个新的文件描述符,指向/dev/pppfd = open("/dev/ppp", O_RDWR);//将当前PPP驱动控制块与通道进行绑定ioctl(fd, PPPIOCATTCHAN, &chindex)//内核代码ppp_ioctlstruct ppp_file *pf = file->private_data;if (pf == 0)ppp_unattached_ioctl(pf, file, cmd, arg);switch (cmd)case PPPIOCATTCHAN:get_user(unit, p);err = -ENXIO;//根据用户侧传来的单元ID查找通道对象chan = ppp_find_channel(unit);//将当前文件描述符与通道对象关联,后续对//应用层fd操作,都是针对此文件描述符相关的//通道对象进行操作。atomic_inc(&chan->file.refcnt);file->private_data = &chan->file;err = 0;flags = fcntl(fd, F_GETFL);//当前ppp_fd即指向/dev/ppp设备描述符,对ppp_fd进行操作,都相当//于对内核创建的PPP通道进行操作。set_ppp_fd(fd);ppp_fd = new_fd;if (!looped)ifunit = -1;//当前没有进行按需拨号处理,则looped为false,同时没有设置多链路,//则创建PPP单元对象。如果当前配置为按需拨号处理,则make_ppp_unit//在前面已经调用过了,这里就不会触发了。if (!looped && !multilink)//上面已经分析过了,该函数主要创建PPP单元对象,同时创建//pppoe虚拟接口设备,并将PPP单元对象与虚拟接口设备进行//关联。make_ppp_unit()//如果当前配置为按需拨号处理,则在上面配置了SC_LOOP_TRAFFIC标//记,内核侧检测到该标记后,如果从LAN侧报文路由到PPPoE虚拟接//口设备,会将此报文传递到PPP驱动控制块的接收队列,当前按需拨号//处理已经完成,向内核侧去除此标记。if (looped)set_flags(ppp_dev_fd, get_flags(ppp_dev_fd) & ~SC_LOOP_TRAFFIC);//单链路if (!multilink)//将ppp_dev_fd描述符加入到in_fds描述符集中。add_fd(ppp_dev_fd);//将PPP驱动控制块与PPP通道对象关联。ioctl(fd, PPPIOCCONNECT, &ifunit)//内核代码ppp_ioctlstruct ppp_file *pf = file->private_data;if (pf->kind == CHANNEL)struct channel *pch = PF_TO_CHANNEL(pf);switch (cmd)case PPPIOCCONNECT:get_user(unit, p)ppp_connect_channel(pch, unit);//根据单元ID找到PPP驱动控制块ppp = ppp_find_unit(unit);if (pch->file.hdrlen > ppp->file.hdrlen)ppp->file.hdrlen = pch->file.hdrlen;hdrlen = pch->file.hdrlen + 2;if (ppp->dev && hdrlen > ppp->dev->hard_header_len)ppp->dev->hard_header_len = hdrlen;//将PPP驱动控制块与PPP通道对象关联//当前PPP驱动控制块已经与PPP单元对象//关联,所以这里间接表示PPP单元对明与//PPP通道对象已经关联。list_add_tail(&pch->clist, &ppp->channels);++ppp->n_channels;pch->ppp = ppp;atomic_inc(&ppp->file.refcnt);//如果之前按需拨号处理已经该looped设置为true,这里再恢复为默认值,//当前按需拨号已经处理完成。looped = 0;return ppp_fd;//当前没有进行按需拨号,则将当前PPPoE虚拟接口名存储到程序私有的环境变量//中。if (!demand && ifunit >= 0)set_ifunit(1);slprintf(ifname, sizeof(ifname), "%s", req_name);script_setenv("IFNAME", ifname, iskey);gettimeofday(&start_time, NULL);link_stats_valid = 0;script_unsetenv("CONNECT_TIME");script_unsetenv("BYTES_SENT");script_unsetenv("BYTES_RCVD");//在LCP处理阶段处理底层链路已经建立完成事件lcp_lowerup(0);lcp_options *wo = &lcp_wantoptions[unit];fsm *f = &lcp_fsm[unit];//设置虚拟接口设备的最大接收单元ppp_send_config(unit, PPP_MRU, 0xffffffff, 0, 0);(*the_channel->send_config)((mtu), (accm), (pc), (acc));send_config_pppoesock = socket(AF_INET, SOCK_DGRAM, 0);strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));ifr.ifr_mtu = mtu;ioctl(sock, SIOCSIFMTU, (caddr_t) &ifr);close (sock);ppp_recv_config(unit, PPP_MRU, (lax_recv? 0: 0xffffffff),wo->neg_pcompression, wo->neg_accompression);(*the_channel->recv_config)((mtu), (accm), (pc), (acc));recv_config_pppoeif (mru > PPPOE_MTU)error("Couldn't increase MRU to %d", mru);//初始化对端的默认MRU值peer_mru[unit] = PPP_MRU;//有延时UP时间,则进行延迟处理。if (listen_time != 0)f->flags |= DELAYED_UP;//会本地callout链表中挂接一个定时器条目,其中回调函数//lcp_delayed_up在超时后会fsm_loweruptimeout(lcp_delayed_up, f, 0, listen_time * 1000);Else//LCP状态机迁为CLOSED状态fsm_lowerup(f);switch( f->state )case INITIAL:f->state = CLOSED;//将与PPP驱动关联的文件描述符添加到in_fds描述符集中。add_fd(fd_ppp);//将进行PPPoE发现阶段报文处理的套接口也加入到in_fds描述符集中add_fd(disc_sock);//LCP open事件处理lcp_open(0);fsm *f = &lcp_fsm[unit];lcp_options *wo = &lcp_wantoptions[unit];lcp_options *ao = &lcp_allowoptions[unit];wo->neg_mschap = 0;wo->neg_chap = 0;wo->neg_chap = 0;ao->neg_mschap = 0;ao->neg_chap = 0;ao->neg_upap = 0;ao->chap_mdtype = CHAP_DIGEST_MD5;//根据用户传入配置确定验证协议方式。//3仅使用CHAP协议,CHAP协议加密方式为CHAP_MICROSOFT//2仅使用CHAP协议,CHAP协议加密方式为CHAP_DIGEST_MD5//1仅使用PAP协议//0通过协商确定使用CHAP、PAP中任意一种,同时CHAP协议加密方式//为 CHAP_DIGEST_MD5//其它通过协商确定使用CHAP、PAP中任意一种,CHAP协议加密方式//也通过协商处理。if (opflag==3)ao->neg_chap = 1;refuse_pap = 1;ao->chap_mdtype = CHAP_MICROSOFT;else if (opflag==2)ao->neg_chap = 1;refuse_pap = 1;ao->chap_mdtype = CHAP_DIGEST_MD5;else if (opflag==1)ao->neg_upap = 1;refuse_chap = 1;else if (opflag==0)ao->neg_chap = 1;ao->neg_upap = 1;elseao->neg_chap = 1;ao->neg_upap = 1;ao->neg_mschap = 1;//根据用户配置确定是否设置被动及静默标记f->flags &= ~(OPT_PASSIVE | OPT_SILENT);if (wo->passive)f->flags |= OPT_PASSIVE;if (wo->silent)f->flags |= OPT_SILENT;fsm_open(f);switch( f->state )case CLOSED://被设置为静默if( f->flags & OPT_SILENT )f->state = STOPPED;else//发送LCP请求fsm_sconfreq(f, 0);if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT )//当前LCP回调为lcp_resetciif( f->callbacks->resetci )//复位配置信息lcp_resetciwo = &lcp_wantoptions[f->unit];go = &lcp_gotoptions[f->unit];ao = &lcp_allowoptions[f->unit];//生成本端使用的魔述字wo->magicnumber = magic();wo->numloops = 0;//将远端的配置项先设置为和本端相同*go = *wo;if (!multilink)go->neg_mrru = 0;go->neg_ssnhf = 0;go->neg_endpoint = 0;if (noendpoint)ao->neg_endpoint = 0;peer_mru[f->unit] = PPP_MRU;f->nakloops = 0;//初始发送LCP请求,设置重传次数,生成请求IDif( !retransmit )f->retransmits = f->maxconfreqtransmits;f->reqid = ++f->id;f->seen_ack = 0;outp = outpacket_buf + PPP_HDRLEN + HEADERLEN;if( f->callbacks->cilen && f->callbacks->addci )//计算配置信息长度cilen = (*f->callbacks->cilen)(f);lcp_cilengo = &lcp_gotoptions[f->unit];return (LENCISHORT(go->neg_mru && go->mru != DEFMRU) +LENCILONG(go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF) +LENCICHAP(go->neg_chap) +......if( cilen > peer_mru[f->unit] - HEADERLEN )cilen = peer_mru[f->unit] - HEADERLEN;//添加配置信息项(*f->callbacks->addci)(f, outp, &cilen);lcp_addcigo = &lcp_gotoptions[f->unit];u_char *start_ucp = ucp;ADDCISHORT(CI_MRU, go->neg_mru && go->mru != DEFMRU, go->mru);ADDCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF,......elsecilen = 0;//发送LCP请求fsm_sdata(f, CONFREQ, f->reqid, outp, cilen);outp = outpacket_buf;if (datalen > peer_mru[f->unit] - HEADERLEN)datalen = peer_mru[f->unit] - HEADERLEN;if (datalen && data != outp + PPP_HDRLEN + HEADERLEN)BCOPY(data, outp + PPP_HDRLEN + HEADERLEN, datalen);outlen = datalen + HEADERLEN;//构造PPP报文头,包括地址域、信息域、子协议域MAKEHEADER(outp, f->protocol);//填充操作码PUTCHAR(code, outp);//填充ID标识PUTCHAR(id, outp);//填充操作码数据长度PUTSHORT(outlen, outp);output(f->unit, outpacket_buf, outlen + PPP_HDRLEN);int fd = ppp_fd;if (new_style_driver)//这里减去2个字节是去除了PPP报头的//地址域、信息域p += 2;len -= 2;proto = (p[0] << 8) + p[1];//对于常用协议号LCP、PAP、CHAP都大于//0xc000,IPCP则小于0xc000。//所以output函数对于LCP、PAP、CHAP的//发送都使用ppp_fd发送,对应内核为PPP//通道对象。而IPCP发送则使用ppp_dev_fd//发送,对应内核为PPP单元对象。if (ifunit >= 0 && !(proto >= 0xc000 || proto == PPP_CCPFRAG))fd = ppp_dev_fd;//将LCP报文发出。//详见《LCP、PAP、CHAP发送》小节及//《IPCP发送》小节。write(fd, p, len);//减小重传次数,同时向程序私有定时器机制中添加定时//器条目,回调函数为fsm_timeout,该回调函数在超时后//确定是否重传LCP请求,还是确定执行是否完成回调。--f->retransmits;TIMEOUT(fsm_timeout, f, f->timeouttime);//LCP状态机迁为REQSENTf->state = REQSENT;status = EXIT_NEGOTIATION_FAILED;//主阶段迁为PHASE_ESTABLISHnew_phase(PHASE_ESTABLISH);//当前进程的主循环处理点,主阶段变为PHASE_DEAD则退出循环。当前程序//触发主阶段迁为PHASE_DEAD是由link_terminated函数触发。通常在LCP阶//阶建立失败,或者在当主循环处理时对PPP单元对象、PPP通道对象数据读取//异常情况下会触发link_terminated函数。while (phase != PHASE_DEAD)//该函数在上面已经分析过,主要处理如下信息://1、调用select对加入到in_fds描述符集的描述符进行读和异常的监听//2、使用PPPD内部定时器机制加入到callout定时器链表的条目进行//处理,如果对应条目超时,则触发条目对应的定时器超时处理回调。//3、对系统信号的捕获处理。handle_events();//使用disc_sock接收PPPoE的PADT报文中,表明远端进行链路断开,//将kill_link置为1,下面会根据这个变量进行本地链路断开。if ((disc_sock != -1) && FD_ISSET(disc_sock, &in_fds_cp))if( is_recv_padt() )kill_link = 1;status = EXIT_PEER_DEAD;//从PPP通道对象或PPP单元对象中读取数据进行处理。get_input();p = inpacket_buf;len = read_packet(inpacket_buf);len = PPP_MRU + PPP_HDRLEN;if (new_style_driver)//BUF前两个字节填充固定值,这两个字节是PPP的地址域和信//息域。*buf++ = PPP_ALLSTATIONS;*buf++ = PPP_UI;len -= 2;nr = -1;if (ppp_fd >= 0)//从PPP通道对象中读取数据,如果返回0则修正nr值也按出//进行处理。if (!(nr = read(ppp_fd, buf, len)))nr = -1;//出错则返回0if (nr < 0 && errno == ENXIO)return 0;//如果PPP通道对象中没有数据,则从PPP单元对象中读取数据。if (nr < 0 && new_style_driver && ifunit >= 0)if (!(nr = read(ppp_dev_fd, buf, len)))nr = -1;if (nr < 0 && errno == ENXIO)return 0;//加2是当前填充了PPP的地址域和信息域return (new_style_driver && nr > 0)? nr+2: nr;if (len < 0)return;//read_packet在出错后返回值都为0,所以这里表示从PPP通道对象或者//PPP单元对象读取数据出错。if (len == 0)hungup = 1;status = EXIT_HANGUP;//LCP触发底层断开事件lcp_lowerdown(0);fsm *f = &lcp_fsm[unit];//如果当前LCP为延迟UP,则不要延迟,直接取消if (f->flags & DELAYED_UP)f->flags &= ~DELAYED_UP;//执行底层断开状态机事件处理else//当前LCP状态不同,则触发底层断开的执行代码也不太//相同,这里不再进一步分析了。fsm_lowerdown(&lcp_fsm[unit]);link_terminated(0);//已经为DEAD阶段,直接退出。if (phase == PHASE_DEAD)return;//有用户侧钩子则执行钩子函数,当前代码没有设置。if (pap_logout_hook)pap_logout_hook();else//只有做为服务端进行PAP子协议的鉴权请求处理成功//时才将logged_in置1,如果该值为1,则在链路中止//时,清除登录信息关于登录名和主机部分。if (logged_in)plogout();//主程序的阶段迁为PHASE_DEAD,则主程序的循环退出。new_phase(PHASE_DEAD);return;//跳过PPP头两个字节的地址域和信息域p += 2;//获取PPP头的协议域值GETSHORT(protocol, p);len -= PPP_HDRLEN;//在LCP阶段没有完成之前,只能处理LCP报文,否则丢弃。if (protocol != PPP_LCP && lcp_fsm[0].state != OPENED)return;//在鉴权阶段只能处理鉴权相关子协议及LCP子协议,否则丢弃。if (phase <= PHASE_AUTHENTICATE && !(protocol == PPP_LCP || protocol == PPP_LQR || protocol == PPP_PAP || protocol == PPP_CHAP))return;for (i = 0; (protp = protocols[i]) != NULL; ++i)//检查当前收到的PPP子协议报文是否与当前子协议列表匹配,//并且当前对应的子协议已经置为开启处理标记。如果条件满足//则使用子协议的input回调函数进行报文处理。if (protp->protocol == protocol && protp->enabled_flag)(*protp->input)(0, p, len);return;//检测当前收到的PPP子协议报文是否匹配小于0x8000协议号//的子协议,并且当前对应的子协议设置为开启时,如果有//datainput回调则触发该回调。//当前常用的子协议LCP、PAP、CHAP、IPCP都大于0x8000,而//ipv4、ipv6这种数据协议则小于0x8000,但当前protocols数组//成员的协议号都大于0x8000,所以这里不会触发。if (protocol == (protp->protocol & ~0x8000) && protp->enabled_flag&& protp->datainput != NULL)(*protp->datainput)(0, p, len);return;//如果收到的报文的PPP子协议我们不识别,或者当前子协议的//enabled_flag还未开启,则给远端发送LCP PROTREJ协议拒绝//报文。lcp_sprotrej(0, p - PPP_HDRLEN, len + PPP_HDRLEN);p += 2;len -= 2;//该函数在前面已经分析过了,对于LCP、PAP、CHAP子协议//则使用PPP通道对象进行发送,对于IPCP则使用PPP单元对//象进行发送。fsm_sdata(&lcp_fsm[unit], PROTREJ, ++lcp_fsm[unit].id,//如果当前因收到中止信号、或者收到远端链路断开的LCP PADT报文,或//者在进行PPP通道对象数据读取或PPP单元对象数据读取时出错,则会//将kill_link置1。当前暂不对代理模式相关进行分析。if (kill_link || ( proxy_mode && !ppp_proxy_check_clients()))//执行本路链路中断lcp_close(0, "User request");fsm *f = &lcp_fsm[unit];//阶段迁为PHASE_TERMINATEif (phase != PHASE_DEAD)new_phase(PHASE_TERMINATE);//当前LCP如果已经打开,但被暂停了,此时如果标识在被动或静默//则将状态迁为关闭CLOSED,同时将链路中止。if (f->state == STOPPED && f->flags & (OPT_PASSIVE|OPT_SILENT))f->state = CLOSED;lcp_finished(f);//前面已经分析过,主要是将阶段迁为PHASE_DEADlink_terminated(f->unit);else//执行正常的链路关闭流程,给远端发送LCP TERMREQ终止//请求报文,待远端回应后或超时后,再将阶段迁为//PHASE_DEADfsm_close(&lcp_fsm[unit], reason);//如果之前配置了自动扫描选项,则在进行本地链路终止时直接退出//PPPD进程???if (autoscan)exit(0);//当前不是按需拨号时,如果有记载进程ID的文件,则删除。if (!demand)if (pidfilename[0] != 0)unlink(pidfilename)pidfilename[0] = 0;//将PPP通道相关的描述符从in_fds中移除remove_fd(fd_ppp);//将用于PPPoE发现处理的套接口描述符从in_fds中移除if (disc_sock != -1)remove_fd(disc_sock);//获取PPP通道的Flags,并根据一些比特位检测是否有错误并打印clean_check();//当前pppoe_channel的回调为generic_disestablish_pppthe_channel->disestablish_ppp(devfd);//断开PPP通道,同时如果当前有按需拨号的配置,则将PPP单元对象//恢复为回环模式。generic_disestablish_ppp//当前按需拨号,将PPP单元对象恢复为回环模式if(demand)restore_loop();looped = 1;//对PPP单元对象的设备描述符调用PPPIOCSFLAGS的ioctl//,给内核侧的PPP单元对象设置SC_LOOP_TRAFFIC标记。//使得内核侧将路由到ppp虚拟接口的报文发往PPP单元对象//的接收队列,可以通过当前PPPD应用程序对/dev/ppp设备//文件描述符的读取获取到该报文。if (new_style_driver)set_flags(ppp_dev_fd, get_flags(ppp_dev_fd) | SC_LOOP_TRAFFIC);return;initfdflags = -1;if (new_style_driver)//关闭PPP通道对象,对应内核代码为ppp_releaseclose(ppp_fd);//内核代码ppp_releasestruct ppp_file *pf = file->private_data;//将文件对象与PPP通道对象之间的关联去除file->private_data = NULL;//将PPP通道对象的引用递减,如果为0则进行通道销毁if (atomic_dec_and_test(&pf->refcnt))switch (pf->kind)case CHANNEL:ppp_destroy_channel(PF_TO_CHANNEL(pf));atomic_dec(&channel_count);//将PPP通道对象接收、发送队列的报文全部//删除。之后将PPP通道对象的内存资源释放skb_queue_purge(&pch->file.xq);skb_queue_purge(&pch->file.rq);kfree(pch);ppp_fd = -1;//如果当前非按需拨号,则将PPP单元对象进行分离if (!looped && ifunit >= 0)ioctl(ppp_dev_fd, PPPIOCDETACH)//内核代码ppp_ioctlstruct ppp_file *pf = file->private_data;if (cmd == PPPIOCDETACH)err = -EINVAL;if (pf->kind == INTERFACE)ppp = PF_TO_PPP(pf);if (file == ppp->owner)ppp_shutdown_interface(ppp);//去除PPP单元对象与虚拟设备//的引用。dev = ppp->dev;ppp->dev = NULL;//将虚拟设备去注册if (dev)unregister_netdev(dev);free_netdev(dev);//去除all_ppp_units中的映射cardmap_set(&all_ppp_units, ppp->file.index, NULL);//标记当前PPP单元对象已经不可用ppp->file.dead = 1;ppp->owner = NULL;//唤醒等待对PPP单元对象进行读取//的进程。wake_up_interruptible(&ppp->file.rwait);//如果当前对/dev/ppp进行操作的文件描述符数小//于等于2个,则进行PPP单元释放。通常在//PPPD应用进程,只打开2次/dev/ppp设备分别//做为PPP单元对象、PPP通道对象。if (atomic_read(&file->f_count) <= 2)ppp_release(inode, file);struct ppp_file *pf = file->private_data;//将文件对象与PPP单元对象关联去除file->private_data = NULL;//对当前PPP单元对象的引用递减,如//果已经不存在引用,则将PPP单元//对象销毁。if (atomic_dec_and_test(&pf->refcnt))switch (pf->kind)case INTERFACE:ppp_destroy_interface(PF_TO_PPP(pf));err = 0;return err;//当前是PPP单链路,则将PPP单元对象的设备描述符从从in_fds//中移除if (!multilink)remove_fd(ppp_dev_fd);fd_ppp = -1;//如果当前终端没有断掉,则触发LCP状态机的底层断开事件处理if (!hungup)lcp_lowerdown(0);fsm *f = &lcp_fsm[unit];//之前是延迟UP,则去除延迟UP标记,不再继续处理if (f->flags & DELAYED_UP)f->flags &= ~DELAYED_UP;//否则进行底层断开事件处理elsefsm_lowerdown(&lcp_fsm[unit]);//非按需拨号,则清除IFNAME环境变量if (!demand)script_unsetenv("IFNAME");disconnect://阶段状态迁为PHASE_DISCONNECTnew_phase(PHASE_DISCONNECT);//断开连接PPPoE会话the_channel->disconnect();disconnect_pppoe_sessession_disconnect(ses);//向服务器发送PADT中止PPPoE会话memset(&padt,0,sizeof(struct pppoe_packet));memcpy(&padt.addr, &ses->remote, sizeof(struct sockaddr_ll));padt.hdr = (struct pppoe_hdr*) ses->curr_pkt.buf;padt.hdr->ver  = 1;padt.hdr->type = 1;padt.hdr->code = PADT_CODE;padt.hdr->sid  = ses->sp.sa_addr.pppoe.sid;send_disc(ses,&padt);//复位会话控制块的SID及状态ses->sp.sa_addr.pppoe.sid = 0 ;ses->state = PADO_CODE;//这里ses->fd是之前在connect_pppoe_ses中使用//ses->fd = socket(AF_PPPOX,SOCK_STREAM,PX_PROTO_OE)创建//的AF_PPPOX协议族的套接口ses->sp.sa_addr.pppoe.sid = 0;connect(ses->fd, (struct sockaddr*)&ses->sp,sizeof(struct sockaddr_pppox));//内核代码pppoe_connectstruct sock *sk = sock->sk;struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr;struct pppox_sock *po = pppox_sk(sk);//无效的协议if (sp->sa_protocol != PX_PROTO_OE)goto end;//当前实现是根据用户侧传入sid来进行处理,如果sid有值,则//表明是将该套接口与PPP通道绑定,如果sid为0,则表明是//需要解除套接口与PPP通道绑定。//如果当前已经绑定了,但用户侧又希望绑定,则返回错误error = -EBUSY;if ((sk->sk_state & PPPOX_CONNECTED) && sp->sa_addr.pppoe.sid)goto end;//如果已经解除,但用户侧又希望解除,则返回错误error = -EALREADY;if ((sk->sk_state & PPPOX_DEAD) && !sp->sa_addr.pppoe.sid )goto end;error = 0;if (po->pppoe_pa.sid)//解除PPP通道与套接口的关联pppox_unbind_sock(sk);if (sk->sk_state & (PPPOX_BOUND | PPPOX_ZOMBIE))ppp_unregister_channel(&pppox_sk(sk)->chan);sk->sk_state = PPPOX_DEAD;//从item_hash_table中删除该pppoe的关联delete_item(po->pppoe_pa.sid,po->pppoe_pa.remote);//去除当前pppoe套接口对关联的真实设备的引用if(po->pppoe_dev)dev_put(po->pppoe_dev);memset(sk_pppox(po) + 1, 0,sizeof(struct pppox_sock) - sizeof(struct sock));//套接口状态复位sk->sk_state = PPPOX_NONE;//po->num = 0po->num = sp->sa_addr.pppoe.sid;release_sock(sk);return error;fail://当前pppoe_channel没有该回调,不进行处理if (the_channel->cleanup)(*the_channel->cleanup)();//代理模式,则继续退回主循环if( proxy_mode )goto proxy_loop;//非按需拨号,如果存在记录进程ID的文件则删除if (!demand)if (pidfilename[0] != 0)unlink(pidfilename)pidfilename[0] = 0;//当前如果是按需拨号if (demand)demand_discard();//通过对PPP单元对象的设备描述符调用PPPIOCSNPMODE的ioctl//操作,通过内核将所有数据相关(子协议号第16位为0的子协议,//比如ipv4、ipv6)的子协议模式设置为NPMODE_ERROR,则后续//如果pppoe虚拟接口再进行数据处理时,如果遇到这些数据都将直//接丢弃。for (i = 0; (protp = protocols[i]) != NULL; ++i)if (protp->enabled_flag && protp->demand_conf != NULL)sifnpmode(0, protp->protocol & ~0x8000, NPMODE_ERROR);//如果之前在按需拨号等待阶段,收到了LAN侧路由到pppoe虚拟接口//的报文,则内核侧会将此报文存储到PPP单元对象的接收队列,当前//在应用层pppd对PPP单元对象关联的设备描述符进行读取,将这些报//文收集到用户空间的pend_q链表中。get_loop_output();if (new_style_driver)while ((n = read_packet(inpacket_buf)) > 0)if (loop_frame(inpacket_buf, n))rv = 1;return rv;//将上面收集到pend_q链表中的报文丢弃。for (pkt = pend_q; pkt != NULL; pkt = nextpkt)nextpkt = pkt->next;free(pkt);pend_q = NULL;framelen = 0;flush_flag = 0;escape_flag = 0;fcs = PPP_INITFCS;//如果在上面主循环处理时,因为一些中断流程(如检测到WAN链路断开、按需//拨号的空闲时间超时、进行鉴权协商失败等)不需要挂断,需要进入主循环继续//处理,则将need_holdoff置为0,表明已经当前不进行挂断,还需要尝试处理。//其它情况则默认将need_holdoff置为1,表明需要进行挂断处理。t = need_holdoff? holdoff: 0;//holdoff为3秒//当前PPPOE不涉及该回调,当前为空指针。if (holdoff_hook)t = (*holdoff_hook)();//当前需要进行挂断处理if (t > 0)//阶段状态迁为PHASE_HOLDOFFnew_phase(PHASE_HOLDOFF);//启动定时器,超时后执行holdoff_end回调,当前回调仅将阶段状态迁为//PHASE_DORMANT,为了退出下面的循环。TIMEOUT(holdoff_end, NULL, t);do//继续进行一小段时间的事件处理,个人分析,由于当前PPPD定时//器机制的触发函数是在handle_events中运行,所以这里应该最主要//的目的是进行定时器的调度,当然不排除确实有pppoe相关的事件//在这里处理。handle_events();//如果之前主循环事件处理,或者当前事件处理触发了链路断开(如收//到远端PADT报文、收到导致进程中止的信号、进行PPP单元或PPP//通道读取异常),则将kill_link设置为1。这里将阶段状态迁//为PHASE_DORMANT,否则靠定时器超时来完成这里的状态切换,//退出这个循环。if (kill_link)new_phase(PHASE_DORMANT);while (phase == PHASE_HOLDOFF);//默认persist为1,表明链路出错后回到主循环继续尝试处理。如果用户//层设置参数将persist置为0,则会退出主循环,从而退出PPPD进程。if (!persist)break;//如果之前有调用子程序,则进行孩子进程回收while (n_children > 0)if (reap_kids(1) < 0)break;die(status);cleanup();sys_cleanup();//如果之前PPPoE虚拟接口UP,则这里通过ioctl通知内核侧去除该接口//UP标记。if (if_is_up)if_is_up = 0;sifdown(0);//如果之前PPPD添加了默认路由项,则这里进行删除if (default_route_gateway != 0)cifdefaultroute(0, 0, default_route_gateway);//因为之前PPPD因为ARP代理处理,添加了ARP条目,则这里进行//删除处理。if (has_proxy_arp)cifproxyarp(0, proxy_arp_addr);//如果还存在PPP通道对象,则进行通道解除,当前函数已在上面分析过。if (fd_ppp >= 0)the_channel->disestablish_ppp(devfd);//当前pppoe_channel没有该回调if (the_channel->cleanup)(*the_channel->cleanup)();//如果存在进程ID文件则删除if (pidfilename[0] != 0)unlink(pidfilename)pidfilename[0] = 0;//如果存在链路名文件则删除if (linkpidfile[0] != 0)unlink(linkpidfile)linkpidfile[0] = 0;//清除之前创建的数据库if (pppdb != NULL)cleanup_db();//如果当前exitnotify链表有监听对象,则分别调用每个链表条目的func回调//进行PPPD进程退出事件的通知notify(exitnotify, status);//清除cms log模块cmsLog_cleanup();//退出PPPD进程exit(status);二、按需拨号接收到LAN侧数据。按需拨号的原理就是先不进行ppp的连接,只有当检测到lan侧有数据通过这个pppoe接口设备进行路由输出时,才触发ppp的连接,大概流程如下:1、应用层PPPD进程创建pppoe接口,同时配置临时的IP地址。2、应用层PPPD进程向内核侧PPP驱动控制块设置SC_LOOP_TRAFFIC标记,用来指示PPP驱动模块当前正处于按需拨号中,如果收到LAN侧报文则需要将报文发到PPP驱动控制块的接收队列中,同时唤醒执行/dev/ppp设备文件描述符导致阻塞的进程。3、应用层PPPD进程向内核侧PPP驱动控制块设置所有子协议都为NPMODE_PASS状态,使得所有子协议都能通过pppoe接口,而不是被丢弃。4、应用层PPPD进程将/dev/ppp设备文件描述符加入到select的读FDSET中,同时调用select进行等待/dev/ppp设备文件描述符有读事件发生。5、当LAN侧有报文发出时,如果路由到pppoe接口,则调用pppoe接口的设备发送回调函数ppp_start_xmit,该函数查看到PPP驱动控制块含有SC_LOOP_TRAFFIC标记,就知道当前正在按需拨号,将报文放入PPP驱动控制块的接收队列,同时唤醒select触发的等待队列。6、此时应用层PPPD进程select唤醒后,对/dev/ppp设备文件描述符进行read读取,/dev/ppp设备文件描述符对应的内核函数为ppp_read,ppp_read将PPP驱动控制块中接收队列的报文复制到应用层PPPD的BUF中。7、应用层PPPD进程将read得到的报文存储到pend_qtail阻塞链表中,并开始进行PPP连接处理,当PPP连接到了IPCP完成阶段,则会将pend_qtail阻塞链表中的报文发送出去。这里主要分析三个点:内核的select处理。内核收到LAN侧报文后,路由到pppoe接口,触发pppoe接口的设备输出回调ppp_start_xmit。内核的read处理。1、sys_select//如果传参含有超时时间,则转换为内核的ticket单位if (tvp)copy_from_user(&tv, tvp, sizeof(tv))timeout = ROUND_UP(tv.tv_usec, USEC_PER_SEC/HZ);timeout += tv.tv_sec * HZ;core_sys_select(n, inp, outp, exp, &timeout);long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];//获取当前线程打开的最大文件描述符fdt = files_fdtable(current->files);max_fds = fdt->max_fds;//修正用户传入的最大文件描述符为实际值。if (n > max_fds)n = max_fds;//计算当前所需最大描述符需要占用多少字节来存储,当前最小存储单元为long,描述//符会做为比特位的方式进行存储。比如说当前n=33,假设当前体系架构的long为4//个字节,则一个long可以存储32位,当前n是33,所以需要占用两个long,则size//就为2。size = FDS_BYTES(n);bits = stack_fds;//stack_fds是上面定义的静态数组,如果上面静态数组空间满足不了,则需要从堆中//分配内存。这里除6是因为上面算出来的size是1个最大描述符占用的字节,在实//际处理中需要占用6倍的最大描述符占用字节空间,6表示传入的读、写、异常fds,//以及最后计算出结果的读、写、异常fds。if (size > sizeof(stack_fds) / 6)ret = -ENOMEM;bits = kmalloc(6 * size, GFP_KERNEL);//以size为大块单位,进行6等分。fds.in      = bits;fds.out     = bits +   size;fds.ex      = bits + 2*size;fds.res_in  = bits + 3*size;fds.res_out = bits + 4*size;fds.res_ex  = bits + 5*size;//将用户侧的读、写、异常fds复制到内核侧if ((ret = get_fd_set(n, inp, fds.in)) || (ret = get_fd_set(n, outp, fds.out)) ||(ret = get_fd_set(n, exp, fds.ex)))goto out;//先将结果的fds比特位全部置0zero_fd_set(n, fds.res_in);zero_fd_set(n, fds.res_out);zero_fd_set(n, fds.res_ex);ret = do_select(n, &fds, timeout);retval = max_select_fd(n, fds);//计算出最大描述符不能被最小单元整除的比特位值,比如当前最小单元为//4*sizeof(long) = 32位,如果n=33,则set为1set = ~(~0UL << (n & (__NFDBITS-1)));//计算出最大描述符可以被整除的个数,假设当前n为33,则整除后n=1n /= __NFDBITS;//得到当前已打开的文件描述符最大单元的比特位。一定要理解当前机制存储//文件描述符是使用的bitmap数据结构,相当前每32位一个hash桶,fds_bits//是一个数组,假设要将33存入到fds_bits,则fds_bits[0]只有32位已经存储//不下这个比特位,这时候就需要把33存储到下一个hash桶fds_bits[1]中,//33整除32的余数为1,则将fds_bits[1]的第1个比特置1。这里取已打开的//文件描述符最大单元比特位,目的是因为最大单元的字节位也是一个不构成//被32整除的字节。fdt = files_fdtable(current->files);open_fds = fdt->open_fds->fds_bits+n;max = 0;//如果用户传入的n确实有不能被32整除的余数if (set)//计算出用户传入的最大描述符、与用户传入的read_fds、write_fds、ex_fds//的第n个字节位的交集。比如用户传入33,则第n个字节位是33/32=1,//假设read_fds为11100000000000000000000000000000000,write_fds和//ex_fds都为0,则这里read_fds的第n个字节位是111,这里111 & 1最//终结果为1。set &= BITS(fds, n);//如果有不被32整除的余数,则直接用户传入fds交集的最大单元比特位//与实际打开描述符的最大单元比特位进行反向与,如果结果为0则表明//正确,否则出现错误,正确后直接走到后面计算最大描述符比特位的//处理流程中。if (set)if (!(set & ~*open_fds))goto get_max;return -EBADF;//走到这里表示最大文件描述符n是一个可以被32整除的数while (n)open_fds--;n--;//依次获取用户传入fds交集的最大单元比特位set = BITS(fds, n);//如果当前最大单元比特位是全0,则取一下字节为最大单元比特//位再次查看。if (!set)continue;//检测用户传入的fds与当前线程真实打开的文件描述符不符,则//返回错误。if (set & ~*open_fds)return -EBADF;//已经计算出来,直接继续if (max)continue;get_max://计算最大单元比特位的数值。domax++;set >>= 1;while (set);//只需要得出最大单元比特位数值,后续都按满比特位值计算。max += n * __NFDBITS;return max;n = retval;poll_initwait(&table);init_poll_funcptr(&pwq->pt, __pollwait);pt->qproc = qproc;//__pollwaitpwq->error = 0;pwq->table = NULL;pwq->inline_index = 0//用户没有传入timeout,则表示一直等待wait = &table.pt;if (!*timeout)wait = NULL;retval = 0;for (;;)//设置当前进程状态为可中断状态set_current_state(TASK_INTERRUPTIBLE);inp = fds->in;outp = fds->out;exp = fds->ex;rinp = fds->res_in;routp = fds->res_out;rexp = fds->res_ex;//n为最大描述符值for (i = 0; i < n; ++rinp, ++routp, ++rexp)//在for循环中每次取用户的read_fd[i]、write_fd[i]、ex_fd[i]的单//字节比特位值。in = *inp++; out = *outp++; ex = *exp++;//将三种比特位值交集在一起,即同一个描述符句柄可以同时在读、写、//异常描述符集中。all_bits = in | out | ex;//如果当前这个单字节比特位值都为0,则选择下一个单字节比特位进行//查看。if (all_bits == 0)i += __NFDBITS;continue;//对当前单字节的所有比特位进行分析for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1)//遍历了所有描述符,退出if (i >= n)break;//当前比特位没有等待的描述符时继续查找下一个if (!(bit & all_bits))continue;//获取当前比特位,从当前进程的文件描述符表中获取文件对象file = fget_light(i, &fput_needed);if (file)f_op = file->f_op;mask = DEFAULT_POLLMASK;//如果当前文件对象的操作集中含有poll回调,则调用该回//调,当前分析的/dev/ppp设备对象的poll回调函数为//ppp_poll,稍后进行分析。if (f_op && f_op->poll)mask = (*f_op->poll)(file, retval ? NULL : wait);//释放文件对象引用fput_light(file, fput_needed);//如果上面的poll回调返回值含POLLIN_SET,同时当前//用户侧监听当前比特位的描述符,则在结果比特位中标//明该比特位的描述符已经有可读的事件。if ((mask & POLLIN_SET) && (in & bit))res_in |= bit;retval++;//在结果比特位中标明该比特位的描述符已经有可写事件。if ((mask & POLLOUT_SET) && (out & bit))res_out |= bit;retval++;//在结果比特位中标明该比特位的描述符已经有异常事件。if ((mask & POLLEX_SET) && (ex & bit))res_ex |= bit;retval++;//如果需要,暂时让出CPUcond_resched();//将就绪的描述符比特位复制到准备给用户的响应结果中。if (res_in)*rinp = res_in;if (res_out)*routp = res_out;if (res_ex)*rexp = res_ex;wait = NULL;//在以下情况下退出主循环//1、有就绪的描述符//2、用户没有设置超时时间//3、有未决的信号//4、处理错误if (retval || !*timeout || signal_pending(current))break;if(table.error)retval = table.error;break;//负数则设置一个最大的等待时间if (*timeout < 0)__timeout = MAX_SCHEDULE_TIMEOUT;//超出最大等待时间值,则进行回卷。else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT - 1))__timeout = MAX_SCHEDULE_TIMEOUT - 1;*timeout -= __timeout;else__timeout = *timeout;*timeout = 0;//进行进程调度让出CPU,同时启动定时器,超时后再将此进程唤醒。__timeout = schedule_timeout(__timeout);//返回剩余的超时时间if (*timeout >= 0)*timeout += __timeout;//设置当前进程为RUNNING状态__set_current_state(TASK_RUNNING);poll_freewait(&table);p = pwq->table;//将轮询工作队列的inline_entries所有条目项移除等待队列,同时释放这些资//源。for (i = 0; i < pwq->inline_index; i++)free_poll_entry(pwq->inline_entries + i);remove_wait_queue(entry->wait_address,&entry->wait);fput(entry->filp);//将轮询表中所有条目项移除等待队列,同时释放这些资源。while (p)entry = p->entry;doentry--;free_poll_entry(entry);while (entry > p->entries);old = p;p = p->next;free_page((unsigned long) old);//返回错误if (ret < 0)goto out;//在没有错误的情况下,需要检测是否有未决的信号处理,如果有则返回//ERESTARTNOHAND,调用该函数的点会将该错误类型转换为EINTRif (!ret)ret = -ERESTARTNOHAND;if (signal_pending(current))goto out;ret = 0;//将就绪的fds返回到用户侧set_fd_set(n, inp, fds.res_in)set_fd_set(n, outp, fds.res_out)set_fd_set(n, exp, fds.res_ex)if (tvp)//如果当前程序含有STICKY_TIMEOUTS标记,则不能向用户侧更新时间戳if (current->personality & STICKY_TIMEOUTS)goto sticky;//将超时剩余时间更新到用户侧rtv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ));rtv.tv_sec = timeout;if (timeval_compare(&rtv, &tv) >= 0)rtv = tv;copy_to_user(tvp, &rtv, sizeof(rtv))sticky://返回值为ERESTARTNOHAND表明有未决的信号处理,修正错误值为EINTRif (ret == -ERESTARTNOHAND)ret = -EINTR;---------------------------------------------------------------------------------------------------------------------//dev/ppp驱动模块的poll回调函数ppp_pollstruct ppp_file *pf = file->private_data;//此时PPP驱动模块还没有建立pppoe接口,这里pf将会是0if (pf == 0)return 0;//将当前进程加入到PPP驱动控制块的rwait等待队列中,后续当从LAN侧发送的//报文路由到pppoe接口时,pppoe接口的发送函数会唤醒rwait等待队列。poll_wait(file, &pf->rwait, wait);//这里的轮询表的qproc回调是在上面poll_initwait中设置的,当前为__pollwaitp->qproc(filp, wait_address, p);__pollwait//分配一个表条目对象poll_table_entry *entry = poll_get_entry(p);//获取轮询工作队列控制块struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt);//获取轮询表的首页struct poll_table_page *table = p->table;//优先从轮询工作队列控制块的inline_entries静态数组中分配if (p->inline_index < N_INLINE_POLL_ENTRIES)return p->inline_entries + p->inline_index++;//当inline_entries静态数组已经分配完,之后再从轮询表中进行动//态分配。这里判断如果检测如果当前轮询表不存在,或者当前这//个表单元已经分配完了,则再次申请一个内存资源。if (!table || POLL_TABLE_FULL(table))new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);new_table->entry = new_table->entries;new_table->next = table;p->table = new_table;table = new_table;return table->entry++;//增加文件对象引用计数get_file(filp);//将新分配的表条目对象加入到PPP驱动的rwait等待队列中entry->filp = filp;entry->wait_address = wait_address;init_waitqueue_entry(&entry->wait, current);add_wait_queue(wait_address,&entry->wait);//返回当前/dev/ppp驱动文件描述符可写mask = POLLOUT | POLLWRNORM;//如果当前PPP驱动的rq链表有报文,则返回当前/dev/ppp驱动文件描述符可读if (skb_peek(&pf->rq) != 0)mask |= POLLIN | POLLRDNORM;//如果当前ppp驱动对象的文件描述符被关闭了,或者被分离了,则当前已不可用,//返回POLLHUP。if (pf->dead)mask |= POLLHUP;//当前ppp_file类型为INTERFACE,则检测到当前建立的pppoe接口还没有关联//PPP通道对象,但当前已经被PPPD应用程序设置了SC_LOOP_TRAFFIC标记,//表明当前是在进行按需拨号,则也返回当前/dev/ppp驱动文件描述符可读。else if (pf->kind == INTERFACE)struct ppp *ppp = PF_TO_PPP(pf);if (ppp->n_channels == 0 && (ppp->flags & SC_LOOP_TRAFFIC) == 0)mask |= POLLIN | POLLRDNORM;return mask;2、ppp_start_xmitppp_start_xmit//获取ppp驱动控制块struct ppp *ppp = (struct ppp *) dev->priv;//进行报文以太网下一协议标识映射,假设当前是ipv4报文,则为NP_IPnpi = ethertype_to_npindex(ntohs(skb->protocol));//用户层PPPD在进行按需拨号处理时,已经使用demand_unblock函数暂时将所有//下一子协议的模式都设置为PASS允许通过。switch (ppp->npmode[npi])case NPMODE_PASS:break;case NPMODE_QUEUE:goto outf;case NPMODE_DROP:case NPMODE_ERROR:goto outf;//当数据包头部空间无法容纳PPP头时,重新分配一个skb资源,同时将老的数据复制//到新的skb中。if (skb_headroom(skb) < PPP_HDRLEN)ns = alloc_skb(skb->len + dev->hard_header_len, GFP_ATOMIC);skb_reserve(ns, dev->hard_header_len);skb_copy_bits(skb, 0, skb_put(ns, skb->len), skb->len);kfree_skb(skb);skb = ns;//填充PPP头的协议字段pp = skb_push(skb, 2);proto = npindex_to_proto[npi];pp[0] = proto >> 8;pp[1] = proto;//限制发送调度netif_stop_queue(dev);//将报文加入PPP驱动对象的发送队列中skb_queue_tail(&ppp->file.xq, skb);//进行数据发送ppp_xmit_process(ppp);//将PPP控制块中悬挂的包进行发送ppp_push(ppp);skb = ppp->xmit_pending;if (skb == 0)return;//当前PPP驱动控制块还没有通道对象,则数据包没有发送目的地,直接//将报文丢弃。list = &ppp->channels;if (list_empty(list))ppp->xmit_pending = NULL;kfree_skb(skb);return;//当前如果是单链路,使用当前通道对象的start_xmit回调进行报文发送。if ((ppp->flags & SC_MULTILINK) == 0)list = list->next;pch = list_entry(list, struct channel, clist);if (pch->chan)pch->chan->ops->start_xmit(pch->chan, skb)ppp->xmit_pending = NULL;elsekfree_skb(skb);ppp->xmit_pending = NULL;return;//PPP多链路协议相关机制暂不分析。//当PPP驱动控制块已经没有悬停的待发数据,同时PPP驱动控制块的发送队列//中还有待发数据,则进行数据发送,这里主要会涉及数据压缩的处理。while (ppp->xmit_pending == 0&& (skb = skb_dequeue(&ppp->file.xq)) != 0)ppp_send_frame(ppp, skb);//小于0x8000的协议包进行过滤if (proto < 0x8000)//过滤器处理4个字节,当前PPP头的protocol字段仅有2个字节,//这里再扩冲2个字节,后面处理没有通过,则还会进行恢复。*skb_push(skb, 2) = 1;//如果应用层通过IOCTL PPPIOCSPASS设置了过滤器,则进行//对应处理。if (ppp->pass_filter&& sk_run_filter(skb, ppp->pass_filter,ppp->pass_len) == 0)kfree_skb(skb);return;//如果应用层通过IOCTL PPPIOCSACTIVE设置了过滤器,则进行//对应处理。if (!(ppp->active_filter&& sk_run_filter(skb, ppp->active_filter,ppp->active_len) == 0))//更新最后发送时间戳ppp->last_xmit = jiffies;//在上面为了进行过滤处理进行了扩冲2字节,这里进行还原。skb_pull(skb, 2);++ppp->stats.tx_packets;ppp->stats.tx_bytes += skb->len - 2;switch (proto)case PPP_IP://对于IPv4子协议,如果用户层未设备VJ压缩器或者没有激活,//则跳出,否则下面进行VJ压缩处理。if (ppp->vj == 0 || (ppp->flags & SC_COMP_TCP) == 0)break;//暂不进行VJ压缩机制相关分析。case PPP_CCP://对于压缩控制子协议的处理,暂时不分析。ppp_ccp_peek(ppp, skb, 0);break;//根据压缩控制子协议状态变迁确定是否进行压缩处理,当前暂不分析//压缩相关机制处理。if ((ppp->xstate & SC_COMP_RUN) && ppp->xc_state != 0&& proto != PPP_LCP && proto != PPP_CCP)if (!(ppp->flags & SC_CCP_UP) && (ppp->flags & SC_MUST_COMP))goto drop;skb = pad_compress_skb(ppp, skb);if (!skb)goto drop;//如果当前用户层PPPD程序设置了SC_LOOP_TRAFFIC标记,则表明//上层PPPD正在进行按需拨号,此时将LAN侧报文放在PPP驱动控制//块的接收队列,同时唤醒正在读取或者select读事件的进程。if (ppp->flags & SC_LOOP_TRAFFIC)//PPP_MAX_RQLEN  32if (ppp->file.rq.qlen > PPP_MAX_RQLEN)goto drop;skb_queue_tail(&ppp->file.rq, skb);wake_up_interruptible(&ppp->file.rwait);return;//将从PPP驱动控制块取出的报文放入PPP驱动控制块发送悬挂点,并//使用ppp_push将悬挂的报文进行发送。ppp->xmit_pending = skb;ppp_push(ppp);//当PPP驱动控制块已经没有悬挂包进行处理,同时PPP驱动控制块接收队列也//没有数据,则触发接收软中断,继续处理从网卡接收到的报文。if (ppp->xmit_pending == 0 && skb_peek(&ppp->file.xq) == 0)netif_wake_queue(ppp->dev);//如果当前设备设置了发送关闭,则清除此状态标记。if (test_and_clear_bit(__LINK_STATE_XOFF, &dev->state))__netif_schedule(dev);//如果当前设备发送侧没有调度,则触发接收软中断if (!test_and_set_bit(__LINK_STATE_SCHED, &dev->state))sd = &__get_cpu_var(softnet_data);dev->next_sched = sd->output_queue;sd->output_queue = dev;raise_softirq_irqoff(NET_TX_SOFTIRQ);3、用户侧程序PPPD使用对/dev/ppp设备描述符进行read读取,则内核对应的函数为ppp_read。ppp_readstruct ppp_file *pf = file->private_data;//将当前进程加入到等待队列add_wait_queue(&pf->rwait, &wait);for (;;)//设置当前进程状态为可中断状态set_current_state(TASK_INTERRUPTIBLE);//从当前PPP驱动控制块中获取报文,如果存在则退出循环,否则需要等待skb = skb_dequeue(&pf->rq);if (skb)break;ret = 0;//当前通道对象已经去注册或者接口对象已经从PPP驱动控制块分离,则退出//循环。if (pf->dead)break;//当前是接口类型if (pf->kind == INTERFACE)//如果当前PPP驱动对角还没有关联通道,同时当前也没有进行按需拨号处//理,则直接退出循环,返回0。struct ppp *ppp = PF_TO_PPP(pf);if (ppp->n_channels == 0&& (ppp->flags & SC_LOOP_TRAFFIC) == 0)break;ret = -EAGAIN;//当前为非阻塞模式,并且没有数据,则返回EAGAINif (file->f_flags & O_NONBLOCK)break;//如果当前进程含有未决的信号,则返回ERESTARTSYS错误ret = -ERESTARTSYS;if (signal_pending(current))break;//进行进程调度schedule();//设置当前进程为TASK_RUNNING状态set_current_state(TASK_RUNNING);//移除等待队列remove_wait_queue(&pf->rwait, &wait);//没有报文if (skb == 0)goto out;//报文长度大于用户给定的BUG大小,返回EOVERFLOW错误ret = -EOVERFLOW;if (skb->len > count)goto outf;//将报文复制到用户侧BUF中。ret = -EFAULT;if (copy_to_user(buf, skb->data, skb->len))goto outf;ret = skb->len;outf:kfree_skb(skb);out:return ret;三、LCP、PAP、CHAP发送应用层PPPD对于LCP、PAP、CHAP发送处理为对ppp_fd文件描述符进行write操作,对应内核处理即向PPP驱动的通道对象进行操作。内核流程:ppp_writestruct ppp_file *pf = file->private_data;skb = alloc_skb(count + pf->hdrlen, GFP_KERNEL);//预留PPPoE头部空间skb_reserve(skb, pf->hdrlen);//从用户空间复制报文到skb中copy_from_user(skb_put(skb, count), buf, count)//将报文放入PPP通道对象的发送队列skb_queue_tail(&pf->xq, skb);switch (pf->kind)case CHANNEL:ppp_channel_push(PF_TO_CHANNEL(pf));if (pch->chan != 0)//当前PPP通道对象的发送队列不空时,则进行报文发送while (!skb_queue_empty(&pch->file.xq))skb = skb_dequeue(&pch->file.xq);//当前回调函数为pppoe_xmitr = pch->chan->ops->start_xmit(pch->chan, skb)pppoe_xmitsk = (struct sock *) chan->private;__pppoe_xmit(sk, skb);struct pppox_sock *po = pppox_sk(sk);//PPP通道对象关联的物理接口设备struct net_device *dev = po->pppoe_dev;int headroom = skb_headroom(skb);//填充PPPoE头,包括版本、类型、操作码、会话ID//、负载长度hdr.ver= 1;hdr.type = 1;hdr.code = 0;hdr.sid= po->num;hdr.length = htons(skb->len);//检测当前skb头部预留空间是否足够,如果不够则//需要重新分配skb,如果够则将skb克隆一份。if (headroom < (sizeof(struct pppoe_hdr) + dev->hard_header_len))skb2 = dev_alloc_skb(32+skb->len +sizeof(struct pppoe_hdr) +dev->hard_header_len);skb_reserve(skb2, dev->hard_header_len + sizeof(struct pppoe_hdr));memcpy(skb_put(skb2, skb->len), skb->data, skb->len);elseskb2 = skb_clone(skb, GFP_ATOMIC);//skb的data指针上移,准备进行PPPoE头部处理。ph = (struct pppoe_hdr *) skb_push(skb2, sizeof(struct pppoe_hdr));//将上面构造好的PPPoE头部复制到skb。memcpy(ph, &hdr, sizeof(struct pppoe_hdr));//设置以太网负载协议类型为PPPoE会话skb2->protocol = __constant_htons(ETH_P_PPP_SES);//初始skb2的三层数据指针及设备关联。skb2->nh.raw = skb2->data;skb2->dev = dev;//调用PPP通道对象关联的真实接口设备的hard_header//回调进行报文的二层头填充,这里给定对端地址为//PPPoE服务器的MAC。dev->hard_header(skb2, dev, ETH_P_PPP_SES,po->pppoe_pa.remote, NULL, data_len);//调用通用的设备输出队列接口函数,将报文通过//发向出口队列子系统,这里不再详细分析,有兴趣//可以了解之前分析的《接口设备发包》的分析文档dev_queue_xmit(skb2);//释放skb,这里由于skb2是复制了一份,所以skb//已经没用了,而skb2通常是在设备驱动输出后才//进行释放。kfree_skb(skb);//没有发送成功,则重新放入PPP通道对象的发送队列,准备下//次再处理。if ( !r )skb_queue_head(&pch->file.xq, skb);break;//当前通道对象没有注册,则直接丢弃报文。elseskb_queue_purge(&pch->file.xq);//如果当前PPP通道对象的发送队列已经空,则尝试看PPP单元对象中是否//有报文需要发送。if (skb_queue_empty(&pch->file.xq))ppp = pch->ppp;if (ppp != 0)//调用PPP单元对象的发送接口,该函数的实现细节可以参考下面//《IPCP》发送小节。ppp_xmit_process(ppp);四、IPCP发送应用层PPPD对于IPCP发送处理为对ppp_dev_fd文件描述符进行write操作,对应内核处理即向PPP驱动的单元对象进行操作。内核流程:ppp_writestruct ppp_file *pf = file->private_data;skb = alloc_skb(count + pf->hdrlen, GFP_KERNEL);//预留PPPoE头部空间skb_reserve(skb, pf->hdrlen);//从用户空间复制报文到skb中copy_from_user(skb_put(skb, count), buf, count)//将报文放入PPP通道对象的发送队列skb_queue_tail(&pf->xq, skb);switch (pf->kind)case INTERFACE:ppp_xmit_process(PF_TO_PPP(pf));//如果当前PPP单元对象有悬挂的报文待发送,则利用PPP通道对象将报文//发出。ppp_push(ppp);struct sk_buff *skb = ppp->xmit_pending;//没有悬挂的报文,则直接返回。if (skb == 0)return;//如果当前PPP单元对象没有关联任何PPP通道对象,则直接将悬挂的//报文丢弃。list = &ppp->channels;if (list_empty(list))ppp->xmit_pending = NULL;kfree_skb(skb);return;//当前PPP为单链路方式if ((ppp->flags & SC_MULTILINK) == 0)list = list->next;pch = list_entry(list, struct channel, clist);//调用PPP通道对象的start_xmit回调将报文发出。当前PPP通道对//象即在LCP阶段创建的PPPoE通道,这里的回调函数为pppoe_xmit//,该函数在上面《LCP、PAP、CHAP发送》小节已经分析过,这里//不在详细分析,主要是通过PPP通道对象关联的真实接口设备将//报文发出。pch->chan->ops->start_xmit(pch->chan, skb);//发送成功后将悬挂指针清空。ppp->xmit_pending = NULL;return;//PPP多链路方式暂不分析。ppp_mp_explode(ppp, skb))ppp->xmit_pending = NULL;kfree_skb(skb);//当PPP单元对象悬挂指针已空,并且当前PPP单元对象的发出队列还有//数据,则进行数据处理(PPP压缩相关机制)后,放入PPP单元对象的//悬挂指针,之后将悬挂指针上的报文通过PPP通道对象发出。while (ppp->xmit_pending == 0 && (skb = skb_dequeue(&ppp->file.xq)) != 0)//该函数在上面按需拨号分析时已经分析过,主要将PPP单元对象中的//数据进行处理(PPP压缩相关机制),放入PPP单元对象的悬挂//指针,之后将悬挂指针上的报文通过PPP通道对象发出。如果标记//含有SC_LOOP_TRAFFIC,则表明当前应用程序PPPD还处于按需拨//号处理阶段,则不将报文发送出去,而是放入PPP单元对象的接收//队列,同时唤醒阻塞上层PPPD使用select或read对/dev/ppp进行读//时的阻塞进程。ppp_send_frame(ppp, skb);//当PPP单元对象已经没有悬挂包进行处理,同时接收队列也没有数据,则//触发接收软中断,继续处理从网卡接收到的报文。if (ppp->xmit_pending == 0 && skb_peek(&ppp->file.xq) == 0)netif_wake_queue(ppp->dev);五、IPv4数据报文发送这里假设当前PPPoE的会话已经建立成功,同时PPP的LCP、PAP或CHAP、IPCP已经交互正常,此时从LAN侧收到的报文通过路由模块发送PPPoE的虚拟接口,当前该虚拟接口进行报文发送,对应的内核函数为ppp_start_xmitppp_start_xmitstruct ppp *ppp = (struct ppp *) dev->priv;//将以太网负载类型转换为PPP子协议类型,当前ipv4会转为NP_IPnpi = ethertype_to_npindex(ntohs(skb->protocol));//检测当前应用程序PPPD是否对ipv4子协议放行,如果未设置为PASS,则报文被丢//弃。switch (ppp->npmode[npi])case NPMODE_PASS:break;case NPMODE_QUEUE:goto outf;case NPMODE_DROP:case NPMODE_ERROR:goto outf;//如果当前skb头部空间不够存放PPP头,则重新分配skbif (skb_headroom(skb) < PPP_HDRLEN)ns = alloc_skb(skb->len + dev->hard_header_len, GFP_ATOMIC);skb_reserve(ns, dev->hard_header_len);skb_copy_bits(skb, 0, skb_put(ns, skb->len), skb->len);kfree_skb(skb);skb = ns;//预留2个字节为PPP的地址域和信息域pp = skb_push(skb, 2);//填充头部的子协议域proto = npindex_to_proto[npi];pp[0] = proto >> 8;pp[1] = proto;//暂停发送软中断的调度netif_stop_queue(dev);//将报文放入PPP单元对象的发送队列skb_queue_tail(&ppp->file.xq, skb);//进行报文传送ppp_xmit_process(ppp);//该函数在上面已经详细分析过,主要是检测当前PPP单元对象是否有悬挂的待//发送报文,同时检测当前PPP单元对象是否有关联的PPP通道对象,如果都//存在,则使用PPP通道对象对应的WAN接口设备将悬挂的报文发送出去。ppp_push(ppp);//当前PPP单元对象已经没有悬挂的报文,但PPP单元对象的发送队列有报文//,则将发送队列的报文进行特定处理后(主要涉及报文压缩相关,未对PPP压缩//机制进行分析),放到PPP单元对象的悬挂指针上,同时再次调用ppp_push//将报文发出。while (ppp->xmit_pending == 0 && (skb = skb_dequeue(&ppp->file.xq)) != 0)ppp_send_frame(ppp, skb);//当前PPP单元对象已经没有悬挂的报文,同时PPP单元对象的发送队列已空,//则重新启动发送软中断的调度。if (ppp->xmit_pending == 0 && skb_peek(&ppp->file.xq) == 0)netif_wake_queue(ppp->dev);六、接收WAN侧的PPPoE发现报文1、所有PPPoE发现报文的接收处理都是靠应用层PPPD创建的PF_PACKET协议族套接口进行接收处理,PF_PACKET套接口的实现机制可以参考《网卡驱动收包》。备注:原版应用层PPPD在发现阶段完成后,进入会话阶段就不再使用PF_PACKET协议族套接口处理任何PPPoE发现报文了,在会话阶段的PADT发现报文是靠内核侧使用dev_add_pack添加的指定报文接收处理函数来处理。当前BCM方案在此做了修改,不管发现阶段、还是会话阶段,应用层PPPD都使用PF_PACKET协议族套接口对发现报文进行接收处理。2、另外内核侧也同时使用dev_add_pack添加的指定报文接收处理函数对发现报文集中的PADT报文进行处理,当前仅分析这种流程,第一种流程麻烦读者自行分析吧。在内核PPPoE模块的pppoe_init中注册了二层PPPoE发现报文的接收处理函数为pppoe_disc_rcv,当从WAN侧收到以太网负载类型为0x8863的PPPoE发现报文后就会触发该函数的调用,具体细节可以参考《网卡驱动收包》pppoe_disc_rcv//报文长度的合法性判断if (!pskb_may_pull(skb, sizeof(struct pppoe_hdr)))goto abort;//检测如果报文共享了,则进行复制一份if (!(skb = skb_share_check(skb, GFP_ATOMIC)))goto out;//这里仅处理发现报文集中的PADT报文,其它丢弃ph = (struct pppoe_hdr *) skb->nh.raw;if (ph->code != PADT_CODE)goto abort;//根据PPPoE服务器地址及PPPoE会话ID,取得之前应用层PPPD触发创建的//PPP通道相关的ppp套接口对象po = get_item((unsigned long) ph->sid, eth_hdr(skb)->h_source);if (po)struct sock *sk = sk_pppox(po);//当前如果没有被用户侧锁定,则将套接口状态置为PPPOX_ZOMBIEif (sock_owned_by_user(sk) == 0)//修改套接口状态,如果后续LAN侧有报文路由到PPPoE虚拟接口,//当检测到这个套接口的状态为非PPPOX_CONNECTED时,会将报//文直接丢弃。sk->sk_state = PPPOX_ZOMBIE;sock_put(sk);kfree_skb(skb);return NET_RX_SUCCESS;七、接收WAN侧PPPoE会话报文(含常见的LCP、PAP、CHAP、IPCP、IPv4等)在内核PPPoE模块的pppoe_init中注册了二层PPPoE会话报文的接收处理函数为pppoe_rcv,当从WAN侧收到以太网负载类型为0x8864的PPPoE会话报文后就会触发该函数的调用,具体细节可以参考《网卡驱动收包》pppoe_rcv//报文长度的合法性判断if (!pskb_may_pull(skb, sizeof(struct pppoe_hdr)))goto drop;//检测如果报文共享了,则进行复制一份if (!(skb = skb_share_check(skb, GFP_ATOMIC)))goto out;ph = (struct pppoe_hdr *) skb->nh.raw;//根据PPPoE服务器地址及PPPoE会话ID,取得之前应用层PPPD触发创建的//PPP通道相关的ppp套接口对象po = get_item((unsigned long) ph->sid, eth_hdr(skb)->h_source);//进行套接口的报文接收sk_receive_skb(sk_pppox(po), skb, 0);//过滤失败,丢弃if (sk_filter(sk, skb))goto discard_and_relse;skb->dev = NULL;//假设当前用户侧没有对该套接口进行锁定if (!sock_owned_by_user(sk))//当前PPPoE套接口的存储回调为pppoe_rcv_coresk->sk_backlog_rcv(sk, skb);pppoe_rcv_corestruct pppox_sock *po = pppox_sk(sk);//通常应用层PPPD在将PPP单元对象与PPP通道对象关联后,//ppp套接口的状态即为PPPOX_BOUNDif (sk->sk_state & PPPOX_BOUND)struct pppoe_hdr *ph = (struct pppoe_hdr *) skb->nh.raw;int len = ntohs(ph->length);//将skb的data指针向下移动,跳过PPPoE头(即剥离//PPPoE头),同时重新计算校验和skb_pull_rcsum(skb, sizeof(struct pppoe_hdr));//如果当前skb的len小于PPPoE头部记载的长度,则将skb//的tail调整为skb->data+len,即校正为PPPoE头部记载的//长度。pskb_trim_rcsum(skb, len)ppp_input(&po->chan, skb);struct channel *pch = chan->ppp;//获取当前报文的子协议proto = PPP_PROTO(skb);//在这个点的会话报文接收开始出现分支,如下条件的报//文都放入PPP通道对象的接收队列。//1、PPP通道与PPP单元对象已经分离//2、子协议号大于0xc000的子协议,常用的包括LCP、//PAP、CHAP。//3、压缩控制协议相关的报文if (pch->ppp == 0 || proto >= 0xc000 || proto == PPP_CCPFRAG)//报文放入PPP通道对象的接收队列。skb_queue_tail(&pch->file.rq, skb);//如果溢出,则丢掉开头的报文。while (pch->file.rq.qlen > PPP_MAX_RQLEN&& (skb = skb_dequeue(&pch->file.rq)) != 0)kfree_skb(skb);//唤醒在PPP通道对象相关的设备文件描述符上等待//接收的用户进程,通常为PPPD。对于PPPD怎么读//取在上面已经分析过,这里不再详细描述。wake_up_interruptible(&pch->file.rwait);//其它报文走此流程。(如IPCP、IPv4数据报文)else//PPP单元对象进行接收处理ppp_do_recv(pch->ppp, skb, pch);ppp_receive_frame(ppp, skb, pch);if (PPP_PROTO(skb) == PPP_MP)//多链路机制暂不分析ppp_receive_mp_frame(ppp, skb, pch);else//PPP单元对象进行非多链路报文//接收处理,下面单独分析。ppp_receive_nonmp_frame(ppp, skb);else if (sk->sk_state & PPPOX_RELAY)//暂不分析中继代理相关实现//其它情况则走标准的套接口接收流程,即将报文放在套接口//的接收队列,同时唤醒阻塞等待的进程进行接收。elsesock_queue_rcv_skb(sk, skb)//释放套接口对象的引用sock_put(sk);return rc;----------------------------------------------------------------------------------------------------------------------//常用的IPCP、IPv4数据报文,在单链路模式下,都使用该函数进行接收处理。ppp_receive_nonmp_frame(ppp, skb)//压缩/解压缩相关机制,暂不分析if (ppp->rc_state != 0 && (ppp->rstate & SC_DECOMP_RUN)&& (ppp->rstate & (SC_DC_FERROR | SC_DC_ERROR)) == 0)skb = ppp_decompress_frame(ppp, skb);if (ppp->flags & SC_MUST_COMP && ppp->rstate & SC_DC_FERROR)goto err;//获取PPP子协议号proto = PPP_PROTO(skb);switch (proto)case PPP_VJC_COMP://VJ压缩相关协议暂不分析case PPP_VJC_UNCOMP://VJ压缩相关协议暂不分析case PPP_CCP://压缩控制协议相关,暂不分析++ppp->stats.rx_packets;ppp->stats.rx_bytes += skb->len - 2;//根据PPP子协议号转为以太网负载协议号,当前常用的有IPV4、IPV6,这里没有//IPCP,所以如果收到IPCP报文,则npi为-EINVAL。npi = proto_to_npindex(proto);//当在上面转换子协议失败(如收到常见的IPCP报文),则走此流程,将报文放入//PPP单元对象的接收队列,之后由应用层的PPPD进行收取,上面已经分析过,当前//不再详细分析。if (npi < 0)//放入PPP单元对象的接收队列skb_queue_tail(&ppp->file.rq, skb);//从开头丢弃溢出的报文while (ppp->file.rq.qlen > PPP_MAX_RQLEN&& (skb = skb_dequeue(&ppp->file.rq)) != 0)kfree_skb(skb);//唤醒在PPP单元对象相关的设备描述符上准备接收的进程。wake_up_interruptible(&ppp->file.rwait);else//过滤器处理4个字节,当前PPP头的protocol字段仅有2个字节,这里再扩冲//2个字节,后面处理没有通过,则还会进行恢复。*skb_push(skb, 2) = 0;//如果应用层通过IOCTL PPPIOCSPASS设置了过滤器,则进行对应处理。if (ppp->pass_filter&& sk_run_filter(skb, ppp->pass_filter,ppp->pass_len) == 0)kfree_skb(skb);return;//如果应用层通过IOCTL PPPIOCSACTIVE设置了过滤器,则进行对应处理。if (!(ppp->active_filter&& sk_run_filter(skb, ppp->active_filter,ppp->active_len) == 0))ppp->last_recv = jiffies;//在上面为了进行过滤处理进行了扩冲2字节,这里进行还原。skb_pull(skb, 2);//如果当前PPP单元对象关联的虚拟PPPoE接口没有UP,或者当前PPP子协议//模式不允许通过,则将报文删除。if ((ppp->dev->flags & IFF_UP) == 0 || ppp->npmode[npi] != NPMODE_PASS)kfree_skb(skb);//剥离PPP头,同时重新计算校验和kb_pull_rcsum(skb, 2);//将skb的接收设备修改为PPPoE虚拟接口skb->dev = ppp->dev;//协义号为IPV4skb->protocol = htons(npindex_to_ethertype[npi]);skb->mac.raw = skb->data;//再次进行网络接收处理,当前接收设备已经替换为PPPoE虚拟接口,并且报//文已经剥离了PPPoE及PPP,按正常接收流程处理。可以参考《网卡驱动收包》netif_rx(skb);//更新最后接收时间,当前应用层的按需拨号机制,空闲断开超时就是靠读取//内核的接收、发送时间来实现的。ppp->dev->last_rx = jiffies;


八、PPPoE关键对象

1 0
原创粉丝点击