怀疑vtun就是将虚拟网卡中的数据读出,然后新建socket发送该数据的。
从上一篇看到了数据包的发送和接收是通过调用 proto_write proto_read两个函数实现的,那么我们就来看这两个函数,进一步了解vtun是如何封装和解封也即发送接收数据的。
一、proto_write封装分析
proto_write是函数指针,在tunnel.c中:
proto_write = udp_write;
proto_read = udp_read;
先看udp_write函数,udp_proto.c中:
/* Functions to read/write UDP frames. */
int udp_write(int fd, char *buf, int len)
{
register char *ptr;
register int wlen;
if (!is_rmt_fd_connected) return 0;
ptr = buf - sizeof(short);
*((unsigned short *)ptr) = htons(len);
len = (len & VTUN_FSIZE_MASK) + sizeof(short);
while( 1 )
{
if( (wlen = write(fd, ptr, len)) < 0 )
{
if( errno == EAGAIN || errno == EINTR )
continue;
if( errno == ENOBUFS )
return 0;
}
/*
* Even if we wrote only part of the frame
* we can't use second write since it will produce
* another UDP frame
*/
return wlen;
}
}//end udp_write
先来看发送的的数据是什么:
经过分析知道buf是从虚拟网卡读出的数据,即截获到的数据包。
看黑体字部分,并不是将buf直接发送,而是进行了处理:
这里要想搞清楚ptr,首先得搞清楚len,
回到linkfd.c,
len与udp_write有关的被赋值的地方,
if( (len=lfd_run_down(len,buf,&out)) == -1 )
break;
下面分析lfd_run_down返回值,
linkfd.c中,
/* Run modules down (from head to tail) */
inline int lfd_run_down(int len, char *in, char **out)
{
register struct lfd_mod *mod;
*out = in;
for(mod = lfd_mod_head; mod && len > 0; mod = mod->next )
if( mod->encode )
{
len = (mod->encode)(len, in, out);
in = *out;
}
return len;
}
再看encode函数,在linkfd.h中,
/* Module */
struct lfd_mod {
char *name;
int (*alloc)(struct vtun_host *host);
int (*encode)(int len, char *in, char **out);
int (*avail_encode)(void);
int (*decode)(int len, char *in, char **out);
int (*avail_decode)(void);
int (*free)(void);
struct lfd_mod *next;
struct lfd_mod *prev;
};
这里涉及到加密模块,找不到encode函数的定义,因此无法确定len到底是什么,但是可以肯定,如果mod->encode返回0,即没有加密?那么len在lfd_run_down调用前后不变。
那就要看lfd_run_down之前的len,
if( (len = dev_read(fd2, buf, VTUN_FRAME_SIZE)) < 0 )
而正常情况下,dev_read就是从虚拟网卡读出的字节数。因此len就是截获的数据包长度(字节数)。
再回头分析udp_write函数中的ptr:
/* Functions to read/write UDP frames. */
int udp_write(int fd, char *buf, int len)
{
register char *ptr;
register int wlen;
if (!is_rmt_fd_connected) return 0;
ptr = buf - sizeof(short);
*((unsigned short *)ptr) = htons(len);
len = (len & VTUN_FSIZE_MASK) + sizeof(short);
while( 1 )
{
if( (wlen = write(fd, ptr, len)) < 0 )
{
if( errno == EAGAIN || errno == EINTR )
continue;
if( errno == ENOBUFS )
return 0;
}
/*
* Even if we wrote only part of the frame
* we can't use second write since it will produce
* another UDP frame
*/
return wlen;
}
}//end udp_write
ptr应该是则buf的基础上增加了两个字节,这两个字节是截获到的数据包的长度!
所以发送的数据包长度并不是原始的数据包长度,而是加了两个字节。
vtun的封装思想和我原先预想的如出一辙,就是新建socket将截获到的数据作为数据发送!!!
那么write进虚拟网卡转发不出去的问题的解决思路应该为,看源码在写进虚拟网卡后做了什么事情,或者是读写冲突,或者进行了什么样的配置!
二、proto_read 解封分析
我们只分析udp_read,在linkfd.c中定义,
int udp_read(int fd, char *buf)
{
unsigned short hdr, flen;
struct iovec iv[2];
register int rlen;
struct sockaddr_in from;
socklen_t fromlen = sizeof(struct sockaddr);
/* Late connect (NAT hack enabled) */
if (!is_rmt_fd_connected)
{
while( 1 )
{
if( (rlen = recvfrom(fd,buf,2,MSG_PEEK,(struct sockaddr *)&from,&fromlen)) < 0 ){
if( errno == EAGAIN || errno == EINTR ) continue;
else return rlen;
}
else break;
}
if( connect(fd,(struct sockaddr *)&from,fromlen) )
{
vtun_syslog(LOG_ERR,"Can't connect socket");
return -1;
}
is_rmt_fd_connected = 1;
}
/* Read frame */
iv[0].iov_len = sizeof(short);
iv[0].iov_base = (char *) &hdr;
iv[1].iov_len = VTUN_FRAME_SIZE + VTUN_FRAME_OVERHEAD;
iv[1].iov_base = buf;
while( 1 )
{
//本函数的核心代码,将fd中的数据读出赋到iv中,iv[0]存接收到数据的前两个字节,也就是封装时添加的数据包长度那两个字节;
//iv[1]就是解封后的数据,buf指向的是iv[1]部分,即buf就是解封后的数据!
if( (rlen = readv(fd, iv, 2)) < 0 )
{
if( errno == EAGAIN || errno == EINTR )
continue;
else
return rlen;
}
hdr = ntohs(hdr);
flen = hdr & VTUN_FSIZE_MASK;
if( rlen < 2 || (rlen-2) != flen )
return VTUN_BAD_FRAME;
return hdr;
}
}//end udp_read
我们看到iv是接收到的数据,iv[1]是解封后的数据,buf是解封后的数据。