[インデックス 19005] ファイルの概要
このコミットは、Go言語の net
パッケージにおけるUnixドメインソケットの挙動に関する修正です。具体的には、コネクションレスモードの UnixConn
(例えば unixgram
タイプ) が既に接続済み(Dial
などで特定のピアに紐付けられている状態)であるにもかかわらず、宛先アドレスを明示的に指定してデータを書き込もうとした際に、正しくエラーを返すように変更されています。
コミット
commit 67a5181045d1601820ea98dff06832339e19fd16
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Apr 2 19:42:05 2014 +0900
net: make WriteTo, WriteToUnix and WriteMsgUnix fail when connectionless-mode UnixConn is already connected
This CL tries to fill the gap between Linux and other Unix-like systems
in the same way UDPConn already did.
Fixes #7677.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/83330045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/67a5181045d1601820ea98dff06832339e19fd16
元コミット内容
このコミットは、net
パッケージ内の UnixConn
型に対して、WriteTo
、WriteToUnix
、および WriteMsgUnix
メソッドが、コネクションレスモードの UnixConn
が既に接続済みである場合に失敗するように変更を加えるものです。これにより、Linuxとその他のUnix系システム間での挙動のギャップを埋め、UDPConn
が既に持っていた一貫した挙動に合わせることを目的としています。
変更の背景
Go言語の net
パッケージは、様々なネットワークプロトコルとソケットタイプを抽象化して提供します。Unixドメインソケット(UDS)もその一つで、同じシステム上のプロセス間通信(IPC)に利用されます。UDSにはストリーム型(SOCK_STREAM
)とデータグラム型(SOCK_DGRAM
)があります。Goでは、unix
ネットワークタイプがストリーム型に、unixgram
ネットワークタイプがデータグラム型に対応します。
データグラムソケット(SOCK_DGRAM
)は本来コネクションレスであり、各データグラムは独立して送受信され、宛先アドレスを明示的に指定する必要があります。しかし、connect()
システムコールをデータグラムソケットに対して呼び出すことで、「接続済み」の状態にすることができます。この「接続」はTCPのような永続的な接続を確立するものではなく、以下の2つの効果をもたらします。
- デフォルトのピアアドレスの設定: 以降の
send()
やwrite()
システムコールで宛先アドレスを省略できるようになります。データはconnect()
で指定されたデフォルトのピアに送信されます。 - 受信パケットのフィルタリング: ソケットは
connect()
で指定されたピアからのデータグラムのみを受信するようになります。
問題は、Goの net.UDPConn
では、Dial
によって「接続済み」となったUDPソケットに対して、宛先アドレスを明示的に指定する WriteTo
メソッドを呼び出すと、ErrWriteToConnected
というエラーを返すように実装されていました。これは、ソケットが既に特定のピアに紐付けられているにもかかわらず、別の宛先への送信を試みるという矛盾した操作を防ぐためです。
しかし、net.UnixConn
の unixgram
タイプでは、同様に Dial
で「接続済み」となった場合でも、WriteTo
、WriteToUnix
、WriteMsgUnix
といった宛先アドレスを明示するメソッドが、一部のUnix系システム(Linux以外)で期待通りにエラーを返さない、または未定義の挙動を示す可能性がありました。この挙動の不一致が、異なるOS間でのアプリケーションの移植性や信頼性に影響を与える可能性があったため、このコミットで修正されました。
この修正は、Fixes #7677
と関連付けられていますが、直接的なIssueの内容は確認できませんでした。しかし、コミットメッセージと変更内容から、この不一致が報告され、その解決のために行われたものと推測されます。
前提知識の解説
Unixドメインソケット (Unix Domain Sockets, UDS)
UDSは、同じホスト上のプロセス間で通信を行うためのメカニズムです。ネットワークソケットと同様のAPI(socket
, bind
, listen
, accept
, connect
, send
, recv
など)を提供しますが、ネットワークスタックを介さず、カーネル内部で直接データをやり取りするため、TCP/IPソケットよりも高速で効率的です。ファイルシステム上のパス名(例: /tmp/my_socket
)をアドレスとして使用します。
UDSには主に以下の2つのタイプがあります。
- ストリームソケット (
SOCK_STREAM
): 信頼性があり、順序が保証された、コネクション指向のバイトストリームを提供します。TCPに似ています。Goではnet.Dial("unix", ...)
やnet.Listen("unix", ...)
で使用されます。 - データグラムソケット (
SOCK_DGRAM
): コネクションレスで、信頼性や順序は保証されませんが、メッセージの境界が保持されます。UDPに似ています。Goではnet.Dial("unixgram", ...)
やnet.ListenPacket("unixgram", ...)
で使用されます。
connect()
システムコールとデータグラムソケット
通常、connect()
システムコールはTCPのようなコネクション指向ソケットで接続を確立するために使われます。しかし、UDPやUnixデータグラムソケットのようなコネクションレスソケットに対しても connect()
を呼び出すことができます。この場合の connect()
は、実際の接続を確立するわけではありませんが、ソケットに「デフォルトのピアアドレス」を設定します。
この設定により、以下の挙動が変化します。
send()
/write()
の簡略化:sendto()
のように毎回宛先アドレスを指定する必要がなくなり、send()
やwrite()
を呼び出すだけで、デフォルトのピアにデータが送信されます。- 受信の制限: ソケットは、
connect()
で指定されたピアからのデータグラムのみを受信するようになります。他の送信元からのデータグラムは破棄されます。
Write
と WriteTo
の違い
Goの net
パッケージでは、ソケットへのデータ書き込みに主に2つのパターンがあります。
Write(b []byte) (n int, err error)
:net.Conn
インターフェースの一部であり、コネクション指向のソケット(TCP、Unixストリームソケット)や、connect()
でデフォルトのピアが設定されたコネクションレスソケットで使用されます。データはソケットが接続している(またはデフォルトのピアが設定されている)相手に送信されます。WriteTo(b []byte, addr net.Addr) (n int, err error)
:net.PacketConn
インターフェースの一部であり、コネクションレスソケット(UDP、Unixデータグラムソケット)で主に使用されます。このメソッドは、送信するデータb
と共に、そのデータグラムの宛先アドレスaddr
を明示的に指定する必要があります。これにより、一つのソケットから複数の異なる宛先にデータグラムを送信できます。
ErrWriteToConnected
Goの net
パッケージで定義されているエラーで、WriteTo
メソッドが、既に connect()
されている(特定のピアに紐付けられている)ソケットに対して呼び出された場合に返されます。これは、ソケットが既に特定の宛先を持つにもかかわらず、WriteTo
で別の宛先を指定しようとする矛盾した操作を防ぐためのものです。
技術的詳細
このコミットの技術的な核心は、Unixデータグラムソケット(SOCK_DGRAM
)が connect()
された場合の WriteTo
系メソッドの挙動を、UDPConn
と同様に一貫させることにあります。
従来の挙動では、Goの net.Dial("unixgram", ...)
を使用して UnixConn
を作成すると、内部的には SOCK_DGRAM
ソケットに対して connect()
システムコールが発行され、特定のピアに「接続済み」の状態になります。この状態の UnixConn
に対して、WriteTo
、WriteToUnix
、WriteMsgUnix
といったメソッドが呼び出された場合、一部のUnix系システムでは、明示的に指定された宛先アドレスにデータが送信されてしまうか、あるいはエラーを返さないという挙動が見られました。これは、ソケットが既に「接続済み」であるという状態と、WriteTo
が任意の宛先への送信を意図しているという機能との間に矛盾を生じさせます。
この不一致は、特にLinuxとその他のUnix系システム(例えばBSD系OSなど)の間で顕著であったと考えられます。OSのソケットAPIの実装詳細の違いにより、connect()
された SOCK_DGRAM
ソケットに対する sendto()
の挙動が異なっていた可能性があります。
コミットは、このギャップを埋めるために、UnixConn
の WriteToUnix
および WriteMsgUnix
メソッドの内部にチェックを追加しています。具体的には、ソケットが SOCK_DGRAM
タイプであり、かつ isConnected
フラグが true
の場合(つまり、Dial
などで特定のピアに接続済みの場合)には、明示的な宛先アドレスが指定されていても、直ちに ErrWriteToConnected
エラーを返すように変更されています。
これにより、Goアプリケーションは、Unixデータグラムソケットを使用する際に、OS間の挙動の違いを意識することなく、一貫したエラーハンドリングを行うことができるようになります。これは、堅牢で移植性の高いネットワークアプリケーションを開発する上で非常に重要です。
コアとなるコードの変更箇所
変更は主に src/pkg/net/unixsock_posix.go
ファイルに集中しています。
--- a/src/pkg/net/unixsock_posix.go
+++ b/src/pkg/net/unixsock_posix.go
@@ -171,6 +171,9 @@ func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (n int, err 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}
}
@@ -200,6 +203,9 @@ func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err
if !c.ok() {
return 0, 0, syscall.EINVAL
}
+ if c.fd.sotype == syscall.SOCK_DGRAM && c.fd.isConnected {
+ return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected}
+ }
if addr != nil {
if addr.Net != sotypeToNet(c.fd.sotype) {
return 0, 0, syscall.EAFNOSUPPORT
また、src/pkg/net/unix_test.go
には、この新しい挙動を検証するためのテストケースが追加されています。
コアとなるコードの解説
UnixConn.WriteToUnix
メソッドの変更
WriteToUnix
メソッドは、Unixドメインソケットにデータを書き込む際に、宛先アドレスを *UnixAddr
型で明示的に指定するものです。
追加された行:
if c.fd.isConnected {
return 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected}
}
このコードは、UnixConn
のファイルディスクリプタ (c.fd
) が isConnected
フラグを持っているかどうかをチェックしています。isConnected
が true
の場合、それはこの UnixConn
が Dial
などによって特定のピアに接続済みであることを意味します。この状況で WriteToUnix
が呼び出された場合、ソケットは既にデフォルトの宛先を持っているため、明示的に別の宛先を指定して書き込もうとするのは矛盾した操作です。したがって、OpError
を作成し、その Err
フィールドに ErrWriteToConnected
を設定して返します。これにより、この操作が不正であることを明確に示します。
UnixConn.WriteMsgUnix
メソッドの変更
WriteMsgUnix
メソッドは、データだけでなく、補助データ(out-of-band data)も送信できる、より汎用的なUnixドメインソケットの書き込みメソッドです。これも宛先アドレスを *UnixAddr
型で明示的に指定できます。
追加された行:
if c.fd.sotype == syscall.SOCK_DGRAM && c.fd.isConnected {
return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected}
}
WriteMsgUnix
の場合、WriteToUnix
と同様の isConnected
チェックに加えて、c.fd.sotype == syscall.SOCK_DGRAM
という条件が追加されています。これは、このチェックがデータグラムソケット(SOCK_DGRAM
)にのみ適用されるべきであることを保証するためです。ストリームソケット(SOCK_STREAM
)は常に接続指向であり、WriteTo
のような概念は適用されないため、このチェックは不要です。データグラムソケットが接続済みである場合に、明示的な宛先指定での書き込みを試みると、同様に ErrWriteToConnected
エラーが返されます。
これらの変更により、Goの net
パッケージは、Unixデータグラムソケットの「接続済み」状態における WriteTo
系メソッドの挙動を、UDPConn
と同様に、より予測可能で一貫性のあるものにしています。
関連リンク
- Go言語の
net
パッケージドキュメント: https://pkg.go.dev/net - Unixドメインソケット (Wikipedia): https://ja.wikipedia.org/wiki/Unix%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88
sendto(2)
man page (Linux): https://man7.org/linux/man-pages/man2/sendto.2.html
参考にした情報源リンク
- Go CL 83330045: https://golang.org/cl/83330045
- Goの
net.UDPConn
のWriteTo
挙動に関する議論 (Stack Overflowなど): - Unixドメインソケットの
connect()
とsendto()
挙動に関する議論 (Stack Overflowなど): - Go issue #15250 (関連する可能性のあるUnixgramの挙動不一致の例): https://github.com/golang/go/issues/15250
- (注: このIssueは直接 #7677 ではありませんが、Unixgramソケットの挙動に関する別の不一致の例として参照しました。)