Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14385] ファイルの概要

このコミットは、Go言語の標準ライブラリである net/http パッケージにおいて、HTTPリクエストのボディやヘッダーが大きすぎる場合にサーバーが返すエラーレスポンス(特に413 Request Entity Too Large)の処理をより堅牢にするための変更です。以前は、DoS攻撃対策などの理由で大きなリクエストボディの読み込みを途中で中断した場合、クライアントがエラーレスポンスを受け取る前にTCPコネクションがRST(リセット)される可能性がありました。このコミットは、同様の問題がリクエストヘッダーが大きすぎる場合にも発生することに対応し、クライアントがエラーレスポンスを確実に受け取れるように、TCPコネクションの終了方法を改善しています。

コミット

commit 73c67606e9659db662b195e241fdffb1d43a75e1
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Nov 12 15:20:18 2012 -0800

    net/http: handle 413 responses more robustly
    
    When HTTP bodies were too large and we didn't want to finish
    reading them for DoS reasons, we previously found it necessary
    to send a FIN and then pause before closing the connection
    (which might send a RST) if we wanted the client to have a
    better chance at receiving our error response. That was Issue 3595.
    
    This issue adds the same fix to request headers which
    are too large, which might fix the Windows flakiness
    we observed on TestRequestLimit at:
    http://build.golang.org/log/146a2a7d9b24441dc14602a1293918191d4e75f1
    
    R=golang-dev, alex.brainman, rsc
    CC=golang-dev
    https://golang.org/cl/6826084

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/73c67606e9659db662b195e241fdffb1d43a75e1

元コミット内容

net/http: handle 413 responses more robustly

When HTTP bodies were too large and we didn't want to finish
reading them for DoS reasons, we previously found it necessary
to send a FIN and then pause before closing the connection
(which might send a RST) if we wanted the client to have a
better chance at receiving our error response. That was Issue 3595.

This issue adds the same fix to request headers which
are too large, which might fix the Windows flakiness
we observed on TestRequestLimit at:
http://build.golang.org/log/146a2a7d9b24441dc14602a1293918191d4e75f1

R=golang-dev, alex.brainman, rsc
CC=golang-dev
https://golang.org/cl/6826084

変更の背景

この変更の背景には、HTTPサーバーがクライアントからの過大なリクエスト(特にボディやヘッダーが大きすぎる場合)を処理する際の課題があります。DoS攻撃を防ぐため、サーバーは不正なリクエストや過大なリクエストの読み込みを途中で中断することがあります。しかし、その際にTCPコネクションを即座に切断(RSTパケットを送信)してしまうと、サーバーがクライアントに送信したエラーレスポンス(例: 413 Request Entity Too Large)がクライアントに届かない可能性がありました。

コミットメッセージでは、特に「Issue 3595」として言及されている問題が挙げられています。これは、HTTPボディが大きすぎる場合に、サーバーがFINパケットを送信し、その後一時停止してからコネクションを閉じる(RSTを送信する可能性がある)ことで、クライアントがエラーレスポンスを受け取る機会を増やす必要があったというものです。このコミットは、同様の修正をリクエストヘッダーが大きすぎる場合にも適用することで、Windows環境での TestRequestLimit の不安定性(flakiness)を解消することを目的としています。

要するに、サーバーがエラーを返しても、クライアントがそのエラーを認識する前にコネクションが強制的に切断されてしまうという問題があり、これを解決するために、より優雅なコネクション終了処理を導入する必要がありました。

前提知識の解説

TCP FINとRSTパケット

  • FIN (Finish): TCPコネクションを正常に終了させるためのパケットです。FINパケットを受信した側は、まだ送信すべきデータがあればそれを送信し終えてから、自身もFINを返してコネクションを閉じます。これにより、両端がデータの送受信を完了したことを確認し、優雅にコネクションを終了できます。
  • RST (Reset): TCPコネクションを強制的にリセットするためのパケットです。RSTパケットを受信した側は、即座にコネクションを閉じ、未処理のデータは破棄されます。これは通常、エラー状態や、コネクションが突然切断された場合(例: アプリケーションのクラッシュ、タイムアウト)に発生します。RSTは、FINによる優雅な終了とは異なり、データの損失やクライアント側の「Broken Pipe」エラーを引き起こす可能性があります。

HTTPステータスコード

  • 400 Bad Request: クライアントからのリクエストが、構文的に誤っているか、サーバーが理解できない形式である場合に返されます。
  • 413 Request Entity Too Large: クライアントからのリクエストボディが、サーバーが処理できる上限を超えている場合に返されます。

DoS攻撃 (Denial of Service Attack)

サービス拒否攻撃。サーバーに過剰な負荷をかけたり、リソースを枯渇させたりすることで、正当なユーザーからのサービス利用を妨害する攻撃です。大きなリクエストボディやヘッダーを送りつけることも、DoS攻撃の一種となり得ます。

技術的詳細

Goの net/http パッケージのサーバーは、クライアントからのHTTPリクエストを処理します。このコミットが修正しようとしているのは、特にリクエストのサイズがサーバーの許容範囲を超えた場合の挙動です。

以前の挙動では、リクエストボディが大きすぎる (errTooLarge) と判断された場合、サーバーはエラーレスポンスを生成し、その後コネクションを閉じようとしました。しかし、この「閉じる」処理が即座にTCP RSTを発生させる可能性があり、クライアントがエラーレスポンスを読み取る前にコネクションが切断されてしまうことが問題でした。特にBSD系システムやWindowsでこの挙動が顕著だったようです。

このコミットでは、この問題を解決するために以下の変更が導入されました。

  1. closeWriteAndWait() 関数の導入: 以前の closeWrite() 関数は、単にTCPコネクションの書き込み側を閉じ(FINを送信)、バッファをフラッシュするだけでした。新しい closeWriteAndWait() 関数は、書き込み側を閉じた後、rstAvoidanceDelay で定義された一定時間(500ミリ秒)スリープします。このスリープにより、サーバーがFINパケットを送信し、クライアントがそれを処理する時間を与え、その後にコネクション全体が閉じられることによるRSTの発生を避ける(またはRSTがクライアントに到達する前にエラーレスポンスが処理される)ことを期待します。
  2. 413エラー処理の改善: readRequest()errTooLarge が返された場合、以前は msg = "413 Request Entity Too Large" と設定し、fmt.Fprintf でレスポンスを書き込んでいました。変更後は、io.WriteString を使って明示的に HTTP/1.1 413 Request Entity Too Large\r\n\r\n を書き込み、その後 c.closeWriteAndWait() を呼び出して優雅にコネクションの書き込み側を閉じ、ブレークしています。これにより、クライアントが413エラーを確実に受け取れるようになります。
  3. 400エラー処理の統一: readRequest()errTooLarge 以外のエラー(io.EOF やタイムアウトを除く)が発生した場合、以前は msg = "400 Bad Request" と設定し、fmt.Fprintf でレスポンスを書き込んでいました。変更後は、io.WriteString を使って明示的に HTTP/1.1 400 Bad Request\r\n\r\n を書き込むように統一されました。

この修正により、サーバーは過大なリクエストに対して、クライアントがエラーレスポンスを適切に処理できるような形でコネクションを終了するようになります。これは、クライアント側のアプリケーションの堅牢性を高め、デバッグを容易にする上で重要です。

コアとなるコードの変更箇所

src/pkg/net/http/server.go ファイルが変更されています。

主な変更点は以下の通りです。

  1. rstAvoidanceDelay 定数の追加:
    const rstAvoidanceDelay = 500 * time.Millisecond
    
  2. closeWrite() 関数のリネームと変更: closeWrite()closeWriteAndWait() にリネームされ、time.Sleep(rstAvoidanceDelay) が追加されました。
    -func (c *conn) closeWrite() {
    +func (c *conn) closeWriteAndWait() {
     	c.finalFlush()
     	if tcp, ok := c.rwc.(*net.TCPConn); ok {
     		tcp.CloseWrite()
     	}
    +	time.Sleep(rstAvoidanceDelay)
     }
    
  3. c.serve() 関数内のエラーハンドリングの変更:
    • err == errTooLarge の場合の処理が変更されました。
      • 以前は msg = "413 Request Entity Too Large" と設定し、最後にまとめて fmt.Fprintf で出力していました。
      • 変更後は、io.WriteString で直接 HTTP/1.1 413 Request Entity Too Large\r\n\r\n を書き込み、c.closeWriteAndWait() を呼び出して break しています。
    • その他のエラー(io.EOF やタイムアウトを除く)の場合の処理が変更されました。
      • 以前は msg = "400 Bad Request" と設定し、最後にまとめて fmt.Fprintf で出力していました。
      • 変更後は、io.WriteString で直接 HTTP/1.1 400 Bad Request\r\n\r\n を書き込み、break しています。
     	for {
     		w, err := c.readRequest()
     		if err != nil {
    -			msg := "400 Bad Request"
     			if err == errTooLarge {
     				// Their HTTP client may or may not be
     				// able to read this if we're
     				// responding to them and hanging up
     				// while they're still writing their
     				// request.  Undefined behavior.
    -				msg = "413 Request Entity Too Large"
    +				io.WriteString(c.rwc, "HTTP/1.1 413 Request Entity Too Large\r\n\r\n")
    +				c.closeWriteAndWait()
    +				break
     			} else if err == io.EOF {
     				break // Don't reply
     			} else if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
     				break // Don't reply
     			}
    -			fmt.Fprintf(c.rwc, "HTTP/1.1 %s\r\n\r\n", msg)
    +			io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request\r\n\r\n")
     			break
     		}
    
  4. w.requestBodyLimitHit の場合の処理の簡素化: 以前は c.closeWrite() の後に time.Sleep(250 * time.Millisecond) がありましたが、これが c.closeWriteAndWait() の呼び出しに置き換えられました。
     		if w.closeAfterReply {
     			if w.requestBodyLimitHit {
    -				// Flush our response and send a FIN packet and wait a bit
    -				// before closing the connection, so the client has a chance
    -				// to read our response before they possibly get a RST from
    -				// our TCP stack from ignoring their unread body.
    -				// See http://golang.org/issue/3595
    -				c.closeWrite()
    -				// Now wait a bit for our machine to send the FIN and the client's
    -				// machine's HTTP client to read the request before we close
    -				// the connection, which might send a RST (on BSDs, at least).
    -				// 250ms is somewhat arbitrary (~latency around half the planet),
    -				// but this doesn't need to be a full second probably.
    -				time.Sleep(250 * time.Millisecond)
    +				c.closeWriteAndWait()
     			}
     			break
     		}
    

コアとなるコードの解説

このコミットの核心は、TCPコネクションの「優雅な」終了を促進し、クライアントがサーバーからのエラーレスポンスを確実に受け取れるようにすることです。

  • rstAvoidanceDelay (500ミリ秒): この定数は、TCPコネクションの書き込み側を閉じた後、コネクション全体を閉じる前にサーバーが待機する時間を定義します。この短い遅延は、サーバーがFINパケットを送信し、クライアントがそのFINパケットと、それに先行して送信されたエラーレスポンスを処理するのに十分な時間を与えることを目的としています。これにより、コネクション全体が閉じられたときに発生する可能性のあるRSTパケットが、クライアントがエラーレスポンスを読み取る前に到達するのを防ぎます。コミットメッセージでは、以前の250ミリ秒から500ミリ秒に延長されたことが示唆されており、より堅牢な挙動を目指していることがわかります。

  • closeWriteAndWait() 関数: この関数は、net.TCPConnCloseWrite() メソッドを呼び出してTCPコネクションの書き込み側を閉じ、その後 rstAvoidanceDelay だけスリープします。これにより、サーバーは「もうこれ以上データは送らない」という意図をFINパケットでクライアントに伝え、クライアントがそのFINとエラーレスポンスを処理するのを待ちます。このアプローチは、特にクライアントがまだリクエストボディを送信している途中でサーバーがエラーを検出した場合に重要です。サーバーが即座にRSTを送信すると、クライアントはエラーレスポンスを受け取れず、「Broken Pipe」のようなエラーに直面する可能性があります。

  • 413エラー処理の改善: errTooLarge (リクエストエンティティが大きすぎる) の場合、サーバーは以前はエラーメッセージを内部的に設定し、後でまとめて出力していました。しかし、この変更では、io.WriteString を使って HTTP/1.1 413 Request Entity Too Large\r\n\r\n即座にクライアントに書き込みます。その後、c.closeWriteAndWait() を呼び出して、優雅にコネクションの書き込み側を閉じます。この「即座に書き込み、待機して閉じる」というシーケンスにより、クライアントが413エラーレスポンスを確実に受信し、その後のRSTによる予期せぬ切断を回避できます。

  • 400エラー処理の統一: errTooLarge 以外の一般的なリクエストエラー(例: 不正なリクエスト構文)の場合も、io.WriteString を使って HTTP/1.1 400 Bad Request\r\n\r\n を直接書き込むように変更されました。これにより、エラーレスポンスの送信方法が一貫し、より予測可能な挙動になります。

これらの変更は、Goの net/http サーバーが、不正なリクエストや過大なリクエストに対して、より堅牢かつクライアントフレンドリーな方法で応答できるようにするための重要な改善です。特に、TCPの挙動とHTTPプロトコルのセマンティクスを深く理解した上で、クライアント側のアプリケーションがサーバーからのエラーを適切に処理できるように配慮されています。

関連リンク

  • Go Issue 3595 (言及されているが、直接のリンクはコミットメッセージにはないため、Web検索結果から推測されるもの):
    • https://go.dev/issue/3595 (GoのIssueトラッカーにおける関連する議論)
  • Go CL 6826084 (このコミットのChange List):
    • https://golang.org/cl/6826084
  • Windows flakiness on TestRequestLimit のログ (コミットメッセージに記載):
    • http://build.golang.org/log/146a2a7d9b24441dc14602a1293918191d4e75f1

参考にした情報源リンク

  • https://go.dev/issue/3595
  • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH9br23GgZqR-VqL-dc0iJxmDX40xROlNK0en03z0kpAbL2MXlU6XmOHuLe1Pxtd5bhjm_XhgGp9_bujK5jX2uwj7YJ1c3elvpcNh5F71XcLHT_Ymp4vPhFUAPle747uR0=
  • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFsaLJaOKILzCda9O0JIkIU55GiRzEZX5RmEWM2F_iH1vl9vmN-M3P4xmSXayk0cgA234OMErkPfn3Jv0RwdKLsDtOngklyvDFrJI_tC4iR1NmdWG2o0BS-M3pzxROU7zGHivoJ6VvYFBrMP2ZszaUH)
  • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEbWZMdjvuCuiMpqVlSJnadflj3ywex5ZFA0UIfr4xuEhaIyquiePaVkLkpoXJh9tmT7zusuGfrVA9l8P5LgEg1x5YTu_y07xBnJCUauwXwWlHWArCZlWZsSBf2fWfCMO5ajiZ4)
  • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGNUdM9o6KdaX77Vxsk_QlSZSRw1lsBz3t3sn-H5wBiYrEcq49YT0Lw45ZQWqiqqpgbHrLtzCnQjFTfZwOtoGt-rDATeGLGafz3tsYynyEG9kfg-WLOJrfb-6304dvKqlqYLrSAAOxdAcG6IP22SOv0i_HX4Jv9eg3c2bIqaYWmR-GmfPaciG==)
  • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEZmpo5e6q6Oi6cengK_fGXxPGSkzR9JV9w5cl76ypR5ZoONkPF0jFO3hBexS3uv1trTbJtiT5v5pClj1tD3uSSlgQ5JDuGHZ-TltCLx41bHz2UQxy3fhyUedrJ46Z6c9rBHgj9lQ==)