Broadcom方案PPPoE实现分析

一、用户程序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/null
if ((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、IPCP
for (i = 0; (protp = protocols[i]) != NULL; ++i)
(*protp->init)(0);

//lcp子协议初始化
lcp_init
fsm *f = &lcp_fsm[unit];
lcp_options *wo = &lcp_wantoptions[unit];
lcp_options *ao = &lcp_allowoptions[unit];

f->unit = unit;		//0
f->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;	//10
f->maxtermtransmits = DEFMAXTERMREQS;		//2
f->maxnakloops = DEFMAXNAKLOOPS;			//5
f->term_reason_len = 0;

BZERO(wo, sizeof(*wo));
wo->neg_mru = 1;
wo->mru = DEFMRU;		//1500
wo->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;		//16384
ao->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_init
upap_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;	//3
u->us_maxtransmits = 10;
u->us_reqtimeout = UPAP_DEFREQTIME;		//30

	//chap子协议初始化
ChapInit
chap_state *cstate = &chap[unit];

BZERO(cstate, sizeof(*cstate));
cstate->unit = unit;
cstate->clientstate = CHAPCS_INITIAL;
cstate->serverstate = CHAPSS_INITIAL;
cstate->timeouttime = CHAP_DEFTIMEOUT;		//3
cstate->max_transmits = CHAP_DEFTRANSMITS;	//10

//ipcp子协议初始化
ipcp_init
fsm *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;					//3
f->maxconfreqtransmits = DEFMAXCONFREQS;	//10
f->maxtermtransmits = DEFMAXTERMREQS;		//2
f->maxnakloops = DEFMAXNAKLOOPS;			//5
f->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_relock
np->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 idleDisconnectTime
parse_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_channel
the_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_options
if (the_channel->process_extra_options)
(*the_channel->process_extra_options)();
//如果存在/etc/ppp/options.设备名,则从该文件中设置配置选项
pppoe_extra_options
//buf = /etc/ppp/options.epon0
snprintf(buf, 256, _PATH_ETHOPT "%s",devnam);
options_from_file(buf, 0, 0, 1)

//当前pppoe_channel的回调为NULL
if (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);	//0
tdb.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);

//设置自身环境变量,记载进程ID
slprintf(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.2
if ((p = strchr(req_name, '.')) != NULL)
//num[0] = 1
//num[2] = 2
sscanf(&(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_ioctl
x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
//内核代码
ppp_ioctl
pf = file->private_data;

//当前还没有关联ppp私有文件信息
if (pf == 0)
ppp_unattached_ioctl(pf, file, cmd, arg);
switch (cmd)
case PPPIOCNEWUNIT:
//将用户侧传入的单元ID存储到p
get_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;	//INTERFACE
skb_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的接口设备名也基于单元ID
ppp->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驱动控制块的flags
switch (cmd)
case PPPIOCSFLAGS:
get_user(val, p)
ppp_lock(ppp);
cflags = ppp->flags & ~val;
ppp->flags = val & SC_FLAG_BITS;
ppp_unlock(ppp);

//设置内核调试等级,对应的内核函数为ppp_ioctl
set_kdebugflag(kdebugflag);
ioctl(ppp_dev_fd, PPPIOCSDEBUG, &requested_level)
//内核函数
ppp_ioctl
ppp = 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_pppoe
the_channel->send_config((mtu), (accm), (pc), (acc));
//设置MTU
send_config_pppoe
sock = 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_pppoe
the_channel->recv_config((mtu), (accm), (pc), (acc))
//函数没有代码处理
recv_config_pppoe

//检查各子协议模块是否有对应的按需拨号配置回调,当前常用的LCP、
//PAP、CHAP、IPCP四个子协议只有IPCP含有该回调函数,IPCP的回调
//函数为ip_demand_conf
for (i = 0; (protp = protocols[i]) != NULL; ++i)
if (protp->enabled_flag && protp->demand_conf != NULL)
protp->demand_conf(0)
//IPCP的回调函数
ip_demand_conf
wo = &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.255
if (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接口up
sifup(u)
//设置当前pppoe接口的flags为UP
strlcpy(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_IP
npi.mode     = mode;		//NPMODE_QUEUE
ioctl(ppp_dev_fd, PPPIOCSNPMODE, (caddr_t) &npi)
//内核代码
ppp_ioctl
ppp = 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侧是否UP
if (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_DORMANT
new_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_packet
len = 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;
else
pend_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_SERIALCONN
new_phase(PHASE_SERIALCONN);

//触发通道对象的connect回调,当前通道对象为pppoe_channel,对应回调为
//connect_pppoe_ses
devfd = 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_data
get_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;	//8
return ses->fd;

strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam));
//临时增大接收BUF
rcvbuf=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_disc
memset(&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的
//TAG
if(ses->filt->ntag)
ses->curr_pkt.tags[TAG_AC_NAME]=ses->filt->ntag;

//如果当前含有服务名参数,则填充SRV_NAME的tag
if(ses->filt->stag)
ses->curr_pkt.tags[TAG_SRV_NAME]=ses->filt->stag;

//如果含有主机标签参数,则填充HOST_UNIQ的tag
if(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_pado
case 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类型的套接口地址,这里最重要
//的是从服务器得到了分配的会话ID
ses->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);

//恢复接收BUF
rcvbuf=256;
setsockopt(disc_sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, 
sizeof(rcvbuf));

//调用系统接connect接口,对应内核函数为pppoe_connect
connect(ses->fd, (struct sockaddr*)&ses->sp,sizeof(struct sockaddr_pppox));
//内核接口
pppoe_connect
struct 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会话阶段已经从服务器获取到会话ID
if (sp->sa_addr.pppoe.sid != 0)
//获PPPoE关联的真实设备,比如当前为epon0.2
dev = 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;	//CHANNEL
skb_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_ppp
fd_ppp = the_channel->establish_ppp(devfd);
generic_establish_ppp
//当前假设使用新的PPP驱动类型,则new_style_driver为true,暂不
//分析老的PPP驱动类型相关流程。

//获取刚才创建的通道索引,对应内核代码为pppox_ioctl
ioctl(fd, PPPIOCGCHAN, &chindex)
//内核代码
pppox_ioctl
struct 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/ppp
fd = open("/dev/ppp", O_RDWR);

//将当前PPP驱动控制块与通道进行绑定
ioctl(fd, PPPIOCATTCHAN, &chindex)
//内核代码
ppp_ioctl
struct 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_ioctl
struct 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_pppoe
sock = 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_pppoe
if (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_lowerup
timeout(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;
else
ao->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_resetci
if( f->callbacks->resetci )
//复位配置信息
lcp_resetci
wo = &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请求,设置重传次数,生成请求ID
if( !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_cilen
go = &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_addci
go = &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,
......
else
cilen = 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状态机迁为REQSENT
f->state = REQSENT;

status = EXIT_NEGOTIATION_FAILED;

//主阶段迁为PHASE_ESTABLISH
new_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;
//出错则返回0
if (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_TERMINATE
if (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_DEAD
link_terminated(f->unit);
else
//执行正常的链路关闭流程,给远端发送LCP TERMREQ终止
//请求报文,待远端回应后或超时后,再将阶段迁为
//PHASE_DEAD
fsm_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_ppp
the_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_release
close(ppp_fd);
//内核代码
ppp_release
struct 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_ioctl
struct 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;
//否则进行底层断开事件处理
else
fsm_lowerdown(&lcp_fsm[unit]);

//非按需拨号,则清除IFNAME环境变量
if (!demand)
script_unsetenv("IFNAME");

disconnect:

//阶段状态迁为PHASE_DISCONNECT
new_phase(PHASE_DISCONNECT);

//断开连接PPPoE会话
the_channel->disconnect();
disconnect_pppoe_ses
session_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_connect
struct 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 = 0
po->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_HOLDOFF
new_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比特位全部置0
zero_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为1
set = ~(~0UL << (n & (__NFDBITS-1)));

//计算出最大描述符可以被整除的个数,假设当前n为33,则整除后n=1
n /= __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:
//计算最大单元比特位的数值。
do
max++;
set >>= 1;
while (set);

//只需要得出最大单元比特位数值,后续都按满比特位值计算。
max += n * __NFDBITS;

return max;

n = retval;

poll_initwait(&table);
init_poll_funcptr(&pwq->pt, __pollwait);
pt->qproc = qproc;		//__pollwait
pwq->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++;

//如果需要,暂时让出CPU
cond_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;

do
entry--;
free_poll_entry(entry);
while (entry > p->entries);

old = p;
p = p->next;
free_page((unsigned long) old);

//返回错误
if (ret < 0)
goto out;

//在没有错误的情况下,需要检测是否有未决的信号处理,如果有则返回
//ERESTARTNOHAND,调用该函数的点会将该错误类型转换为EINTR
if (!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表明有未决的信号处理,修正错误值为EINTR
if (ret == -ERESTARTNOHAND)
ret = -EINTR;
---------------------------------------------------------------------------------------------------------------------
//dev/ppp驱动模块的poll回调函数
ppp_poll
struct ppp_file *pf = file->private_data;

//此时PPP驱动模块还没有建立pppoe接口,这里pf将会是0
if (pf == 0)
return 0;

//将当前进程加入到PPP驱动控制块的rwait等待队列中,后续当从LAN侧发送的
//报文路由到pppoe接口时,pppoe接口的发送函数会唤醒rwait等待队列。
poll_wait(file, &pf->rwait, wait);
//这里的轮询表的qproc回调是在上面poll_initwait中设置的,当前为__pollwait
p->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_xmit
ppp_start_xmit
//获取ppp驱动控制块
struct ppp *ppp = (struct ppp *) dev->priv;

//进行报文以太网下一协议标识映射,假设当前是ipv4报文,则为NP_IP
npi = 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;	
else
kfree_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  32
if (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_read
struct 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;

//当前为非阻塞模式,并且没有数据,则返回EAGAIN
if (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_write
struct 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_xmit
r = pch->chan->ops->start_xmit(pch->chan, skb)
pppoe_xmit
sk = (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);
else
skb2 = 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;
//当前通道对象没有注册,则直接丢弃报文。
else
skb_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_write
struct 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_xmit

ppp_start_xmit
struct ppp *ppp = (struct ppp *) dev->priv;
//将以太网负载类型转换为PPP子协议类型,当前ipv4会转为NP_IP
npi = 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头,则重新分配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;

//预留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_ZOMBIE
if (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_core
sk->sk_backlog_rcv(sk, skb);
pppoe_rcv_core
struct pppox_sock *po = pppox_sk(sk);

//通常应用层PPPD在将PPP单元对象与PPP通道对象关联后,
//ppp套接口的状态即为PPPOX_BOUND
if (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)
//暂不分析中继代理相关实现
//其它情况则走标准的套接口接收流程,即将报文放在套接口
//的接收队列,同时唤醒阻塞等待的进程进行接收。
else
sock_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;
//协义号为IPV4
skb->protocol = htons(npindex_to_ethertype[npi]);
skb->mac.raw = skb->data;

//再次进行网络接收处理,当前接收设备已经替换为PPPoE虚拟接口,并且报
//文已经剥离了PPPoE及PPP,按正常接收流程处理。可以参考《网卡驱动收包》
netif_rx(skb);

//更新最后接收时间,当前应用层的按需拨号机制,空闲断开超时就是靠读取
//内核的接收、发送时间来实现的。
ppp->dev->last_rx = jiffies;


八、PPPoE关键对象

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值