一:tcp分析:应用层
,
1:tcp协议流程分析:
程序流程:Af_inet.c(/net/ipv4)文件中;用于tcp,ip协议接口的初始化。
1:inet_init:表示会话层初始化
1.1:协议层注册:
1.2:添加所有的基本控制
1.3:设置tcp,urp,icmp等初始化
1.4:ipv4初始化
struct proto tcp_prot = { //用于初始化tcp协议的接口
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
.sendpage = tcp_sendpage,
.backlog_rcv = tcp_v4_do_rcv,
.release_cb = tcp_release_cb,
.mtu_reduced = tcp_v4_mtu_reduced,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.sockets_allocated = &tcp_sockets_allocated,
.orphan_count = &tcp_orphan_count,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp_sock),
.slab_flags = SLAB_DESTROY_BY_RCU,
.twsk_prot = &tcp_timewait_sock_ops,
.rsk_prot = &tcp_request_sock_ops,
.h.hashinfo = &tcp_hashinfo,
.no_autobind = true,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_tcp_setsockopt,
.compat_getsockopt = compat_tcp_getsockopt,
#endif
#ifdef CONFIG_MEMCG_KMEM
.init_cgroup = tcp_init_cgroup,
.destroy_cgroup = tcp_destroy_cgroup,
.proto_cgroup = tcp_proto_cgroup,
#endif
};
if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)用来添加tcp的接收协议控制
static const struct net_protocol tcp_protocol = {
.early_demux = tcp_v4_early_demux,
.handler = tcp_v4_rcv,//,当验证是tcp协议,则调用该接口,用来完成tcp后功能
.err_handler = tcp_v4_err,
.no_policy = 1,
.netns_ok = 1,
};
tcp协议层接口函数
rc = proto_register(&tcp_prot, 1);
ip包接收接口设置
static struct packet_type ip_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
};
二:接收:
网卡接收到数据后:stmmac_rx:netif_receive_skb(标准接收接口)->__netif_receive_skb_core
如果采用gro装包模式:napi_gro_receive
2.1:__netif_receive_skb_core:
https://www.cnblogs.com/wanpengcoder/p/7577088.html
net_timestamp_check(!netdev_tstamp_prequeue, skb);记录收包时间
orig_dev = skb->dev;记录网络设备
skb_reset_transport_header(skb);设置传输头,指向ip层,
skb->skb_iif = skb->dev->ifindex;:收包网络设备索引
list_for_each_entry_rcu(ptype, &ptype_all, list) { //tcpdump抓包接口
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
if (vlan_tx_tag_present(skb))//判断是vlan接口
if (vlan_do_receive(&skb))vlan处理函数,
type = skb->protocol;得到上册传输协议,然后调用配置好的接口ip_packet_type :(ip包的接口是ip_rcv)
2.2:ip_rcv
https://www.cnblogs.com/wanpengcoder/p/7577398.html
IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);获取net信息
iph = ip_hdr(skb);获取ip头if (!pskb_may_pull(skb, iph->ihl*4)) 测试实际使用的ip头
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);//钩子函数,选项处理,路由查询,并购根据路由决定数据是转发还是发往本机
ip_rcv_finish:函数主要做路由选择
if (!skb_dst(skb)) //从外界接收到的数据包,不会包含路由信息,ip_route_input函数会根据路由表设置路由信息
rt = skb_rtable(skb);//skb_rtable函数等同于skb_dst,获取skb->dst
return dst_input(skb); //函数指针,如果是递交到本地的则为ip_local_deliver,若是转发则为ip_forward.
ip_local_deliver(struct sk_buff *skb)->ip_local_deliver_finish->ret = ipprot->handler(skb);//调用到tcp层接口。
2.3:tcp接收入口函数:tcp_v4_rcv->tcp_v4_do_rcv->tcp_rcv_established->
tcp_v4_rcv:设置tcp块,查找控制块。这只块的不同状态,包括等待状态,响应状态,接收状态,接收tcp包
https://www.cnblogs.com/wanpengcoder/p/11751763.html
{
const struct iphdr *iph;
const struct tcphdr *th;
struct sock *sk;
int ret;
struct net *net = dev_net(skb->dev);
if (skb->pkt_type != PACKET_HOST)
goto discard_it;
/* Count it even if it's bad */
TCP_INC_STATS_BH(net, TCP_MIB_INSEGS);
//检查头部数据,若不满足,拷贝分片
if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
goto discard_it;
//取tcp头
th = tcp_hdr(skb);
// printk(KERN_EMERG "th->doff: %d\n",th->doff); //7
if (th->doff < sizeof(struct tcphdr) / 4)
goto bad_packet;
//检查tcp首部和tcp偏移是否越界
if (!pskb_may_pull(skb, th->doff * 4))
goto discard_it;
// printk(KERN_EMERG "Here I am66: tcp_v4_rcv\n");
// printk(KERN_EMERG "tcp_v4_rcv:skb->ip_summed: %d\n",skb->ip_summed);
if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))
goto csum_error;
// printk(KERN_EMERG "Here I am77: tcp_v4_rcv\n");
th = tcp_hdr(skb);
iph = ip_hdr(skb);
//获取开始序列号,结束序列号(实际是期待tcp确认),确认序列号
//获取ip头服务字段
TCP_SKB_CB(skb)->seq = ntohl(th->seq);
TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
skb->len - th->doff * 4);
TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
TCP_SKB_CB(skb)->when = 0;
TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);
TCP_SKB_CB(skb)->sacked = 0;
// printk(KERN_EMERG "Here I am2: tcp_v4_rcv\n");
//查找对应的sock块,根据思源数组查找对应的sock结构
//首先用__inet_lookup_established查找函数已经处于establish状态的链接
//匹配处于listen状态的sock,这个时候实际是被动的接收来自其他主句的链接请求
sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
if (!sk)
goto no_tcp_socket;
process:
//检查sock是否处于半等待状态
if (sk->sk_state == TCP_TIME_WAIT)
goto do_time_wait;
if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) {
NET_INC_STATS_BH(net, LINUX_MIB_TCPMINTTLDROP);
goto discard_and_relse;
}
//检查ipsec规则
if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
goto discard_and_relse;
nf_reset(skb);
//检查bpf规则
if (sk_filter(sk, skb))
goto discard_and_relse;
// printk(KERN_EMERG "Here I am3: tcp_v4_rcv\n");
skb->dev = NULL;
//调用和socket是否互斥
bh_lock_sock_nested(sk);
ret = 0;
//检查用户态是否对socket锁定,如果为真,socket不能修改
if (!sock_owned_by_user(sk)) {
#ifdef CONFIG_NET_DMA
struct tcp_sock *tp = tcp_sk(sk);
if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
tp->ucopy.dma_chan = net_dma_find_channel();
if (tp->ucopy.dma_chan)
ret = tcp_v4_do_rcv(sk, skb);
else
#endif
{
if (!tcp_prequeue(sk, skb))
//进入预处理队列
ret = tcp_v4_do_rcv(sk, skb);
}
} else if (unlikely(sk_add_backlog(sk, skb,
sk->sk_rcvbuf + sk->sk_sndbuf))) {
//如果数据包被用户京城锁定,则数据包进入后备处理队列,并且该进程
//进入套接字后备处理等待队列
bh_unlock_sock(sk);
NET_INC_STATS_BH(net, LINUX_MIB_TCPBACKLOGDROP);
goto discard_and_relse;
}
bh_unlock_sock(sk);
// printk(KERN_EMERG " tcp_v4_rcv_over\n");
sock_put(sk);
return ret;}
tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb):
if (sk->sk_state == TCP_ESTABLISHED)与sock已经建立了链接->tcp_rcv_established
if (sk->sk_state == TCP_LISTEN)处于监听状态,则调用tcp_v4_hnd_req,函数生成一个新的sock用于传输
if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len))//处理sk的状态转换
tcp_rcv_established:表示已经在网络接收的状态下,,首先根据首部预测是快速路径还是慢速路径,在快速路径中,对有数据的负荷进行不同的处理,若无数据,处理输入ack,释放该skb,检查是否有数据发送,有则发送,若有数据局,检查当前处理上下文,并且是期望速去的数据,则将数据加入到接收队列中,加入的方式合并到已有的数据段,或者加入到数据段的尾部,并唤醒用户进程有数据刻度,在慢路中,进行详细的校验,处理ack,处理紧急数据,处理接收数据段,其中数据段可能包含乱序的情况,进行有效的数据和ack的发送检查,
{
// unsigned int a1,a2, b1,b2, c1 ,c2,d1,d2;
struct tcp_sock *tp = tcp_sk(sk);
// printk(KERN_EMERG "tcp_rcv_established1\n");
if (unlikely(sk->sk_rx_dst == NULL))
inet_csk(sk)->icsk_af_ops->sk_rx_dst_set(sk, skb);
tp->rx_opt.saw_tstamp = 0;
//快速检查,序号正确,ack正确
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&
!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {
//tcp头部长度
int tcp_header_len = tp->tcp_header_len;
// printk(KERN_EMERG "len =%d,tcp_header_len=%d,tp->tcp_header_len =%d\n",len,tcp_header_len,tp->tcp_header_len);
/* Check timestamp
时间戳选项
*/
if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
if (!tcp_parse_aligned_timestamp(tp, th))
goto slow_path;
if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)
goto slow_path;
}
if (len <= tcp_header_len) {
/* Bulk data transfer: sender */
if (len == tcp_header_len) {
//有时间戳选项,所有接收端都确认完毕,保存时间戳
if (tcp_header_len ==
(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp);
//输入ack处理
tcp_ack(sk, skb, 0);
__kfree_skb(skb);
tcp_data_snd_check(sk);
return 0;
} else { /* Header too small */
TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}
} else {
int eaten = 0;
int copied_early = 0;
bool fragstolen = false;
//期待读取的期待接收的序列号一致,数据<=带读取数据长度
if (tp->copied_seq == tp->rcv_nxt &&
len - tcp_header_len <= tp->ucopy.len) {
#ifdef CONFIG_NET_DMA
if (tp->ucopy.task == current &&
sock_owned_by_user(sk) &&
tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {
copied_early = 1;
eaten = 1;
}
#endif
//读取当前进程上下文,控制块被用户锁定
if (tp->ucopy.task == current &&
sock_owned_by_user(sk) && !copied_early) {
__set_current_state(TASK_RUNNING);
//拷贝数据到msghdr
if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
eaten = 1;
}
if (eaten) {
if (tcp_header_len ==
(sizeof(struct tcphdr) +
TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp);
tcp_rcv_rtt_measure_ts(sk, skb);
__skb_pull(skb, tcp_header_len);
tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);
}
if (copied_early)
tcp_cleanup_rbuf(sk, skb->len);
}
//未拷贝数据,或者拷贝数据是失败
if (!eaten) {
// printk(KERN_EMERG "tcp_rcv_established:skb->ip_summed =%d\n",skb->ip_summed );
if (tcp_checksum_complete_user(sk, skb))
goto csum_error;
//skb长度>预分配长度
if ((int)skb->truesize > sk->sk_forward_alloc)
goto step5;
//有时间戳选项,数据均以确认完毕,更新时间戳
if (tcp_header_len ==
(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp);
tcp_rcv_rtt_measure_ts(sk, skb);
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);
/* Bulk data transfer: receiver */
//数据加入接收队列
eaten = tcp_queue_rcv(sk, skb, tcp_header_len,
&fragstolen);
}
tcp_event_data_recv(sk, skb);
//确认序列号确认数据
if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
/* Well, only one small jumplet in fast path... */
tcp_ack(sk, skb, FLAG_DATA);
//检查是否有数据要发送,需要则发送
tcp_data_snd_check(sk);
if (!inet_csk_ack_scheduled(sk))
goto no_ack;
}
if (!copied_early || tp->rcv_nxt != tp->rcv_wup)
__tcp_ack_snd_check(sk, 0);
no_ack:
// printk(KERN_EMERG "tcp_rcv_established8\n");
#ifdef CONFIG_NET_DMA
if (copied_early)
__skb_queue_tail(&sk->sk_async_wait_queue, skb);
else
#endif
if (eaten)
kfree_skb_partial(skb, fragstolen);
sk->sk_data_ready(sk, 0);
return 0;
}
}
}
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags && TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&
!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) //快速检查,序号检查,ack检查
拷贝tcp数据到用户空间:
tcp_copy_to_iovec->skb_copy_and_csum_datagram_iovec->skb_copy_datagram_iovec->memcpy_toiovec->copy_to_user
至此tcp协议传输到用户空间。
三:tcp发包:socket建立连接以后,用户态使用send发送tcp包,send调用tcp_sendmsg来实现,这个函数在中的参数: .sendmsg = tcp_sendmsg,tcp_sendmsg(net/ipv4/tcp.c)
https://blog.csdn.net/xiaoyu_750516366/article/details/85228023
tcp_sendmsg->tcp_push->__tcp_push_pending_frames->tcp_write_xmit->tcp_transmit_skb->err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);(调用队列发送函数)
主要功能:发送队列尾部skb尚未发送的而且还有的剩余空间,将用户缓存中的数据copy进去,如果没有空间,则申请一个空间,再copy数据,如果一个空间不够,则在申请固定的skb,知道所有的数据copy完,或者skb的缓存空间无法申请,或者缓存达到限制,将申请的skb放到队列的尾部。在调用tcp_push,__tcp_push_pending_frames或者tcp_push_one函数队列。
iovlen = msg->msg_iovlen; //应用层的数据保存在msg中,以数组方式组织,msg_iovlen为数组大小,
iov = msg->msg_iov; //msg_iov为数组的第一个元素
copied = 0;//本次能够写入的tcp字节数,
size_t seglen = iov->iov_len; //每个元素包含的数据可以不同,每个元素记录在自己的iov_len字段中
unsigned char __user *from = iov->iov_base;//from拷贝的字节起点
if (!sk_stream_memory_free(sk))检车内存分配是否超限,如果会则先等待可用的内存。
数据拷贝分为内外两层循环,外层循环遍历数组,内存将一个数组拷贝完成,寻找skb,如果当前发送队列最后一个skb可以继续听填充,那么先往skb中填充数据,如果没有现成的skb可用,那么就分配一个,找到skb后,决定往哪个skb中拷贝数据,线性优先缓冲,拷贝过程中,调用不同接口,进行数据发送
3.1:tcp_push
tcp_send_head :是否有数据未被发送, tcp_mark_urg:设置要发送的OOB数据,记录数据的下一个字节的序列号 __tcp_push_pending_frames:发送skb数据
3.2:tcp_write_xmit :根据拥塞窗口,发送窗口,nagle算法判断发送数据以及发送多少,即新放入的数据不会立即发送,,并且发包时只发送skb的副本,原来的skb会一直呆在发送队列中,如果数据丢失,则tcp会将数据重新发送一次,直到数据被确认
https://blog.csdn.net/Al_xin/article/details/52304732
tcp_mtu_probe:获取mtu数据,tcp_send_head获取最老的skb头,tcp_init_tso_segs:获取网卡将skb分割成段的个数 ,cwnd_quota = tcp_cwnd_test(tp, skb);计算拥有阻塞窗口容许的字节数, if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))检测发送窗口是否容许发送数据
if (tso_segs == 1) { //网卡按一个包发送数据
if (unlikely(!tcp_nagle_test(tp, skb, mss_now, (tcp_skb_is_last(sk, skb) ?
nonagle : TCP_NAGLE_PUSH)))) // nagle算法是否发送当前包
break;
} else {
if (!push_one && tcp_tso_should_defer(sk, skb)) //将skb分割成多个包发送
break;
}
limit = tcp_mss_split_point(sk, skb, mss_now, min_t(unsigned int,cwnd_quota,
sk->sk_gso_max_segs))//计算网卡一次性发送的字节数
if (skb->len > limit &&unlikely(tso_fragment(sk, skb, limit, mss_now, gfp))) 包过大,拆成两个包,最后减为与limit一致
if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp))):发送数据的(重要。。。。。)
sent_pkts += tcp_skb_pcount(skb);计算已经发送的包的个数。
3.3:tcp_transmit_skb:发送skb队列
if (likely(clone_it)):clone一个副本发送出去,等待队列中的ack确认后在删除,
tcp_options_size = tcp_established_options(sk, skb, &opts, &md5);构建确认后的配置信息,
tcp_header_size = tcp_options_size + sizeof(struct tcphdr);:计算tcp头长度,
skb_push(skb, tcp_header_size);skb->data指向tcp的数据头,
th->source = inet->inet_sport; //设置源端口
th->dest = inet->inet_dport; //设置目的端口
th->seq = htonl(tcb->seq); //设置序列号
th->ack_seq = htonl(tp->rcv_nxt);//设置确认号
if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))):有紧急包并且目前的序列号小于紧急数据的下一个字节的序列号。
icsk->icsk_af_ops->send_check(sk, skb);:调用tcp_v4_check检查tcp校验和
err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);调用ip_queue_xmit构建IP头并将数据发送出去。(net/ipv4/tcp_output.c文件中)(调用函数在net/dccp/ipv4.c中)
传输层和网络层联系的函数是tcp_transmit_skb(...),net/dccp/tcp_ipv4.c文件中,用于传输层和网络层的数据接口的传输
const struct inet_connection_sock_af_ops ipv4_specific = {
.queue_xmit = ip_queue_xmit,
.send_check = tcp_v4_send_check,
.rebuild_header = inet_sk_rebuild_header,
.sk_rx_dst_set = inet_sk_rx_dst_set,
.conn_request = tcp_v4_conn_request,
.syn_recv_sock = tcp_v4_syn_recv_sock,
.net_header_len = sizeof(struct iphdr),
.setsockopt = ip_setsockopt,
.getsockopt = ip_getsockopt,
.addr2sockaddr = inet_csk_addr2sockaddr,
.sockaddr_len = sizeof(struct sockaddr_in),
.bind_conflict = inet_csk_bind_conflict,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_ip_setsockopt,
.compat_getsockopt = compat_ip_getsockopt,
#endif
};
四:ip层发包(net/ipv4/ip_output.c文件中)
4.1:int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)完成面向套接字的输出,当套接字处于连接状态,所有由套接字发出的包都具有确定的路由,无需为每个输出包查询它的目的入口,可将套接字绑定到路由入口上,这由套接字的目的缓冲指针来完成,,数显为输入包建立报头,经过本地过滤器,再将ip包分片输出,
rt = skb_rtable(skb);检查skb是否已经制定了路由功能,rt = (struct rtable *)__sk_dst_check(sk, 0);检查socket的合法性,
if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_uses_gateway)增加路由缓存引用计数,查找的路由目标地址不等于网关地址前往no_route
skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));
skb_reset_network_header(skb);
iph = ip_hdr(skb); 在skb的数据中预留出ip首部包选项的空间给ip报头。
*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));填入ip版本号,ip首部长度,后面根据选项增加长度,
if (ip_dont_fragment(sk, &rt->dst) && !skb->local_df):如果使用路径mtu发现要求部分偏,容许分片参数等于0.
ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);opt从sock中获得,
ip_select_ident_more(iph, &rt->dst, sk,
(skb_shinfo(skb)->gso_segs ?: 1) - 1); 填入ip首部的id字段
res = ip_local_out(skb);:ip包发送 return skb_dst(skb)->output(skb);
最终实际调用函数对于单包:ip_output(net/ipv4/route.c
__mkroute_input函数中设置,用于设置路由输入输出函数接口)
ip_output->ip_finish_output->ip_finish_output2->dst_neigh_output(调用邻居子系统(与网卡驱动挂钩)输出)