Carpe Diem

備忘録

Go の http.Server は各種 Timeout をセットした方が良い

概要

以前↓の記事を紹介しましたが、

christina04.hatenablog.com

http.Serverの各Timeoutを使わないと

による大量接続攻撃を受けた時に困るので注意してください。

検証

以下のようなコネクション確立後何もしないという悪意あるクライアントを用意してみます。

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())

先ほどのクライアントを実行してみると、

f:id:quoll00:20191118141728p:plain

このようにずっとESTABLISHのままです。

もしクライアントの処理をloopで回したりすると

f:id:quoll00:20191118234512p:plain

このように大量のコネクションが残り続け、fdが枯渇してしまいます。

ReadHeaderTimeoutをセットする場合

今度のサーバはReadHeaderTimeoutをセットします。
http.Serverの各種timeoutは先の記事で紹介してありますが、とりあえず今回の攻撃を防ぐにはこれだけで十分です。

srv := &http.Server{
    ReadHeaderTimeout: 20 * time.Second,
    Addr:              ":8080",
    Handler:           serveMux,
}
log.Println(srv.ListenAndServe())

先ほどと同じようにクライアントを実行すると、まずESTABLISHになります。

f:id:quoll00:20191118141728p:plain

ReadHeaderTimeoutを超えるとFIN_WAIT2に遷移します。

f:id:quoll00:20191118141803p:plain

tcp_fin_timeoutの時間が経つと(デフォルト60s)コネクション自体が消えます。

f:id:quoll00:20191118141714p:plain

L7のLBやReverse Proxyがあれば別

L7のLBやNginxのようなproxyがある場合、クライアントと直接コネクションを確立するのはそれらです。
なのでそれらがちゃんとハンドリングする限り、Goのhttp.Server側でtimeoutを設定しなくても影響は受けません。

AWS ALBの場合

AWSのALBは

があり、先ほどの悪意あるクライアントで接続してもデフォルトの60sでコネクションが閉じられました。

Nginxの場合

Nginxはデフォルトで

といったtimeoutがセットされているので、明示的に設定しなくても大丈夫です。

ソース