客户端Connection reset by peer怎么办?——可能只是服务端挂了
cdn線網運營總會遇到有各種各樣的奇怪問題,而導致這些問題卻對應的各種各樣的原因。有些原因查出來卻總叫人哭笑不得,比如本案例所說的,服務端的程序掛了導致的connection reset by peer問題。這種最基礎的原因卻往往最能懷疑到,我個人覺得主要原因有以下兩點。
一,定式思維總會讓人最先排出掉最接近真相到原因。有點類似高中做題,總容易先入為主,陷入死胡同。
二,沒有人對各種原因做歸納總結,不同的原因導致的雖然都是連接失敗這一現象,但如果深入研究,一定有獨特的特征。需要有人說出“白馬”和“黑馬”的各自獨特性。網上搜索了下connection reset by peer關鍵字,發現只有應用層原因說明各種原因,并沒有這種問題的tcp層的原因介紹。那就我來根據線網運營遇到的案例來分析具體tcp層發生來什么,各種connection reset by peer的原因在tcp層有啥不同的特征來區分。
廢話不多扯,直接開始說事。下圖是某下載業務在第三方競速平臺的錯誤點,錯誤類型是建立連接失敗。幸好第三方測試平臺自帶錯誤點抓包功能,保留了錯誤發生的現場。
從圖中可以看到,客戶端沒發送一個syn包都會回復一個reset包。很直觀的感覺三次握手階段,連接還沒建立肯定還沒有到應用層,必然跟應用層沒有關系。并且如果應用層有問題,必然會導致大面積的連接失敗,而第三方測試平臺顯示只是兩個錯誤點。而我首先懷疑的是前一條連接的time_wait一直存在,導致的新鏈接被reset,因為我們從入門就有意無意的被灌輸“前一條流會影響后一條流的建聯,尤其是time_wait狀態的連接總是充滿著各種神秘”。
首先tcp層發送的reset是分為兩種——active_reset和非active_reset, active_reset是調用tcp_send_active_reset()進行發送的,而非active_reset是調用tcp_v4_send_reset()進行發送。那從抓包來看怎么確認是哪種類型的reset呢? 關鍵是要看Win值是否有設置, tcp_send_active_reset()調用的是tcp_transmit_skb()進行發送的,在tcp_transmit_skb()會計算和設置接收窗口。而tcp_v4_send_reset()函數設置的Win是零。
void tcp_send_active_reset(struct sock *sk, gfp_t priority){struct sk_buff *skb;/* NOTE: No TCP options attached and we never retransmit this. */skb = alloc_skb(MAX_TCP_HEADER, priority);if (!skb) {NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTFAILED);return;}/* Reserve space for headers and prepare control bits. */ skb_reserve(skb, MAX_TCP_HEADER);tcp_init_nondata_skb(skb, tcp_acceptable_seq(sk),TCPHDR_ACK | TCPHDR_RST);/* Send it off. */if (tcp_transmit_skb(sk, skb, 0, priority))NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTFAILED);TCP_INC_STATS(sock_net(sk), TCP_MIB_OUTRSTS);}wireshark上來看Win的值為0,所以是非active_reset。那查看下調用tcp_v4_send_reset()的位置,只有四處地方會調用。
1,監聽套接口不存在
tcp層入口函數tcp_v4_rcv函數中,會調用__inet_lookup_skb找到對應的sock,如果連符合條件的listen狀態的sock都沒有找到,并且數據包校驗和沒問題就會發送reset包斷開對端。
int tcp_v4_rcv(struct sk_buff *skb){ ....sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);if (!sk)goto no_tcp_socket; ... no_tcp_socket:if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))goto discard_it;if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) { //校驗和出錯 csum_error:TCP_INC_STATS_BH(net, TCP_MIB_CSUMERRORS); bad_packet:TCP_INC_STATS_BH(net, TCP_MIB_INERRS);} else {tcp_v4_send_reset(NULL, skb); //發送reset包} }2,四元組復用,但上一條流還未結束
使用四元組在__inet_lookup_skb查詢找到一個time_wait的sock,也就是上一條流并沒有結束,在tcp_timewait_state_process()調用后返回TCP_TW_RST,認為對端有錯誤發送reset關閉對端。但該case中客戶端一共發送了三次syn包,如果第一次發送reset是有time_wait狀態到sock存在,第二次和第三次syn包就不會響應reset,因為發送完第一次reset包就已經在time_wait的sock釋放。
int tcp_v4_rcv(struct sk_buff *skb) {... do_time_wait:...switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {case TCP_TW_SYN: {struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),&tcp_hashinfo,iph->saddr, th->source,iph->daddr, th->dest,inet_iif(skb));if (sk2) {inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);inet_twsk_put(inet_twsk(sk));sk = sk2;goto process;}/* Fall through to ACK */} case TCP_TW_ACK:tcp_v4_timewait_ack(sk, skb);break;case TCP_TW_RST:tcp_v4_send_reset(sk, skb);inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);inet_twsk_put(inet_twsk(sk));goto discard_it;case TCP_TW_SUCCESS:;}goto discard_it; }3,第三次握手檢查發現設置了syn或者rst標志的包,并且并不是此前重傳的syn包
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,struct request_sock *req,struct request_sock **prev,bool fastopen) { .../* RFC793: "second check the RST bit" and* "fourth, check the SYN bit"*/if (flg & (TCP_FLAG_RST|TCP_FLAG_SYN)) {TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_ATTEMPTFAILS);goto embryonic_reset;} ... embryonic_reset:if (!(flg & TCP_FLAG_RST)) {/* Received a bad SYN pkt - for TFO We try not to reset* the local connection unless it's really necessary to* avoid becoming vulnerable to outside attack aiming at* resetting legit local connections.*/req->rsk_ops->send_reset(sk, skb);} else if (fastopen) { /* received a valid RST pkt */reqsk_fastopen_remove(sk, req, true);tcp_reset(sk);}if (!fastopen) {inet_csk_reqsk_queue_drop(sk, req, prev);NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);}return NULL; }4,TCP狀態機處理
tcp_v4_do_rcv()函數中調用tcp_child_process()和tcp_rcv_state_process()返回值非零時,都會觸發發送reset,tcp_child_process()是服務端三次握手完成才會調用,tcp_rcv_state_process傳遞進去的sk仍為listen狀態的sk,所以只剩listen狀態下收到ack包,或者含有syn標志,但是調用conn_request()返回值小于零。然而tcp_conn_request只會返回0。
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) { ...if (sk->sk_state == TCP_LISTEN) {struct sock *nsk = tcp_v4_hnd_req(sk, skb);if (!nsk)goto discard;if (nsk != sk) {sock_rps_save_rxhash(nsk, skb);if (tcp_child_process(sk, nsk, skb)) {rsk = nsk;goto reset;}return 0;}} elsesock_rps_save_rxhash(sk, skb);if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {rsk = sk;goto reset;}return 0;reset:tcp_v4_send_reset(rsk, skb); discard: ... } int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len) { ...case TCP_LISTEN:if (th->ack)return 1;if (th->rst)goto discard;if (th->syn) {if (th->fin)goto discard;if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)return 1;/* Now we have several options: In theory there is* nothing else in the frame. KA9Q has an option to* send data with the syn, BSD accepts data with the* syn up to the [to be] advertised window and* Solaris 2.1 gives you a protocol error. For now* we just ignore it, that fits the spec precisely* and avoids incompatibilities. It would be nice in* future to drop through and process the data.** Now that TTCP is starting to be used we ought to* queue this data.* But, this leaves one open to an easy denial of* service attack, and SYN cookies can't defend* against this problem. So, we drop the data* in the interest of security over speed unless* it's still in use.*/kfree_skb(skb);return 0;}goto discard; ... }上述四種情況,雖然不愿意相信,只剩第一種可能性最大。當查看服務端日志之后發現,該時刻服務端程序確實掛了。只是第三方測試的測試點發起請求的間隔比較大,并沒有造成大面積的失敗點。總結來說,服務端程序掛了,客戶端報錯connection reset by peer,在tcp層表現就為重試好幾次都發生失敗。
?
總結
以上是生活随笔為你收集整理的客户端Connection reset by peer怎么办?——可能只是服务端挂了的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html i标签重置样式,去掉斜体I标签
- 下一篇: 过拟合的原因和解决方案