vtun 数据包的封装和解封

怀疑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是解封后的数据。

posted @ 2012-09-22 22:45  helloweworld  阅读(573)  评论(0编辑  收藏  举报