[インデックス 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)」と記載されていることから、以前にも同様の修正が試みられたものの、完全には解決されていなかったことが示唆されます。
また、テストの堅牢性を高めるために、テスト失敗時の挙動も改善されています。
前提知識の解説
-
getpeername
システムコール: Unix系OSにおけるソケットAPIの一つで、接続されたソケットのピア(リモート側)のアドレスを取得するために使用されます。TCPのような接続指向プロトコルでは、接続が確立されるとピアのアドレスが設定されるため、このシステムコールで取得できます。しかし、UDPのようなコネクションレスプロトコルや、connect()
が呼び出されていないソケットでは、ピアのアドレスが設定されていないか、一時的なものであるため、getpeername
が失敗したり、意味のある情報を返さないことがあります。 -
UnixAddr
とUnixドメインソケット: Unixドメインソケットは、同じホスト上のプロセス間通信(IPC)に使用されるソケットの一種です。ファイルシステム上のパス(例:/tmp/mysocket.sock
)にバインドされることが多いですが、Linuxでは「抽象名前空間(abstract namespace)」と呼ばれる、ファイルシステムに依存しない名前なしソケットも存在します。UnixAddr
はGo言語のnet
パッケージでUnixドメインソケットのアドレスを表す構造体です。名前なしソケットは、Name
フィールドが空文字列または@
で始まる特殊な形式で表現されることがあります。 -
t.Errorf
vst.Fatalf
(Goのテスト): Go言語のtesting
パッケージで提供されるテストヘルパー関数です。t.Errorf(...)
: テスト中にエラーが発生したことを報告しますが、テストの実行は継続します。複数のエラーを一度のテスト実行で検出したい場合に便利です。t.Fatalf(...)
: テスト中にエラーが発生したことを報告し、直ちに現在のテストの実行を停止します。これは、その後のテストステップが意味をなさない、またはさらなるエラーを引き起こす可能性が高い場合に特に有用です。このコミットでは、アドレス関連のテストでt.Fatalf
に置き換えることで、問題が検出された際にテストがより早く、明確に失敗するように改善されています。
-
netFD
: Go言語のnet
パッケージ内部で使用されるファイルディスクリプタ(File Descriptor)の抽象化です。ソケット操作の低レベルな詳細をカプセル化し、アドレス情報(ローカルアドレスladdr
とリモートアドレスraddr
)を保持します。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
getpeername
失敗時のリモートアドレスのフォールバック (src/pkg/net/sock_posix.go
):socket
関数内で、syscall.Getpeername(s)
がリモートソケットアドレス(rsa
)を取得できなかった場合(rsa == nil
)、ソケット作成時に渡された元のリモートソケットアドレス(ursa
)をfd.raddr
として設定するように変更されました。これにより、getpeername
が常に有効なアドレスを返さないようなシナリオ(例: UDPソケット)でも、正しいリモートアドレスがnetFD
に保持されるようになります。以前はfd.raddr == nil
の場合にursa
をセットしていましたが、getpeername
がnil
でないが意味のない値を返すケースに対応するため、より直接的にrsa == nil
をチェックするようになりました。 -
名前なし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のコーディングスタイルにおける可読性と堅牢性の向上に寄与します。
-
テストケースの拡充と改善:
src/pkg/net/udp_test.go
にTestUDPConnLocalAndRemoteNames
が追加され、UDP接続におけるローカルアドレスとリモートアドレスの取得が、様々なシナリオ(特にladdr
が空の場合)で正しく行われることを検証します。src/pkg/net/unix_test.go
にTestUnixConnLocalAndRemoteNames
とTestUnixgramConnLocalAndRemoteNames
が追加され、Unixドメインソケット(ストリームとデータグラムの両方)におけるローカルアドレスとリモートアドレスの取得、特に名前なしソケット(Linuxのautobind機能を含む)の挙動が詳細にテストされます。- 既存のテストケース(
TestTCPListenerName
,TestUDPConnLocalName
,TestReadUnixgramWithUnnamedSocket
など)において、t.Errorf
がt.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ソケットのように接続指向ではない場合や、何らかの理由でピアアドレスが取得できない場合、rsa
(Getpeername
の結果)がnil
になることがあります。
変更前は、fd.raddr
がnil
の場合にのみ、ソケット作成時に指定された元のリモートアドレスursa
を使用していました。しかし、Getpeername
がnil
ではないが、例えば空のアドレス構造体のような意味のない値を返す場合、この条件ではursa
が使われず、不正確なraddr
が設定される可能性がありました。
変更後は、rsa == nil
というより厳密なチェックを行うことで、Getpeername
が完全に失敗した場合にのみursa
をフォールバックとして使用するようにしました。これにより、リモートアドレスの信頼性が向上します。
src/pkg/net/unixsock_posix.go
の変更
-
func (a *UnixAddr) isUnnamed() bool
: この新しいヘルパー関数は、UnixAddr
が「名前なし」であるかどうかを簡潔に判定します。UnixAddr
ポインタがnil
であるか、またはそのName
フィールドが空文字列である場合にtrue
を返します。これは、Linuxの抽象名前空間ソケットや、ファイルシステムパスにバインドされていないUnixドメインソケットを識別するために使用されます。 -
unixSocket
関数内のladdr.isUnnamed()
の使用:dial
モードでUnixソケットを扱う際、ローカルアドレスladdr
が指定されている場合でも、それが名前なしソケットであれば、syscall.SockaddrUnix
のName
フィールドを設定する必要がありません。以前はladdr != nil
というチェックだけでしたが、名前なしソケットの場合にladdr.Name
が空であるため、isUnnamed()
を使うことでより正確な条件分岐が可能になりました。また、sotype != syscall.SOCK_DGRAM || laddr.isUnnamed()
という条件は、データグラムソケットでない場合、またはデータグラムソケットであってもローカルアドレスが名前なしである場合に、リモートアドレスが必須であることを示しています。これにより、不必要なerrMissingAddress
エラーの発生を防ぎます。 -
UnixAddr
構造体リテラルの明示的なフィールド名指定:sockaddrToUnix
などの関数でUnixAddr
構造体を初期化する際に、&UnixAddr{s.Name, "unix"}
のように位置引数で初期化していた箇所を、&UnixAddr{Name: s.Name, Net: "unix"}
のようにフィールド名を明示的に指定する形式に変更しました。これはGoの慣習に沿ったもので、コードの可読性と保守性を向上させます。フィールドの順序が変わってもコードが壊れることがなく、意図がより明確になります。 -
sotypeToNet
の順序変更:sotypeToNet
関数内のswitch
文のケースの順序が変更されました。機能的な変更はありませんが、論理的な順序付けやコードの整理の一環と考えられます。
これらの変更は、Goのネットワークスタックが、特にUnixドメインソケットのような特殊なケースにおいて、より堅牢で正確なアドレス情報を提供できるようにするための重要な改善です。
関連リンク
- Go CL 7627048: https://golang.org/cl/7627048
- Go Issue #3721: https://golang.org/issue/3721
- Go Issue #3838: https://golang.org/issue/3838
参考にした情報源リンク
- Go言語
net
パッケージ公式ドキュメント: https://pkg.go.dev/net - Go言語
testing
パッケージ公式ドキュメント: https://pkg.go.dev/testing getpeername
man page (Linux): https://man7.org/linux/man-pages/man2/getpeername.2.html- Unix domain socket (Wikipedia): https://en.wikipedia.org/wiki/Unix_domain_socket
- Go言語における構造体リテラル: https://go.dev/tour/moretypes/10 (Go Tour)