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

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

コミット

commit ad341843a7661e4883c8a66b12433e55db1dfc7e
Author: Michael Teichgräber <mteichgraeber@gmx.de>
Date:   Wed Jan 30 09:25:16 2013 -0800

    net: SplitHostPort: adjust error message for missing port in IPv6 addresses
    
    An hostport of "[::1]" now results in the same error message
    "missing port in address" as the hostport value "127.0.0.1",
    so SplitHostPort won't complain about "too many colons
    in address" anymore for an IPv6 address missing a port.
    
    Added tests checking the error values.
    
    Fixes #4526.
    
    R=dave, rsc, mikioh.mikioh
    CC=golang-dev
    https://golang.org/cl/7038045

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

https://github.com/golang/go/commit/ad341843a7661e4883c8a66b12433e55db1dfc7e

元コミット内容

このコミットは、Go言語のnetパッケージにおけるSplitHostPort関数のエラーメッセージの挙動を調整するものです。具体的には、ポート番号が欠落しているIPv6アドレス(例: [::1])に対して、これまでは「too many colons in address」(アドレスにコロンが多すぎる)という誤解を招くエラーメッセージを返していた問題を修正し、IPv4アドレス(例: 127.0.0.1)と同様に「missing port in address」(アドレスにポートがありません)という、より適切なエラーメッセージを返すように変更しています。

この変更には、エラー値を確認する新しいテストケースの追加も含まれています。これにより、Go 1.0では失敗しなかった特定の不正なアドレス形式に対する挙動も改善されています。

変更の背景

Go言語のnetパッケージには、ホストとポートの文字列を分離するSplitHostPort関数が存在します。この関数は、ネットワークアドレスの解析において非常に重要な役割を担っています。

従来のSplitHostPortの実装では、IPv6アドレスの特殊な形式が原因で、ポートが指定されていない場合に不適切なエラーメッセージを返す問題がありました。IPv6アドレスは複数のコロン(:)を含むため、ポート番号が欠落している場合でも、関数がアドレス内のコロンの数を誤って解釈し、「too many colons in address」というエラーを返していました。これは、IPv4アドレスではコロンがポートセパレータとしてのみ使用されるため、コロンが複数ある場合は不正な形式と判断されるのに対し、IPv6アドレスではアドレス自体にコロンが含まれるという違いに起因します。

この挙動は、ユーザーや開発者にとって混乱を招くものであり、真の問題(ポートの欠落)を正確に伝えるものではありませんでした。GitHub Issue #4526で報告されたこの問題に対処するため、SplitHostPort関数がIPv6アドレスの解析をより適切に行い、ポートが欠落している場合には一貫して「missing port in address」というエラーを返すように修正されました。これにより、エラーメッセージの正確性が向上し、デバッグが容易になります。

前提知識の解説

1. ホストとポート (Host and Port)

ネットワーク通信において、ホストは通信相手のコンピュータやデバイスを識別するもので、IPアドレス(例: 192.168.1.1::1)やホスト名(例: www.example.com)で表されます。ポートは、そのホスト上で動作している特定のアプリケーションやサービスを識別するための番号です。例えば、HTTP通信は通常ポート80、HTTPSはポート443を使用します。

ホストとポートは通常、ホスト:ポートという形式で結合されます。例: 127.0.0.1:8080www.google.com:443

2. IPv4アドレスとIPv6アドレス

  • IPv4アドレス: 32ビットの数値で構成され、通常はドットで区切られた4つの10進数(例: 192.168.1.1)で表現されます。
  • IPv6アドレス: 128ビットの数値で構成され、通常はコロンで区切られた8つの16進数グループ(例: 2001:0db8:85a3:0000:0000:8a2e:0370:7334)で表現されます。IPv6アドレスは、その性質上、複数のコロンを含みます。また、IPv6アドレスをホストとして使用する場合、曖昧さを避けるために角括弧([])で囲むのが一般的です。例: [::1]:8080

3. SplitHostPort関数

Go言語のnetパッケージに含まれるSplitHostPort関数は、ホスト:ポート形式の文字列を受け取り、ホスト部分とポート部分に分割して返します。この関数は、ネットワーク接続を確立する際のアドレス解析において頻繁に利用されます。

4. エラーハンドリングとエラーメッセージ

プログラミングにおいて、エラーハンドリングは非常に重要です。特に、ユーザーからの入力や外部システムからのデータを受け取る際には、予期せぬ形式や値に対して適切に対処する必要があります。エラーメッセージは、何が問題であったかをユーザーや開発者に正確に伝えるための重要な情報源です。不正確なエラーメッセージは、問題の特定と解決を困難にします。

5. Go言語のAddrError

Go言語のnetパッケージでは、ネットワークアドレスに関連するエラーを表すためにAddrErrorという構造体が定義されています。この構造体は、エラーの種類を示す文字列(Errフィールド)と、エラーが発生したアドレス文字列(Addrフィールド)を含みます。このコミットでは、AddrErrorErrフィールドの値が適切に設定されるように修正されています。

技術的詳細

このコミットの核心は、src/pkg/net/ipsock.go内のsplitHostPort関数のロジック変更にあります。この関数は、SplitHostPortから内部的に呼び出され、実際のホストとポートの分離処理を行います。

変更前は、splitHostPort関数はまず文字列の最後のコロン(:)を探し、それより前をホスト、後をポートと仮定していました。IPv6アドレスが角括弧で囲まれている場合(例: [::1]:80)、関数は角括弧を取り除いてからホスト部分を処理していました。しかし、ポートが欠落しているIPv6アドレス(例: [::1])の場合、最後のコロンが見つからないため、missing port in addressエラーを返すはずでした。

問題は、角括弧で囲まれていないIPv6アドレス(例: ::1)や、不正な形式のIPv6アドレスが与えられた場合に発生していました。これらのケースでは、アドレス内に複数のコロンが存在するため、splitHostPortは「too many colons in address」というエラーを返していました。これは、IPv6アドレスの構造を考慮せず、IPv4アドレスの解析ロジックを単純に適用した結果でした。

今回の修正では、splitHostPort関数は以下の点を改善しています。

  1. IPv6アドレスの角括弧の扱い:
    • 入力文字列が[で始まり、]で終わる場合、IPv6アドレスとして特別に処理されます。
    • ]の位置が最後のコロンの直前にあることを確認し、正しい形式のIPv6アドレスとポートの組み合わせを識別します。
    • ]の後にコロンが続かない場合、または]の後に続くコロンが最後のコロンでない場合、missing port in addressまたはtoo many colonsの適切なエラーに分岐します。
  2. コロンの数のチェックの改善:
    • ホスト部分にコロンが複数含まれる場合のチェックが、IPv6アドレスの角括弧の有無に応じて適切に調整されました。
    • これにより、[::1]のようにポートが欠落しているIPv6アドレスが、too many colonsではなくmissing portとして正しく扱われるようになります。
  3. 不正な角括弧の検出:
    • ホスト部分の解析中に、予期せぬ[]が出現した場合に、それぞれunexpected '[' in addressunexpected ']' in addressというエラーを返すようになりました。これにより、より具体的なエラー情報が提供されます。
  4. gotoステートメントの導入:
    • エラー処理のロジックを簡潔にするために、missingPorttooManyColonsというラベルへのgotoステートメントが導入されました。これにより、複数の場所から共通のエラー処理ロジックにジャンプできるようになり、コードの重複が削減されています。

これらの変更により、SplitHostPortはIPv6アドレスの解析においてより堅牢になり、ユーザーにとってより分かりやすいエラーメッセージを提供するようになりました。

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

src/pkg/net/ip_test.go

  • splitfailuretestsという新しいテストケースのスライスが追加されました。このスライスは、SplitHostPort関数が特定の不正なホストポート文字列に対して期待されるエラーメッセージを返すことを検証します。
    • 特に、"[::1]"に対して"missing port in address"が期待されるようになりました。
    • Go 1.0では失敗しなかったが、この修正で失敗するようになるケース(例: "[foo:bar]")も追加されています。
  • TestSplitHostPort関数内に、splitfailuretestsをイテレートし、SplitHostPortが期待されるエラーを返すかどうかをチェックする新しいループが追加されました。

src/pkg/net/ipsock.go

  • splitHostPort関数の実装が大幅に変更されました。
  • j, k := 0, 0という変数が追加され、文字列の走査範囲を管理するために使用されます。
  • IPv6アドレスの角括弧([])の処理ロジックが強化されました。
    • hostport[0] == '['の場合の分岐が追加され、]の位置とそれに続く文字(コロンの有無)を詳細にチェックします。
    • end + 1len(hostport)と等しい場合(]の後に何も続かない場合)はmissingPortにジャンプします。
    • end + 1iと等しい場合(]の直後に最後のコロンが続く場合)は正常なパスとして処理されます。
    • それ以外の場合(]の後にコロンが続かない、またはコロンが最後のコロンでない場合)は、tooManyColonsまたはmissingPortにジャンプします。
  • 角括弧がない場合のホスト部分のコロンチェックも調整されました。
  • byteIndex(hostport[j:], '[' ) >= 0byteIndex(hostport[k:], ']' ) >= 0というチェックが追加され、予期せぬ位置に角括弧がある場合にエラーを返すようになりました。
  • エラー処理のためにgotoステートメントが導入され、missingPorttooManyColonsというラベルが定義されました。これにより、エラーメッセージの生成ロジックが一元化されました。

コアとなるコードの解説

src/pkg/net/ip_test.goの変更

var splitfailuretests = []struct {
	HostPort string
	Err      string
}{
	{"www.google.com", "missing port in address"},
	{"127.0.0.1", "missing port in address"},
	{"[::1]", "missing port in address"}, // この行が重要
	{"::1", "too many colons in address"}, // この行も重要

	// Test cases that didn't fail in Go 1.0
	{"[foo:bar]", "missing port in address"},
	{"[foo:bar]baz", "missing port in address"},
	{"[foo]:[bar]:baz", "too many colons in address"},
	{"[foo]bar:baz", "missing port in address"},
	{"[foo]:[bar]baz", "unexpected '[' in address"},
	{"foo[bar]:baz", "unexpected '[' in address"},
	{"foo]bar:baz", "unexpected ']' in address"},
}

func TestSplitHostPort(t *testing.T) {
	// ... 既存の成功テスト ...

	for _, tt := range splitfailuretests {
		if _, _, err := SplitHostPort(tt.HostPort); err == nil {
			t.Errorf("SplitHostPort(%q) should have failed", tt.HostPort)
		} else {
			e := err.(*AddrError)
			if e.Err != tt.Err {
				t.Errorf("SplitHostPort(%q) = _, _, %q; want %q", tt.HostPort, e.Err, tt.Err)
			}
		}
	}
}

このテストコードは、SplitHostPort関数が特定の不正な入力に対して、期待される正確なエラーメッセージ(AddrError.Errフィールド)を返すことを保証します。特に{"[::1]", "missing port in address"}の追加は、IPv6アドレスのポート欠落に対するエラーメッセージの修正を直接検証しています。

src/pkg/net/ipsock.goの変更

func splitHostPort(hostport string) (host, port, zone string, err error) {
	j, k := 0, 0 // 新しく追加された変数

	// The port starts after the last colon.
	i := last(hostport, ':')
	if i < 0 {
		goto missingPort // ポートがない場合はmissingPortラベルへジャンプ
	}

	if hostport[0] == '[' {
		// Expect the first ']' just before the last ':'.
		end := byteIndex(hostport, ']')
		if end < 0 {
			err = &AddrError{"missing ']' in address", hostport}
			return
		}
		switch end + 1 {
		case len(hostport):
			// There can't be a ':' behind the ']' now.
			goto missingPort
		case i:
			// The expected result.
		default:
			// Either ']' isn't followed by a colon, or it is
			// followed by a colon that is not the last one.
			if hostport[end+1] == ':' {
				goto tooManyColons
			}
			goto missingPort
		}
		host = hostport[1:end]
		j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions
	} else {
		host = hostport[:i]

		if byteIndex(host, ':') >= 0 {
			goto tooManyColons // ホスト部分にコロンが多すぎる場合はtooManyColonsラベルへジャンプ
		}
	}
	if byteIndex(hostport[j:], '[') >= 0 {
		err = &AddrError{"unexpected '[' in address", hostport}
		return
	}
	if byteIndex(hostport[k:], ']') >= 0 {
		err = &AddrError{"unexpected ']' in address", hostport}
		return
	}

	port = hostport[i+1:]
	return

missingPort: // missing port in addressエラーを返す共通の処理
	err = &AddrError{"missing port in address", hostport}
	return

tooManyColons: // too many colons in addressエラーを返す共通の処理
	err = &AddrError{"too many colons in address", hostport}
	return
}

この修正の主要な部分は、IPv6アドレスの角括弧の処理をより厳密にした点です。

  • hostport[0] == '['のチェックにより、IPv6アドレスの形式を早期に識別します。
  • end := byteIndex(hostport, ']')で閉じ角括弧の位置を探し、その位置が最後のコロンの直前にあるかどうかをswitch end + 1ブロックで詳細に検証します。
  • この検証により、[::1]のようにポートがないIPv6アドレスが、missingPortラベルに正しく誘導されるようになりました。
  • また、jkというインデックスを導入することで、既に処理した部分を除外して、文字列の残りの部分に予期せぬ角括弧がないかをチェックしています。
  • gotoステートメントの使用は、エラー処理ロジックを簡潔にし、コードの重複を避けるためのGo言語における一般的なパターンの一つです。これにより、missing port in addresstoo many colons in addressという2つの主要なエラーメッセージが、それぞれ対応するラベルにジャンプすることで一貫して生成されます。

関連リンク

参考にした情報源リンク