netstat -o (--timers) オプションに関するメモを少々。
(Qiita では、はじめてちゃんとした技術ネタかもしれない(笑))
netstat の -o オプションとは
さて、このオプション、これまで実は私はあんまりまじめに使ったことがなかったのだが、netstat の man page の該当部分を引用すると以下の通りで、たとえばTCPのコネクション単位で、なんらかの timer が有効になっている場合に詳細情報を知ることができる。
| -o, --timers
| Include information related to networking timers.
まずは、手元で調査に使った CentOS7環境の条件を。
[stack@centos7-1 ~]$ uname -a
Linux centos7-1 3.10.0-327.36.1.el7.x86_64 #1 SMP Sun Sep 18 13:04:29 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
よくある使い方としては、watch -n 1 "sudo netstat -antpo | grep timewait" 等とやって、TIME_WAIT状態になった TCP endpoint が順次消えているのを観察する等というのがある。
端末出力例を見てみてみると、以下のTimerと表示されているカラムのような感じになる。
(余談だが、beam とか mysqld とか動いているということは、アレを動かしているシステム?と思った人、その通りである(笑))
[root@centos7-1 ~]# netstat -antpo
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name Timer
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 23305/mysqld off (0.00/0/0)
tcp 0 0 0.0.0.0:4369 0.0.0.0:* LISTEN 1/systemd off (0.00/0/0)
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 14151/sshd off (0.00/0/0)
tcp 0 0 0.0.0.0:25672 0.0.0.0:* LISTEN 18024/beam off (0.00/0/0)
tcp 0 0 192.168.91.11:4369 192.168.91.11:41918 TIME_WAIT - timewait (22.35/0/0)
tcp 0 0 127.0.0.1:37388 127.0.0.1:4369 ESTABLISHED 18024/beam off (0.00/0/0)
tcp 0 0 127.0.0.1:4369 127.0.0.1:37388 ESTABLISHED 18112/epmd off (0.00/0/0)
tcp 0 0 192.168.91.11:22 192.168.91.146:44936 ESTABLISHED 12444/sshd: stack [ keepalive (3.61/0/0)
tcp6 0 0 :::22 :::* LISTEN 14151/sshd off (0.00/0/0)
tcp6 0 0 :::5672 :::* LISTEN 18024/beam off (0.00/0/0)
この例では、 State が TIME_WAIT になっているコネクションの右端の Timer カラムが timewait (22.35/0/0)
と表示されているのがわかると思う。これは、あと22.35秒で TIME_WAIT 状態を終えて消滅するということである。
そもそもの発端
ところで最近、上記の出力として、文字列部分が空白かつ、数値が全部0というのを見た(ような気がした)。あれー?なんで?そもそも、2つめと3つめの数値って何だっけ?というような話からこの記事を書くに至った発端である(笑)
さて、このためには、netstat (パッケージとしては net-tools) のソースを調べることになる。
netstat の処理
まず、この情報はどんなところからとってきているのか?
話は簡単で、この手の古いコマンドによくあるように、/proc/net/tcp から(IPv6を有効にしていれば、/proc/net/tcp6 も)テキストで情報を読み出して整形しているだけである。
上記の netstat -antpo を実行した直後の /proc/net/tcp4 の内容はこんな感じだった。
[root@centos7-1 ~]# cat /proc/net/tcp
↓これの tr は timer run のようだ。3 だと timeout っぽい。
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000:0CEA 00000000:0000 0A 00000000:00000000 00:00000000 00000000 27 0 119135 1 ffff8800790f5680 100 0 0 10 0
1: 00000000:1111 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 77449 1 ffff8800790f6580 100 0 0 10 0
2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 36997 1 ffff88007bc24000 100 0 0 10 0
3: 00000000:6448 00000000:0000 0A 00000000:00000000 00:00000000 00000000 994 0 78289 1 ffff88007bc25680 100 0 0 10 0
4: 0B5BA8C0:1111 0B5BA8C0:A3BE 06 00000000:00000000 03:000008BB 00000000 0 0 0 3 ffff880079f81400
5: 0100007F:920C 0100007F:1111 01 00000000:00000000 00:00000000 00000000 994 0 78291 1 ffff88007bc25e00 20 4 30 10 -1
6: 0100007F:1111 0100007F:920C 01 00000000:00000000 00:00000000 00000000 995 0 78292 1 ffff88007bc27480 20 4 29 10 -1
7: 0B5BA8C0:0016 925BA8C0:AF88 01 00000000:00000000 02:00000169 00000000 0 0 4832185 4 ffff88007bc26d00 20 4 23 10 -1
上記各行を parse した後、Timer カラムを出力している部分が以下である。
/proc/net/tcp で 'tr' という名前のカラムが以下の case 文の 'timer_run' に相当する。また、case 3 が、TIME_WAIT状態の対応しているようだ。(が、シンボルを使わずに即値を書くのはどうかと思う...)
switch (timer_run) {
case 0:
snprintf(timers, sizeof(timers), _("off (0.00/%ld/%d)"), retr, timeout);
break;
case 1:
snprintf(timers, sizeof(timers), _("on (%2.2f/%ld/%d)"),
(double) time_len / clk_tck, retr, timeout);
break;
case 2:
snprintf(timers, sizeof(timers), _("keepalive (%2.2f/%ld/%d)"),
(double) time_len / clk_tck, retr, timeout);
break;
case 3:
snprintf(timers, sizeof(timers), _("timewait (%2.2f/%ld/%d)"),
(double) time_len / clk_tck, retr, timeout);
break;
case 4:
snprintf(timers, sizeof(timers), _("probe (%2.2f/%ld/%d)"),
(double) time_len / clk_tck, retr, timeout);
break;
default:
snprintf(timers, sizeof(timers), _("unkn-%d (%2.2f/%ld/%d)"),
timer_run, (double) time_len / clk_tck, retr, timeout);
break;
}
kernel の処理
ところで、さきほどの Timer カラムが timewait (22.35/0/0)
と表示されていた2つの0は何か?だが、上記の case 3 によれば、/proc/net/tcpの retransmit と timeout に相当することになっている。…が、出力している kernel のほうを見てみると、(CentOS7 の 3.10系kernelのベースでは)
あたりで出力を行っていて
static void get_timewait4_sock(const struct inet_timewait_sock *tw,
struct seq_file *f, int i, int *len)
{
__be32 dest, src;
__u16 destp, srcp;
int ttd = tw->tw_ttd - jiffies;
if (ttd < 0)
ttd = 0;
dest = tw->tw_daddr;
src = tw->tw_rcv_saddr;
destp = ntohs(tw->tw_dport);
srcp = ntohs(tw->tw_sport);
seq_printf(f, "%4d: %08X:%04X %08X:%04X"
" %02X %08X:%08X %02X:%08lX %08X %5d %8d %d %d %pK%n",
i, src, srcp, dest, destp, tw->tw_substate, 0, 0,
3, jiffies_to_clock_t(ttd), 0, 0, 0, 0,
atomic_read(&tw->tw_refcnt), tw, len);
}
このように、retransmit, uid, inode の先頭2カラムは必ず0になるので意味がないのであった...orz
netstat -o の落とし穴(?)
もう一点、net-tools の netstat.c の case 文をながめてみると、Timer カラムには、tr の値に関わらず、必ず 'timewait' 等の文字列が出るはずであることがわかる。
一瞬、他の人が操作している画面を見ただけだったので手元には端末ログすらなく、迷宮入りになってしまったのが残念である。
まとめ
- netstat の -o (--timers) オプションを使うと、TCP等のendpoint単位で、今どんなタイマーが動いているのか詳細を観察することができる。(ss コマンドの -o オプションでも同じ)
- 上記は /proc/net/tcp や /proc/net/tcp6 が元情報である。
- TIME_WAIT 状態の場合に -o で最後(右端)に出る timewait(ff.ff/0/0) の2つの数字は必ず0になり、意味はない。
余談
余談だが、調べているついでにこんな記事を発見した。
みんな同じようなことで悩むようだ(笑)