如果你喜欢小编往期内容,关注一下小编公众号 – 微信公众号,或者私信回复"802.11"获取Wi-Fi学习方法和资料,也可以和小编交流一下经验~
本章开篇之前,我们先介绍下经典的网络协议体系架构,不管是7层架构还是5层架构,传输层都必不可少,其中最终的两个协议代表就是TCP&UDP,在应用上网过程中,所有的数据包都会经过这两种形式的传输,例如浏览器页面刷新会通过TCP连接收发数据,音视频流会通过UDP进行传输,而我们在实际上网过程中,所有的上网问题可以归结为两种:
(1)丢包;
(2)socket连接中断;
图片
本文重点,本文主要通过以下几方面教会你如何应对网络问题:
(1)连接中断中主动断开与异常断开如何区分;
(2)RST是什么;
(3)如何应对RST问题;
(4)有什么方法可以排查RST问题
接下来带着问题开始整理笔记;
一、如何区分TCP连接是主动断开还是异常断开
主动断开:active RST,根据协议来看,如果断开一个预期的TCP连接,RST seq会使用ack的seq填充到RST-ack字段中,如下截图:
图片
RST对应发起位置如下:
图片
函数内部调用逻辑如下:
tcp_send_active_reset()
-> skb = alloc_skb(MAX_TCP_HEADER, priority);
-> tcp_init_nondata_skb(skb, tcp_acceptable_seq(sk), TCPHDR_ACK | TCPHDR_RST);
-> tcp_transmit_skb()
根据代码的实现,总结以下几种情况会触发主动RST:
(1)数据还没有READ完,但是主动调用了tcp close;
(2)调用close的时候,通过setsockopt设置了SOCK_LINGER字段,
(3)低内存场景,系统内存不够TCP包使用,如果发生内存问题在kernel log中可以看到如下关键字:
too many orphaned sockets
out of memory – consider tuning tcp_mem
在debug时,可以通过调整如下参数扩大内存:
XXX:/proc/sys/net/ipv4 # cat tcp_mem
181779 242375 363558
//案例
echo 3196000> /sys/devices/system/cpu/bus_dcvs/DDR/boost_freq
echo 4194304 > /proc/sys/net/ipv4/tcp_limit_output_bytes
echo “138426 138426 138426” > /proc/sys/net/ipv4/tcp_mem
echo "8291456 8291456 8291456 " > /proc/sys/net/ipv4/tcp_rmem
echo "8291456 8291456 8291456 " > /proc/sys/net/ipv4/tcp_wmem
echo “271356 361808 542712” > /proc/sys/net/ipv4/udp_mem
低内存的kernel代码如下(如果对代码流程不感兴趣可以直接跳过):
//在TCP write/probe过程中,如果内存不够分配,将会主动RST
102 static int tcp_out_of_resources(struct sock sk, bool do_reset)
103 {
104 struct tcp_sock tp = tcp_sk(sk);
105 int shift = 0;
106
107 / If peer does not open window for long time, or did not transmit
108 * anything for long time, penalize it. /
109 if ((s32)(tcp_jiffies32 - tp->lsndtime) > 2TCP_RTO_MAX || !do_reset)
110 shift++;
111
112 / If some dubious ICMP arrived, penalize even more. /
113 if (sk->sk_err_soft)
114 shift++;
115 //这里检查内存
116 if (tcp_check_oom(sk, shift)) {
117 / Catch exceptional cases, when connection requires reset.
118 * 1. Last segment was sent recently. /
119 if ((s32)(tcp_jiffies32 - tp->lsndtime) <= TCP_TIMEWAIT_LEN ||
120 / 2. Window is closed. */
121 (!tp->snd_wnd && !tp->packets_out))
122 do_reset = true;
123 if (do_reset)
124 tcp_send_active_reset(sk, GFP_ATOMIC);
//调用tcp_check_oom方法
2835 bool tcp_check_oom(struct sock *sk, int shift)
2836 {
2837 bool too_many_orphans, out_of_socket_memory;
2838
2839 too_many_orphans = tcp_too_many_orphans(shift);
2840 out_of_socket_memory = tcp_out_of_memory(sk);
2841
2842 if (too_many_orphans)
2843 net_info_ratelimited(“too many orphaned sockets\n”);
2844 if (out_of_socket_memory)
2845 net_info_ratelimited(“out of memory – consider tuning tcp_mem\n”);
2846 return too_many_orphans || out_of_socket_memory;
2847 }
287 static inline bool tcp_out_of_memory(struct sock *sk)
288 {
289 if (sk->sk_wmem_queued > SOCK_MIN_SNDBUF &&
290 sk_memory_allocated(sk) > sk_prot_mem_limits(sk, 2))
291 return true;
292 return false;
293 }

1643 /* sysctl_mem values are in pages */
1644 static inline long sk_prot_mem_limits(const struct sock *sk, int index)
1645 {
1646 return READ_ONCE(sk->sk_prot->sysctl_mem[index]);
1647 }
//sysctl_mem映射关系如下
3051 struct proto tcp_prot = {
3089 .sysctl_mem = sysctl_tcp_mem,
472 {
473 .procname = “tcp_mem”,
474 .maxlen = sizeof(sysctl_tcp_mem),
475 .data = &sysctl_tcp_mem,
476 .mode = 0644,
477 .proc_handler = proc_doulongvec_minmax,
478 },
//在设备中读取节点信息
XXX:/proc/sys/net/ipv4 # cat tcp_mem
181779 242375 363558
异常断开:
RST报文中没有填充ACK字段,并且RST seq等于它断开的TCP连接的ack seq;
图片
内核代码如下:
图片
678 static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
//这里就是为什么RST的seq等ack
715 if (th->ack) {
716 rep.th.seq = th->ack_seq;
//这种情况概率比较低
717 } else {
718 rep.th.ack = 1;
719 rep.th.ack_seq = htonl(ntohl(th->seq) + th->syn + th->fin +
720 skb->len - (th->doff << 2));
721 }
二、什么是RST
首先,我们需要一份TCP协议。
这里如果不知道怎么下载协议l的小伙伴请移步至文末“参考一”;
其次,我们开始了解什么是RST以及什么时候会发送RST:
RST全名RESET,即重置/断开连接,在TCP协议中,以下几种场景需要发送reset包:
1.连接不存在;
2.TCP双端不在LISTEN、SYN-SENT、SYN-RECEIVED状态,收到了不符合预期的ack,或者安全级别不符合预期时;
3.在TCP接收端,如果其处于listen状态,会忽略RST请求,如果处于SYN-RECEIVED状态,则返回到LISTEN状态,否则会响应RST断开连接,此外,处于其他任何状态时,都会响应RST断开连接;
图片
图片
三、如何应对reset问题
1.抓取tcpdump,初步分类RST的类型;
2.根据RST类型找到对应的函数,获取打印栈;
这里一个比较牛掰的工具就是bpf,只不过小编现在也没搞明白如何使用,这里先留个记号,后续分享;
四、分析案例
案例一:某应用莫名其妙提示网络连接断开,socket连接消失;
1.抓取tcpdump;
图片
根据tcpdump可以知道这是主动断线;
2.针对性的对函数tcp_send_active_reset进行跟踪,最后定位到时应用设置了的SOCK_LINGER FLAG触发的主动断线;
结论:这种就属于应用侧的行为,需要应用判断是不是符合预期;
案例二:
这里涉及到kernel协议栈的一些问题debug,涉及的只是点比较多,小编放到下一篇文章中来说;
参考一:如何下载TCP协议
TCP协议官网下载指南:
作为一名合格的协议工程师,遇到问题时要学会翻协议,因为所有的TCP产品实现必须遵循协议规范,这里小编分享下如何获取TCP rfc 793协议文档;
(1)官方链接
https://www.rfc-editor.org/
(2)下载方式
第一步进入官网搜索
图片
输入协议编号下载各种格式的资源
图片
其他协议文档编号
[PPPOE]
RFC 1661:The Point-to-Point Protocol (PPP)。
RFC 4638:Accommodating a Maximum Transit Unit/Maximum Receive Unit (MTU/MRU) Greater Than 1492 in the Point-to-Point Protocol over Ethernet (PPPoE)。
RFC 2516:A Method for Transmitting PPP Over Ethernet (PPPoE)。
[IPCP]
RFC 1332:The PPP Internet Protocol Control Protocol (IPCP)。
[NAT]
RFC 5382:NAT Behavioral Requirements for TCP。
RFC 3489:STUN - Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs)。
RFC 2663:IP Network Address Translator (NAT) Terminology and Considerations。[IPv6]
RFC 2373:IP Version 6 Addressing Architecture。
RFC 2461:Neighbor Discovery for IP Version 6 (IPv6)。
RFC 2462:IPv6 Stateless Address Autoconfiguration。
RFC 3306:Unicast-Prefix-based IPv6 Multicast Addresses。
RFC 3315:Dynamic Host Configuration Protocol for IPv6 (DHCPv6)。
RFC 3484:Default Address Selection for Internet Protocol version 6 (IPv6)。
RFC 3513:Internet Protocol Version 6 (IPv6) Addressing Architecture。
RFC 3587:IPv6 Global Unicast Address Format。
RFC 3633:IPv6 Prefix Options for Dynamic Host Configuration Protocol (DHCP) version 6
如果不知道文档协议编号,可以点击左侧的文档索引页面,下载对应协议文档
图片
图片
如果读到这里,你觉得有所收获,就关注下小编的公众号,未来一起进步~
以上分享内容均是小编个人观点,如果有问题,欢迎各位老板留言纠正~