地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【Ruby】SYNCNTを調整して早めにタイムアウトさせる

自宅で動いてるVMが15台を超えそれぞれどのポートで何してるんだっけってなることが増えてきた。コマンド一発でどのサーバのどのポートがopenになってるのかを確認するコマンドが欲しくなったのでRubyで実装しようと思ってやったメモ。

実装方針

docs.ruby-lang.org

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と言うのがあるらしく公式ドキュメントを読む練習にもなりそうなのでこちらを使ってみる。

docs.ruby-lang.org

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より大きくすることは型を見た感じは可能なようなのでコンパイルし直せば使えそう。(やる機会は永遠になさそうですが😅)

github.com