在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等函数的使用,及这些函数的深刻含义。
后续将分析套接字编程的这些函数。

