[インデックス 19253] ファイルの概要
このコミットは、Go言語の net
パッケージにおいて、IPConn
型のソケットが既に接続されている場合に、WriteTo
、WriteToIP
、および WriteMsgIP
メソッドが失敗するように挙動を変更するものです。これにより、Linuxと他のUnix系システム間での一貫性のない挙動を解消し、UDPConn
や UnixConn
と同様の振る舞いを実現することを目的としています。
コミット
commit 7e41abbc6b9f00f84374c69f3acb63b56fcf4728
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Apr 29 12:37:16 2014 +0900
net: make WriteTo, WriteToIP and WriteMsgIP fail when IPConn is already connected
This CL tries to fill the gap between Linux and other Unix-like systems
in the same way UDPConn and UnixConn already did.
Fixes #7887.
LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/97810043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7e41abbc6b9f00f84374c69f3acb63b56fcf4728
元コミット内容
このコミットは、Go言語の標準ライブラリである net
パッケージにおける IPConn
型のソケットに関する挙動の修正を目的としています。具体的には、IPConn
が既に特定の宛先アドレスに接続されている(connect()
システムコールが呼び出されている)場合、WriteTo
、WriteToIP
、および WriteMsgIP
といった、明示的に宛先アドレスを指定してデータを送信する関数がエラーを返すように変更されます。
この変更の背景には、Linuxと他のUnix系システム(例えばBSD系OSなど)の間で、接続済みソケットに対するこれらの関数の挙動に差異があったという問題があります。Goの net
パッケージでは、UDPConn
や UnixConn
といった他のコネクションタイプでは既にこの挙動の統一が図られており、本コミットは IPConn
においても同様の一貫性を持たせることを意図しています。これにより、#7887
で報告された問題が修正されます。
変更の背景
Go言語の net
パッケージは、ネットワークプログラミングのための抽象化されたインターフェースを提供します。その中で IPConn
は、IPプロトコル(IPv4またはIPv6)のソケットを表し、UDPやTCPのような上位プロトコルに依存しない、より低レベルな通信を可能にします。
ソケットには「接続済み (connected)」状態と「非接続 (unconnected)」状態があります。
- 非接続ソケット:
sendto()
やsendmsg()
のようなシステムコールを使用して、送信ごとに宛先アドレスを明示的に指定します。 - 接続済みソケット:
connect()
システムコールを呼び出すことで、特定のピアアドレスに紐付けられます。この状態では、send()
のようなシステムコールを使用し、宛先アドレスを省略してデータを送信できます。データはconnect()
で指定されたピアに自動的に送信されます。
問題は、接続済みソケットに対して sendto()
や sendmsg()
のような「宛先指定付き」の送信関数を呼び出した際の挙動が、OSによって異なっていた点にあります。
- 一部のUnix系システムでは、接続済みソケットに対して宛先指定付きの送信関数を呼び出すとエラーを返します。これは、ソケットが既に特定のピアに「接続」されているため、別の宛先への送信を試みるのは論理的に矛盾するという考え方に基づいています。
- しかし、Linuxでは、接続済みソケットに対して宛先指定付きの送信関数を呼び出すと、指定された宛先へデータが送信され、
connect()
で設定されたピアへの接続は一時的に上書きされるか、無視される挙動を示していました。
この挙動の不一致は、クロスプラットフォームで動作するGoアプリケーションにおいて、予期せぬバグや非一貫な動作を引き起こす可能性がありました。特に、UDPConn
や UnixConn
では既にこの問題が修正され、接続済みソケットに対する宛先指定付き送信はエラーを返すように統一されていました。本コミットは、IPConn
においても同様の挙動を強制することで、Goの net
パッケージ全体でのソケット操作の一貫性を高めることを目的としています。
#7887
は、この挙動の不一致によって引き起こされる具体的な問題点を報告したGoのIssue番号です。このコミットは、その問題を解決するために作成されました。
前提知識の解説
1. Go言語の net
パッケージ
Goの net
パッケージは、TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うためのプリミティブを提供します。このパッケージは、OSのシステムコールを抽象化し、Goの並行処理モデルと統合された使いやすいAPIを提供します。
2. ソケットプログラミングの基本
- ソケット: ネットワーク通信のエンドポイントです。ファイルディスクリプタのように扱われ、データの送受信に使用されます。
bind()
: ソケットにローカルアドレス(IPアドレスとポート番号)を割り当てます。listen()
: サーバーソケットがクライアントからの接続要求を待ち受ける状態にします。accept()
: サーバーソケットがクライアントからの接続を受け入れ、新しい接続済みソケットを作成します。connect()
: クライアントソケットが特定のサーバーアドレスに接続を確立します。これにより、ソケットは「接続済み」状態になります。send()
/write()
: 接続済みソケットからデータを送信します。宛先はconnect()
で指定されたピアです。sendto()
/sendmsg()
: 非接続ソケット、または接続済みソケットであっても、明示的に宛先アドレスを指定してデータを送信します。
3. IPConn
、UDPConn
、UnixConn
これらは net
パッケージで提供されるコネクションタイプの一部です。
IPConn
: IPプロトコルレベルでの通信を扱うためのコネクションです。通常、IPヘッダやICMP、IGMPなどのプロトコルを直接操作する際に使用されます。RAWソケットなどがこれに該当します。UDPConn
: UDPプロトコルを扱うためのコネクションです。コネクションレス型プロトコルですが、DialUDP
やConnect
メソッドを使って特定の宛先に「接続済み」状態にすることも可能です。この場合、Write
メソッドでデータを送信すると、接続された宛先に自動的に送られます。UnixConn
: Unixドメインソケットを扱うためのコネクションです。同じホスト上のプロセス間通信に使用されます。ファイルシステム上のパスをアドレスとして使用します。
4. WriteTo
、WriteToIP
、WriteMsgIP
これらは IPConn
型のメソッドで、データを送信する際に宛先アドレスを明示的に指定するものです。
WriteTo(b []byte, addr Addr) (n int, err error)
: 汎用的なAddr
インターフェースを使用してデータを送信します。WriteToIP(b []byte, addr *IPAddr) (n int, err error)
:IPAddr
型の宛先アドレスにデータを送信します。WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error)
: データ (b
) と補助データ (oob
- out-of-band data) をIPAddr
型の宛先アドレスに送信します。通常、oob
は制御メッセージ(例: タイムスタンプ、エラー情報)に使用されます。
5. syscall.EINVAL
と OpError
syscall.EINVAL
:syscall
パッケージで定義されているエラーコードの一つで、"Invalid argument"(無効な引数)を意味します。システムコールに渡された引数が不正な場合に返されます。OpError
:net
パッケージで定義されているエラー型です。ネットワーク操作中に発生したエラーの詳細(操作の種類、ネットワークタイプ、アドレス、元のエラーなど)を提供します。ErrWriteToConnected
は、このOpError
のErr
フィールドに設定される可能性のある特定のエラー値です。
技術的詳細
このコミットの技術的な核心は、src/pkg/net/iprawsock_posix.go
ファイル内の IPConn
の WriteToIP
および WriteMsgIP
メソッドに、ソケットが既に接続済みであるかどうかのチェックを追加した点にあります。
Goの net
パッケージでは、ソケットのファイルディスクリプタ(fd
)構造体内に isConnected
というフィールドを持っています。これは、そのソケットが connect()
システムコールによって特定のピアに接続されているかどうかを示すブール値です。
変更前は、IPConn
の WriteToIP
や WriteMsgIP
メソッドは、c.fd.isConnected
が true
であっても、宛先アドレスが指定されていればそのアドレスにデータを送信しようとしていました。これはLinuxの挙動に沿ったものでしたが、他のUnix系システム(例えばFreeBSDなど)では、接続済みソケットに対して sendto()
や sendmsg()
を呼び出すと EISCONN
(Socket is already connected) エラーを返すのが一般的です。
このコミットでは、WriteToIP
と WriteMsgIP
の処理の冒頭に以下のチェックを追加しています。
if c.fd.isConnected {
return 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected}
}
このコードは、IPConn
が connect()
されている(c.fd.isConnected
が true
)場合、直ちにエラーを返します。返されるエラーは OpError
型で、操作 (Op
) は "write"、ネットワークタイプ (Net
) は c.fd.net
、宛先アドレス (Addr
) は引数で渡された addr
、そして内部エラー (Err
) は ErrWriteToConnected
となります。
ErrWriteToConnected
は net
パッケージ内で定義されているエラー変数で、通常は「接続済みソケットに対して WriteTo
系関数が呼び出された」ことを示す具体的なエラーメッセージを含んでいます。このエラーを返すことで、Goアプリケーションはプラットフォームに依存せず、接続済み IPConn
に対して宛先指定付き送信を試みた場合に一貫してエラーを受け取ることができるようになります。
この変更により、IPConn
の挙動は UDPConn
や UnixConn
と同様になり、GoのネットワークAPI全体での一貫性が向上します。開発者は、ソケットが接続済みであるかどうかに関わらず、Write
メソッド(宛先指定なし)と WriteTo
メソッド(宛先指定あり)の使い分けについて、より明確な期待を持つことができるようになります。
ただし、Gerritのコメントで示唆されているように、この変更は linux-386
ビルドを壊したという報告があり、その後のコミットで修正またはリバートされた可能性があります。これは、特定のプラットフォームでの挙動の差異を統一しようとした際に、予期せぬ副作用が発生する可能性を示しています。
コアとなるコードの変更箇所
diff --git a/src/pkg/net/iprawsock_posix.go b/src/pkg/net/iprawsock_posix.go
index 26fc06e1b6..bbb3f3ed66 100644
--- a/src/pkg/net/iprawsock_posix.go
+++ b/src/pkg/net/iprawsock_posix.go
@@ -133,6 +133,9 @@ func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) {
if !c.ok() {
return 0, syscall.EINVAL
}
+ if c.fd.isConnected {
+ return 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected}
+ }
if addr == nil {
return 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress}
}
@@ -162,6 +165,9 @@ func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error
if !c.ok() {
return 0, 0, syscall.EINVAL
}
+ if c.fd.isConnected {
+ return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected}
+ }
if addr == nil {
return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress}
}
コアとなるコードの解説
上記の差分は、src/pkg/net/iprawsock_posix.go
ファイル内の IPConn
型の二つのメソッド、WriteToIP
と WriteMsgIP
に追加された変更を示しています。
-
func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error)
メソッドへの変更:- このメソッドは、バイトスライス
b
を指定されたIPAddr
addr
へ送信するために使用されます。 - 追加された3行 (
+
で始まる行) は、既存のif !c.ok() { ... }
チェックの直後に挿入されています。 if c.fd.isConnected { ... }
という条件文が追加されました。c.fd
はIPConn
に関連付けられたファイルディスクリプタの構造体です。isConnected
は、このソケットがconnect()
システムコールによって既に特定のピアに接続されているかどうかを示すブール型のフィールドです。
- もし
c.fd.isConnected
がtrue
(つまり、ソケットが接続済み) であれば、このメソッドは直ちに0
バイトの書き込みとエラーを返します。 - 返されるエラーは
&OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected}
です。Op: "write"
: 発生した操作が書き込みであることを示します。Net: c.fd.net
: ネットワークタイプ(例: "ip4", "ip6")を示します。Addr: addr
: データを送信しようとした宛先アドレスを示します。Err: ErrWriteToConnected
:net
パッケージで定義された特定のエラー値で、接続済みソケットに対して宛先指定付きの書き込みが試みられたことを示します。これにより、呼び出し元はエラーの種類を具体的に判別できます。
- このメソッドは、バイトスライス
-
func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error)
メソッドへの変更:- このメソッドは、データ
b
と補助データoob
を指定されたIPAddr
addr
へ送信するために使用されます。 WriteToIP
と同様に、if !c.ok() { ... }
チェックの直後に全く同じロジックが追加されています。if c.fd.isConnected { ... }
の条件が満たされた場合、0
バイトのデータ書き込み、0
バイトの補助データ書き込み、そしてWriteToIP
と同じOpError
が返されます。
- このメソッドは、データ
これらの変更により、Goの net
パッケージは、接続済み IPConn
に対して WriteToIP
や WriteMsgIP
のような宛先指定付きの送信操作が行われた際に、プラットフォームに依存せず一貫してエラーを返すようになります。これは、GoのネットワークAPIの堅牢性と予測可能性を高めるための重要な修正です。
関連リンク
- Go Gerrit Change: https://golang.org/cl/97810043
- Go Issue #7887: (直接的なリンクは見つかりませんでしたが、コミットメッセージおよびGerritの記述から、このコミットが解決した問題のIssue番号です。)
参考にした情報源リンク
- Go Gerrit Code Review: https://go-review.googlesource.com/
- Go
net
package documentation: https://pkg.go.dev/net - Linux
sendto
man page (for understanding connected socket behavior): https://man7.org/linux/man-pages/man2/sendto.2.html - FreeBSD
sendto
man page (for understanding connected socket behavior): https://www.freebsd.org/cgi/man.cgi?query=sendto&sektion=2 - Go
syscall
package documentation: https://pkg.go.dev/syscall - Go
OpError
source code (for understanding its structure): (Go source code on GitHub, e.g.,src/net/net.go
) - Go
ErrWriteToConnected
source code (for understanding its definition): (Go source code on GitHub, e.g.,src/net/net.go
)