[インデックス 11418] ファイルの概要
このコミットは、Go言語の標準ライブラリnet
パッケージにおけるUDPコネクションの振る舞いを修正するものです。具体的には、net.Dial
によって「接続済み」状態になったUDPConn
に対して、宛先アドレスを明示的に指定するWriteTo
メソッド(およびWriteToUDP
)が呼び出された際に、エラーを返すように変更されています。これにより、UDPソケットのセマンティクスがより明確になり、予期せぬ動作を防ぎます。
コミット
commit 974fa755573cbcad4e6ff48e4faae25ffa2cca43
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Fri Jan 27 01:31:42 2012 +0900
net: make WriteTo fail when UDPConn is already connected
Fixes #2773.
R=rsc
CC=golang-dev
https://golang.org/cl/5571056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/974fa755573cbcad4e6ff48e4faae25ffa2cca43
元コミット内容
このコミットは、net
パッケージのUDPコネクションに関する以下の変更を行っています。
netFD
構造体にisConnected
というブーリアンフィールドを追加。net.Dial
によってUDPコネクションが確立された際に、このisConnected
フラグをtrue
に設定。UDPConn
が既に接続済み(isConnected
がtrue
)である場合、WriteToUDP
メソッドがErrWriteToConnected
という新しいエラーを返すように変更。- この変更の動作を検証するための新しいテストファイル
src/pkg/net/udp_test.go
を追加。
変更の背景
この変更は、GoのIssue #2773「net: WriteTo on connected UDPConn should fail」を解決するために行われました。
UDP(User Datagram Protocol)は通常、コネクションレスなプロトコルであり、各データグラムは独立して送信され、宛先アドレスを含みます。しかし、多くのソケットAPI(POSIXソケットなど)では、UDPソケットを特定のピアアドレスに「接続」する機能(connect
システムコール)を提供しています。Go言語のnet.Dial("udp", ...)
もこの機能を利用し、内部的にUDPソケットを接続済み状態にします。
接続済みUDPソケットの主な利点は以下の通りです。
Write
メソッド(宛先アドレスを指定しない)を使用できるようになる。Read
メソッドが、接続されたピアからのデータのみを受け取るようになる。- 一部のOSでは、
connect
によってルーティング情報がキャッシュされ、連続するデータグラム送信のパフォーマンスが向上する可能性がある。
問題は、接続済みUDPソケットに対して、宛先アドレスを明示的に指定するsendto
(GoのWriteTo
に相当)を呼び出した際の振る舞いが、OSによって異なっていた点です。
- 一部のOS(例: Linux)では、接続済みソケットに対して異なる宛先アドレスで
sendto
を呼び出すと、EINVAL
(無効な引数)エラーを返します。 - 他のOSでは、エラーを返さずに、データが接続済みのアドレスに送信されてしまったり、ソケットの接続状態が壊れてしまったりする可能性がありました。
このようなプラットフォーム間の非一貫性や、開発者が意図しない動作を防ぐため、Goのnet
パッケージは、接続済みUDPConn
に対してWriteTo
が呼び出された場合に、明示的にエラーを返すように統一的な振る舞いを導入しました。これにより、開発者はコードの意図を明確にし、予期せぬデータ送信を防ぐことができます。
前提知識の解説
- UDP (User Datagram Protocol): TCPと異なり、コネクションの確立や信頼性のあるデータ転送の保証を行わない、軽量なプロトコルです。各データグラムは独立して送信され、宛先アドレスを含みます。
- コネクションレスソケット: UDPソケットの一般的な形態で、データを送信するたびに宛先アドレスを指定します(例:
sendto
システムコール、GoのWriteTo
)。 - 接続済みUDPソケット:
connect
システムコール(Goのnet.Dial
に相当)によって、特定のピアアドレスに関連付けられたUDPソケットです。この状態では、send
システムコール(GoのWrite
に相当)を使用して、事前に接続されたアドレスにデータを送信できます。また、recv
(GoのRead
)は接続されたピアからのデータのみを受け取ります。 net.Dial
: Go言語でネットワークコネクションを確立するための関数です。TCPだけでなく、UDPに対しても使用でき、UDPの場合はソケットを特定のピアアドレスに「接続済み」状態にします。net.ListenPacket
: UDPなどのパケット指向プロトコルで、任意の送信元からのデータを受信するために使用されます。この関数で作成されたPacketConn
は、通常「コネクションレス」状態です。WriteTo
とWrite
:WriteTo(b []byte, addr Addr) (n int, err error)
: 宛先アドレスを明示的に指定してデータを送信します。主にコネクションレスソケットで使用されます。Write(b []byte) (n int, err error)
: 宛先アドレスを指定せずにデータを送信します。主にTCPのようなコネクション指向ソケット、またはDial
によって接続済みになったUDPソケットで使用されます。
技術的詳細
このコミットの技術的な核心は、netFD
構造体にisConnected
という状態フラグを導入し、このフラグに基づいてWriteToUDP
の振る舞いを制御することです。
-
netFD
構造体の拡張:netFD
は、Goのnet
パッケージが内部的に使用するネットワークファイルディスクリプタ(ソケット)の抽象化です。この構造体にisConnected bool
フィールドが追加されました。これは、ソケットがDial
によって特定のピアに接続されているかどうかを示すシンプルなフラグです。 -
socket
関数でのisConnected
の設定:net/sock.go
のsocket
関数は、新しいソケットを作成し、必要に応じてconnect
システムコールを呼び出す役割を担っています。この関数内で、リモートアドレス(ra
)が指定されている場合(つまりnet.Dial
が呼び出された場合)、fd.isConnected = true
が設定されます。これにより、netFD
インスタンスが接続済み状態であることを内部的に記録します。 -
WriteToUDP
でのエラーチェック:net/udpsock_posix.go
(およびWindows版のnet/fd_windows.go
)のWriteToUDP
メソッドに、以下のチェックが追加されました。if c.fd.isConnected { return 0, &OpError{"write", c.fd.net, addr, ErrWriteToConnected} }
c.fd.isConnected
がtrue
の場合、つまりUDPConn
がDial
によって接続済みであるにもかかわらず、WriteToUDP
が呼び出された場合、新しいエラーErrWriteToConnected
を含むOpError
が返されます。このErrWriteToConnected
は、"use of WriteTo with pre-connected UDP"
というメッセージを持つerrors.New
で定義されたエラーです。 -
新しいテストの追加:
src/pkg/net/udp_test.go
が新規に追加され、この変更の動作を厳密に検証しています。testWriteToConn
では、net.Dial
で接続されたUDPConn
に対してWriteToUDP
やWriteTo
を呼び出し、ErrWriteToConnected
が返されることを確認しています。同時に、接続済みソケットに対する通常のWrite
メソッドが引き続き機能することも検証しています。testWriteToPacketConn
では、net.ListenPacket
で作成されたコネクションレスなUDPConn
に対してWriteToUDP
やWriteTo
が正常に機能することを確認しています。また、コネクションレスソケットに対するWrite
メソッドがエラーを返すことも検証しています。
これらの変更により、Goのnet
パッケージは、接続済みUDPソケットに対するWriteTo
の振る舞いを明確にし、プラットフォーム間の差異を吸収して、一貫性のあるエラーハンドリングを提供します。
コアとなるコードの変更箇所
-
src/pkg/net/fd.go
:--- a/src/pkg/net/fd.go +++ b/src/pkg/net/fd.go @@ -22,15 +22,16 @@ type netFD struct { closing bool // immutable until Close - sysfd int - family int - sotype int - sysfile *os.File - cr chan bool - cw chan bool - net string - laddr Addr - raddr Addr + sysfd int + family int + sotype int + isConnected bool // 追加 + sysfile *os.File + cr chan bool + cw chan bool + net string + laddr Addr + raddr Addr // owned by client rdeadline int64
isConnected
フィールドが追加されています。 -
src/pkg/net/fd_windows.go
: Windows版のnetFD
構造体にも同様にisConnected
フィールドが追加されています。 -
src/pkg/net/sock.go
:--- a/src/pkg/net/sock.go +++ b/src/pkg/net/sock.go @@ -49,6 +49,7 @@ func socket(net string, f, t, p int, la, ra syscall.Sockaddr, toAddr func(syscal fd.Close() return nil, err } + fd.isConnected = true // 追加 } sa, _ := syscall.Getsockname(s)
net.Dial
によってリモートアドレスが指定された場合にfd.isConnected
がtrue
に設定されます。 -
src/pkg/net/udpsock_posix.go
:--- a/src/pkg/net/udpsock_posix.go +++ b/src/pkg/net/udpsock_posix.go @@ -9,11 +9,14 @@ package net import ( + "errors" // 追加 "os" "syscall" "time" ) +var ErrWriteToConnected = errors.New("use of WriteTo with pre-connected UDP") // 追加 + func sockaddrToUDP(sa syscall.Sockaddr) Addr { switch sa := sa.(type) { case *syscall.SockaddrInet4: @@ -182,6 +185,9 @@ func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) { if !c.ok() { return 0, os.EINVAL }\n + if c.fd.isConnected { // 追加 + return 0, &OpError{"write", c.fd.net, addr, ErrWriteToConnected} // 追加 + } sa, err := addr.sockaddr(c.fd.family) if err != nil { return 0, &OpError{"write", c.fd.net, addr, err}
ErrWriteToConnected
が定義され、WriteToUDP
内でisConnected
のチェックとエラー返却ロジックが追加されています。 -
src/pkg/net/udp_test.go
: このファイルは新規追加されており、TestWriteToUDP
関数とそのヘルパー関数testWriteToConn
、testWriteToPacketConn
が含まれています。これらは、接続済みUDPコネクションとコネクションレスUDPコネクションの両方でWriteTo
およびWrite
の振る舞いを検証するテストケースです。
コアとなるコードの解説
このコミットの核心は、UDPConn
の内部状態を正確に管理し、その状態に基づいてWriteTo
メソッドの振る舞いを調整することにあります。
-
netFD.isConnected
フラグ: このフラグは、UDPConn
がnet.Dial
によって特定のピアに「接続」されているかどうかを示すシンプルな真偽値です。Dial
が呼び出されると、内部的にconnect
システムコールが実行され、ソケットが接続済み状態になります。このとき、netFD.isConnected
がtrue
に設定されます。 -
WriteToUDP
のガード句:WriteToUDP
メソッドは、データを特定の宛先アドレスに送信するために使用されます。このメソッドの冒頭に、if c.fd.isConnected
という条件が追加されました。- もし
c.fd.isConnected
がtrue
であれば、それはUDPConn
が既にDial
によって接続済みであることを意味します。接続済みUDPソケットに対して、WriteTo
のように異なる宛先アドレスを指定してデータを送信しようとすることは、セマンティックな矛盾やプラットフォーム依存の予期せぬ動作を引き起こす可能性があります。 - このため、このような状況では、
WriteToUDP
は直ちにErrWriteToConnected
エラーを返します。これにより、開発者は接続済みソケットに対してWriteTo
を使用すべきではないことを明確に知ることができます。
- もし
-
ErrWriteToConnected
: この新しいエラーは、接続済みUDPソケットに対してWriteTo
が呼び出された場合に返される特定のエラーです。これにより、エラーハンドリングが容易になり、開発者はこの特定のエラーを捕捉して適切な処理を行うことができます。
この変更は、Goのnet
パッケージが提供するネットワークAPIの堅牢性と予測可能性を高めるものです。開発者は、Dial
で接続したUDPソケットではWrite
を、ListenPacket
で作成したコネクションレスなUDPソケットではWriteTo
を使用するという、より明確なガイドラインに従うことができます。
関連リンク
- Go Issue #2773: net: WriteTo on connected UDPConn should fail
- Gerrit Change-ID: https://golang.org/cl/5571056
参考にした情報源リンク
- Go言語のソースコード(上記コミットの差分)
- Go Issue #2773の議論
- POSIX
connect(2)
man page (UDPソケットのconnect
に関する情報) - Go
net
パッケージのドキュメント (UDPConn, Dial, ListenPacket, Write, WriteToなど)