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コマンドとほぼ同じですね。
上記が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では、以下の通りきちんとUDPとTCPは分けて表示されました。
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コマンドでTCPとUDPをまとめて出力した結果をgrepするシェルスクリプトは、絶対に書いてはいけません。
一つの策としては、UDPならばStateが[UNCONN]になっていますから、ここでgrepするという案があります。しかし若干雑なので、あまりおすすめはできません。
やはりサーバの開放ポート一覧を取得するときは、UDPとTCPをまとめて表示せず、それぞれ分けて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オプションを使うと以下の順に表示されます。
以下が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コマンドでは、TCPやUDPなどのINETドメインソケットとUNIXドメインソケットは、はっきり区別して表示されていました。しかしssコマンドでは、一緒くたに出るようになりました。
- netstatコマンドでは、先にINETドメインを表示してからUNIXドメインを表示するようになっていましたが、ssコマンドでは逆にUNIXドメインが先に表示されてINETドメインが後に表示されるようになりました。
- 細かい点ですが、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コマンドのバグは直っていないことを確認しました。