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

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

このコミットは、Go言語の標準ライブラリnetパッケージにおける、リモートアドレス(raddr)の取得と設定に関するバグ修正と堅牢性向上を目的としています。特に、getpeernameシステムコールが失敗した場合の挙動と、Unixドメインソケットにおける「名前なし(unnamed)」アドレスの扱いを改善しています。

コミット

commit bfb32dc6d1b8cbe6f4d2d52d3226e4c31e36b576
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sat Mar 23 22:32:19 2013 +0900

    net: use original raddr if getpeername fails
    
    This CL updates CL 7511043;
    - adds new test cases for both UDPConn and UnixConn,
    - makes sure unnamed UnixAddr handling,
    - replaces t.Errorf with t.Fatalf in sockname related test cases.
    
    Fixes #3721 (again).
    Fixes #3838 (again).
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7627048

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

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

元コミット内容

上記の「コミット」セクションに記載されている内容が、このコミットの元々の内容です。

変更の背景

この変更は、以前の変更リスト(CL 7511043)を更新し、ネットワーク接続におけるリモートアドレスの取得に関する既存の問題を解決するために行われました。具体的には、getpeernameシステムコールがピア(接続相手)のアドレスを取得できない場合に、接続確立時に指定された元々のリモートアドレス(ursa)を使用するようにフォールバックする挙動を導入しています。

この問題は、特にUDP(User Datagram Protocol)やUnixドメインソケットのようなコネクションレスなプロトコル、または抽象的な名前空間を使用するUnixドメインソケットにおいて顕在化していました。getpeernameは通常、接続指向ソケットでピアのアドレスを取得するために使用されますが、コネクションレスソケットでは常に有効なピアが存在するわけではないため、失敗する可能性があります。このような場合、Goのnetパッケージが誤ったリモートアドレスを報告したり、アドレスが欠落したりするバグ(Issue #3721と#3838)が発生していました。これらの問題が「(again)」と記載されていることから、以前にも同様の修正が試みられたものの、完全には解決されていなかったことが示唆されます。

また、テストの堅牢性を高めるために、テスト失敗時の挙動も改善されています。

前提知識の解説

  1. getpeernameシステムコール: Unix系OSにおけるソケットAPIの一つで、接続されたソケットのピア(リモート側)のアドレスを取得するために使用されます。TCPのような接続指向プロトコルでは、接続が確立されるとピアのアドレスが設定されるため、このシステムコールで取得できます。しかし、UDPのようなコネクションレスプロトコルや、connect()が呼び出されていないソケットでは、ピアのアドレスが設定されていないか、一時的なものであるため、getpeernameが失敗したり、意味のある情報を返さないことがあります。

  2. UnixAddrとUnixドメインソケット: Unixドメインソケットは、同じホスト上のプロセス間通信(IPC)に使用されるソケットの一種です。ファイルシステム上のパス(例: /tmp/mysocket.sock)にバインドされることが多いですが、Linuxでは「抽象名前空間(abstract namespace)」と呼ばれる、ファイルシステムに依存しない名前なしソケットも存在します。UnixAddrはGo言語のnetパッケージでUnixドメインソケットのアドレスを表す構造体です。名前なしソケットは、Nameフィールドが空文字列または@で始まる特殊な形式で表現されることがあります。

  3. t.Errorf vs t.Fatalf (Goのテスト): Go言語のtestingパッケージで提供されるテストヘルパー関数です。

    • t.Errorf(...): テスト中にエラーが発生したことを報告しますが、テストの実行は継続します。複数のエラーを一度のテスト実行で検出したい場合に便利です。
    • t.Fatalf(...): テスト中にエラーが発生したことを報告し、直ちに現在のテストの実行を停止します。これは、その後のテストステップが意味をなさない、またはさらなるエラーを引き起こす可能性が高い場合に特に有用です。このコミットでは、アドレス関連のテストでt.Fatalfに置き換えることで、問題が検出された際にテストがより早く、明確に失敗するように改善されています。
  4. netFD: Go言語のnetパッケージ内部で使用されるファイルディスクリプタ(File Descriptor)の抽象化です。ソケット操作の低レベルな詳細をカプセル化し、アドレス情報(ローカルアドレスladdrとリモートアドレスraddr)を保持します。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. getpeername失敗時のリモートアドレスのフォールバック (src/pkg/net/sock_posix.go): socket関数内で、syscall.Getpeername(s)がリモートソケットアドレス(rsa)を取得できなかった場合(rsa == nil)、ソケット作成時に渡された元のリモートソケットアドレス(ursa)をfd.raddrとして設定するように変更されました。これにより、getpeernameが常に有効なアドレスを返さないようなシナリオ(例: UDPソケット)でも、正しいリモートアドレスがnetFDに保持されるようになります。以前はfd.raddr == nilの場合にursaをセットしていましたが、getpeernamenilでないが意味のない値を返すケースに対応するため、より直接的にrsa == nilをチェックするようになりました。

  2. 名前なしUnixアドレスの適切なハンドリング (src/pkg/net/unixsock_posix.go):

    • UnixAddr構造体が名前なしであるかを判定する新しいメソッドisUnnamed() boolが追加されました。これはa == nil || a.Name == ""の場合にtrueを返します。
    • unixSocket関数内で、dialモードの場合にローカルアドレス(laddr)が名前なしであるかをladdr.isUnnamed()でチェックするようになりました。これにより、名前なしのローカルアドレスが正しく扱われ、不必要なエラー(errMissingAddress)が回避されます。
    • sockaddrToUnix, sockaddrToUnixgram, sockaddrToUnixpacket, ReadFromUnix, ReadMsgUnixといった関数でUnixAddrを生成する際に、構造体リテラルで明示的にフィールド名(Name: s.Name, Net: "unix"など)を指定するようになりました。これはGoのコーディングスタイルにおける可読性と堅牢性の向上に寄与します。
  3. テストケースの拡充と改善:

    • src/pkg/net/udp_test.goTestUDPConnLocalAndRemoteNamesが追加され、UDP接続におけるローカルアドレスとリモートアドレスの取得が、様々なシナリオ(特にladdrが空の場合)で正しく行われることを検証します。
    • src/pkg/net/unix_test.goTestUnixConnLocalAndRemoteNamesTestUnixgramConnLocalAndRemoteNamesが追加され、Unixドメインソケット(ストリームとデータグラムの両方)におけるローカルアドレスとリモートアドレスの取得、特に名前なしソケット(Linuxのautobind機能を含む)の挙動が詳細にテストされます。
    • 既存のテストケース(TestTCPListenerName, TestUDPConnLocalName, TestReadUnixgramWithUnnamedSocketなど)において、t.Errorft.Fatalfに置き換えられました。これにより、アドレス関連のテストが失敗した場合に、その後のテストが実行されずに即座に終了し、デバッグが容易になります。

これらの変更により、Goのnetパッケージは、特にエッジケースにおけるネットワークアドレスのハンドリングにおいて、より正確で堅牢な挙動を示すようになりました。

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

src/pkg/net/sock_posix.go

--- a/src/pkg/net/sock_posix.go
+++ b/src/pkg/net/sock_posix.go
@@ -65,10 +65,10 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,
 	lsa, _ := syscall.Getsockname(s)
 	laddr := toAddr(lsa)
 	rsa, _ := syscall.Getpeername(s)
+	if rsa == nil {
+		rsa = ursa
+	}
 	raddr := toAddr(rsa)
 	fd.setAddr(laddr, raddr)
-	if fd.raddr == nil {
-		fd.raddr = toAddr(ursa)
-	}
 	return fd, nil
 }

src/pkg/net/unixsock_posix.go

--- a/src/pkg/net/unixsock_posix.go
+++ b/src/pkg/net/unixsock_posix.go
@@ -15,6 +15,13 @@ import (
 	"time"
 )
 
+func (a *UnixAddr) isUnnamed() bool {
+	if a == nil || a.Name == "" {
+		return true
+	}
+	return false
+}
+
 func unixSocket(net string, laddr, raddr *UnixAddr, mode string, deadline time.Time) (*netFD, error) {
 	var sotype int
 	switch net {
@@ -31,12 +38,12 @@ func unixSocket(net string, laddr, raddr *UnixAddr, mode string, deadline time.T
 	var la, ra syscall.Sockaddr
 	switch mode {
 	case "dial":
-		if laddr != nil {
+		if !laddr.isUnnamed() {
 			la = &syscall.SockaddrUnix{Name: laddr.Name}
 		}
 		if raddr != nil {
 			ra = &syscall.SockaddrUnix{Name: raddr.Name}
-		} else if sotype != syscall.SOCK_DGRAM || laddr == nil {
+		} else if sotype != syscall.SOCK_DGRAM || laddr.isUnnamed() {
 			return nil, &OpError{Op: mode, Net: net, Err: errMissingAddress}
 		}
 	case "listen":
@@ -69,21 +76,21 @@ error:
 
 func sockaddrToUnix(sa syscall.Sockaddr) Addr {
 	if s, ok := sa.(*syscall.SockaddrUnix); ok {
-		return &UnixAddr{s.Name, "unix"}
+		return &UnixAddr{Name: s.Name, Net: "unix"}
 	}
 	return nil
 }
 
 func sockaddrToUnixgram(sa syscall.Sockaddr) Addr {
 	if s, ok := sa.(*syscall.SockaddrUnix); ok {
-		return &UnixAddr{s.Name, "unixgram"}
+		return &UnixAddr{Name: s.Name, Net: "unixgram"}
 	}
 	return nil
 }
 
 func sockaddrToUnixpacket(sa syscall.Sockaddr) Addr {
 	if s, ok := sa.(*syscall.SockaddrUnix); ok {
-		return &UnixAddr{s.Name, "unixpacket"}
+		return &UnixAddr{Name: s.Name, Net: "unixpacket"}
 	}
 	return nil
 }
@@ -92,10 +99,10 @@ func sotypeToNet(sotype int) string {
 	switch sotype {
 	case syscall.SOCK_STREAM:
 		return "unix"
-	case syscall.SOCK_SEQPACKET:
-		return "unixpacket"
 	case syscall.SOCK_DGRAM:
 		return "unixgram"
+	case syscall.SOCK_SEQPACKET:
+		return "unixpacket"
 	default:
 		panic("sotypeToNet unknown socket type")
 	}
@@ -124,7 +131,7 @@ func (c *UnixConn) ReadFromUnix(b []byte) (n int, addr *UnixAddr, err error) {
 	switch sa := sa.(type) {
 	case *syscall.SockaddrUnix:
 		if sa.Name != "" {
-			addr = &UnixAddr{sa.Name, sotypeToNet(c.fd.sotype)}
+			addr = &UnixAddr{Name: sa.Name, Net: sotypeToNet(c.fd.sotype)}
 		}
 	}
 	return
@@ -151,7 +158,7 @@ func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAdd
 	switch sa := sa.(type) {
 	case *syscall.SockaddrUnix:
 		if sa.Name != "" {
-			addr = &UnixAddr{sa.Name, sotypeToNet(c.fd.sotype)}
+			addr = &UnixAddr{Name: sa.Name, Net: sotypeToNet(c.fd.sotype)}
 		}
 	}
 	return

コアとなるコードの解説

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

この変更は、ソケットのピアアドレス(リモートアドレス)の取得ロジックを改善しています。 syscall.Getpeername(s)は、ソケットsに接続されているピアのアドレスを取得しようとします。しかし、UDPソケットのように接続指向ではない場合や、何らかの理由でピアアドレスが取得できない場合、rsaGetpeernameの結果)がnilになることがあります。 変更前は、fd.raddrnilの場合にのみ、ソケット作成時に指定された元のリモートアドレスursaを使用していました。しかし、Getpeernamenilではないが、例えば空のアドレス構造体のような意味のない値を返す場合、この条件ではursaが使われず、不正確なraddrが設定される可能性がありました。 変更後は、rsa == nilというより厳密なチェックを行うことで、Getpeernameが完全に失敗した場合にのみursaをフォールバックとして使用するようにしました。これにより、リモートアドレスの信頼性が向上します。

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

  1. func (a *UnixAddr) isUnnamed() bool: この新しいヘルパー関数は、UnixAddrが「名前なし」であるかどうかを簡潔に判定します。UnixAddrポインタがnilであるか、またはそのNameフィールドが空文字列である場合にtrueを返します。これは、Linuxの抽象名前空間ソケットや、ファイルシステムパスにバインドされていないUnixドメインソケットを識別するために使用されます。

  2. unixSocket関数内のladdr.isUnnamed()の使用: dialモードでUnixソケットを扱う際、ローカルアドレスladdrが指定されている場合でも、それが名前なしソケットであれば、syscall.SockaddrUnixNameフィールドを設定する必要がありません。以前はladdr != nilというチェックだけでしたが、名前なしソケットの場合にladdr.Nameが空であるため、isUnnamed()を使うことでより正確な条件分岐が可能になりました。また、sotype != syscall.SOCK_DGRAM || laddr.isUnnamed()という条件は、データグラムソケットでない場合、またはデータグラムソケットであってもローカルアドレスが名前なしである場合に、リモートアドレスが必須であることを示しています。これにより、不必要なerrMissingAddressエラーの発生を防ぎます。

  3. UnixAddr構造体リテラルの明示的なフィールド名指定: sockaddrToUnixなどの関数でUnixAddr構造体を初期化する際に、&UnixAddr{s.Name, "unix"}のように位置引数で初期化していた箇所を、&UnixAddr{Name: s.Name, Net: "unix"}のようにフィールド名を明示的に指定する形式に変更しました。これはGoの慣習に沿ったもので、コードの可読性と保守性を向上させます。フィールドの順序が変わってもコードが壊れることがなく、意図がより明確になります。

  4. sotypeToNetの順序変更: sotypeToNet関数内のswitch文のケースの順序が変更されました。機能的な変更はありませんが、論理的な順序付けやコードの整理の一環と考えられます。

これらの変更は、Goのネットワークスタックが、特にUnixドメインソケットのような特殊なケースにおいて、より堅牢で正確なアドレス情報を提供できるようにするための重要な改善です。

関連リンク

参考にした情報源リンク