ssコマンドはバグと地雷の塊なのでnetstatの代わりにならない

既に有名な話ですが、CentOS 7およびRed Hat Enterprise Linux 7からはifconfigコマンドやnetstatコマンドが非推奨となり、デフォルトインストールすらされなくなりました。代替として、ifconfigコマンドはipコマンド、netstatコマンドはssコマンドが用意されています。

というわけでさっそくssコマンドを試していたのですが、明らかに動きがおかしなところがあり、少し調べてみました。

そして、「netstatコマンドの代替と思って安易にssコマンドを使うと、これは痛い目に遭うな……」ということが分かったので、不幸になる人を少なくするためにこのエントリを書きました。

概要

結論から先に言うと、CentOS 7/ RHEL 7のssコマンドには「UDPの開放ポートがTCPと報告される」というひどいバグがあり、使うべきではありません。

また、ssコマンドの-aオプションの挙動がCentOS 6の時から大きく変わっており、この点も注意が必要です。一言でいうと、「ssコマンド、かなり雑」です。

開放ポート確認時のNetidカラムでのUDPバグ

まずは最大の不具合である、UDPソケット表示のバグを見てみましょう。ssコマンドに、以下オプションを指定してTCP/UDPの開放ポートを確認してみます。これらオプションは、netstatコマンドとほぼ同じですね。

  • -a:LISTENなものもそうじゃないものも表示
  • -n:ポート番号のサービス名変換をしない
  • -t:TCPソケットを表示
  • -u:UDPソケットを表示


上記がCentOS 7での、このコマンドの実行結果です。赤線で囲った部分に注目してください。「Netid」カラムは全て[tcp]になっていますが、このソケット、本当に全部TCPでしょうか?

例えば上から2つ目の、ポート123のサービスは明らかにntpdです。いつからntpがTCPになったんでしょうか? (^^;) 実はここで[State]が[UNCONN]になっているものは全てUDPのソケットです。しかし、ssコマンドのバグによりtcpと表示されてしまっているのです。なお上例では「-antu」という長いオプションで発生しましたが、一番単純な、「ss -a」でもUDPソケットは[tcp]と誤表示されます。雑です。

このバグをkernel.orgのgit履歴で追ってみましたが、以下コミットで修正されているようです。「TCPソケットとUDPソケットを同時に表示させると、全てがTCPと表示される」という、ちょっと信じられないひどいバグです。

このコミットは2014/02/10で、iprouteのバージョンで言うとv3.14.0から修正されています。CentOS 7のiprouteはv3.10.0なのでバグ付きです。

ややこしいことに、このバグは古いiproute(ver.2系)には無く、CentOS 6.5のssコマンドは正常に動作します。CentOS 6.5では、以下の通りきちんとUDPTCPは分けて表示されました。

ssコマンドのバージョンの差異

CentOS 6.5のssコマンドのバージョンと、iprouteのパッケージ情報は以下の通りです。

[ozuma@cent6 ~]$ ss --version
ss utility, iproute2-ss091226
[ozuma@cent6 ~]$ rpm -qf /usr/sbin/ss
iproute-2.6.32-31.el6.x86_64
[ozuma@cent6 ~]$ yum info iproute-2.6.32-31.el6.x86_64
...(省略)...
Name        : iproute
Arch        : x86_64
Version     : 2.6.32
Release     : 31.el6
...(省略)...

一方、CentOS 7では以下の通りでした。

[ozuma@cent7 ~]$ ss --version
ss utility, iproute2-ss130716
[ozuma@cent7 ~]$ rpm -qf /usr/sbin/ss
iproute-3.10.0-13.el7.x86_64
[ozuma@cent7 ~]$ LANG=C yum info iproute
...(省略)...
Name        : iproute
Arch        : x86_64
Version     : 3.10.0
Release     : 13.el7
...(省略)...

iprouteのv3.10.0なのでバグ付きです。

Red Hat Enterprise Linux 7ではどうなのか

CentOS 7はRHELのクローンということは分かっていますが、なんぼなんでもあれだけクソ高い金を取っているRed Hatのことです、実はバグfixしていたりしないでしょうか。ということでRHEL7でも試しました。

……変な期待をした私がアホでした。しかも、

# yum update iproute

しても何も出てきません。いつ直るんだろう……(というか、バグに気が付いてすらいないのでは……)。

回避策

ssコマンドのNetidカラムが信用できない以上、ssコマンドでTCPUDPをまとめて出力した結果をgrepするシェルスクリプトは、絶対に書いてはいけません。

一つの策としては、UDPならばStateが[UNCONN]になっていますから、ここでgrepするという案があります。しかし若干雑なので、あまりおすすめはできません。

やはりサーバの開放ポート一覧を取得するときは、UDPTCPをまとめて表示せず、それぞれ分けて2回コマンドを実行するのが確実です。

# UDPの開放ポートを取得
ss -anu

# TCPの開放ポートを取得
ss -ant

面倒ですが、デフォルトのssコマンドがバグ持ちである以上、これしか確実な方法がありません。こうなると、非推奨と言われようが、net-toolsパッケージを入れてnetstatコマンドを素直に使った方がよっぽどマシな気がします。

もう一つの案としては、/proc/net 配下のprocファイルを自分で調べてしまうという手法もあります。この場合、例えばUDPは「udp」と「udp6」と、2つのファイルが必要なことに注意してください。

-aオプションの仕様変更

2つめの話題。これはCentOS 6とCentOS 7を両方使う人がハマる地雷です。

ssコマンドでは、以下の2012年12月のコミットがされるまで、「-a/--all」オプションが指定された場合は「TCPソケットのみ表示」をしていました。

このため、CentOS 6ではss -aとするとUDPは無視され、TCPソケットしか表示されません。

[ozuma@cent6 ~]$ ss -a
State      Recv-Q Send-Q      Local Address:Port          Peer Address:Port
LISTEN     0      128                    :::ssh                     :::*
LISTEN     0      128                     *:ssh                      *:*
LISTEN     0      128             127.0.0.1:ipp                      *:*
LISTEN     0      128                   ::1:ipp                     :::*
LISTEN     0      100                   ::1:smtp                    :::*
LISTEN     0      100             127.0.0.1:smtp                     *:*
ESTAB      0      0            192.168.2.66:ssh           192.168.2.50:52688
[ozuma@cent6 ~]$

一方、CentOS 7ではss -aとするとTCP/UDPなどINETドメインソケットとUNIXドメインソケットの両方が表示されます。これはnetstatコマンドと同じ動きなので修正は妥当と言えば妥当なのですが、netstatコマンドの後継を名乗るなら、2012年まで放置しないで最初からそうしとけよ……って気は、まぁしますね。

この、「ssコマンドの-aオプションは、バージョンによってはTCPソケットしか表示されない(UDPソケットは出ない)」ということを知らないと、シェルスクリプトなどから利用するとき思わぬ地雷を踏むことになるので注意してください。

netstatの-aオプションとの相違

(先に書いたバージョンによる差があるため、この節はCentOS 6ではなくCentOS 7のssコマンド、つまりiproute v3.10.0を対象とします)

3つめの話題。ssコマンドとnetstatコマンドでは意図的にオプションを似せており、例えば-aオプションはどちらもmanを見ると同じようなことが書いてあります。

netstatコマンドのman

-a, --all
Show both listening and non-listening (for TCP this means established connections) sockets. With the --interfaces option, show interfaces that are not up

ssコマンドのman

-a, --all
Display both listening and non-listening (for TCP this means established connections) sockets.

しかし、両者の出力には結構差があります。まずnetstatコマンドでは、-aオプションを使うと以下の順に表示されます。

  • Active Internet connections (TCP, UDP, raw)
  • Active UNIX domain Sockets

以下がnetstatコマンドの例です:

[root@cent7 /]# netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 localhost:smtp          0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:ssh             0.0.0.0:*               LISTEN
tcp        0     96 192.168.2.67:ssh        192.168.2.50:49687      ESTABLISHED
tcp6       0      0 localhost:smtp          [::]:*                  LISTEN
tcp6       0      0 [::]:ssh                [::]:*                  LISTEN
udp        0      0 0.0.0.0:51686           0.0.0.0:*
udp        0      0 0.0.0.0:ntp             0.0.0.0:*
udp        0      0 0.0.0.0:mdns            0.0.0.0:*
udp        0      0 localhost:323           0.0.0.0:*
udp6       0      0 [::]:ntp                [::]:*
udp6       0      0 localhost:323           [::]:*
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node   Path
unix  2      [ ACC ]     STREAM     LISTENING     17343    private/verify
unix  2      [ ACC ]     STREAM     LISTENING     17349    private/proxymap
unix  2      [ ACC ]     STREAM     LISTENING     17352    private/proxywrite
unix  2      [ ACC ]     STREAM     LISTENING     17355    private/smtp
...(省略)...


一方ssコマンドの-aオプションは、UNIXドメインソケットに加えて、Netlinkソケット(カーネルとユーザ空間をやりとりするソケット)も表示されるようになりました。

[root@cent7 /]# ss -a
Netid  State      Recv-Q Send-Q   Local Address:Port       Peer Address:Port
nl     UNCONN     0      0                 rtnl:kernel                 *
nl     UNCONN     0      0                 rtnl:avahi-daemon/542                *
nl     UNCONN     4352   0              tcpdiag:ss/3575                *
...(省略)...
u_str  LISTEN     0      100     private/verify 17343                 * 0
u_str  LISTEN     0      100    private/proxymap 17349                 * 0
u_str  LISTEN     0      100    private/proxywrite 17352                 * 0
...(省略)...
tcp    UNCONN     0      0                    *:ipproto-51686               *:*
tcp    UNCONN     0      0                    *:ptp                   *:*
tcp    UNCONN     0      0                    *:ipproto-5353               *:*
tcp    UNCONN     0      0            127.0.0.1:ipproto-323               *:*
tcp    UNCONN     0      0                   :::ptp                  :::*
tcp    UNCONN     0      0                  ::1:ipproto-323              :::*
tcp    LISTEN     0      100          127.0.0.1:smtp                  *:*
tcp    LISTEN     0      128                  *:ssh                   *:*
tcp    ESTAB      0      0         192.168.2.67:ssh        192.168.2.50:49687
tcp    LISTEN     0      100                ::1:smtp                 :::*
tcp    LISTEN     0      128                 :::ssh                  :::*

上記例では先のバグの通り、UDPソケットが[tcp]と表示されていますがそれは置いといて。

  • netstatコマンドでは、TCPUDPなどのINETドメインソケットとUNIXドメインソケットは、はっきり区別して表示されていました。しかしssコマンドでは、一緒くたに出るようになりました。
  • netstatコマンドでは、先にINETドメインを表示してからUNIXドメインを表示するようになっていましたが、ssコマンドでは逆にUNIXドメインが先に表示されてINETドメインが後に表示されるようになりました。
    • netstatコマンドを使う場合に一番多いのは開放ポートの確認だと思うので、なぜこのような仕様変更をしたのか理解に苦しみます……。パイプでlessに繋いだ際、膨大なUNIXドメインソケットを全部スクロールさせて、一番下まで行かないと開放ポートが分からないのはなかなか不便です。
  • 細かい点ですが、netstatコマンドではIPv6の場合は[tcp6]や[udp6]として表示していましたが、ssコマンドはIPv4でもIPv6でも[tcp]や[udp]としか表示しません。そのため、対象のポートがIPv4なのかIPv6なのかは、LISTENしているIPアドレス表記を見て自分で確認しないといけません。例えば「*:ssh」となっていればIPv4ですし、「:::ssh」というIPv6独自のコロン連続表記があればIPv6、という具合です。これもかなり不便です。

結論

netstatコマンドの後継だと思って安易にssコマンドを使うと、確実に地雷にハマります。特にデフォルトインストールされているssコマンドがUDPを誤表示する問題は深刻で、「サーバ構築時に開放ポートを確認して顧客に引き渡し → UDPのチェック漏れ」なんてことがこれから世界中で起きないか心配です。(さすがに本番環境をRHEL 7で構築している人柱はまだ少ないでしょうから、みんな様子見の今のうちに、Red Hatはさっさと7.1を出して修正した方がいいんじゃないでしょうか)

業務でssコマンドを使う方は、その出力結果には十分な注意を払うことをおすすめします。可能ならばnet-toolsをインストールして、netstatコマンドの出力も合わせて取っておいた方がいいと思います。現在のssコマンドは、Red Hat Enterprise Linuxが利用されるようなエンタープライズ用途での利用シーンで、信用できるレベルには達していないなぁ……というのが個人的な感想です。

追記(2015-03-07)

2015年3月に、Red Hat Enterprise Linux 7.1がリリースされました。しかしこのバージョンでも、ssコマンドのバグは直っていないことを確認しました。