zoukankan      html  css  js  c++  java
  • 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是解封后的数据。

  • 相关阅读:
    DC综合流程
    DC set_tcl脚本配置
    同步FIFO设计
    顺序脉冲 发生器
    状态机的写法
    verilog串并转换
    indexOf()
    jQuery 效果
    jQuery 事件
    jQuery css
  • 原文地址:https://www.cnblogs.com/helloweworld/p/2698528.html
Copyright © 2011-2022 走看看