nc -v -z すると 2 つ結果を返すのはなぜか？

開発時など、突発的に適当なポートの開閉を調べたい時に、 Netcat を使って
`nc -v -z host port` という風にチェックすることがあると思う。
(Nmap や telnet を使うケースもあると思う)

同僚が OS X 上でこの方法でポートの開閉を調べようとしていたのだが、なぜだか結果が 2 つ出力されていた。
最初は謎だと思っていたのだが、原因が分かったので書いておく。

```console
$ nc -v -z localhost 8000
nc: connectx to localhost port 8000 (tcp) failed: Connection refused
nc: connectx to localhost port 8000 (tcp) failed: Connection refused
```

最初は OS X 固有の現象かと思ったが
実験したところ同じ設定をすれば OS X 以外でも普通に起きることだとわかった。

なお OS X 上では Homebrew などを使って GNU Netcat を優先している方も居ると思う。
その場合は再現しない。再現したければ `/usr/bin/nc` と明示すれば良い。

# /etc/hosts を見よ

初期状態の OS X の /etc/hosts はこのようになっていた。

```sh:/etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1	localhost
255.255.255.255	broadcasthost
::1             localhost
```

見ての通り、 localhost が IPv4 と IPv6 でそれぞれエントリが書かれている。
そのため 2 箇所ともチェックしているようだ。

# 解決方法 1: 明示的にどちらかを指定する

`localhost` と書かずに、 IP そのままで書けば問題ない。

```console
$ nc -v -z 127.0.0.1 8000
nc: connectx to 127.0.0.1 port 8000 (tcp) failed: Connection refused
```

```console
$ nc -v -z ::1 8000
nc: connectx to ::1 port 8000 (tcp) failed: Connection refused
```

# 解決方法 2: オプションで IPv4, IPv6 のどちらかを明示すればよい

```console
$ man nc
```

して読み進めていくと分かるのだが、 `-4` や `-6` のオプションでそれぞれ IPv4, IPv6 を強制できる

```console
$ nc -v -z -4 localhost 8000
nc: connectx to localhost port 8000 (tcp) failed: Connection refused
```

```console
$ nc -v -z -6 localhost 8000
nc: connectx to localhost port 8000 (tcp) failed: Connection refused
```

[OpenBSD の nc](http://man.openbsd.org/OpenBSD-current/man1/nc.1) や [FreeBSD 5.4 以上の nc](http://www.freebsd.org/cgi/man.cgi?nc) も同様だ。
どうやら BSD 由来の netcat は全てこのようであるらしい。

[GNU Netcat](http://netcat.sourceforge.net/) にはこのようなオプションはない。

Nmap の付属コマンドとして広く使われている [Ncat](http://man7.org/linux/man-pages/man1/ncat.1.html) にもこのオプションがある。

BSD と GNU と書いてあると混乱しそうだが、最近の有名な Linux ディストリビューションも、
BSD 由来の Netcat が入っていることが多い。
(例えば Ubuntu 14 の Netcat をチェックしたが、 BSD 由来の Netcat だった。)

また、 [BusyBox](https://busybox.net/downloads/BusyBox.html) の nc にもこのオプションはない。

# 解決方法 3: /etc/hosts から片方を削除する

なるほど、たしかに 1 つになる。
しかし、このような破壊的な変更は個人的にはおすすめしない。

# 解決方法 4: GNU Netcat を使う

上で言及したように [GNU Netcat](http://netcat.sourceforge.net/) にはそのようなオプションはない。
実は Homebrew を使ってインストールする際に `brew install netcat` でインストールされるのも GNU Netcat である。
Source: [netcat.rb](https://github.com/Homebrew/homebrew-core/blob/master/Formula/netcat.rb)

```
$ /usr/local/bin/nc -v -z localhost 8000
localhost [127.0.0.1] 8000 (irdmi): Connection refused
```

この Netcat は安定しているが、最新版のリリース日が 2004 年である。
保守状況がやや気になるので、個人的にはこの解決方法はおすすめしない。

# オマケ

## Q. もっとたくさん /etc/hosts に書いたらどうなるか？

```sh:/etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1	localhost
127.0.0.2	localhost
127.0.0.3	localhost
127.0.0.4	localhost
255.255.255.255	broadcasthost
::1             localhost
```

## A. それぞれに対して試行する

```console
$ /usr/bin/nc -v -z -G 1 localhost 8000
nc: connectx to localhost port 8000 (tcp) failed: Connection refused
nc: connectx to localhost port 8000 (tcp) failed: Connection refused
nc: connectx to localhost port 8000 (tcp) failed: Operation timed out
nc: connectx to localhost port 8000 (tcp) failed: Operation timed out
nc: connectx to localhost port 8000 (tcp) failed: Operation timed out
```

つまり `-4`, `-6` オプションの話も、あくまで IPv4 と IPv6 と 1 つずつしか書かれていない場合にだけ有効な話だということ。

(なお上の例では、待つのが面倒なので `-G` オプションでタイムアウトを 1 秒にしている。)

## Q. 片方が Open, もう片方が Close だった場合、終了ステータスはどうなる？

```console
$ nc -v -z  localhost 8000 ; echo $?
nc: connectx to localhost port 8000 (tcp) failed: Connection refused
found 0 associations
found 1 connections:
     1:	flags=XX<CONNECTED,PREFERRED>
	outif XXX
	src 127.0.0.1 port XXXXX
	dst 127.0.0.1 port 8000
	rank info not available
	TCP aux info available

Connection to localhost port 8000 [tcp/irdmi] succeeded!
0
```

実験してみたところ `0` を返した。どうやら接続成功と解釈するようだ (?)
片方だけ失敗して片方だけ成功すると、上の例のように失敗した方について詳細が表示される。