netstatやlsofでLISTENしているアドレスポートが IPv6と表示されてもIPv4でアクセスできる罠

netstatやlsofでLISTENしているポートを調べることはよくあると思いますが、
LISTENしているアドレスポートが IPv6形式で表示されてもIPv4でアクセスできる罠があるので、その説明をします。

## 罠の説明

例えばCentOS 6.7にてhttpdをyumから入れて、httpdをスタートさせた後に、netstatを実行すると以下の様に表示されます。

```
# netstat -natp | grep LISTEN
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      1084/sshd
tcp        0      0 127.0.0.1:25                0.0.0.0:*                   LISTEN      1165/master
tcp        0      0 :::80                       :::*                        LISTEN      2316/httpd
tcp        0      0 :::22                       :::*                        LISTEN      1084/sshd
tcp        0      0 ::1:25                      :::*                        LISTEN      1165/master
```

sshdについては`0.0.0.0:22`というIPv4形式の出力と、`:::22`というIPv6形式の出力の両方が表示されています。
それに対して、httpdは`:::80`というIPv6形式の出力しか表示されていません。

lsofでも、sshdはIpv4とIPv6の両方がでますが

```
# lsof -i:22
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
sshd    1084 root    3u  IPv4  10943      0t0  TCP *:ssh (LISTEN)
sshd    1084 root    4u  IPv6  10954      0t0  TCP *:ssh (LISTEN)
```

httpdはIPv6のみが表示されます

```
# lsof -i:80
COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
httpd   2316   root    4u  IPv6  30431      0t0  TCP *:http (LISTEN)
```

一見するとhttpdはIPv6でしかアクセスできないように見えます。

**しかし！**

実際には、IPv4でアクセスできます。

```
# telnet 192.168.1.241 80
Trying 192.168.1.241...
Connected to 192.168.1.241.
Escape character is '^]'.
```

パケットキャプチャしてもIPv4で接続しているのがわかります！

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/33447/cd356aa5-4bbb-9637-aca6-a25c4f3cecfb.png)

試しに、OSの設定を変えてIPv6を無効にすると、IPv6表現の表示は消えることが分かりました。

```
# netstat -napt | grep LISTEN
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN      1271/httpd
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      968/sshd
tcp        0      0 127.0.0.1:25                0.0.0.0:*                   LISTEN      1059/master
```


つまり、IPv6が有効な状態でしか起きない問題ということです。


## これは一体なぜなのか・・・

この問題は、ググるといくつかヒットします

- [Netstat not displaying all listening ports when using IPv4 and IPv6](https://bugs.launchpad.net/ubuntu/+source/net-tools/+bug/657270)
- [HTTPD listening in IPv6, according netstat, but reacheable in IPv4](http://serverfault.com/questions/755765/httpd-listening-in-ipv6-according-netstat-but-reacheable-in-ipv4)

しかしどこにも明快な答えはないようです。


試しにnetstatのソースコードを軽く検索してみたら、すぐに発見

http://src.gnu-darwin.org/src/usr.bin/systat/netstat.c.html

```
/*
 * Construct an Internet address representation.
 * If the nflag has been supplied, give
 * numeric value, otherwise try for symbolic name.
 */
static char *
inetname(sa)
	struct sockaddr *sa;
{
	char *cp = 0;
	static char line[NI_MAXHOST];
	struct hostent *hp;
	struct netent *np;
	struct in_addr in;

#ifdef INET6
	if (sa->sa_family == AF_INET6) {
		if (memcmp(&((struct sockaddr_in6 *)sa)->sin6_addr,
		    &in6addr_any, sizeof(in6addr_any)) == 0)
			strcpy(line, "*");
		else
			getnameinfo(sa, sa->sa_len, line, sizeof(line), NULL, 0,
			    nflag ? NI_NUMERICHOST : 0);
		return (line);
	}
#endif
```

この部分で文字列を生成しているっぽいけど、`#ifdef INET6`はIPv6が有効の時のみに通るのでしょう。
そして`if (sa->sa_family == AF_INET6) {`の部分は、ソケットアドレス構造体のsa_familyがIPv6のものかどうかを判定する分岐で、これが真だとIPv6の表現を計算して即リターンしているようです。

うーん、つまり、sa構造体をどのように作るかで、文字列表現が変わってるということなのでしょう。

さらに調べていくと、[IPV6のmapページ](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/ipv6.7.html)に決定的な内容を発見

> v4-mapped-on-v6 アドレス型を用いることで、 IPv4 接続も v6 API で扱うことができる。 こうすれば、プログラムは v6 の API をサポートするだけで、 両方のプロトコルをサポートできる。 v4-mapped-on-v6 アドレス型は C ライブラリ内部のアドレスを 扱う関数によって透過的に処理される。
>IPv4 と IPv6 はローカルポート空間を共有する。 IPv4 の接続 (またはパケット) を IPv6 ソケットが取得すると、 発信元アドレスが v6 にマップされ、その接続 (パケット) も v6 にマップされる。  


さらに

> IPV6_V6ONLY (Linux 2.4.21 以降および 2.6 以降)
> このフラグを真 (0 以外) に設定すると、そのソケットは IPv6 パケットだけを 送受信するように制限される。 この場合、IPv4 アプリケーションと IPv6 アプリケーションが同時に 一つのポートをバインドできる。
このフラグを偽 (0) に設定すると、そのソケットはパケットの送受信に IPv6 アドレスと IPv4-mapped IPv6 アドレスの両方を使用できる。
> 引き数はブール値の入った整数へのポインターである。
> このフラグのデフォルト値はファイル /proc/sys/net/ipv6/bindv6only の内容により定義される。 このファイルのデフォルト値は 0 (偽) である。

完全にわかった。

`:::80`というのは`0.0.0.0:80`の**IPv4-mapped IPv6アドレス**であり、
sshdはIPV6_V6ONLYが偽であるため、IPv4とIPv6の両方のポートが見えたが、
httpdはIPV6_V6ONLYが真であるため、IPv6のポートしか見えないということだ。

## 結論

**OSのIPv6設定が有効の場合で、かつデーモンがIPV6_V6ONLYフラグを真にしてソケットを作成していた場合、**
**netstatやlsofではIPv6の「IPv4-mapped IPv6アドレス」しか見えていなくても、IPv4でもアクセスできる**