概要
netstat
やss
コマンドにより、あるホストと他のホストとのコネクションを一覧表示できる。しかし、Webシステムの場合、クライアントが並行接続するため、 同一ホストから複数のポートを介してコネクションを確立しているケースが多い。コネクション数が大きい場合は、1万以上のコネクションが表示され、ホスト間のコネクション状況を人間の目で概観することが難しかった。
そこで、同一ホストとのコネクションを集約表示し、コネクション状況を概観する 「lstf」 (「えるえすてぃーえふ」)コマンドをつくった。
lstfの特徴は以下の通り。
- コマンド実行ホストを起点に、active openコネクションかpassive openコネクションを判定する。つまり、接続をする側かされる側かを判定する。
- 各ホストフローごとにコネクション数を表示する
- Goで実装されているポータビリティ。i386バイナリであれば、CentOS5でも動作する。*1
- JSONサポート
実行結果
コマンド実行結果をみてみよう。
$ lstf -n Local Address:Port <--> Peer Address:Port Connections 10.0.1.9:many --> 10.0.1.10:3306 22 10.0.1.9:many --> 10.0.1.11:3306 14 10.0.2.10:22 <-- 192.168.10.10:many 1 10.0.1.9:80 <-- 10.0.2.13:many 120 10.0.1.9:80 <-- 10.0.2.14:many 202
ローカルホスト(10.0.1.9)はWebサーバでポート80番で待ち受けており、10.0.2.13と10.0.2.14からHTTPリクエストを受け付けていることがわかる。逆にローカルホストから10.0.1.10と10.0.1.11のMySQLサーバのポート3306番へ接続していることがわかる。ホスト同士の接続状況を知るためだけであれば、クライアントソケットが利用するポート番号を表示する意味はないため、many
として集約している。
--json
オプションでJSON表示もできるため、他のツールと連携し、利用できる。
$ lstf -n --json | jq -r -M '.' [ { "direction": "active", "local": { "Addr": "localhost", "Port": "many" }, "peer": { "addr": "10.0.100.1", "port": "3306" }, "connections": 20 }, { "direction": "passive", "local": { "addr": "localhost", "port": "80" }, "peer": { "addr": "10.0.200.1", "port": "many" }, "connections": 27 }, ... ]
実装
/proc/net/tcp
をパースする部分は、GitHub - shirou/gopsutil: psutil for golang を使わせてもらってる。
active openかpassive openかの判定は簡単で、コマンド実行ホストのLISTENポートに対するコネクションをpassive open、それ以外をactive openとしている。 *2
おまけ
Linuxのnetfilterには、conntrackというL4のコネクションフローに関するパケットを追跡する機構がある。(/proc/net/ipconntrack
, /proc/net/nfconntrack
からdump結果が読める) これを利用して、同じようにコネクションフローを集約表示するツールも作ってみた。github.com
inboundとoutboundとそれぞれについて、各パケットのサイズを合計し、conntrack tableに存在するフローごとのトラフィック量とパケット数を表示できるというメリットはある。 しかし、コネクション数が大きいホストではconntrack tableあふれを避けるために、conntrackを無効にしているため、どのホストでも使えるわけではなかった。
distributed black-box tracingをするための部品づくりを最近やってる。
追記
Linuxサーバ上でホスト間コネクションを集約表示するツール lstf をつくった - ゆううきメモb.hatena.ne.jp
- [golang]
とても良さそう。コネクション数が多い場合は ss のように netlink 経由のほうが良いと思うがどうなんだろ
2018/03/25 16:57
コマンド実行速度の観点で言えば、/proc/net/tcp
をパースするより、netlinkのほうが有利なようです。*3
GitHub - vishvananda/netlink: Simple netlink library for go. を使えば、わりと簡単にnetlink対応できそうですが、レガシーOSに対応しなければいけない事情*4でポータビリティの確保を優先し、ファイルをパースする実装を選んでいます。
*1:CentOS5でも古いバージョンでは動作しない
*2:https://github.com/yuuki/lstf/blob/d65651e9c3d1956aa9db88812acb6199a2473235/tcpflow/tcpflow.go#L130-L142
*3:参考:netstatコマンドを高速化する - Qiita posix_fadvise(2)でPOSIX_FADV_SEQUENTIALを使えばもうちょいread(2)の回数減らせないかなと思ったけど、それも込みでページサイズ固定ということかもしれない。
*4:同僚調べによるとnetlinkパッケージはCentOS5でサポートされていないシステムコールオプションを使っている。Go処理系そのものがCentOS5をサポートしていないため、仕方がない。cgo依存は排除されている様子。Eliminate cgo from netlink. by hugelgupf · Pull Request #308 · vishvananda/netlink · GitHub