ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

何もツールがなくてもコンテナの中のTCP通信の状態を見たい

完全に消費税に負けた...

今日も小ネタです。

一般に、以下のようなことを調べる時 netstatss などのツールは便利です。

  • あるポートがリッスンされているか知りたい
  • あるコネクションに実際に通信があるか知りたい
  • MySQLサーバなど外部プロセス/サーバにコネクションが貼られているか知りたい

でもコンテナ環境では、そんな余計なツールは入っていない!!!ことも多い。

そんな時でも、 /proc ファイルシステムはほぼ間違いなくマウントされているはずです。 なのでそこを直接見ることも検討しましょう。

/proc/net/tcp(6) を眺める

コンテナのネットワークに直接入るには、 nsenter などを使うことができます。今回はすぐ用意できる環境があったので Haconiwa ですが、 Docker などで置き換えて試してください。

$ ps auxf
...
root     13252  0.0  0.1  21600  4400 pts/3    S    11:20   0:00 ./mruby/bin/haconiwa run sample/criu-rails.haco
root     13253  1.0  0.1  21600  5440 pts/3    S    11:20   0:00  \_ ./mruby/bin/haconiwa run sample/criu-rails.haco
root     13295 25.3  1.6 658332 67404 ?        Ssl  11:20   0:01      \_ puma 3.12.0 (tcp://0.0.0.0:3000) [helloworld] <- ここからコンテナ

$ sudo nsenter --net -t 13295
# cat /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
   0: 00000000:0BB8 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 197041 1 ffff9a80671f5d80 100 0 0 10 0        

netstatやssは基本的にこの情報を加工しているだけです。

あるいは、こうなっているかもしれません。

  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
   0: 7C02010A:A6F2 8E0116AC:0CEA 06 00000000:00000000 03:00000ACA 00000000     0        0 0 3 ffff881f6de59c70                                      
   1: 7C02010A:E076 8E0116AC:0CEA 06 00000000:00000000 03:00000AC0 00000000     0        0 0 3 ffff881f7fa76990                          

読み方は man proc やカーネルのドキュメントにある通りなんですが、

https://www.kernel.org/doc/Documentation/networking/proc_net_tcp.txt

とりあえず見るのは...

2番目 local_address 自分側のアドレスポート

前者の例は 00000000:0BB8 で、見慣れている表記に直すと:

#!ruby
"00000000".scan(/(..)/).flatten.map{|v| v.to_i(16)}.reverse
#=> [0, 0, 0, 0]
"0BB8".to_i(16)
#=> 3000

他の言語でも適宜置き換えてください(String#to_i が便利なのであれですが、他の言語にもあるのかな...)。

ようするに自分のアドレスを 0.0.0.0 で 3000 番ポートをリスンしています。

3番目 rem_address 相手側のアドレスポート

後者の例では3番目に 8E0116AC:0CEA というアドレスがあり、見慣れている表記に直すと:

#!ruby
"8E0116AC".scan(/(..)/).flatten.map{|v| v.to_i(16)}.reverse # 逆になるのに注意
#=> [172, 22, 1, 142]
"0CEA".to_i(16)
#=> 3306

172.22.1.142:3306 につなぎに行っています。 3306 なのでMySQLかなんかでしょう。

4番目 st ステータス

この数字がどの状態なのかは例えばカーネルのヘッダー net/tcp_states.h にあるので、

elixir.bootlin.com

これを参考に10進数に変換するといいです。

  • ESTABLISHED = 01
  • TIME_WAIT = 06
  • LISTEN = 0A (10)

など。前者はLISTEN、後者はTIME_WAITですね。

9番目 timeout

この値があまりにカウントアップしていたら、その通信は何かしらの理由で腐っているかもしれません。相手側が応答しないとか。

ちなみに

tcp でリスンしていなくても tcp6 でリスンしている場合もあります。以下はとある Apache の例。

$ sudo nsenter --net -t 27866 cat /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                         
$ sudo nsenter --net -t 27866 cat /proc/net/tcp6
  sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
   0: 00000000000000000000000000000000:1F90 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 41622137 1 ffff955d74551100 100 0 0 10 0

tcp6、めっちゃ長い。ポートは同じように "1F90".to_i(16) => 8080

にも関わらず ipv4 127.0.0.1:8080 などでアクセスできるのですが、これはIPv4-mapped IPv6アドレスというやつですね。こちらが詳しいです。

qiita.com

www.geekpage.jp

全く知らないとハマる(ハマった)こともあると思いますので一応。