zoukankan      html  css  js  c++  java
  • vtun 源码中nat什么意思

    在main函数中,有一行,

    clear_nat_hack_flags(svr);

    在cfg_file.y中定义,

    /* Clear the VTUN_NAT_HACK flag which are not relevant to the current operation mode */
    inline void clear_nat_hack_flags(int svr)
    {
        if (svr)
            llist_trav(&host_list,clear_nat_hack_server,NULL);
        else
            llist_trav(&host_list,clear_nat_hack_client,NULL);
    }

    先看svr=1,也就是server端的情况,

    llist_trav(&host_list,clear_nat_hack_server,NULL);

    在llist.c中,

    /* Travel list from head to tail */
    void * llist_trav(llist *l, int (*f)(void *d, void *u), void *u)
    {
        llist_elm *i = l->head;

        while( i ){
           if( f(i->data,u) ) return i->data;
           i = i->next;
        }
        return NULL;
    }

    为搞清l是什么,需搞清host_list是什么,

    经分析host_list是配置文件中所有会话名。

    再看clear_nat_hack_server函数,

    int clear_nat_hack_server(void *d, void *u)
    {
        ((struct vtun_host*)d)->flags &= ~VTUN_NAT_HACK_CLIENT;

    //清除VTUN_NAT_HACK_CLIENT标志
        return 0;
    }

    其中

    /* Flags for the NAT hack with delayed UDP socket connect */

    #define VTUN_NAT_HACK_CLIENT    0x4000
    #define VTUN_NAT_HACK_SERVER    0x8000

    结合

    while( i ){
           if( f(i->data,u) ) return i->data;
           i = i->next;
        }
    可知 llist_trav(&host_list,clear_nat_hack_server,NULL);

    就是清除所有会话的nat_hack的flags.

     

     

    最终关于main函数中clear_nat_hack_flags(svr);的结论:

    当是server端时,清除所有会话的表示client的nat_hack的flags.

    当是client端时,清除所有会话的表示server的nat_hack的flags.(client端分析略)

    nat_hack的flags在server端和client端并不相同,因为

    #define VTUN_NAT_HACK_CLIENT    0x4000
    #define VTUN_NAT_HACK_SERVER    0x8000

    为什么要这样做呢?因为nat_hack只能在client和server一端使用,不能两端一起使用。

     

    那么又为什么只能在一端使用呢,看下面分析,

     

    http://permalink.gmane.org/gmane.network.vtun.devel/6这个maillist讨论了此问题,关于vtun中涉及nat的源码就是第一个发邮件的那个人写的,他说这个nat_hack只能用于一端。好像源码中关于nat的部分是为了解决——使用udp时client经过nat到达server时的连接问题,即隧道中有nat的问题。

    理解到此为止,那就看一下源码中的nat_hack到底做了何种操作,影响了谁,搞清楚源码做了什么,那么nat_hack也就好理解了。

    下面再分析源码中关于nat的代码。

    在linkfd.c中,

    /* Delay sending of first UDP packet over broken NAT routers
        because we will probably be disconnected.  Wait for the remote
        end to send us something first, and use that connection. */
        if (!VTUN_USE_NAT_HACK(lfd_host))
            proto_write(fd1, buf, VTUN_ECHO_REQ);

    在vtun.h中,

    #ifdef ENABLE_NAT_HACK

    /* Flags for the NAT hack with delayed UDP socket connect */
    #define VTUN_NAT_HACK_CLIENT    0x4000
    #define VTUN_NAT_HACK_SERVER    0x8000
    #define VTUN_NAT_HACK_MASK    (VTUN_NAT_HACK_CLIENT | VTUN_NAT_HACK_SERVER)

    #define VTUN_USE_NAT_HACK(host)    ((host)->flags & VTUN_NAT_HACK_MASK)
    #else
    #define VTUN_USE_NAT_HACK(host)    0
    #endif

    在ENABLE_NAT_HACK已经定义的情况下,

    #define VTUN_USE_NAT_HACK(host)    ((host)->flags & VTUN_NAT_HACK_MASK)

    该宏VTUN_USE_NAT_HACK(host)   到底是多少?

    容易得出VTUN_NAT_HACK_MASK是0xc000

    host->flags呢?host最终值从配置文件得来,根据文件cfg_file.y知host->flags是当配置文件中配置相关选项时,给flags赋予相应值,如下代码,

     

    | K_SPEED NUM     {
                  if( $2 ){
                     parse_host->spd_in = parse_host->spd_out = $2;
                     parse_host->flags |= VTUN_SHAPE;
                  } else
                     parse_host->flags &= ~VTUN_SHAPE;
                }

      | K_SPEED DNUM     {
                  if( yylval.dnum.num1 || yylval.dnum.num2 ){
                     parse_host->spd_out = yylval.dnum.num1;
                         parse_host->spd_in = yylval.dnum.num2;    
                     parse_host->flags |= VTUN_SHAPE;
                  } else
                     parse_host->flags &= ~VTUN_SHAPE;
                }

      | K_COMPRESS         {
                  parse_host->flags &= ~(VTUN_ZLIB | VTUN_LZO);
                }
                compress

      | K_ENCRYPT NUM     { 
                  if( $2 ){
                     parse_host->flags |= VTUN_ENCRYPT;
                     parse_host->cipher = $2;
                  } else
                     parse_host->flags &= ~VTUN_ENCRYPT;
                }

      | K_KALIVE         {
                  parse_host->flags &= ~VTUN_KEEP_ALIVE;
                }
                keepalive   

      | K_STAT NUM        {
                  if( $2 )
                     parse_host->flags |= VTUN_STAT;
                  else
                     parse_host->flags &= ~VTUN_STAT;
                }

      | K_PERSIST NUM     {
                        parse_host->persist = $2;

                  if(vtun.persist == -1)
                     vtun.persist = $2;    
                }

      | K_TYPE NUM         { 
                  parse_host->flags &= ~VTUN_TYPE_MASK;
                  parse_host->flags |= $2;
                }   

      | K_PROT NUM         { 
                  parse_host->flags &= ~VTUN_PROT_MASK;
                  parse_host->flags |= $2;
                }
      | K_NAT_HACK NUM     { 
    #ifdef ENABLE_NAT_HACK
                  parse_host->flags &= ~VTUN_NAT_HACK_MASK;
                  parse_host->flags |= $2;

    所以(host)->flags & VTUN_NAT_HACK_MASK相与的意思是——只要host设置了VTUN_NAT_HACK那个选项,(host)->flags & VTUN_NAT_HACK_MASK的结果就不为0.(很好理解,比如先定义好使用加密时的flags为0010,那么将flags和0010进行与运算,结果为0010则说明采用加密了,为0000则没使用。)

    而ENABLE_NAT_HACK的定义是在软件开始安装前就设置了,即在./configure的选项中就指明了是否nat_hack,在configure文件中,

    if test "$NATHACK" = "yes"; then
       cat >>confdefs.h <<\_ACEOF
    #define ENABLE_NAT_HACK 1
    _ACEOF

    fi

    之后nat_hack是否使用取决于配置文件(即若安装时没定义nat_hack,则后续不管配置文件是否对nat_hack进行配置,宏VTUN_USE_NAT_HACK(host) 的值始终为0;若安装时定义了nat_hack,宏VTUN_USE_NAT_HACK(host) 的值取决于配置文件中对nat_hack的配置)。

     

    回到linkfd.c

    if (!VTUN_USE_NAT_HACK(lfd_host))
            proto_write(fd1, buf, VTUN_ECHO_REQ);

    当proto_write=udp_write时,

    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);        //sizeof(short) = 2

        *((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

    结合netlib.c

    if (VTUN_USE_NAT_HACK(host))
             is_rmt_fd_connected=0;
         else
         {
             if( connect(s,(struct sockaddr *)&saddr,sizeof(saddr)) )
             {
                 vtun_syslog(LOG_ERR,"Can't connect socket");
                 return -1;
             }
             is_rmt_fd_connected=1;
         }

    结合上面分析知,当

    VTUN_USE_NAT_HACK(host)为0时,linkfd.c的

    if (!VTUN_USE_NAT_HACK(lfd_host))
            proto_write(fd1, buf, VTUN_ECHO_REQ);

    做了一次发送空信息的操作,因为VTUN_USE_NAT_HACK(host)为0---》

    is_rmt_fd_connected=1;---》udp_write中if (!is_rmt_fd_connected) return 0;….write();….

    很不理解linkfd.c中下面这段代码的意思,

    if (!VTUN_USE_NAT_HACK(lfd_host))
            proto_write(fd1, buf, VTUN_ECHO_REQ);//此处的buf为空?

    当VTUN_USE_NAT_HACK(host)不为0时,

    is_rmt_fd_connected=0;

    linkfd.c的

    if (!VTUN_USE_NAT_HACK(lfd_host))
            proto_write(fd1, buf, VTUN_ECHO_REQ);

    啥也没做!!

    分析is_rmt_fd_connected最终得出结论,

    VTUN_USE_NAT_HACK(lfd_host)主要影响的是udp_read.

    也就是源码中的nat启用与否实际主要影响下面的函数,

    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

    最终结论:使用nat_hack时(配置文件决定使用与否),源码中实际影响的操作是udp_read中多了一段代码rlen = recvfrom(fd,buf,2,MSG_PEEK,(struct sockaddr *)&from,&fromlen).

    要深刻理解源码中的nat_hack,就是要深刻理解套接字编程recvfrom等函数的使用,及这些函数的深刻含义。

    后续将分析套接字编程的这些函数。

  • 相关阅读:
    如何DIY陶瓷制作方法
    陶瓷的分类
    陶瓷
    机器视觉之——认知篇
    Halcon学习笔记(一)
    编程过程遇到的问题及解决方法
    前端技术——WebFont与Sprite
    HTML学习总结(四)【canvas绘图、WebGL、SVG】
    CSS3学习之——【特殊属性】
    HTML5学习总结——本地存储
  • 原文地址:https://www.cnblogs.com/helloweworld/p/2704097.html
Copyright © 2011-2022 走看看