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等函数的使用,及这些函数的深刻含义。

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

原文地址:https://www.cnblogs.com/helloweworld/p/2704097.html