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

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

このコミットは、Go言語の net パッケージにおいて、IPConn 型のソケットが既に接続されている場合に、WriteToWriteToIP、および WriteMsgIP メソッドが失敗するように挙動を変更するものです。これにより、Linuxと他のUnix系システム間での一貫性のない挙動を解消し、UDPConnUnixConn と同様の振る舞いを実現することを目的としています。

コミット

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() システムコールが呼び出されている)場合、WriteToWriteToIP、および WriteMsgIP といった、明示的に宛先アドレスを指定してデータを送信する関数がエラーを返すように変更されます。

この変更の背景には、Linuxと他のUnix系システム(例えばBSD系OSなど)の間で、接続済みソケットに対するこれらの関数の挙動に差異があったという問題があります。Goの net パッケージでは、UDPConnUnixConn といった他のコネクションタイプでは既にこの挙動の統一が図られており、本コミットは IPConn においても同様の一貫性を持たせることを意図しています。これにより、#7887 で報告された問題が修正されます。

変更の背景

Go言語の net パッケージは、ネットワークプログラミングのための抽象化されたインターフェースを提供します。その中で IPConn は、IPプロトコル(IPv4またはIPv6)のソケットを表し、UDPやTCPのような上位プロトコルに依存しない、より低レベルな通信を可能にします。

ソケットには「接続済み (connected)」状態と「非接続 (unconnected)」状態があります。

  • 非接続ソケット: sendto()sendmsg() のようなシステムコールを使用して、送信ごとに宛先アドレスを明示的に指定します。
  • 接続済みソケット: connect() システムコールを呼び出すことで、特定のピアアドレスに紐付けられます。この状態では、send() のようなシステムコールを使用し、宛先アドレスを省略してデータを送信できます。データは connect() で指定されたピアに自動的に送信されます。

問題は、接続済みソケットに対して sendto()sendmsg() のような「宛先指定付き」の送信関数を呼び出した際の挙動が、OSによって異なっていた点にあります。

  • 一部のUnix系システムでは、接続済みソケットに対して宛先指定付きの送信関数を呼び出すとエラーを返します。これは、ソケットが既に特定のピアに「接続」されているため、別の宛先への送信を試みるのは論理的に矛盾するという考え方に基づいています。
  • しかし、Linuxでは、接続済みソケットに対して宛先指定付きの送信関数を呼び出すと、指定された宛先へデータが送信され、connect() で設定されたピアへの接続は一時的に上書きされるか、無視される挙動を示していました。

この挙動の不一致は、クロスプラットフォームで動作するGoアプリケーションにおいて、予期せぬバグや非一貫な動作を引き起こす可能性がありました。特に、UDPConnUnixConn では既にこの問題が修正され、接続済みソケットに対する宛先指定付き送信はエラーを返すように統一されていました。本コミットは、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. IPConnUDPConnUnixConn

これらは net パッケージで提供されるコネクションタイプの一部です。

  • IPConn: IPプロトコルレベルでの通信を扱うためのコネクションです。通常、IPヘッダやICMP、IGMPなどのプロトコルを直接操作する際に使用されます。RAWソケットなどがこれに該当します。
  • UDPConn: UDPプロトコルを扱うためのコネクションです。コネクションレス型プロトコルですが、DialUDPConnect メソッドを使って特定の宛先に「接続済み」状態にすることも可能です。この場合、Write メソッドでデータを送信すると、接続された宛先に自動的に送られます。
  • UnixConn: Unixドメインソケットを扱うためのコネクションです。同じホスト上のプロセス間通信に使用されます。ファイルシステム上のパスをアドレスとして使用します。

4. WriteToWriteToIPWriteMsgIP

これらは 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.EINVALOpError

  • syscall.EINVAL: syscall パッケージで定義されているエラーコードの一つで、"Invalid argument"(無効な引数)を意味します。システムコールに渡された引数が不正な場合に返されます。
  • OpError: net パッケージで定義されているエラー型です。ネットワーク操作中に発生したエラーの詳細(操作の種類、ネットワークタイプ、アドレス、元のエラーなど)を提供します。ErrWriteToConnected は、この OpErrorErr フィールドに設定される可能性のある特定のエラー値です。

技術的詳細

このコミットの技術的な核心は、src/pkg/net/iprawsock_posix.go ファイル内の IPConnWriteToIP および WriteMsgIP メソッドに、ソケットが既に接続済みであるかどうかのチェックを追加した点にあります。

Goの net パッケージでは、ソケットのファイルディスクリプタ(fd)構造体内に isConnected というフィールドを持っています。これは、そのソケットが connect() システムコールによって特定のピアに接続されているかどうかを示すブール値です。

変更前は、IPConnWriteToIPWriteMsgIP メソッドは、c.fd.isConnectedtrue であっても、宛先アドレスが指定されていればそのアドレスにデータを送信しようとしていました。これはLinuxの挙動に沿ったものでしたが、他のUnix系システム(例えばFreeBSDなど)では、接続済みソケットに対して sendto()sendmsg() を呼び出すと EISCONN (Socket is already connected) エラーを返すのが一般的です。

このコミットでは、WriteToIPWriteMsgIP の処理の冒頭に以下のチェックを追加しています。

if c.fd.isConnected {
    return 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected}
}

このコードは、IPConnconnect() されている(c.fd.isConnectedtrue)場合、直ちにエラーを返します。返されるエラーは OpError 型で、操作 (Op) は "write"、ネットワークタイプ (Net) は c.fd.net、宛先アドレス (Addr) は引数で渡された addr、そして内部エラー (Err) は ErrWriteToConnected となります。

ErrWriteToConnectednet パッケージ内で定義されているエラー変数で、通常は「接続済みソケットに対して WriteTo 系関数が呼び出された」ことを示す具体的なエラーメッセージを含んでいます。このエラーを返すことで、Goアプリケーションはプラットフォームに依存せず、接続済み IPConn に対して宛先指定付き送信を試みた場合に一貫してエラーを受け取ることができるようになります。

この変更により、IPConn の挙動は UDPConnUnixConn と同様になり、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 型の二つのメソッド、WriteToIPWriteMsgIP に追加された変更を示しています。

  1. func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) メソッドへの変更:

    • このメソッドは、バイトスライス b を指定された IPAddr addr へ送信するために使用されます。
    • 追加された3行 (+ で始まる行) は、既存の if !c.ok() { ... } チェックの直後に挿入されています。
    • if c.fd.isConnected { ... } という条件文が追加されました。
      • c.fdIPConn に関連付けられたファイルディスクリプタの構造体です。
      • isConnected は、このソケットが connect() システムコールによって既に特定のピアに接続されているかどうかを示すブール型のフィールドです。
    • もし c.fd.isConnectedtrue (つまり、ソケットが接続済み) であれば、このメソッドは直ちに 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 パッケージで定義された特定のエラー値で、接続済みソケットに対して宛先指定付きの書き込みが試みられたことを示します。これにより、呼び出し元はエラーの種類を具体的に判別できます。
  2. 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 に対して WriteToIPWriteMsgIP のような宛先指定付きの送信操作が行われた際に、プラットフォームに依存せず一貫してエラーを返すようになります。これは、GoのネットワークAPIの堅牢性と予測可能性を高めるための重要な修正です。

関連リンク

  • Go Gerrit Change: https://golang.org/cl/97810043
  • Go Issue #7887: (直接的なリンクは見つかりませんでしたが、コミットメッセージおよびGerritの記述から、このコミットが解決した問題のIssue番号です。)

参考にした情報源リンク