tcpdump+wireshark分析数据笔记(2)
来源:互联网 发布:百电通打电话要网络 编辑:程序博客网 时间:2024/05/16 08:17
TCP建立连接
图1中编号为3、4、5的是TCP建立连接的包,是TCP建立的三次握手的过程。PC2作为server端,启动监听程序,监听端口65044,一开始处于LISTEN状态。
图1
客户端发送SYN
图4 三次握手之第一次握手
TCP连接的建立需要三次的握手,图4是三次握手的第一次握手。PC1作为client端,发送一个SYN段指明打算连接的服务器端PC2。图4中红色框部分是IP首部,可以看到源地址为67.153.0.0,目的地址为67.153.0.10,协议为TCP。蓝色框部分是TCP首部,目标端口号为65044,序列号seq为0,Flags标志为SYN,表示这是一个SYN段。
PC1首先调用connect来发送SYN段,将执行主动打开。connect对应的系统调用是sys_connect。大致的一个函数调用流程如图5所示。
下面结合着图5中的流程来分析下内核相关代码。sys_connect系统调用调用tcp的BSD socket操作集inet_stream_ops对应的inet_stream_connect函数。
int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
int addr_len, int flags)
{
if (uaddr->sa_family == AF_UNSPEC) { /*地址簇未指定*/
err = sk->sk_prot->disconnect(sk, flags);
sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
goto out;
}
switch (sock->state) {
case SS_CONNECTED: /* 该BSD socket已连接*/
err = -EISCONN;
goto out;
case SS_CONNECTING: /* 该BSD socket正在连接*/
err = -EALREADY;
break;
case SS_UNCONNECTED: /* 该BSD socket还未连接*/
err = -EISCONN;
if (sk->sk_state != TCP_CLOSE)
goto out;
/*对于INET socket中的tcp连接,协议特有操作符集为tcp_prot
INET SOCKET 调用协议特有connect操作符 tcp_v4_connect函数*/
err = sk->sk_prot->connect(sk, uaddr, addr_len);
if (err < 0)
goto out;
/* 上面的调用完成后,连接并没有完成*/
sock->state = SS_CONNECTING;
err = -EINPROGRESS;
break;
}
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); /*获取链接超时时间*/
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
int writebias = (sk->sk_protocol == IPPROTO_TCP) &&
tcp_sk(sk)->fastopen_req &&
tcp_sk(sk)->fastopen_req->data ? 1 : 0;
/*发送sync后进入等待状态*/
if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))
goto out;
}
代码最开始会判断connect是指定的地址簇如果为AF_UNSPEC,则不会建立链接,直接返回。(不理解为什么?地址簇未指定不是可以接收ipv4和ipv6的吗?)
接着判断如果该socket还未链接,就调用tcp操作集的tcp_v4_connect函数建立链接。下面会看下此函数具体做了些什么?
发送sync后,会先获取链接的超时时间,然后调用inet_wait_for_connect进入等待状态,等待超时时间内收到服务器段回的sync ack。
下面接着分析tcp_v4_connect函数:
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
/*目标地址,也即路由的下一跳地址设置为connect参数指定值*/
nexthop = daddr = usin->sin_addr.s_addr;
inet_opt = rcu_dereference_protected(inet->inet_opt, sock_owned_by_user(sk));
/*如果使用源地址路由,下一跳地址设置为ip选项中的faddr*/
if (inet_opt && inet_opt->opt.srr) {
if (!daddr)
return -EINVAL;
nexthop = inet_opt->opt.faddr;
}
/*调用路由相关函数获取出口信息*/
rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
IPPROTO_TCP,
orig_sport, orig_dport, sk);
/*获取的路由为广播或者多播,由于tcp不支持,直接返回不可达错误*/
if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
ip_rt_put(rt);
return -ENETUNREACH;
}
tcp_set_state(sk, TCP_SYN_SENT); /*状态从CLOSING转到TCP_SYN_SENT*/
/*获取合适的源端口号,将连接加入到bind链表中*/
err = inet_hash_connect(&tcp_death_row, sk);
/*找到合适源端口后重新建立路由表项*/
rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
inet->inet_sport, inet->inet_dport, sk);
err = tcp_connect(sk); /*完成连接*/
此函数在调用tcp_connect完成连接之前,主要的工作就是路由相关的一些事情。首先是调用路由模块获取出口相关的信息;如果路由时广播或者多播,就返回不可达的错误信息,不建立连接;接着设置client端为SYNC_SENT状态;
inet_hash_connect函数的实现主要是查找合适的源端口,并将此端口与合适的bind表项绑定。Tcp内核表可参见inet_hashinfo结构体,由三个表项组成,分别是ehash, bhash, listening_hash。ehash表对应于socket处在tcp的ESTABLISHED状态,listening_hash表对应于socket处在tcp的LISTEN状态,bhash对应于socket已绑定了地址。这里socket还在建立,使用bhash表。
找到合适的源端口后,调用ip_route_newports函数重新建立路由表项。最后调用函数tcp_connect来发送sync段,完成连接建立。
下面接着分析tcp_connect函数的具体实现:
int tcp_connect(struct sock *sk)
{
tcp_connect_init(sk); /*初始化此socket链接的参数*/
/*分配一个skb的内存空间,sync包不包含数据,大小为tcp头*/
buff = alloc_skb_fclone(MAX_TCP_HEADER + 15, sk->sk_allocation);
skb_reserve(buff, MAX_TCP_HEADER); /*为tcp头预留空间*/
/*构建一个不包含数据的sync skb包*/
tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
tp->retrans_stamp = TCP_SKB_CB(buff)->when = tcp_time_stamp; /*保存数据包发送时间*/
tcp_connect_queue_skb(sk, buff); /*将此skb包加入发送队列中*/
TCP_ECN_send_syn(sk, buff); /*设置ECN*/
/*对于是否开启TFO,调用不同的函数来实现发送sync*/
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
/*重传定时器*/
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
}
此函数功能就是构建一个不包含数据的sync包并发送出去。在发送sync报文之前,初始化此次socket连接的参数;分配sync报文的内存空间。
TFO(TCP fast open)是一种非标准的TCP行为,它利用三次握手的sync报文来传递数据。这里如果使用TFO,就调用tcp_send_syn_data来发送sync报文,否则调用tcp_transmit_skb来发送sync报文。
下面接着分析不支持TFO情况下tcp_transmit_skb函数的具体实现:
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
gfp_t gfp_mask)
{
/*发送时要保留skb以备份重传,所以大部分时间都设置了clone_it,不过发送ack除外*/
if (clone_it) {
if (unlikely(skb_cloned(skb)))
skb = pskb_copy(skb, gfp_mask);
else
skb = skb_clone(skb, gfp_mask);
}
/*获取tcp头的选项部分信息,分成sync包和已建立连接的普通包两种情况*/
if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
else
tcp_options_size = tcp_established_options(sk, skb, &opts, &md5);
tcp_header_size = tcp_options_size + sizeof(struct tcphdr);
/*已发出但还未确认的数据包为0,初始化拥塞控制钩子*/
if (tcp_packets_in_flight(tp) == 0)
tcp_ca_event(sk, CA_EVENT_TX_START);
/* Build TCP header and checksum it. */
th = tcp_hdr(skb);
th->source = inet->inet_sport; //源端口号
th->dest = inet->inet_dport; //目的端口号
th->seq = htonl(tcb->seq); //seq序列号
th->ack_seq = htonl(tp->rcv_nxt); //确认号
*(((__be16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) |
tcb->tcp_flags); //首部长度和标志
if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) { //sync段报文,窗口设置为初始值
th->window = htons(min(tp->rcv_wnd, 65535U));
} else { //其他报文,调用tcp_select_window计算当前窗口的大小
th->window = htons(tcp_select_window(sk));
}
th->check = 0; //TCP校验和
th->urg_ptr = 0; //紧急指针
/*在tcp协议中调用ip_queue_xmit发送报文,进入ip层*/
icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);
前面在__inet_stream_connect函数中,发送sync包之后,会先获取链接超时时间,然后调用inet_wait_for_connect进入等待状态,等待sync Ack的回来。
图6 握手示意图1
到目前为止客户端发送SYN包已经完成,下面看下服务器在收到客户端发送过来的sync包之后的情况。
- tcpdump+wireshark分析数据笔记(2)
- tcpdump+wireshark分析数据笔记(1)
- linux网络工具使用tcpdump和使用wireshark进行数据分析
- Android下使用TcpDump抓包Wireshark分析数据
- Android下使用TCPDUMP抓包Wireshark分析数据啦。
- Android下使用TCPDUMP抓包Wireshark分析数据啦。
- Android下使用TCPDUMP抓包Wireshark分析数据啦
- Android下使用TCPDUMP抓包Wireshark分析数据
- tcpdump 抓包让wireshark来分析
- tcpdump 抓包让wireshark来分析
- tcpdump 抓包,wireshark分析。
- Android 流量分析 tcpdump & wireshark
- tcpdump 抓包让wireshark来分析
- TCPDump抓包&WireShark分析
- tcpdump 抓包让wireshark来分析
- tcpdump 抓包让wireshark来分析
- tcpdump抓包-Wireshark分析
- linux tcpdump分析抓包用wireshark分析
- hihocoder #1040 矩形判断
- android的linearlayout
- 如何禁止函数的传值调用
- 【Linux】RedHat9.0添加module和编译内核
- Hibernate3.6 入门(笔记二):映射_inverse_lazy
- tcpdump+wireshark分析数据笔记(2)
- 关于validate验证表单与js语法的一个bug
- lightoj 1154 - Penguins 【拆点建图后枚举汇点 满流判可行解】
- 函数指针与指针函数
- OpenCV之滑动条
- C#程序员经常用到的10个实用代码片段
- Android应用程序的消息处理机制
- hibernate二级缓存VS查询缓存
- 利用大白菜制作多系统启动U盘(ubuntu+windows)