hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

ECONNREFUSEDとECONNRESETについて



1 ECONNREFUSEDとECONNRESETとは?

エラー種別 意味
ECONNREFUSED TCPの状態がSYN-SENT状態のとき、RSTパケットを受信すると発生します(TCPの場合)
ICMP port unreachableを受信すると発生します(UDPの場合)
ECONNRESET TCPの状態がSYN-SENT以外のとき、RSTパケットを受信すると発生します。たとえば、TCPコネクション確立後、相手からRSTパケットを受信すると発生します

2 検証環境

2.1 ネットワーク構成

サーバとクライアントの2台構成です。図中のeth0はNICの名前です。

                           192.168.122.0/24
client(eth0) ------------------------------------------(eth0) server
        .181                                           .216

2.2 版数

CentOS版数は、サーバ、クライアントともに下記版数です。

[root@agent ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

カーネル版数は以下のとおりです。

[root@agent ~]# uname -r
3.10.0-1160.el7.x86_64

3 検証

ECONNREFUSEDとECONNRESETがどのような時に発生するか、検証環境で確認してみます。

3.1 ECONNREFUSED(TCPの場合)

クライアントが、Listenしていないポート番号(TCP)に対してSYNパケットを送信すると、サーバがRSTパケットを送信します。このとき、クライアントでECONNREFUSEDが発生します。

             client(192.168.122.181)                   server(192.168.122.216)
                  |                                          |
                  |                                          |
                  |                                          |
  nc server 11111 |--------------- SYN --------------------->|
                  |                                          |
                  |                                          |
     ECONNREFUSED |<-------------- RST ----------------------|
                  |                                          |
                  |                                          |

テストで使用するTCPの11111番ポートを開放します。なお、firewall-cmdの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# firewall-cmd --add-port=11111/tcp
success

開放したポート番号を確認します。TCPの11111番ポートが開放されたことがわかります。

[root@server ~]# firewall-cmd --list-ports
11111/tcp

サーバでtcpdumpを実行します。このとき、TCPの11111番ポートのみをキャプチャします。なお、tcpdumpの使い方は、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tcpdump -i eth0 port 11111 -nn

クライアントでncコマンドを実行します。このとき、サーバのIPアドレス、ポート番号(11111)を指定します。ncコマンドを実行すると、"Connection refused."が表示されることがわかります。なお、ncコマンドの使い方は、ncコマンドの使い方(ネットワーク実験の幅が広がるなぁ~) - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# nc 192.168.122.216 11111
Ncat: Connection refused.

tcpdumpの実行結果を確認すると、サーバからクライアントにRSTパケットが送信されていることがわかります。

[root@server ~]# tcpdump -i eth0 port 11111 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
19:49:04.276073 IP 192.168.122.181.45640 > 192.168.122.216.11111: Flags [S], seq 2464899466, win 29200, options [mss 1460,sackOK,TS val 2328460 ecr 0,nop,wscale 7], length 0
19:49:04.276148 IP 192.168.122.216.11111 > 192.168.122.181.45640: Flags [R.], seq 0, ack 2464899467, win 0, length 0

3.2 ECONNREFUSED(UDPの場合)

待ち受けしていないUDPポートにパケットを送信すると、サーバからICMPのICMP port unreachableが送信されます。

             client(192.168.122.181)                   server(192.168.122.216)
                  |                                          |
                  |                                          |
                  |                                          |
  nc server 11111 |--------------- UDP datagram ------------>|
                  |                                          |
                  |                                          |
     ECONNREFUSED |<-------------- ICMP port unreachable ----|
                  |                                          |
                  |                                          |

tcpdumpを実行します。このとき、UDPの11111番ポート宛てとICMPパケットのみをキャプチャします。

[root@server ~]# tcpdump -i eth0 port 11111 or icmp -nn

ncコマンドを実行します。

[root@client ~]# nc -u 192.168.122.216 11111

次に、クライアントからサーバにUDPデータグラムを送信します。12345とタイプしたあとリターンキーを押下します。リターンを押下すると、"Connection refused."が表示されることがわかります。

[root@client ~]# nc -u 192.168.122.216 11111
12345
Ncat: Connection refused.

サーバからクライアントにICMP port unreachableが送信されていることがわかります。

[root@server ~]# tcpdump -i eth0 udp port 11111 or icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:46:54.778677 IP 192.168.122.181.59201 > 192.168.122.216.11111: UDP, length 6
20:46:54.778757 IP 192.168.122.216 > 192.168.122.181: ICMP 192.168.122.216 udp port 11111 unreachable, length 42

3.3 ECONNRESET

ECONNRESETは、SYN-SENT以外のときに、RSTパケットを受信すると発生します。ここでは、TCPコネクションが確立したあと、サーバからクライアントにRSTパケットを送信することで、ECONNRESETを発生させてみます。

             client(192.168.122.181)                   server(192.168.122.216)
                  |                                          |
                  |                                          | nc -kl 11111
                  |                                          |
                  |                                          |
  nc server 11111 |--------------- SYN --------------------->|
                  |<-------------- SYN+ACK ------------------|
                  |--------------- ACK --------------------->| -*-
                  |                                          |  |
                  |                                          |  |
                  |                                          |  | TCPコネクション確立状態
                  |                                          |  |    (ESTABLISHED状態)
                  |                                          |  |
       ECONNRESET |<-------------- RST ----------------------| -*-
                  |                                          |

サーバでncコマンドを実行します。

[root@server ~]# nc -kl 11111

クライアントでncコマンドを実行します。ncコマンドを実行すると、サーバとクライアントの間でTCPコネクションが確立されます。詳細は、TCPコネクションの確立、解放シーケンス - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# nc 192.168.122.216 11111

もう1つターミナルをオープンしてssコマンドを実行します。サーバとクライアント間でTCPコネクションが確立されたことがわかります。なお、ssコマンドの使い方は、ssコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

root@client ~]# ss -n state established 'dport == :11111'
Netid Recv-Q Send-Q                                 Local Address:Port                                                Peer Address:Port
tcp   0      0                                    192.168.122.181:41616                                            192.168.122.216:11111

サーバでCtrl+zを押下して、サーバのncプロセスを停止します。

[root@server ~]# nc -kl 11111
^Z
[1]+  停止                  nc -kl 11111

psコマンドを実行してプロセスの状態を確認します。ncプロセスが停止状態(T)であることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C nc -o comm,pid,state
COMMAND           PID S
nc               1467 T

クライアントからサーバにデータを送信します。改行コードも含めて6バイト送信します。

[root@client ~]# nc 192.168.122.216 11111
12345

サーバのncプロセスは停止しているので、カーネルの受信バッファからデータを読み出すことができません。ssコマンドを使ってそのときの様子を確認します。Recv-Qはカーネル受信バッファに残っている受信データのバイト数を表しています。したがって、6バイトのデータがカーネル受信バッファに残ったままになっていることがわかります。詳細はssコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ss -tn4 'sport == :11111'
State       Recv-Q Send-Q Local Address:Port                Peer Address:Port
ESTAB       6      0      192.168.122.216:11111              192.168.122.181:41616

サーバのncプロセスを終了します。

[root@server ~]# kill -9 1467
[1]+  強制終了            nc -kl 11111

カーネル受信バッファに受信データが残ったままプロセスが終了すると、データ送信元にRSTパケットが送信されます。

[root@server ~]# tcpdump -i eth0 tcp port 11111 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:21:42.428698 IP 192.168.122.216.11111 > 192.168.122.181.41616: Flags [R.], seq 2836811943, ack 622903595, win 227, options [nop,nop,TS val 2681900 ecr 1590444], length 0

ちなみに、カーネル受信バッファに受信データが残っているかどうかの判定処理は以下の場所です。カーネル受信バッファに受信データが残っている場合、tcp_send_active_reset関数の延長でRSTパケットを送信します。

2004 void tcp_close(struct sock *sk, long timeout)
-snip-
2050         } else if (data_was_unread) {
2051                 /* Unread data was tossed, zap the connection. */
2052                 NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
2053                 tcp_set_state(sk, TCP_CLOSE);
2054                 tcp_send_active_reset(sk, sk->sk_allocation);

クライアントでECONNRESETが発生していることがわかります。

[root@client ~]# nc 192.168.122.216 11111
12345
Ncat: Connection reset by peer.

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ