Edited at

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

More than 1 year has passed since last update.

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

試しに、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のソースコードを軽く検索してみたら、すぐに発見

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ページに決定的な内容を発見


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:80IPv4-mapped IPv6アドレスであり、

sshdはIPV6_V6ONLYが偽であるため、IPv4とIPv6の両方のポートが見えたが、

httpdはIPV6_V6ONLYが真であるため、IPv6のポートしか見えないということだ。


結論

OSのIPv6設定が有効の場合で、かつデーモンがIPV6_V6ONLYフラグを真にしてソケットを作成していた場合、

netstatやlsofではIPv6の「IPv4-mapped IPv6アドレス」しか見えていなくても、IPv4でもアクセスできる