4月 052009
 

1.新建socket
函数原形:
static int inet_create(struct socket *sock, int protocol)
在net/ipv4/af_inet.c中
详细解释

static int inet_create(struct socket *sockint protocol

struct sock *sk

struct proto *prot

sock->state SS_UNCONNECTED; 
/* 设置状态为未连接 */ 
sk sk_alloc(PF_INETGFP_KERNEL1); 
/* 申请sock所需的内存 */ 
   
/* net/core/sock.c */ 
if (sk == NULL

 goto do_oom

switch (
sock->type
) { 
case 
SOCK_STREAM:  
/* TCP协议 */ 
 if (protocol && protocol != IPPROTO_TCP

  goto free_and_noproto

 protocol IPPROTO_TCP

 prot = &tcp_prot; 
/* tcp_prot定义在net/ipv4/tcp_ipv4.c */ 
 sock->ops = &inet_stream_ops
/* 针对STREAM的socket操作 */ 
 break

case 
SOCK_SEQPACKET:  
/* 不支持 */ 
 goto free_and_badtype

case 
SOCK_DGRAM:  
/* UDP协议 */ 
 if (protocol && protocol != IPPROTO_UDP

  goto free_and_noproto

 protocol IPPROTO_UDP

 sk->no_check UDP_CSUM_DEFAULT

 prot=&udp_prot;  
/* udp_prot定义在net/ipv4/udp.c */ 
 sock->ops = &inet_dgram_ops
/* 针对DGRAM的socket操作 */ 
 break

case 
SOCK_RAW:  
/* RAW */ 
 if (!capable(CAP_NET_RAW)) 
/* 判断是否有权利建立SOCK_RAW */ 
  goto free_and_badperm

 if (!protocol)  
/* protocol不能为0 */ 
  goto free_and_noproto

 prot = &raw_prot; 
/* raw_prot定义在net/ipv4/raw.c */ 
 sk->reuse 1;  
/* 允许地址重用 */ 
 sk->num protocol

 sock->ops = &inet_dgram_ops
/* RAW的一些特性和DGRAM相同 */ 
 if (protocol == IPPROTO_RAW

  sk->protinfo.af_inet.hdrincl 1

   
/* 允许自己定制ip头 */ 
 break

default: 
 goto free_and_badtype


if (
ipv4_config.no_pmtu_disc

 sk->protinfo.af_inet.pmtudisc IP_PMTUDISC_DONT

else 
 sk->protinfo.af_inet.pmtudisc IP_PMTUDISC_WANT

sk->protinfo.af_inet.id 0

sock_init_data(sock,sk); 
/* 初始化一些数据 */ 
   
/* net/core/sock.c */ 
sk->destruct inet_sock_destruct
/* 当销毁socket时调用inet_sock_destruct */ 
sk->zapped 0

sk->family PF_INET

sk->protocol protocol

sk->prot prot

sk->backlog_rcv prot->backlog_rcv
/* prot->backlog_rcv()见各个类型的定义 */ 
sk->protinfo.af_inet.ttl sysctl_ip_default_ttl
/* 设置默认ttl */ 
   
/* 修改/proc/sys/net/ipv4/ip_default_ttl */ 
sk->protinfo.af_inet.mc_loop 1

sk->protinfo.af_inet.mc_ttl 1

sk->protinfo.af_inet.mc_index 0

sk->protinfo.af_inet.mc_list NULL

#ifdef INET_REFCNT_DEBUG 
atomic_inc(&inet_sock_nr
); 
#endif 
if (sk->num
) { 
 
/* It assumes that any protocol which allows 
  * the user to assign a number at socket 
  * creation time automatically 
  * shares. 
  */ 
 sk->sport htons(sk->num); 
/* 设置本地端口 */ 
 
/* Add to protocol hash chains. */ 
 sk->prot->hash(sk
); 

if (
sk->prot->init
) { 
 int err sk->prot->init(sk); 
/* 协议对socket的初始化 */ 
 if (err != 0
) { 
  inet_sock_release(sk
); 
  return(err
); 
 


return(
0
); 
free_and_badtype

sk_free(sk);  
/* 释放内存 */ 
return –ESOCKTNOSUPPORT

free_and_badperm

sk_free(sk
); 
return –
EPERM

free_and_noproto

sk_free(sk
); 
return –
EPROTONOSUPPORT

do_oom

return –
ENOBUFS


在net/core/sock.

void sock_init_data
(struct socket *sockstruct sock *sk


skb_queue_head_init(&sk->receive_queue); 
/* 初始化3条队列 接受,发送,错误*/ 
skb_queue_head_init(&sk->write_queue
); 
skb_queue_head_init(&sk->error_queue
); 
init_timer(&sk->timer);  
/* 初始化timer */ 

sk->allocation GFP_KERNEL
sk->rcvbuf sysctl_rmem_default

sk->sndbuf sysctl_wmem_default

sk->state = TCP_CLOSE

sk->zapped 1

sk->socket sock

if(
sock


 sk->type sock->type

 sk->sleep = &sock->wait

 sock->sk sk

} else 
 sk->sleep NULL

sk->dst_lock RW_LOCK_UNLOCKED

sk->callback_lock RW_LOCK_UNLOCKED

   
/* sock_def_wakeup(),sock_def_readable(), 
    sock_def_write_space(),sock_def_error_report(), 
    sock_def_destruct() 在net/core/sock.c */ 
sk->state_change sock_def_wakeup

sk->data_ready sock_def_readable

sk->write_space sock_def_write_space

sk->error_report sock_def_error_report

sk->destruct      =    sock_def_destruct

sk->peercred.pid 0

sk->peercred.uid = –1

sk->peercred.gid = –1

sk->rcvlowat 1

sk->rcvtimeo MAX_SCHEDULE_TIMEOUT
/* 设置接受,发送超时 */ 
sk->sndtimeo MAX_SCHEDULE_TIMEOUT

atomic_set(&sk->refcnt1
); 

1.1 SOCK_STREAM的初始化 
在net
/ipv4/tcp_ipv4.

static int tcp_v4_init_sock(struct sock *sk


struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp
); 
skb_queue_head_init(&tp->out_of_order_queue
); 
tcp_init_xmit_timers(sk
); 
tcp_prequeue_init(tp
); 
tp->rto TCP_TIMEOUT_INIT

tp->mdev TCP_TIMEOUT_INIT

    
/* So many TCP implementations out there (incorrectly) count the 
 * initial SYN frame in their delayed-ACK and congestion control 
 * algorithms that we must have the following bandaid to talk 
 * efficiently to them. -DaveM 
 */ 
tp->snd_cwnd 2

/* See draft-stevens-tcpca-spec-01 for discussion of the 
 * initialization of these values. 
 */ 
tp->snd_ssthresh 0x7fffffff
/* Infinity */ 
tp->snd_cwnd_clamp = ~0

tp->mss_cache 536

tp->reordering sysctl_tcp_reordering

sk->state TCP_CLOSE

sk->write_space tcp_write_space
/* tcp_write_space() 在net/ipv4/tcp.c */ 
sk->use_write_queue 1

sk->tp_pinfo.af_tcp.af_specific = &ipv4_specific

   
/* ipv4_specific 在net/ipv4/tcp_ipv4.c */ 
sk->sndbuf sysctl_tcp_wmem[1]; 
/* 设置发送和接收缓冲区大小 */ 
sk->rcvbuf sysctl_tcp_rmem[1]; 
/* sysctl_tcp_* 在net/ipv4/tcp.c */ 
atomic_inc(&tcp_sockets_allocated); 
/* tcp_sockets_allocated是当前TCP socket的数量 */ 
return 0

}

SOCK_DGRAM无初始化
1.2 SOCK_RAW初始化
在net/ipv4/raw.c

static int raw_init(struct sock *sk

struct raw_opt *tp = &(sk->tp_pinfo.tp_raw4
); 
if (
sk->num == IPPROTO_ICMP

 memset(&tp->filter0sizeof(tp->filter
)); 
return 
0

}

2.Server
2.1 bind

static int inet_bind(struct socket *sockstruct sockaddr *uaddrint addr_len

struct sockaddr_in *addr=(struct sockaddr_in *)uaddr

struct sock *sk=sock->sk

unsigned short snum

int chk_addr_ret

int err

/* If the socket has its own bind function then use it. (RAW) */ 
if(sk->prot->bind

 return sk->prot->bind(skuaddraddr_len
); 
    
/* 只有SOCK_RAW定义了自己的bind函数 */ 
if (addr_len sizeof(struct sockaddr_in
)) 
 return EINVAL

chk_addr_ret inet_addr_type(addr->sin_addr.s_addr
); 
    
/* inet_addr_type返回地址的类型 */ 
    
/* 在net/ipv4/fib_frontend.c */ 
/* Not specified by any standard per-se, however it breaks too 
 * many applications when removed. It is unfortunate since 
 * allowing applications to make a non-local bind solves 
 * several problems with systems using dynamic addressing. 
 * (ie. your servers still start up even if your ISDN link 
 * is temporarily down) 
 */ 
if (sysctl_ip_nonlocal_bind == 
&& 
   sk->protinfo.af_inet.freebind == 
&& 
   addr->sin_addr.s_addr != INADDR_ANY 
&& 
   chk_addr_ret != RTN_LOCAL 
&& 
   chk_addr_ret != RTN_MULTICAST 
&& 
   chk_addr_ret != RTN_BROADCAST

 return EADDRNOTAVAIL

snum ntohs(addr->sin_port
); 
if (
snum && snum PROT_SOCK && !capable(CAP_NET_BIND_SERVICE
)) 
   
/* 检查是否有权利bind端口到1-1024 */ 
 return EACCES

/*   We keep a pair of addresses. rcv_saddr is the one 
 *   used by hash lookups, and saddr is used for transmit. 
 * 
 *   In the BSD API these are the same except where it 
 *   would be illegal to use them (multicast/broadcast) in 
 *   which case the sending device address is used. 
 */ 
lock_sock(sk
); 
/* Check these errors (active socket, double bind). */ 
err = –EINVAL

if ((
sk->state != TCP_CLOSE)  
|| 
   (sk->num != 0
)) 
 goto out

sk->rcv_saddr sk->saddr addr->sin_addr.s_addr

if (
chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST

 sk->saddr 0; 
/* Use device */ 
/* Make sure we are allowed to bind here. */ 
if (sk->prot->get_port(sksnum) != 0) { 
/* get_port检查是否重用 */ 
 sk->saddr sk->rcv_saddr 0

 err = –EADDRINUSE

 goto out


if (
sk->rcv_saddr

 sk->userlocks |= SOCK_BINDADDR_LOCK

if (
snum

 sk->userlocks |= SOCK_BINDPORT_LOCK

sk->sport htons(sk->num
); 
sk->daddr 0

sk->dport 0

sk_dst_reset(sk
); 
err 0

out

release_sock(sk
); 
return 
err

SOCK_STREAM和SOCK_DGRAM用默认的bind
2.1.1 SOCK_RAW的bind
在net/ipv4/raw.c

static int raw_bind(struct sock *skstruct sockaddr *uaddrint addr_len

struct sockaddr_in *addr = (struct sockaddr_in *) uaddr

int ret = –EINVAL

int chk_addr_ret

if (
sk->state != TCP_CLOSE || addr_len sizeof(struct sockaddr_in
)) 
 goto out

chk_addr_ret inet_addr_type(addr->sin_addr.s_addr
); 
    
/* inet_addr_type返回地址的类型 */ 
    
/* 在net/ipv4/fib_frontend.c */ 
ret = –EADDRNOTAVAIL

if (
addr->sin_addr.s_addr && chk_addr_ret != RTN_LOCAL 
&& 
   chk_addr_ret != RTN_MULTICAST && chk_addr_ret != RTN_BROADCAST

 goto out

sk->rcv_saddr sk->saddr addr->sin_addr.s_addr

    
/* sk->rcv_saddr 捆绑的本地地址 */ 
    
/* sk->saddr 源地址 */ 
if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST

 sk->saddr 0; 
/* Use device */ /* 地址类型如为多播或是广播源地址为0 */ 
sk_dst_reset(sk
); 
ret 0

out: return ret


2.2 listen
2.2.1 SOCK_STREAM的listen
在net/ipv4/af_inet.c

int inet_listen(struct socket *sockint backlog

struct sock *sk sock->sk

unsigned char old_state

int err

lock_sock(sk
); 
err = –EINVAL

if (
sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM

 goto out

old_state sk->state

if (!((
1<<old_state)&(TCPF_CLOSE|TCPF_LISTEN
))) 
 goto out

/* Really, if the socket is already in listen state 
 * we can only allow the backlog to be adjusted. 
 */ 
if (old_state != TCP_LISTEN
) { 
 err tcp_listen_start(sk); 
/* 真正实现TCP协议listen */ 
 if (err

  goto out


sk->max_ack_backlog backlog

err 0

out

release_sock(sk
); 
return 
err


//tcp_listen_start在net/ipv4/tcp.h 
int tcp_listen_start(struct sock *sk


struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp
); 
struct tcp_listen_opt *lopt

sk->max_ack_backlog 0

sk->ack_backlog 0

tp->accept_queue tp->accept_queue_tail NULL

tp->syn_wait_lock RW_LOCK_UNLOCKED

tcp_delack_init(tp);  
/* tp清0 */ 
   
/* include/net/tcp.h */ 
lopt kmalloc(sizeof(struct tcp_listen_opt), GFP_KERNEL
); 
if (!
lopt

 return ENOMEM

memset(lopt0sizeof(struct tcp_listen_opt
)); 
for (
lopt->max_qlen_log 6; ; lopt->max_qlen_log
++) 
 if ((1<<lopt->max_qlen_log) >= sysctl_max_syn_backlog

  
break; 
write_lock_bh(&tp->syn_wait_lock
); 
tp->listen_opt lopt

write_unlock_bh(&tp->syn_wait_lock
); 
/* There is race window here: we announce ourselves listening, 
 * but this transition is still not validated by get_port(). 
 * It is OK, because this socket enters to hash table only 
 * after validation is complete. 
 */ 
sk->state TCP_LISTEN

if (
sk->prot->get_port(sksk->num) == 0) { 
/* 确认地址没有重用 */ 
 sk->sport htons(sk->num); 
/* 设置源端口 */ 
 sk_dst_reset(sk
); 
 sk->prot->hash(sk);  
/* 将端口加到hash表中 */ 
 return 0


sk->state TCP_CLOSE

write_lock_bh(&tp->syn_wait_lock
); 
tp->listen_opt NULL

write_unlock_bh(&tp->syn_wait_lock
); 
kfree(lopt
); 
return –
EADDRINUSE


SOCK_DGRAM 和 SOCK_RAW 不支持listen
2.3 accept
2.3.1 SOCK_STREAM的accept
在net/ipv4/af_inet.c

int inet_accept(struct socket *sockstruct socket *newsockint flags

struct sock *sk1 sock->sk

struct sock *sk2

int err = –EINVAL

if((
sk2 sk1->prot->accept(sk1,flags,&err)) == NULL

 goto do_err

lock_sock(sk2
); 
BUG_TRAP((1<<sk2->state)&(TCPF_ESTABLISHED|TCPF_CLOSE_WAIT|TCPF_CLOSE
)); 
sock_graft(sk2newsock); 
/* 将sk2转接到newsock */ 
   
/* 在include/net/sock.h */ 
newsock->state SS_CONNECTED

release_sock(sk2
); 
return 
0

do_err

return 
err


SOCK_DGRAM 和 SOCK_RAW 不支持 accept
2.3.1.1 TCP协议的accept
在net/ipv4/tcp.c

struct sock *tcp_accept(struct sock *skint flagsint *err

struct tcp_opt *tp = &sk->tp_pinfo.af_tcp

struct open_request *req

struct sock *newsk

int error

lock_sock(sk
); 
/* We need to make sure that this socket is listening, 
 * and that it has something pending. 
 */ 
error = –EINVAL

if (
sk->state != TCP_LISTEN) 
/* 检查socket是否处于listen状态 */ 
 goto out

/* Find already established connection */ 
if (!tp->accept_queue) { 
/* 判断accept队列是否准备好 */ 
 long timeo sock_rcvtimeo(skflags O_NONBLOCK
); 
   
/* 判断是否为堵塞模式 */ 
   
/* 在include/net/sock.h */ 
 
/* If this is a non blocking socket don’t sleep */ 
 error = –EAGAIN

 if (!timeo)  
/* 不堵塞模式,直接返回 */ 
  goto out

 error wait_for_connect(sktimeo); 
/* 进入空闲等待连接 */ 
 if (error

  goto out


req tp->accept_queue

if ((
tp->accept_queue req->dl_next) == NULL

 tp->accept_queue_tail NULL

 newsk req->sk

tcp_acceptq_removed(sk);  
/* sk当前连接数减1 */ 
    
/*在include/net/tcp.h */ 
tcp_openreq_fastfree(req);  
/* 释放内存 */ 
    
/*在include/net/tcp.h */ 
BUG_TRAP(newsk->state != TCP_SYN_RECV);
  
release_sock
(sk
); 
return 
newsk

out

release_sock(sk
); 
*
err error

return 
NULL


/* 只有当socket为堵塞模式,该函数才会被调用 */ 
/* 在net/ipv4/tcp.c */ 
static int wait_for_connect(struct sock sklong timeo


DECLARE_WAITQUEUE(waitcurrent
); 
int err

/* 
 * True wake-one mechanism for incoming connections: only 
 * one process gets woken up, not the ‘whole herd’. 
 * Since we do not ‘race & poll’ for established sockets 
 * anymore, the common case will execute the loop only once. 
 * 
 * Subtle issue: "add_wait_queue_exclusive()" will be added 
 * after any current non-exclusive waiters, and we know that 
 * it will always _stay_ after any new non-exclusive waiters 
 * because all non-exclusive waiters are added at the 
 * beginning of the wait-queue. As such, it’s ok to "drop" 
 * our exclusiveness temporarily when we get woken up without 
 * having to remove and re-insert us on the wait queue. 
 */ 
add_wait_queue_exclusive(sk->sleep, &wait
); 
for (;;) { 
 current->state TASK_INTERRUPTIBLE

 release_sock(sk
); 
 if (sk->tp_pinfo.af_tcp.accept_queue == NULL

  timeo schedule_timeout(timeo); 
/* 休眠timeo时长 */ 
 lock_sock(sk
); 
 err 0

 if (sk->tp_pinfo.af_tcp.accept_queue) 
/* accept队列可用 */ 
    
/* 也就是有连接进入 */ 
  
break; 
 err = –EINVAL

 if (sk->state != TCP_LISTEN

  
break; 
 err sock_intr_errno(timeo
); 
 if (signal_pending(current
)) 
  
break; 
 err = –EAGAIN

 if (!timeo

  
break; 

current->state TASK_RUNNING

remove_wait_queue(sk->sleep, &wait
); 
return 
err


3.Client
3.1 connect
3.1.1 SOCK_STREAM的connect
在net/ipv4/af_inet.c

int inet_stream_connect(struct socket *sockstruct sockaddr uaddr
  int addr_lenint flags


struct sock *sk=sock->sk

int err

long timeo

lock_sock(sk
); 
if (
uaddr->sa_family == AF_UNSPEC
) { 
 err sk->prot->disconnect(skflags); 
/* 关闭连接 */ 
 sock->state err SS_DISCONNECTING SS_UNCONNECTED

 goto out


switch (
sock->state
) { 
default: 
 err = –EINVAL

 goto out

case 
SS_CONNECTED

 err = –EISCONN

 goto out

case 
SS_CONNECTING

 err = –EALREADY

 
/* Fall out of switch with err, set for this state */ 
 break

case 
SS_UNCONNECTED

 err = –EISCONN

 if (sk->state != TCP_CLOSE

  goto out

 err = –EAGAIN

 if (sk->num == 0
) { 
  if (sk->prot->get_port(sk0) != 0
/* 是否重用 */ 
  goto out

  sk->sport htons(sk->num
); 
 

 err sk->prot->connect(skuaddraddr_len); 
/* 调用协议的connect */ 
 if (err 0

  goto out

  sock->state SS_CONNECTING;  
/* socket状态设置成连接中 */ 
 
/* Just entered SS_CONNECTING state; the only 
  * difference is that return value in non-blocking 
  * case is EINPROGRESS, rather than EALREADY. 
  */ 
 err = –EINPROGRESS

 break


timeo sock_sndtimeo(skflags&O_NONBLOCK); 
/* 是否为堵塞模式 */ 
    
/* 在include/net/sock.h */ 
if ((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV)) { 
/* 连接为完成 */ 
 
/* Error code is set above */ 
 if (!timeo || !inet_wait_for_connect(sktimeo
)) 
    
/* 非堵塞模式立即返回 */ 
    
/* 堵塞模式调用inet_wait_for_connect() */ 
  goto out

 err sock_intr_errno(timeo
); 
 if (signal_pending(current
)) 
  goto out


/* Connection was closed by RST, timeout, ICMP error 
 * or another process disconnected us. 
 */ 
if (sk->state == TCP_CLOSE

 goto sock_error

/* sk->err may be not zero now, if RECVERR was ordered by user 
 * and error was received after socket entered established state. 
 * Hence, it is handled normally after connect() return successfully. 
 */ 
sock->state SS_CONNECTED;  
/* 设置状态为已连接 */ 
err 0

out

release_sock(sk
); 
return 
err

sock_error

err sock_error(sk) ? : –ECONNABORTED

sock->state SS_UNCONNECTED

if (
sk->prot->disconnect(skflags
)) 
 sock->state SS_DISCONNECTING

goto out


/* 只有当socket为堵塞模式,该函数才会被调用 */ 
/* 在/net/ipv4/af_inet.c */ 
static long inet_wait_for_connect(struct sock *sklong timeo


DECLARE_WAITQUEUE(waitcurrent
); 
__set_current_state(TASK_INTERRUPTIBLE
); 
add_wait_queue(sk->sleep, &wait
); 
/* Basic assumption: if someone sets sk->err, he _must_ 
 * change state of the socket from TCP_SYN_*. 
 * Connect() does not allow to get error notifications 
 * without closing the socket. 
 */ 
while ((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV
)) { 
 release_sock(sk
); 
 timeo schedule_timeout(timeo); 
/* 进入休眠 */ 
 lock_sock(sk
); 
 if (signal_pending(current) || !timeo

  
break; 
 set_current_state(TASK_INTERRUPTIBLE
); 

__set_current_state(TASK_RUNNING
); 
remove_wait_queue(sk->sleep, &wait
); 
return 
timeo


摘自: http://www.infosecurity.org.cn/article/hacker/tutor/12424.html

 Posted by at 上午 8:14
1月 112009
 

对于一些非长连接的应用,可以通过计算连接的持续时间来知道处理时间的长短,首先通过tcpdump抓包,通过tcptrace可以知道连接的开始和结束时间,再用awk就可以统计了,基本命令如下:

tcpdump -i eth1 -w -| tcptrace -xrealtime stdin | awk ‘{ link = $2 "-" $3;if($4=="new")sum[link] = $1; else if($4 =="connection" && sum[link] > 0){ print link "\t" ($1-sum[link]) "\t" $6 " " $7 " " $8; delete sum[link];}}’

好像tcptrace本身是可以计算连接时间的长短的,应该是我还没有发现。

找见了:

tcptrace -l a.tcpdump | grep "elapsed time"

 

 Posted by at 上午 7:15
1月 112009
 

一、建立连接

在发送SYN报文段后,如果在75没有收到相应相应,连接建立将中止。这个时间也就是阻塞connect系统调用的超时时间。

 

二、保活

SO_KEEPALIVE选项保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方 发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:对方接收一切正常:以期望的ACK响应。2小时后,TCP将发出另一个探测分节。对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接口本身则被关闭。对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为 EHOSTUNREACH。

注意这里面有三个数字:

1.开始首次KeepAlive探测前的TCP空闭时间,2小时

2.两次KeepAlive探测间的时间间隔,75

3.判定断开前的KeepAlive探测次数,10

也就是说,总共需要2小时+75秒*10次 = 7950

 

三、重传

重传定时器在发送数据时设定,超时值是计算出来的,取决于RTT和报文已被重传次数(并没有包括在以下公式内,但是BSD的实现确实用到了这个数据)。

RTT估计其,R是RTT,M是测量到的RTT,a推荐值为0.9

R = a * R + (1-a) * M

重传超时时间RTO, b推荐值为2

RTO = R * b

例外:快速重传,收到3个重复的ACK会立即重传

 

四、延时确认,捎带确认

TCP协议栈收到数据后并不立即发送ACK,而是等待200ms(推荐值,但是这个值不能高于500ms),如果在这段时间有用户数据需要发送则一同随着这个ACK发送。

 

五、FIN_WAIT_2定时器

某个连接准备关闭连接时,调用close函数,发送FIN报文,状态从SYN_RCVD迁移到FIN_WAIT_1,收到这个FIN的ACK后,迁移到FIN_WAIT_2状态。

为了防止对端一直不发送FIN,在等待10分钟后再等待75(定时器确实被设置了两次,所以分开说),超时,连接关闭。

 

六、2MSL定时器

MSL是最大报文段生存时间,RFC1122建议是2分钟,但BSD传统实现采用了30。当连接转移到TIME_WAIT状态时,即连接主动关闭时,定时器启动,为两倍的MSL。定时器超时,这时才能重新使用之前连接使用的端口号。这也是为了避免一些意想不到的边界情况,TCPIP详解第一卷的18.6.1给出了一个极端的例子。

 

七、平静时间

对于来自某个连接的较早替身的迟到报文段, 2 M S L等待可防止将它解释成使用相同插口对的新连接的一部分。但这只有在处于2 M S L等待连接中的主机处于正常工作状态时才有效。

如果使用处于2 M S L等待端口的主机出现故障,它会在M S L秒内重新启动,并立即使用故障前仍处于2 M S L的插口对来建立一个新的连接吗?如果是这样,在故障前从这个连接发出而迟到的报文段会被错误地当作属于重启后新连接的报文段。无论如何选择重启后新连接的初始序号,都会发生这种情况。

为了防止这种情况,RFC 793指出TCP在重启动后的MSL内不能建立任何连接。这就称为平静时间(quiet time)

 

八、SO_LINGER的时间

这个选项会影响close的行为。如果linger结构中的l_onoff域设为非零,并设置了零超时间隔,则close不被阻塞立即执行,不论是否有排队数据未发送或未被确认。这种关闭方式会发送RST报文段,且丢失了未发送的数据。在远端的读调用将以ECONNRESET出错。

若设置了SO_LINGER并确定了非零的超时间隔,则closes调用阻塞进程,直到所剩数据发送完毕或超时。这种关闭称为“优雅的”关闭。如果套接口置为非阻塞且SO_LINGER设为非零超时,则close调用将以EWOULDBLOCK错误返回。

若在一个流类套接口上设置了SO_DONTLINGER(等效于SO_LINGER将linger结构的l_onoff域设为零),则close调用立即返回。但是,如果可能,排队的数据将在套接口关闭前发送。

 Posted by at 上午 4:57
11月 222008
 

文章标题 iptables下udp穿越结尾篇—-iptables和socks5
张贴者: shixudong (member)
张贴日期 10/31/04 10:36 PM
iptables和socks5
从“iptables和natcheck”一文可知,只要在两端都采用了iptables作NAT后,即使两侧都通过了natcheck的兼容性测试,但iptables两侧永远也不能互相穿越。
怎么办呢,一种办法是在公网上添加中转服务器,两侧内网机器之间的UDP通讯都由中转服务器来中转(其实只要中转一侧足矣)。这种方法的好处是,因为中转服
务器在公网,所有NAT后面的机器都能和中转服务器建立连接,也就是说,不同内网之间的机器总是能通过中转服务器实现双向通信的。然而,该办法的缺点
是,对中转服务器的需求比较高,包括CPU处理能力和网络带宽两方面,而且客户机之间的通讯延迟也是不可避免的(目前网上最为盛行的Skype是个例外,
他采用了分布式中转技术,直接挂在互连网上不在防火墙后面的Skype客户端都能为他人提供中转服务,因此Skype在提供非常高呼叫成功率的同时还能确
保超高质量的语音效果)。更有一个更为重要的因素,即中转服务器的标准不统一,导致每种不同类型的P2P程式都需要一个专用的中转服务器。倘若这些中转服
务器之间不能做到资源共享的话,必然存在资源浪费现象(标准的中转协议似乎正在推出,名称为Traversal Using Relay NAT
即TURN)。
另一种比较好的办法就是采用Socks5(Rfc1928)代理服务器取代专用的中转服务器,一是因为Socks5能够非常好的
支持UDP,二是Socks5代理服务器的品种及在公网上部署的数量都比较多,而且最重要的是Socks5是个已标准化了的协议。客户端采用
Socks5代理后,其UDP通信通过Socks5中转出去,在对方的P2P程式看来,使用Socks5代理后的客户就像直接连在公网上,也就是说,只要有一方使用Socks5代理,则另一方不论采用何种NAT,都不会受Stun或natcheck的限制。因此,iptables和Socks5理论上应该合作愉快,但在实现Socks5代理时,如果对Socks5协议理解不够透彻,在和iptables合作时,还是有一些不愉快的。下文试举两例说明之。
假设一端采用iptables,另一端采用Socks5,目前Socks5后面的QQ要向iptables后面的在线(不隐身)QQ发送消息。当QQ通过Socks5代理登陆QQ服务器时,首先要和Socks5服务器建立一个基于TCP连接的Socks5通道,用于控制QQ和Socks5服务器之间的后续UDP联结。一旦该通道成功建立,Socks5服务器将动态分配一个UDP端口为该QQ担任UDP中转的任务,当QQ给远端iptables后面在线QQ发送消息时,先将消息(UDP包)发送到Socks5服务器上先前分配的UDP端口,再由服务器将该UDP包转发到远端QQ。当UDP包到达远端QQ时,
由iptables端口受限的属性可知,除非使用Socks5代理的QQ在线且在不久前(三分钟)原来收到过iptables后面QQ主动发来的UDP包,否则这个包无法被转发到iptables后面的QQ上。
那么这个UDP包最终又往哪里去了呢?我们来分析一下,该UDP包到达iptables所在那台Linux主机后的前进流程。当一个UDP包到达Linux时,先交由iptables处理,iptables则先看该UDP包在/proc/net/ip_conntrack文件里有无匹配项,如有,则进行匹配处理;如无,再看PREROUTING链里有无匹配规则,如有,则继续进行匹配处理;如无,由于该包目的地址就是Linux本身,所以iptables将其放入INPUT链。由于INPUT链的默认策略为ACCEPT,则该UDP包将被转交给Linux的本地进程处理,但事实上由于不存在这样的本地进程,结果是Linux向产生该UDP包的机器发回一个icmp-port-unreachable包(注意,这个包是Linux系统产生的,不是iptables的REJECT目标产生的)。如果INPUT链的策略为DROP,则该UDP包就被DROP。
由上分析可知,一般情况下,最终iptables所在机器将向发送QQ方返回一个icmp-port-unreachable包。当Socks5服务器收到这个icmp-port-unreachable包后,不同的Socks5服务器有不同的处理方式。
一些Socks5服务器(如Ccproxy带的Socks5)简单的忽略这个icmp-port-unreachable包,而QQ则由于没有发送成功
(没有收到应答信息),继续重复上述过程若干次,但都无法发送成功,最后只能通过服务器中转,虽然由于中转造成发送速度非常慢,但总是能够成功发送。而另外
一些Socks5服务器(如Wingate带的Socks5)收到远端返回的icmp-port-unreachable包后,认为先前QQ发起的UDP
联结无效,通过先前建立的Socks5通道(TCP)给客户端返回一个general SOCKS server failure(Reply
Code‘01’),随后即时关闭该通道,同时还关闭了Socks5服务器上担任中转任务的UDP端口。然而QQ似乎并不知道他和Socks5服务器之间
的联系已被切断,由于没有发送成功(没有收到应答信息),仍然向Socks5服务器上已关闭的UDP端口发送消息。此时由于Socks5服务器已关闭了相
应的UDP端口,所以也向QQ返回一个icmp-port-unreachable包。QQ收到这个包后,继续重复上述过程若干次,最终因超时而失败,并
且因为同样的原因也不能通过服务器中转。更为严重的是,即便到了此时,QQ仍然不知道他和Socks5服务器之间的联系已被切断,因此,即使当QQ向其他
离线或隐身QQ或不在NAT后面的在线QQ发送消息时,也不能成功。
上面这个例子表明,由于某些Socks5服务器和Socks5客户端对Socks5协议理解不够透彻,即使采用了Socks5,在和iptables互通时也会导致通讯不畅。
另一个例子,考虑这样一种情形,Socks5服务器位于公网,内网客户端先通过iptables进行NAT,然后再去连接Socks5服务器。
先引用Socks5协议(Rfc1928)里如下一段文字:
The
UDP ASSOCIATE request is used to establish an association within the
UDP relay process to handle UDP datagrams. The DST.ADDR and DST.PORT
fields contain the address and port that the client expects to use to
send UDP datagrams on for the association. The server MAY use this
information to limit access to the association. If the client is not in
possesion of the information at the time of the UDP ASSOCIATE, the
client MUST use a port number and address of all zeros.
Socks5客户端在进行UDPASSOCIATE时(通过TCP通道),需求用自己将来发送UDP包的地址和端口填充DST.ADDR和DST.PORT字段,以便Socks5服务器确保其分配的UDP端口仅为对应的Socks5客户端提供中转服务。然而,当Socks5客户端跨越NAT去连接Socks5服务器时,考虑到要经过
NAT地址转换环节,Socks5客户端无法预知将来Socks5服务器看到的自己用来发送UDP包的地址和端口。针对这种情况,Rfc1928建议
Socks5客户端使用0填充DST.ADDR和DST.PORT字段,这样一来,Socks5服务器就不会限制Socks5客户端对中转端口的使用了。
然而,目前好多Socks5客户端似乎没有意识到这一点,像最通用的NEC公司的e-Border
Driver,便使用了客户端的真实IP地址和端口填充DST.ADDR和DST.PORT字段;而其他一些P2P程式自带的Socks5客户端,仅用0
填充DST.ADDR字段。鉴于跨越NAT后,IP地址必然发生变化,导致e-Border
Driver永远不能用于跨越NAT的场合,而像iptables,由于具有Preserves port
number的属性,所以,那些仅用0填充DST.ADDR字段的Socks5客户端,能用于跨越iptables的场合。此外,跨越NAT还需要
Socks5服务器的配合,遗憾的是,仍然有些Socks5服务器并没有充分理解Rfc1928的这一建议。在这点上,Wingate带的Socks5做
得比较好,支持客户端用0填充DST.ADDR和DST.PORT字段;而Ccproxy带的Socks5,在客户端进行UDP
ASSOCIATE时,虽然也支持客户端用0填充DST.ADDR和DST.PORT字段,并能在服务器上顺利为客户端分配一个UDP端口,但当后来该
UDP端口和客户端通信时,不是发往客户端对应的UDP端口,而是发往客户端前述用0填充的DST.PORT,导致无法实现中转任务。至于NEC公司提供
的广为流传的免费Socks5服务器,则干脆申明“if the destination port is 0, we will assume
the same port as tcp’s”,也将导致Socks5客户端无法跨越NAT。
在这个例子里,iptables对
Socks5代理没有所有影响,纯粹是因为Socks5本身的原因导致通讯不畅。相反,利用iptables的保护源端口特性,还能确保部分Socks5
客户端能够顺利跨越用iptables实现的NAT去连接公网上的Socks5服务器。
希望本文两个例子能对即将或已实现Socks5代理的研发者有所帮助。

 Posted by at 上午 3:41
11月 222008
 

文章标题 iptables下udp穿越实用篇—-iptables与natcheck
张贴者: shixudong (member)
张贴日期 10/16/04 05:51 PM

iptables与natcheck

Stun协议(Rfc3489、详见http://www.ietf.org/rfc/rfc3489.txt) 提出了4种NAT类型的定义及其分类,并给出了如何检测在用的NAT究竟属于哪种分类的标准。但是,具体到P2P程序如何应用Stun协议及其分类法穿越NAT,则是仁者见仁、智者见智。(因为Stun协议并没有给出也没有必要给出如何穿越NAT的标准)
在拙作“iptables与stun”一文中,笔者花大幅精力阐述了iptables理论上属于Symmetric NAT而非Port Restricted Cone。对此,很多人(包括笔者最初学习Stun协议时)心中都有一个疑惑,即仅就Stun协议本身来说,Port Restricted Cone和Symmetric NAT的区别似乎不大,虽然两者的映射机制是有点不同,但他们都具有端口受限的属性。初看起来,这两者在穿越NAT方面的特性也差不多,尤其是对于外部地址欲往NAT内部地址发包的情况。既然如此,又为何有必要把iptables分得这么清呢,本文顺带解决了读者在这一方面的疑惑。
网站
http://midcom-p2p.sourceforge.net/给出了P2P程序具体如何穿越NAT的一个思路,并提供了一个P2P协议穿越NAT兼容性的测试工具natcheck。让我们仍旧用实例(例1)来说明这一思路吧!
A机器在私网(192.168.0.4)
A侧NAT服务器(210.21.12.140)
B机器在另一个私网(192.168.0.5)
B侧NAT服务器(210.15.27.140)
C机器在公网(210.15.27.166)作为A和B之间的中介
A机器连接过C机器,假使是 A(192.168.0.4:5000)-> A侧NAT(转换后210.21.12.140:8000)-> C(210.15.27.166:2000)
B机器也连接过C机器,假使是 B(192.168.0.5:5000)-> B侧NAT(转换后210.15.27.140:8000)-> C(210.15.27.166:2000)
A 机器连接过C机器后,A向C报告了自己的内部地址(192.168.0.4:5000),此时C不仅知道了A的外部地址(C通过自己看到的 210.21.12.140:8000)、也知道了A的内部地址。同理C也知道了B的外部地址(210.15.27.140:8000)和内部地址(192.168.0.5:5000)。之后,C作为中介,把A的两个地址告诉了B,同时也把B的两个地址告诉了A。
假设A先知道了B的两个地址,则A从192.168.0.4:5000处同时向B的两个地址192.168.0.5:5000和210.15.27.140:8000发包,由于 A和B在两个不同的NAT后面,故从A(192.168.0.4:5000)到B(192.168.0.5:5000)的包肯定不通,现在看A (192.168.0.4:5000)到B(210.15.27.140:8000)的包,分如下两种情况:
1、B侧NAT属于Full Cone NAT
则无论A侧NAT属于Cone NAT还是Symmetric NAT,包都能顺利到达B。如果P2P程序设计得好,使得B主动到A的包也能借用A主动发起建立的通道的话,则即使A侧NAT属于Symmetric NAT,B发出的包也能顺利到达A。
结论1:只要单侧NAT属于Full Cone NAT,即可实现双向通信。
2、B侧NAT属于Restricted Cone或Port Restricted Cone
则包不能到达B。再细分两种情况
(1)、A侧NAT属于Restricted Cone或Port Restricted Cone
虽然先前那个初始包不曾到达B,但该发包过程已经在A侧NAT上留下了足够的记录:A(192.168.0.4:5000)-> (210.21.12.140:8000)->B(210.15.27.140:8000)。如果在这个记录没有超时之前,B也重复和A一样的动作,即向A(210.21.12.140:8000)发包,虽然A侧NAT属于Restricted Cone或Port Restricted Cone,但先前A侧NAT已经认为A已经向B(210.15.27.140:8000)发过包,故B向A(210.21.12.140:8000)发包能够顺利到达A。同理,此后A到B的包,也能顺利到达。
结论2:只要两侧NAT都不属于Symmetric NAT,也可双向通信。换种说法,只要两侧NAT都属于Cone NAT,即可双向通信。
(2)、A侧NAT属于Symmetric NAT
因为A侧NAT属于Symmetric NAT,且最初A到C发包的过程在A侧NAT留下了如下记录:A(192.168.0.4:5000)->(210.21.12.140: 8000)-> C(210.15.27.166:2000),故A到B发包过程在A侧NAT上留下的记录为:A(192.168.0.4:5000)-> (210.21.12.140:8001)->B(210.15.27.140:8000)(注意,转换后端口产生了变化)。而B向A的发包,只能根据C给他的关于A的信息,发往A(210.21.12.140:8000),因为A端口受限,故此路不通。再来看B侧NAT,由于B也向A发过了包,且 B侧NAT属于Restricted Cone或Port Restricted Cone,故在B侧NAT上留下的记录为:B(192.168.0.5:5000)->(210.15.27.140:8000)->A (210.21.12.140:8000),此后,如果A还继续向B发包的话(因为同一目标,故仍然使用前面的映射),如果B侧NAT属于 Restricted Cone,则从A(210.21.12.140:8001)来的包能够顺利到达B;如果B侧NAT属于Port Restricted Cone,则包永远无法到达B。
结论3:一侧NAT属于Symmetric NAT,另一侧NAT属于Restricted Cone,也可双向通信。
显然,还可得出另一个不幸的结论4,两个都是Symmetric NAT或者一个是Symmetric NAT、另一个是Port Restricted Cone,则不能双向通信。
上面的例子虽然只是分析了最初发包是从A到B的情况,但是,鉴于两者的对称性,并且如果P2P程序设计得足够科学,则前面得出的几条结论都是没有方向性,双向都适用的。
通过上述分析,我们得知,在穿越NAT方面,Symmetric NAT和Port Restricted Cone是有本质区别的,尽管他们表面上看起来相似。我们上面得出了四条结论,而natcheck网站则把他归结为一条:只要两侧NAT都属于Cone NAT(含Full Cone、Restricted Cone和Port Restricted Cone三者),即可双向通信。而且natcheck网站还建议尽量使用Port Restricted Cone,以充分利用其端口受限的属性确保安全性。目前,国内充分利用了上述思路的具有代表性的P2P软件是“E话通”(
www.et66.com)。
在对natcheck提供的思路进行详细分析后,开始探讨本文主题:iptables与natcheck。
Natcheck 脱胎于Stun协议,由拙作“iptables与stun”一文可知,其对iptables进行的穿越NAT兼容性测试结果必然是GOOD。此外,我在该文中还提到一句,如果在每个NAT后面仅有一个客户端这种特殊情况下,iptables就是一个标准的Port restricted Cone。根据前面natcheck的结论,这样两个iptables后面的客户端应该可以互相穿越对方的NAT。让我们来看一下实际情况(例2)呢?
仍然参考前例,只是两侧都使用iptables来进行地址转换。(因为采用了iptables,故此处和前例稍有点区别,即转换后源端口不变)
A 与B通过C交换对方地址的初始化环节此处略去,我们从A(192.168.0.4:5000)向B(210.15.27.140:5000)(注意因使用 iptables而导致端口和前例不一样)发包开始分析,因为在本例中,两侧均只有一个客户端,我们姑且把iptables简化成Port restricted Cone看待。
如前例一样,从A(192.168.0.4:5000)到B(210.15.27.140: 5000)的第一个包必不能到达B,但其会在A侧iptables上留下记录,在这条记录没有超时之前(iptables下默认30秒),如果B也向A (210.21.12.140:5000)发包,如前所述,按理该包应该能够到达A,但事实上却是永远到不了。
难道是natcheck的结论错了,或者是特殊情况下iptables并不是Port restricted Cone(即仍然是Symmetric NAT),我们还是别忙着再下结论,先来看看来两侧iptables上留下的记录吧:
A侧:cat /proc/net/ip_conntrack | grep 192.168.0.4 | grep udp
udp 17 18 src=192.168.0.4 dst=210.15.27.140 sport=5000 dport=5000 [UNREPLIED] src=210.15.27.140 dst=210.21.12.140 sport=5000 dport=5000 use=1
B侧:cat /proc/net/ip_conntrack | grep 192.168.0.5 | grep udp
udp 17 26 src=192.168.0.5 dst=210.21.12.140 sport=5000 dport=5000 [UNREPLIED] src=210.21.12.140 dst=210.15.27.140 sport=5000 dport=1026 use=1
把两条记录翻译如下:(关于ip_conntrack文件的分析,请见
http://www.sns.ias.edu/~jns/secu … bles_conntrack.html
A(192.168.0.4:5000)-> A侧NAT(转换后210.21.12.140:5000)-> B(210.15.27.140:5000)
B(192.168.0.5:5000)-> B侧NAT(转换后210.15.27.140:1026)-> A(210.21.12.140:5000)
奇怪,B到A的包在映射后源端口号怎么变了呢,按理不应该呀?因为按照iptables转换原则(详见“iptables与stun”),要求尽量保持源端口号不变,除非socket有重复。难道B侧NAT上还有重复记录,再cat一下呢?
B侧:cat /proc/net/ip_conntrack | grep 210.21.12.140 | grep udp
udp 17 10 src=210.21.12.140 dst=210.15.27.140 sport=5000 dport=5000 [UNREPLIED] src=210.15.27.140 dst=210.21.12.140 sport=5000 dport=5000 use=1
udp 17 16 src=192.168.0.5 dst=210.21.12.140 sport=5000 dport=5000 [UNREPLIED] src=210.21.12.140 dst=210.15.27.140 sport=5000 dport=1026 use=1
操!还果真有两条差不多的记录,第一条与NAT无关,是A到B的包在B侧iptables上留下的记录,产生时间上略早于第二条记录,其构成的socket是(210.21.12.140:5000,210.15.27.140:5000)。第二条即B到A的包产生的记录,其构成的socket是(210.15.27.140:1026,210.21.12.140:5000),如果其源端口不改动,即是(210.15.27.140:5000,210.21.12.140:5000),还真和第一条记录重复了呢,怪不得转换后需要修改源端口,也怪不得B 发包到不了A。
为什么是这样的结果呢?我们知道,iptables是一个有状态的防火墙,他通过连接跟踪模块来实现状态检测的功能,该模块检查所有到来的数据包,也就是说,该模块不仅对NAT起作用,而且对普通的包过滤也起作用。显然,在上述例子里,A到B的包就是作为普通的包过滤而被记载在 B侧iptables的连接跟踪表里,导致后来B到A的包为避免socket重复而不得不改换端口号,从而导致无法实现双向通信。看来,natcheck 的结论并没有错,只是由于iptables具有状态检测的新特性导致即使在特殊情况下iptables又从Port restricted Cone变成了Symmetric NAT而已。
那么,有办法解决这一问题吗?根据连接跟踪的特性,在iptables下,只要启用了 NAT,就肯定要启用连接跟踪功能,而只要启用了连接跟踪功能,就必然顺带跟踪普通包过滤(启用连接跟踪后,似乎无法控制不让跟踪普通包过滤),也就是说,只要用NAT,就无法避免上述情况,真残酷!然而,这却是事实,即只要两端都采用了iptables作为NAT,则尽管两侧都通过了natcheck 的兼容性测试,但iptables两侧永远也不能互相穿越。
在“iptables与stun”一文中曾经附带提到,Win2000下的ics 或nat在Stun协议下的表现和iptables是完全一样的。那么,在natcheck下,表现是否还一致呢?答案是否定的,虽然Win2000下的 ics或nat也具有状态检测的功能,但该状态检测,仅对NAT起作用,不对普通包过滤起作用。所以在两侧都是Win2000下的ics或nat可以作为 Port restricted Cone的特殊情况下,是允许被穿越的。另外,在一侧使用iptables,另一侧使用Win2000下的ics或nat,但两者都表现为Port restricted Cone的特殊情况下,从某个方向发起,最终是允许互相穿越的,但是这种穿越不具有对称性,即从另一个方向发起,则永远无法穿越,具体原理,读者可以参考例2自行分析。

 Posted by at 上午 3:37
11月 222008
 

摘自:http://www.360doc.com/content/070205/13/17255_356485.html

iptables与stun

Stun协议(Rfc3489、详见http://www.ietf.org/rfc/rfc3489.txt)将NAT粗略分为4种类型,即Full Cone、Restricted Cone、Port Restricted Cone和Symmetric。举个实际例子(例1)来说明这四种NAT的区别:
A机器在私网(192.168.0.4)
NAT服务器(210.21.12.140)
B机器在公网(210.15.27.166)
C机器在公网(210.15.27.140)
现在,A机器连接过B机器,假设是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8000)-> B(210.15.27.166:2000)。
同时A从来没有和C通信过。
则对于不同类型的NAT,有下列不同的结果:
Full Cone NAT:C发数据到210.21.12.140:8000,NAT会将数据包送到A(192.168.0.4:5000)。因为NAT上已经有了192.168.0.4:5000到210.21.12.140:8000的映射。
Restricted Cone:C无法和A通信,因为A从来没有和C通信过,NAT将拒绝C试图与A连接的动作。但B可以通过210.21.12.140:8000与A的192.168.0.4:5000通信,且这里B可以使用任何端口与A通信。如:210.15.27.166:2001 -> 210.21.12.140:8000,NAT会送到A的5000端口上。
Port Restricted Cone:C无法与A通信,因为A从来没有和C通信过。而B也只能用它的210.15.27.166:2000与A的192.168.0.4:5000通信,因为A也从来没有和B的其他端口通信过。该类型NAT是端口受限的。
Symmetric NAT:上面3种类型,统称为Cone NAT,有一个共同点:只要是从同一个内部地址和端口出来的包,NAT都将它转换成同一个外部地址和端口。但是Symmetric有点不同,具体表现在:只要是从同一个内部地址和端口出来,且到同一个外部目标地址和端口,则NAT也都将它转换成同一个外部地址和端口。但如果从同一个内部地址和端口出来,是到另一个外部目标地址和端口,则NAT将使用不同的映射,转换成不同的端口(外部地址只有一个,故不变)。而且和Port Restricted Cone一样,只有曾经收到过内部地址发来包的外部地址,才能通过NAT映射后的地址向该内部地址发包。
现针对Symmetric NAT举例说明(例2):
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8000)-> B(210.15.27.166:2000)
如果此时A机器(192.168.0.4:5000)还想连接C机器(210.15.27.140:2000),则NAT上产生一个新的映射,对应的转换可能为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8001)-> C(210.15.27.140:2000)。此时,B只能用它的210.15.27.166:2000通过NAT的210.21.12.140:8000与A的192.168.0.4:5000通信, C也只能用它的210.15.27.140:2000通过NAT的210.21.12.140:8001与A的192.168.0.4:5000通信,而B或者C的其他端口则均不能和A的192.168.0.4:5000通信。
通过上面的例子,我们清楚了Stun协议对NAT进行分类的依据。那么,我们现在根据上述分类标准(或例子),来简要分析一下iptables的工作原理(仅指MASQUERADE、下同),看看他又是属于哪种NAT呢?
首先,我们去网上下载一个使用Stun协议检测NAT的工具,网址在http://sourceforge.net/projects/stun/,使用该工具对iptables的检测结果是Port restricted NAT detected。
我们先不要急着接受这个检测结果,还是先来分析一下iptables的工作原理吧!
iptables在转换地址时,遵循如下两个原则:
1、尽量不去修改源端口,也就是说,ip伪装后的源端口尽可能保持不变。(即所谓的Preserves port number)
2、更为重要的是,ip伪装后只需保证伪装后的源地址/端口与目标地址/端口(即所谓的socket)唯一即可。
仍以前例说明如下(例3):
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)。(注意,此处NAT遵循原则1、故转换后端口没有改变)
如果此时A机器(192.168.0.4:5000)还想连接C机器(210.15.27.140:2000),则NAT上产生一个新的映射,但对应的转换仍然有可能为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)。这是因为NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)和NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)这两个socket不重复。因此,对于iptables来说,这既是允许的(第2条原则)、也是必然的(第1条原则)。
在该例中,表面上看起来iptables似乎不属于Symmetric NAT,因为它看起来不符合Symmetric NAT的要求:如果从同一个内部地址和端口出来,是到另一个目标地址和端口,则NAT将使用不同的映射,转换成不同的端口(外部地址只有一个,故不变)。相反,倒是符合除Symmetric NAT外的三种Cone NAT的要求:从同一个内部地址和端口出来的包,NAT都将它转换成同一个外部地址和端口。加上iptables具有端口受限的属性(这一点不容置疑,后面举反例证明之),所以好多检测工具就把iptables报告为Port restricted NAT类型了。
下面仍以前例接着分析(例4):
在前例中增加D机器在A同一私网(192.168.0.5)
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)
D机器连接过C机器,假使是 D(192.168.0.5:5000)-> NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)
由iptables转换原则可知,上述两个转换是允许且必然的。
如果此时A机器(192.168.0.4:5000)还想连接C机器(210.15.27.140:2000),则NAT上产生一个新的映射,但对应的转换则变为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5001)-> C(210.15.27.140:2000)。这是因为,如果仍然将其转换为210.21.12.140:5000的话,则其所构成的socket(210.21.12.140:5000->210.15.27.140:2000)将和D->C的socket一致,产生冲突,不符合iptables的第2条原则(注意,此处以5001表示转换后不同的端口,但事实上,iptables却并不按照内部端口+1的原则来产生新的端口)。在本例中我们注意到,从同一个内部地址和端口A(192.168.0.4:5000)出来,到不同的目标地址和端口,则NAT使用了不同的映射,转换成不同的端口。
上面这个例子在实际环境中比较少见,我们再以QQ为例举一个真实且常见的例子(例5)。
假设
A(192.168.0.4)和 D(192.168.0.5)是同一NAT服务器(210.21.12.140)保护的两台私网机器,都运行了QQ客户端程序。
B机器在公网(210.15.27.166),运行QQ服务器程序。
C机器在公网(210.15.27.140),运行QQ客户端程序。
A上QQ先登陆到B,按照原则1,使用如下映射:
A(192.168.0.4:4000)-> NAT(转换后210.21.12.140:4000)-> B(210.15.27.166:8000)(原则1,端口不变)
接着D上QQ也登陆到B,按照原则2,使用如下映射:
D(192.168.0.5:4000)-> NAT(转换后210.21.12.140:4001)-> B(210.15.27.166:8000)(原则2,scoket不能有重复,此处4001仅表示转换后不同的端口,实际环境中决不是4001)
然后D欲和公网C(210.15.27.140)上的QQ通信,按照iptables转换原则,使用如下映射:
D(192.168.0.5:4000)-> NAT(转换后210.21.12.140:4000)-> C(210.15.27.140:4000)
到此我们发现,和上例一样,从同一个内部地址和端口D(192.168.0.5:4000)出来,到不同的目标地址和端口,则NAT使用了不同的映射,转换成不同的端口。但和上例不一样的是,本例显然普遍存在于实际环境中。
上面所举两例表明,结论刚好和例3相反,即iptables应该属于Symmetric NAT。
为什么会出现彼此矛盾的情况呢?首先从NAT分类的定义上来看, Stun协议和iptables对映射的理解不同。Stun协议认为,一个映射的要素是:内部地址端口和NAT转换后地址端口的组合。而在iptables看来,一个映射的要素是:NAT转换后地址端口和外部目标地址端口的组合。另一方面则是Stun协议里Discovery Process给出的测试环境不够全面之故,他只考虑了NAT后面仅有一台私网机器的特例(例3),没有考虑NAT后面可以有多台私网机器的普遍例子(例5)。正是由于这两个原因,直接导致了上述矛盾的发生。所以,凡按照Stun协议标准设计的NAT分类检测工具对iptables的检测结果必然是Port restricted NAT。(事实上,在例3那样的特例下,iptables确实是一个标准的Port restricted NAT)
那么,iptables究竟属于哪种NAT呢?我们再来回顾一下Stun协议对Cone NAT的要求:所有(或只要是)从同一个内部地址和端口出来的包,NAT都将它转换成同一个外部地址和端口。虽然iptables在部分情况下满足“从同一个内部地址和端口出来的包,都将把他转换成同一个外部地址和端口”这一要求,但它不能在所有情况下满足这一要求。所以理论上,我们就只能把iptables归为Symmetric NAT了。
下面,我们再来分析一下iptables的端口受限的属性,我们举一个反例证明之(例6),仍以前例说明如下:
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)
D机器连接过C机器,假使是 D(192.168.0.5:5000)-> NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)
现假设iptables不具有端口受限的属性,则另一E机器在公网(210.15.27.153:2000)或C(210.15.27.140:2001)向210.21.12.140:5000发包的话,应该能够发到内部机器上。但事实是,当这个包到达NAT(210.21.12.140:5000)时,NAT将不知道把这个包发给A(192.168.0.4:5000)还是D(192.168.0.5:5000)。显然,该包只能丢弃,至此,足以证明iptables具有端口受限的属性。
所以,iptables是货真价实的Symmetric NAT。

附:
1、Stun全称Simple Traversal of UDP Through NATs,所以本文所涉及的包,皆为UDP包。
2、本文虽然是针对linux下iptables的分析,但倘若把本文中关键词“iptables”换成“Win2000下的ics或nat”,则本文的分析过程完全适用于Win2000下的ics或nat。即理论上Win2000下的ics或nat也是货真价实的Symmetric NAT,但实际上,凡按照Stun协议标准设计的NAT分类检测工具对其检测结果也必然是Port restricted NAT。其实,不光是linux下iptables、或者Win2000下的ics或nat、再或者任何其他NAT产品,只要他们遵循和iptables一样的两条转换原则,那么他们在Stun协议下的表现是完全一样的。
3、虽然Win2000下的ics或nat在Stun协议下的表现和iptables完全一样,但其NAT时所遵循的原则1和iptables还是略有差别:iptables对内部私网机器来的所有源端口都将适用Preserves port number,除非确因原则2发生冲突而不得不更换源端口号,但在更换端口号时并不遵循内部端口+1原则(似乎没有规律)。Win2000下的ics或nat则仅对内部私网机器来的部分源端口(1025–3000)适用Preserves port number,对于那些超过3000的源端口号或者因原则2发生冲突的端口号,系统从1025开始重新按序分配端口,在此过程中,仍然遵循如前两个原则,只是原则1不再Preserves port number而已(在不与原则2发生冲突的前提下,尽量重复使用小的端口号,故使用1025的几率远远大于1026、1027…)。

 Posted by at 上午 3:31
11月 222008
 
穿越NAT的意义:
  NAT是为了节省IP地址而设计的,但它隐藏了内网机器的地址,“意外”起到了安全的作用。对外不可见,不透明的内部网络也与互联网的“公平”应用,“相互共享”的思想所不容,尤其是P2P网络中“相互服务”的宗旨,所以穿越NAT,让众多内部网络的机器也参与到P2P网络中的大集体中来,一直是P2P开发者的所希望的。穿越NAT需要借助外部的支持,说白了就是“内外勾结”,骗过NAT。很多P2P网络成功地实现了这一目标,但还是有一些“遗憾”—并非所有的情况下都可以。由于客户端是主动登录P2P网络才可穿越,所以P2P的方式也没有违背企业的内部管理原则,毕竟“自由世界”的加入都是自觉自愿的。
  NAT原理:
  NAT(Network Address Translation)网络地址转换/网络地址翻译。
  工作原理:NAT主要的通过对数据包头的地址替换来完成内网计算机访问外网服务的。当内部机器要访问外部网络时,NAT设备把内部的IP1与端口号1(网络层地址与传输层地址),转换成NAT的外部IP2与新的端口号2,再送给外部网络,数据返回时,再把目的为IP2:端口2的数据包替换为IP1:端口1,送给内网机器。若通讯协议的内容中有IP地址的传递,如FTP协议,NAT在翻译时还要注意数据包内涉及协议地址交互的地方也要替换,否则协议就会出现地址混乱。在NAT设备中维护了这个要替换地址的映射表,并根据内部计算机的通讯需求维护该表。外部网络来数据包能否进入NAT,主要是看是否已经有可映射的表项,若没有就会丢弃。
  
  NAT的外部公网地址可以是一个IP,也可以是一个网段,形成地址池。NAT还可以把某个外网地址直接影射给内网的某个服务器,让外网的用户可以直接访问到这台服务器。NAT的工作的隐藏内网的机器,但允许内网主动打开到外网的通讯“通道”,也就是建立映射表项。
  NAT给P2P带来的问题是:NAT只允许单方面发起连接,通讯的双方不是平等的,P2P网络的基础有了问题,具体的表现为:
  内网主机IP是私有的,外部主机看不到,也无法主动发起连接
  即使知道了内网IP,但NAT会丢弃没有在影射表的数据包
  内网主机可以作为客户端访问外网,但不能作为服务器提供服务
  当两个主机都位于各自的NAT之后,要实现P2P的连接,就不仅是谁主动的问题,而是如何解决在两个NAT上同时有对方映射表项的问题。
STUN协议(IETF RFC 3489):
  STUN协议是一种通道协议,可以作为正式通讯前的通路建立,它采用的是用户终端干预的一种方法,可以解决应用协议内部传递IP地址给NAT带来的麻烦。用户通过其他方法得到其地址对应在NAT出口上的对外地址,然后在报文负载中所描述的地址信息就直接填写NAT上对外地址,而不是内网的私有IP,这样报文的内容在经过NAT时就按普通的NAT流程转换报文头部的IP地址即可,负载内的IP地址信息无需再修改。利用STUN的思路可以穿越NAT。STUN协议是客户端/服务器协议,分两种请求方式:一是UDP发送的绑定请求(Binding Requests),二是TCP发送的秘密请求(Shared Secret Requests)。绑定请求用于确定NAT分配的绑定地址。
  STUN标准中,根据内部终端的地址(P:p)到NAT出口的公网地址(A:b)的影射方式,把NAT分为四种类型:
  
  1. Full Cone:来自相同的内部地址的请求消息映射为相同的外部地址,与外部地址(目的地址)无关。映射关系为P:p↔A:b,任何外部主机可通过(A:b)发送到数据到(P:p)上。
  2. Restricted Cone:来自相同的内部地址的请求消息映射为相同的外部地址,返回的数据只接受该内部节点曾发数据的那个目的计算机地址X。映射关系为P:p↔A:b↔X,只有来自X的数据包才可通过(A:b)发送到数据到(P:p)上。
  3. Port Restricted Cone:来自相同的内部地址的请求消息映射为相同的外部地址,返回的数据只接受该内部节点曾发数据的那个目的地址X:x。映射关系为P:p↔A:b↔X:x,只有来自X:x的数据包才可通过(A:b)发送到数据到(P:p)上。
  4. Symmetric(对称) NAT:只有来自相同的内部地址(P:p),并且发送到同一个地址(X:x) 的请求消息,才被映射为相同的外部地址(A:b),返回的数据只接受该内部节点曾发数据的那个目的地址X:x。映射关系为P:p↔A:b↔X:x,当(P:p)访问(Y:y)时,映射为P:p↔B:c↔Y:y。
  P2P利用STUN穿越NAT:
  位于NAT后面终端A与B要穿越NAT直接通讯,可以借助在公网上的第三者Server来帮助。
  穿越NAT的情况分为为两种方式:1、一方在NAT之后,一方在公网上。这种情况相对简单,只要让NAT之后的终端先发起通讯,NAT就没有作用了,它可以从Server上取得另一个Peer的地址,主动连接,回来的数据包就可以方便地穿越NAT。2、双方都在NAT之后,连接的成功与否与两个NAT的类型有关。主要的思路的先通过终端与Server的连接,获得两个终端在NAT外部的地址(IP与端口号),再由终端向对方的外部地址发邀请包,获取自己与对方通讯的外部地址,俗称为“打洞”。关键是获取了NAT外部映射的地址,就可以发包直接沟通,建立连接。但当一方是对称型,另一方是Port Restricted或对称型时,无法有效获取外部地址,邀请包无法到达对方,也就无法穿越NAT。具体的分析可以根据两个NAT的类型分成若干情况分析,这里给一般的穿越例子。
  
实例:UDP穿越NAT:
  A登录Server,NAT A分配端口11000,Server得到A的地址为100.10.10.10:11000
  B登录Server,NAT B分配端口22000,Server得到B的地址为200.20.20.20:22000
  此时B会把直接来自A的包丢弃,所以要在NAT B上打一个方向为A的洞,那么A就可以向200.20.20.20:22000发送数据了
  打洞的指令来自Server。B向A的地址100.10.10.10:11000发一个UDP报文,被NAT A丢弃,但在NAT B上建立映射记录,NAT B不在丢弃来自A的报文。
  Server通知A可以通讯,A发起数据UDP包给B,NAT B放行,B收到A的包,双方开始通讯
  注:若是对称NAT,当B向A打洞的端口要重新分配(NAT A不会再分配11000端口),B无法获取这个端口,所以不适用本方法。
  实例:TCP穿越NAT:
  A登录Server,NAT A分配端口11000,Server得到A的地址为100.10.10.10:11000
  B登录Server,NAT B分配端口22000,Server得到B的地址为200.20.20.20:22000
  A向B发送TCP数据包SYN:192.168.10.11:1234=>200.20.20.20:22000,在NAT A上打洞
  B向A发送TCP数据包SYN:192.168.20.22:1234=>100.10.10.10:11000,在NAT B上打洞
  通道建立,A与B三次握手建立TCP连接
 Posted by at 上午 3:26
11月 222008
 
IP 协议中的checksum
Author:zfive5(zhaozidong)
Email :zfive5@yahoo.com.cn
 
最近一段时间,对网络又开始追根溯源,最好的办法就是打开开源协议栈看一个究竟,不求写一个完整的ip协议栈,但求通达解惑!
 
众所周知,IP头定义如下:
struct IPHeader
{
   unsigned char ver_hlen;  
   unsigned char tos;
   unsigned short  len;
   unsigned short  id;
   unsigned short  offset;
   unsigned char  ttl;
   unsigned char  type;
   unsigned short   cksum_header;
   unsigned long  ipsrc;
   unsigned long  ipdest;
   /*
     后面可能存在option数据
   */
}
IP头中的大多字段都好理解,只要一本TCP/IP入门的书就可以明明白白了,对cksum字段理解,如果只是看书,到头来很可能还不清楚怎么算它!
 
关于cksum_header的描述在《tcp/ip卷一》中是这样描述的:
   首部检验和字段是根据I P 首部计算的检验和码。它不对首部后面的数据进行计算。I C M P I G M P U D P T C P 在它们各自的首部中均含有同时覆盖首部和数据检验和码。
   为了计算一份数据报的I P 检验和,首先把检验和字段置为0 。然后,对首部中每个16 bit进行二进制反码求和(整个首部看成是由一串16 bit 的字组成),结果存在检验和字段中。当收到一份I P 数据报后,同样对首部中每个16 bit 进行二进制反码的求和。由于接收方在计算过,程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全1 。如果结果不是全1 (即检验和错误),那么I P 就丢弃收到的数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。
 
不知道有多少能人看完此描述后,写出算法或函数来!
 
正确的函数如下:
unsigned short CheckSum(unsigned short *szBUF,int iSize)
{       
       unsigned long ckSum=0;
    for(;iSize>1;iSize-=sizeof(unsigned short))
              ckSum+=*szBUF++;
 
       if(iSize==1)
              ckSum+=*(unsigned char *)szBUF;
 
       ckSum=(ckSum>>16)+(ckSum&0xffff);
       ckSum+=(ckSum>>16);
       return(unsigned short )(~ckSum);
}
 
让我们假设一个IP头数据,来解cksum的惑!
 
IP头数据:
 
01000101   / ver_hlen;
00000000   /tos*/
00000000 00000010/*len*/
00000000 00000000/*id*/
00000000 00000000/*offset*/
00000001/*ttl*/
00010001/*type*/
00000000 00000000/*cksum0)*/
01111111 00000000 00000000 0000001/*sip*/
01111111 00000000 00000000 0000001/*dip*/
 
运算过程(注意是大端格式加):
 
01000101 00000000
01000101 00000000
00000000 00000010
01000101 00000010
00000000 00000000
01000101 00000010
00000000 00000000
01000101 00000010
00000100 00010001
01001001 00010011
00000000 00000000
01001001 00010011
01111111 00000000
11001000 00010011
00000000 00000001
11001000 00010100
01111111 00000000
01000111 00010100
00000000 00000001
和: 101000111 00010101
                  
ckSum=(ckSum>>16)+(ckSum&0xffff)后:
 
   00000000 00000001                1
 01000111 00010101
和: 01000111 00010110
 
ckSum+=(ckSum>>16)后:
   
   01000111 00010110
   00000000 00000000
和: 01000111 00010110
 
~: 10111000 11101000(效检和)
 
 
运算过程(注意用小端格式加):
 
00000000 01000101
00000000 01000101
00000010 00000000
00000010 01000101
00000000 00000000
00000010 01000101
00000000 00000000
00000010 01000101
00010001 00000100
00010011 01001001
00000000 00000000  
00010011 01001001
00000000 01111111  
00010011 11001000
00000001 00000000  
00010100 11001000
00000000 01111111  
00010101 01000111
00000001 00000000  
和: 00010110 01000111
ckSum=(ckSum>>16)+(ckSum&0xffff)后:
 
   00000000 00000000                1
 00010110 01000111
和: 00010110 01000111
 
ckSum+=(ckSum>>16)后:
   
   00010110 01000111
   00000000 00000000
和: 00010110 01000111
 
~: 11101001 10111000(效检和)
 
 
checksum211101000 10111000(小端)
 
checksum110111000 11101000(大端)
 
算法一样,说白了就是循环加,加到没有进位为止,然后在取反!
 
在现在《TCPIP卷二》中的cksum实现有以下语句:
while(sum>>16)
 sum=(sum&0xffff)+(sum>>16);
 
通过它更能说明就是在作循环加操作,现在又有一个疑问:
       ckSum=(ckSum>>16)+(ckSum&0xffff);
       ckSum+=(ckSum>>16);         
while(sum>>16)
   sum=(sum&0xffff)+(sum>>16);
等价吗?
 
等价,在一定条件下等价,大家都知道ip理论上最长是0xffff
那么
checksum最大不会超过:0xffff0000
这样>>16后为0xffff
0xffff+checksum最大0x1fffe
0x1fffe >>16+0x1fffe=0xffff
注意了没有了进位
所以得到等价的结论!
 
 
以前每读到cksum注解时,书上只是草草曰16位反码和之云云,没有强调进位也要参加运算一点征兆,直到最近写了不少程序才看清楚这个细节!
 
源码之下必解惑!
 Posted by at 上午 2:54
11月 162008
 

官方网站:http://www.tcptrace.org/

简介:

tcptrace is a tool written by Shawn Ostermann at Ohio University, for analysis of TCP dump files. It can take as input the files produced by several popular packet-capture programs, including tcpdump, snoop, etherpeek, HP Net Metrix, and WinDump. tcptrace can produce several different types of output containing information on each connection seen, such as elapsed time, bytes and segments sent and recieved, retransmissions, round trip times, window advertisements, throughput, and more. It can also produce a number of graphs for further analysis.

 Posted by at 上午 12:17