自宅で動いてるVMが15台を超えそれぞれどのポートで何してるんだっけってなることが増えてきた。コマンド一発でどのサーバのどのポートがopenになってるのかを確認するコマンドが欲しくなったのでRubyで実装しようと思ってやったメモ。
実装方針
connectを使う。vmごとに色々なportにconnectして成功するかを確認していくイメージで作る。
require 'socket' vms = [ "192.168.1.1" ] ports = [21, 22, 23, 80, 443] s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) vms.each do |vm| ports.each do |port| begin s.connect(Socket.sockaddr_in(port, vm)) rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT puts "close #{vm}:#{port}" else puts "open #{vm}:#{port}" end end end
実装してみたらこんな感じ。openしてるポートがわかれば良いかなぐらいで実行してみる。するとめちゃめちゃ時間がかかることがわかった。原因はRSTを返さずdropするポートがあるらしく再送してくれている模様。
自宅サーバだしfirewalldの設定を変えるか/proc/sys/net/ipv4/tcp_syn_retries
辺りを変えてすぐにプロセスに戻すのでも良いかなとか思ったが絶賛Ruby入門中だったのでRubyでやることにした。
実装方針2
setsockopt(2)を直接実行するかとも思ったがBasicSocket#setsockoptと言うのがあるらしく公式ドキュメントを読む練習にもなりそうなのでこちらを使ってみる。
tcpのソケットにはTCP_SYNCNTというオプションがあるのでそちらを指定するように修正。再送回数は1回で指定して実行してみる。実行時間が数秒まで短くなることが確認できた。ちなみにMacだとTCP_SYNCNTがないらしく動かなかった。
require 'socket' vms = [ "192.168.1.207" ] ports = [1, 2, 3, 21, 22, 23, 80, 443, 8080, 8443] s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) s.setsockopt("TCP", "SYNCNT", 1) vms.each do |vm| ports.each do |port| begin s.connect(Socket.sockaddr_in(port, vm)) rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT puts "close #{vm}:#{port}" else puts "open #{vm}:#{port}" end end end
再送回数の最大値
man tcpとかで見ると255 より大きくはできないと書いてある。255まで設定できるのかなと思って設定しようとすると以下のようなエラーとなった。127あたりが限界値となっていそう。
[root@c4acc3700c47 /]# ruby a.rb a.rb:4:in `setsockopt': Invalid argument (Errno::EINVAL) from a.rb:4:in `<main>'
設定しているのは以下のあたりでMAX_TCP_SYNCNTは127とdefineされていた。ここで制限されている模様。
int tcp_sock_set_syncnt(struct sock *sk, int val) { if (val < 1 || val > MAX_TCP_SYNCNT) return -EINVAL; lock_sock(sk); inet_csk(sk)->icsk_syn_retries = val; release_sock(sk); return 0; }
127より大きくすることは型を見た感じは可能なようなのでコンパイルし直せば使えそう。(やる機会は永遠になさそうですが😅)