vtun 对虚拟网卡的读写操作
一、对虚拟网卡的读写操作都在哪里?
对虚拟网卡写操作函数tun_write在tun_dev.c中定义;
函数指针dev_write在tunnel.c中指向tun_write函数;
函数指针dev_write在linkfd.c中对虚拟网卡进行写操作。
因此实际对虚拟网卡的写操作在linkfd.c中。
涉及到写操作的linkfd.c中的代码:
if( len && dev_write(fd2,out,len) < 0 ){
if( errno != EAGAIN && errno != EINTR )
break;
else
continue;
}
简单的理解dev_write函数是向虚拟网卡fd2中写入out数据。
二、dev_write(fd2,out,len) 中fd2 是啥?
那么我们就搞清楚fd2 out len这三个参数到底是什么!尤其是fd2和out!
猜测fd2是虚拟设备文件描述符,out是截获到的数据?
1、在linkfd.c中有如下定义:
int fd2 = lfd_host->loc_fd;
猜测:loc应该是local的缩写,loc_fd应该是本地的描述符?
struct vtun_host *lfd_host;
vtun_host结构体都是在vtun.h中定义的。
到此,貌似陷入死局了!已经找到了fd2对应的最原始变量lfd_host,但lfd_host到底是哪个设备文件的描述符?!应该是host的虚拟网卡设备描述符,但是根源在哪里,也就是说,在哪里将host的设备描述符赋予lfd_host的?!
请相信,山重水复疑无路!注意到lfd_host的定义上面有两行注释,相当重要:
/* Host we are working with.* Used by signal handlers that's why it is global.
*/
也就是说lfd_host是信号处理函数调用的,是全局变量!
全局变量就说明其他地方可以使用该变量!那么在哪里被使用的呢?
于是查找lfd_host出现的地方,发现了依然子linkfd.c中定义这么一句:
lfd_host=host
int linkfd(struct vtun_host *host){…}
好了,下面就看linkfd函数在哪里被调用了,即host获取的是谁的值。
找啊找 。。。
找到了,在tunnel.c的函数int tunnel(struct vtun_host *host)中出现了:
opt = linkfd(host);
尼玛,这里的host又是啥!看host的定义处:
int tunnel(struct vtun_host *host){...}
我去,又得看tunnel在哪被调用的,继续找!
找到了,在client.c和server.c中都有调用,先看client.c:
client_term = tunnel(host);
再看此处的host定义的地方:
void client(struct vtun_host *host){…}
哎,又要找client被调用的地方了。。。。
我去,终于有点眉目了,在main.c中出现了!
client(host);
好,再看此处host的定义:
struct vtun_host *host = NULL;
初始值是空,那么我们看host被赋值的地方:
依然在main.c中,
hst = argv[optind++];//vtund server [ip]的第二个参数给hst
if( !(host = find_host(hst)) )
{
vtun_syslog(LOG_ERR,"Host %s not found in %s", hst, vtun.cfg_file);
exit(1);
}
好了,下面就看find_host(hst)的返回值了!
找到find_host()函数定义的地方:
在vtun.h中,有原型说明,
struct vtun_host * find_host(char *host);
find_host在cfg_file.y文件中定义
/* Find host in the hosts list.
* NOTE: This function can be called only once since it deallocates hosts list.
*/
inline struct vtun_host* find_host(char *host)
{
return (struct vtun_host *)llist_free(&host_list, free_host, host);
}
host_list是一个llist型的全局变量,其值是从配置文件中获取的。
下面分析llist_free函数,
在llist.c中定义:
/* Travel list from head to tail, deallocate each element */
void * llist_free(llist *l, int (*f)(void *d, void *u), void *u)
{
llist_elm *i = l->head, *n;
void *ff = NULL;
while( i )
{
n = i->next;
if( f(i->data,u) )
ff = i->data;
else
free(i);
i = n;
}
l->head = l->tail = NULL;
return ff;
}
其中llist_elm在llist.h中定义:
struct llist_element {
struct llist_element * next;
void * data;
};
typedef struct llist_element llist_elm;
typedef struct {
llist_elm * head;
llist_elm * tail;
} llist;
llist_element是单项链表;typedef重定义了llist_element,所以llist_elm就是llist_element单项链表;而llist是一个自定义结构体类型名,有两个llist_elm成员head和tail。
下面分析llist_free函数
void * llist_free(llist *l, int (*f)(void *d, void *u), void *u)
{
llist_elm *i = l->head, *n; //定义两个节点指针i和n;初始化i
void *ff = NULL;
while( i )
{
n = i->next;
if( f(i->data,u) ) //简单理解为判断命令行host和配置文件中host是否相等。
ff = i->data;
else
free(i);
i = n;
}
l->head = l->tail = NULL;
return ff;
}
free_host在cfg_file.y中定义:
功能简单理解为判断命令行参数和配置文件中获取的参数是否相同。
int free_host(void *d, void *u)
{
struct vtun_host *h = d;
if (u && !strcmp(h->host, u))
return 1;
free(h->host);
free(h->passwd);
llist_free(&h->up, free_cmd, NULL);
llist_free(&h->down, free_cmd, NULL);
free_addr(h);
/* releases only host struct instances which were
* allocated in the case of K_HOST except default_host */
if( h->passwd )
free(h);
return 0;
}
综上所述,fd2来源于配置文件,但是配置文件中具体哪个参数与fd2有关?
那就要看host_list的值是什么了,因为host决定了fd2,而llist_free的返回值决定了host,而该返回值与host_list有关,所以要看host_list是啥!
下面分析host_list:
在cfg_file.y中定义了host_list全局变量:llist host_list;
有一段代码是给host_list赋值的:
'{' host_options '}'
{
/* Check if session definition is complete */
if (!parse_host->passwd) {
cfg_error("Ignored incomplete session definition '%s'", parse_host->host);
free_host(parse_host, NULL);
free(parse_host);
} else {
/* Add host to the list */
llist_add(&host_list, (void *)parse_host);
}
}
下面分析llist_add:
在llist.c中定义,
int llist_add(llist *l, void * d)
{
llist_elm *e;
if( !(e=malloc(sizeof(llist_elm))) )
return -1;
if( !l->head )
l->head = l->tail = e;
else
l->tail->next = e;
l->tail = e;
e->next = NULL;
e->data = d;
return 0;
}
llist_add函数就是把节点d增加到链表l上。
那么parse_host是什么?
在cfg_file.y中定义,
struct vtun_host *parse_host;
cfg_file.y中对parse_host进行了赋值,这部分是提取操作,不是很懂。。。
找到了vtun_host成员passwd等等在此处获取值了,但是没有找到loc_fd!!!!
重新回顾思路,终于发现问题啦!
在tunnel.c中已经对loc_fd进行赋值了!是赋完值再对将参数host传给linkfd函数的:
int fd[2]={-1, -1};//初试值,下面对fd进行了赋值
………
if( ! interface_already_open )
{
switch( host->flags & VTUN_TYPE_MASK ){
case VTUN_TTY:
if( (fd[0]=pty_open(dev)) < 0 )
{
vtun_syslog(LOG_ERR,"Can't allocate pseudo tty. %s(%d)", strerror(errno), errno);
return -1;
}
break;
case VTUN_PIPE:
if( pipe_open(fd) < 0 )
{
vtun_syslog(LOG_ERR,"Can't create pipe. %s(%d)", strerror(errno), errno);
return -1;
}
break;
case VTUN_ETHER:
if( (fd[0]=tap_open(dev)) < 0 )
{
vtun_syslog(LOG_ERR,"Can't allocate tap device %s. %s(%d)", dev, strerror(errno), errno);
return -1;
}
break;
case VTUN_TUN:
if( (fd[0]=tun_open(dev)) < 0 )
{
vtun_syslog(LOG_ERR,"Can't allocate tun device %s. %s(%d)", dev, strerror(errno), errno);
return -1;
}
break;
}//end switch
…………
host->loc_fd = fd[0];
………
opt = linkfd(host);
最新总结,调用dev_write(fd2,out,len) 时fd2在tunnel.c中定义,而fd2就是tun_open(dev)返回的设备描述符!
下面分析tun_open函数:
我们来看generic文件夹下的定义,
int tun_open(char *dev)
{
char tunname[14];
int i, fd;
if( *dev ) {
sprintf(tunname, "/dev/%s", dev);
return open(tunname, O_RDWR);
}
for(i=0; i < 255; i++){
sprintf(tunname, "/dev/tun%d", i);
/* Open device */
if( (fd=open(tunname, O_RDWR)) > 0 ){
sprintf(dev, "tun%d", i);
return fd;
}
}
return -1;
}
找到打开虚拟设备根源了就是open(tunname, O_RDWR),tunname是虚拟设备名,O_RDWR是以读写方式打开。
这里tunname可以从配置文件中获取:
tunnel.c中有下面代码可以说明:
/* Initialize device. */
if( host->dev )
{
strncpy(dev, host->dev, VTUN_DEV_LEN);
dev[VTUN_DEV_LEN-1]='\0';
}
也可以是默认的即
从sprintf(tunname, "/dev/tun%d", i);可以看出,默认为tun0一直到tun255.
最终总结:在tunnel.c定义了fd2,fd2就是虚拟网卡的设备描述符。且tunnel.c对虚拟网卡进行了打开操作。
对虚拟网卡的操作基本清楚了,
1、tunnel.c调用tun_open打开虚拟网卡;
2、实际的打开动作在generic文件中,generic中使用系统函数open打开虚拟网卡,返回虚拟设备描述符;
3、tunnel.c中,获取描述符,该描述符被赋予变量host的成员loc_fd中;
4、linkfd.c中,调用dev_write dev_read对虚拟网卡进行读写操作。
三、dev_write(fd2,out,len) 中out 是啥?
我们回到dev_write函数被调用的地方:
linkfd.c中的一段代码,
if( len && dev_write(fd2,out,len) < 0 ){
if( errno != EAGAIN && errno != EINTR )
break;
else
continue;
}
if( (len=lfd_run_up(len,buf,&out)) == -1 )
break;
if( len && dev_write(fd2,out,len) < 0 )
{
if( errno != EAGAIN && errno != EINTR )
break;
else
continue;
}
lfd_run_up简单的理解是buf数据给out了。
那么buf是什么?找到下面代码:
if( (len=proto_read(fd1, buf)) <= 0 )
可以看出buf是接收到的数据。
综述所述,dev_write就是把接收到的数据写进虚拟网卡。