概要
以前↓の記事を紹介しましたが、
http.Serverの各Timeoutを使わないと
- TCPハンドシェイクをしたあと全くリクエストを送ってこないクライアント
- Slowloris DDoS attack
による大量接続攻撃を受けた時に困るので注意してください。
検証
以下のようなコネクション確立後何もしないという悪意あるクライアントを用意してみます。
require 'socket' socket = Socket.new(:INET, :STREAM) remote_addr = Socket.pack_sockaddr_in(8080, '192.168.33.10') socket.connect(remote_addr) sleep(1000)
ReadHeaderTimeoutをセットしない場合
以下のようにbindするアドレスとhandlerのみセットします。
srv := &http.Server{
Addr: ":8080"
Handler: serveMux,
}
log.Println(srv.ListenAndServe())
先ほどのクライアントを実行してみると、
このようにずっとESTABLISHのままです。
もしクライアントの処理をloopで回したりすると
このように大量のコネクションが残り続け、fdが枯渇してしまいます。
ReadHeaderTimeoutをセットする場合
今度のサーバはReadHeaderTimeout
をセットします。
http.Serverの各種timeoutは先の記事で紹介してありますが、とりあえず今回の攻撃を防ぐにはこれだけで十分です。
srv := &http.Server{ ReadHeaderTimeout: 20 * time.Second, Addr: ":8080", Handler: serveMux, } log.Println(srv.ListenAndServe())
先ほどと同じようにクライアントを実行すると、まずESTABLISHになります。
ReadHeaderTimeout
を超えるとFIN_WAIT2
に遷移します。
tcp_fin_timeout
の時間が経つと(デフォルト60s)コネクション自体が消えます。
L7のLBやReverse Proxyがあれば別
L7のLBやNginxのようなproxyがある場合、クライアントと直接コネクションを確立するのはそれらです。
なのでそれらがちゃんとハンドリングする限り、Goのhttp.Server側でtimeoutを設定しなくても影響は受けません。
AWS ALBの場合
AWSのALBは
があり、先ほどの悪意あるクライアントで接続してもデフォルトの60sでコネクションが閉じられました。
Nginxの場合
Nginxはデフォルトで
といったtimeoutがセットされているので、明示的に設定しなくても大丈夫です。