Skip navigation.
主页

jccz_zys的Blog

jccz_zys 的图片

understanding linux network internals 第二十六章

|

translated by jccz_zys
2007/5

jccz_zys 的图片

结合linux内核源码理解SYN_RECV状态

|

(以下基于linux内核2.4.0)
SYN_RECV状态,顾名思义,是收到SYN包后应该置的状态。关于SYN_RECV状态,受某些教科书的误导,我以前一直理解为服务器收到SYN包后应该置此状态。也没细想到底是置那个socket的状态,最近在看三次握手协议在linux内核中的实现时,才仔细思考这个问题应该是置连接套接字的状态而非监听套接字的状态。
通常,SYN包只用于TCP三次握手协议中。常见的tcp三次握手协议过程(当然还有同时连接、
半连接等其它一些情况)如下:
1、client SYN包---> server
2、client <---SYN包/ACK包 server
3、client ACK包---> server
根据tcp状态图,对应下述4个状态的变化
a、client发送完毕,状态变成SYN_SEND;
b、server收到SYN报并发送ack确认包和SYN包,状态变为SYN_RECV
c、client发送ack包完毕,状态变成ESTABLISHED
d、server发送ack包完毕,状态变成ESTABLISHED

在linux内核中,上述几个状态对应为TCP_SYN_SEND、TCP_SYN_RECV、TCP_ESTABLISHED.
RFC793中关于SYN_RECV状态的描述如下:
SYN-RECEIVED - represents waiting for a confirming connection
request acknowledgment after having both received and sent a
connection request.
从上面可以看出,这个状态是在本端接收到对端连接请求,并发送连接对端请求后,等待对端应答时所置的状态。所以,本质上连接的过程是双方请求应答的来回,应该称四次握手,只是常见的应用以c/s模式为主,而linux、包括绝大部分操作系统都把服务器端的应答和请求封装在一个包里面。
但在linux内核中,却是在监听套接字收到了客户端的ACK包后,才创建连接套接字并初始化为TCP_SYN_RECV状态,如下函数调用关系:
tcp_v4_rcv-->tcp_v4_do_rcv-->tcp_v4_hnd_req-->tcp_check_req-->
tcp_v4_syn_recv_sock-->tcp_create_openreq_child...
struct sock *tcp_create_openreq_child(struct sock *sk, struct open_request *req, struct sk_buff *skb)
{
struct sock *newsk = sk_alloc(PF_INET, GFP_ATOMIC, 0); /*创建连接sock结构*/

if(newsk != NULL) {
struct tcp_opt *newtp;
...
memcpy(newsk, sk, sizeof(*newsk));
newsk->state = TCP_SYN_RECV; /*置初始状态为SYN_RECV*/

//以下为一些初始化newsk结构的操作
...
}
这里似乎都正常了,但还有一点,服务器收到ACK包后,状态应该改为连接状态,而此时连接套接字的状态还是TCP_SYN_RECV
原因在于现在对ack包还没处理完,^_^,如下:
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
...
if (sk->state == TCP_LISTEN) { //此处是监听套接字的状态
struct sock *nsk = tcp_v4_hnd_req(sk, skb); //获得了上面讲的连接套接字
if (!nsk)
goto discard;

if (nsk != sk) { //显然监听与连接套接字不等
if (tcp_child_process(sk, nsk, skb)) //此处调用tcp_rcv_state_process置套接字为连接建立状态
goto reset;
return 0;
}
}
...
}

可见,在linux内核中,SYN_RECV状态的保持时间是非常短暂的(也很难创建条件让此状态保持),这也是我们实际应用中通过netstat基本看不到这个状态的原因。

jccz_zys 的图片

位域的字节序表示法

|

字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,有小端、大端两种字节顺序。小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处。不同的CPU有不同的字节序类型,基于X86平台的PC机是小端字节序的;而网络字节序就是大端字节序。
关于位域,通常是以结构的形式出现。且位域定义时一般不是规则的字节数,以二进制表示通常少于8位,多个位域最好字节对齐(不足补0),那位域的在内存中是如何布局的呢?我写了个测试程序:
#include
int main()
{
struct bitfield {
int ia:2;
int ib:6;
} field;
field.ia=1;
field.ib=4;

char * c;
c=(char *)&field;
printf("%d\n",*c);
return 1;
}

在X86上运行结果是17(000100 01),即附件图中所示的内存布局。说明相邻的位域字段也要遵循与cpu一致的内存数据的表示法。
但有一点不明白,在内核中(linux2.4.0内核,include/linux/ip.h),定义了如下ip头结构:
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;
#else
#error "Please fix "
#endif
__u8 tos;
...
}
根据前面的测试,可知些x86平台下,在内存中ihl排在低位,如果要转化位网络字节序,则应该是ihl排在高位,但网络上或很多教科书上关于IP数据报格式(图片)是版本+首部长度+服务类型+...,不知道这个格式怎么与这种图片怎么与struct iphdr的定义对应起来,待进一步研究了在来回答这个问题。

jccz_zys 的图片

understanding linux network internals 第八章:设备注册和初始化

translated by jccz_zys
2007/01

Syndicate content