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