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

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

本解説は、Go言語のsyscallパッケージにおけるReadFrom関数の挙動改善に関するコミット(インデックス15040、ハッシュ6563d8623ddd3eb4e92563e8e76fc8f4829e6cfc)について、その背景、技術的詳細、およびコード変更点を包括的に説明します。特に、カーネルがアドレスを返さない場合のrecvfromからの戻り値のハンドリングに焦点を当てています。

コミット

  • コミットハッシュ: 6563d8623ddd3eb4e92563e8e76fc8f4829e6cfc
  • 作者: Jeff R. Allen jra@nella.org
  • コミット日時: Wed Jan 30 10:02:01 2013 -0800
  • コミットメッセージ:
    syscall: handle empty address in ReadFrom better
    
    Handle return values from recvfrom correctly when the
    kernel decides to not return an address.
    
    Fixes #4636.
    Fixes #4352.
    
    R=rsc, mikioh.mikioh, dave
    CC=golang-dev
    https://golang.org/cl/7058062
    

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/6563d8623ddd3eb4e92563e8e76fc8f4829e6cfc

元コミット内容

syscall: handle empty address in ReadFrom better

このコミットは、カーネルがアドレスを返さない場合にrecvfromからの戻り値を正しく処理するように、ReadFrom関数の挙動を改善することを目的としています。具体的には、Goのnetパッケージとsyscallパッケージにおける、Unixドメインソケットからのデータ読み取り時のアドレス情報の取り扱いに関する問題を修正します。

変更の背景

このコミットは、Goのネットワークプログラミングにおいて、特にUnixドメインソケットを使用する際に発生していた2つの既知の問題(Fixes #4636Fixes #4352)を解決するために導入されました。

  • Fixes #4636: この問題は、ReadFromが名前のないソケットからデータを受信した際に、送信元アドレスが正しく処理されない、または予期せぬ値が返される可能性があったことを示唆しています。Unixドメインソケットでは、接続指向ではないデータグラムソケット(SOCK_DGRAM)において、送信元が名前を持たない(バインドされていない)場合があります。このような場合、recvfromシステムコールはアドレス情報を返さないことがあり、Goのnetパッケージがこれを適切にハンドリングできないと、fromアドレスがnilであるべきなのにnilでない値が返されたり、不正なアドレス情報が設定されたりする問題が発生していました。
  • Fixes #4352: この問題は、ReadFromにゼロバイトのバッファが提供された場合に、"address family not supported by protocol family"というエラーが発生する可能性があったことを示唆しています。これは、recvfromシステムコールが、受信バッファのサイズがゼロの場合に、アドレス情報の取得方法に関して特定のOSやカーネルの挙動に依存してエラーを返すことが原因であると考えられます。Goのnetパッケージがこのシナリオを適切に処理しないと、アプリケーションがクラッシュしたり、予期せぬエラーが発生したりする可能性がありました。

これらの問題は、Goのネットワークスタックの堅牢性と互換性を向上させるために、ReadFromがカーネルからの戻り値をより正確に解釈し、特にアドレス情報が提供されないケースを適切に処理する必要があることを示していました。

前提知識の解説

このコミットを理解するためには、以下の概念についての知識が役立ちます。

  • syscallパッケージ: Go言語の標準ライブラリの一部で、オペレーティングシステムが提供する低レベルなシステムコールにアクセスするための機能を提供します。ネットワーク操作、ファイルシステム操作、プロセス管理など、OS固有の機能を利用する際に使用されます。
  • ReadFrom関数: GoのnetパッケージにおけるPacketConnインターフェースの一部であり、データグラム指向のネットワーク接続(例: UDP、Unixドメインデータグラムソケット)からデータを読み取るために使用されます。この関数は、受信したデータのバイト数、送信元のアドレス、およびエラーを返します。
  • recvfromシステムコール: Unix系OSにおけるシステムコールの一つで、ソケットからデータを受信し、同時に送信元のアドレス情報も取得します。データグラムソケットで特に使用され、送信元のアドレスが不明な場合でもデータを受信できます。
  • SockaddrUnix: syscallパッケージで定義されている構造体で、Unixドメインソケットのアドレスを表します。通常、ファイルシステム上のパス(Nameフィールド)を含みます。
  • AF_UNSPEC (Address Family Unspecified): アドレスファミリーが指定されていないことを示す定数です。Sockaddr構造体のアドレスファミリーフィールドがこの値を持つ場合、通常は有効なアドレス情報が含まれていないことを意味します。
  • Unixドメインソケット (Unix Domain Socket): 同じホスト上のプロセス間通信(IPC)に使用されるソケットの一種です。ネットワークスタックを介さず、ファイルシステム上のパスを介して通信するため、TCP/IPソケットよりも高速でオーバーヘッドが少ないという特徴があります。unixgramは、Unixドメインソケットのデータグラムモードを指します。

技術的詳細

このコミットの技術的な核心は、recvfromシステムコールがアドレス情報を返さない、または不完全なアドレス情報を返す場合のGoのnetおよびsyscallパッケージの挙動を改善することにあります。

従来のGoの実装では、recvfromが呼び出された後、返されたSockaddr構造体から無条件にアドレス情報を抽出しようとしていました。しかし、特定のシナリオ(特に名前のないUnixドメインデータグラムソケットからの受信や、ゼロバイトバッファでの受信)では、カーネルがrecvfromsockaddr引数に有効なアドレス情報を書き込まないことがあります。この場合、SockaddrUnixNameフィールドが空文字列であったり、SockaddrAddr.FamilyAF_UNSPECであったりする可能性があります。

このコミットは、これらのエッジケースを明示的にチェックすることで、ReadFromがより堅牢になるように修正します。

  1. src/pkg/net/unixsock_posix.goにおける変更:

    • ReadFromUnix関数とReadMsgUnix関数において、syscall.SockaddrUnix型のアドレスが返された際に、そのNameフィールドが空文字列でない場合にのみUnixAddr構造体を生成するように変更されました。これにより、カーネルが名前のないソケットからのデータに対して空のアドレスを返した場合に、Go側で不適切なUnixAddrが生成されるのを防ぎます。
    • ReadFrom関数のシグネチャが(n int, addr Addr, err error)から(int, Addr, error)に変更されました。これは、戻り値の変数名を明示的に指定しないことで、より簡潔な記述を可能にするGoの慣習に合わせたものです。機能的な変更ではありませんが、コードの可読性と一貫性を向上させます。
  2. src/pkg/syscall/syscall_bsd.goおよびsrc/pkg/syscall/syscall_linux.goにおける変更:

    • Recvfrom関数において、anyToSockaddrを呼び出す前に、返されたSockaddr構造体(rsa)のAddr.FamilyフィールドがAF_UNSPECでないことを確認する条件が追加されました。AF_UNSPECはアドレスファミリーが指定されていないことを意味し、これは通常、有効なアドレス情報が含まれていないことを示します。このチェックにより、無効なアドレス情報からSockaddrオブジェクトを生成しようとすることを防ぎ、ReadFromnilのアドレスを正しく返すことができるようになります。この変更は、BSD系OS(syscall_bsd.go)とLinux系OS(syscall_linux.go)の両方に適用され、クロスプラットフォームでの一貫した挙動を保証します。

これらの変更により、Goのネットワークスタックは、カーネルがアドレス情報を返さない、または不完全なアドレス情報を返すような特殊なケースにおいても、より正確かつ予測可能な挙動を示すようになります。これにより、アプリケーションはこれらのエッジケースを適切に処理できるようになり、堅牢性が向上します。

コアとなるコードの変更箇所

このコミットでは、主に以下の4つのファイルが変更されています。

  1. src/pkg/net/unix_test.go: 新規追加されたテストファイル。
    • TestReadUnixgramWithUnnamedSocket: 名前を持たないUnixドメインデータグラムソケットからの読み取りをテストします。ReadFromnilfromアドレスを返すことを期待します。
    • TestReadUnixgramWithZeroBytesBuffer: ゼロバイトのバッファでReadFromを呼び出した際の挙動をテストします。issue 4352の修正に関連し、エラーが発生しないこと、およびpeerアドレスがnilであることを期待します。
  2. src/pkg/net/unixsock_posix.go: UnixドメインソケットのPOSIX実装。
    • ReadFromUnix関数:
      --- a/src/pkg/net/unixsock_posix.go
      +++ b/src/pkg/net/unixsock_posix.go
      @@ -124,18 +124,20 @@ func (c *UnixConn) ReadFromUnix(b []byte) (n int, addr *UnixAddr, err error) {
       	n, sa, err := c.fd.ReadFrom(b)
       	switch sa := sa.(type) {
       	case *syscall.SockaddrUnix:
      -		addr = &UnixAddr{sa.Name, sotypeToNet(c.fd.sotype)}
      +		if sa.Name != "" {
      +			addr = &UnixAddr{sa.Name, sotypeToNet(c.fd.sotype)}
      +		}
       	}
       	return
       }
      
      // ReadFrom implements the PacketConn ReadFrom method.
      -func (c *UnixConn) ReadFrom(b []byte) (n int, addr Addr, err error) {
      +func (c *UnixConn) ReadFrom(b []byte) (int, Addr, error) {
       	if !c.ok() {
       		return 0, nil, syscall.EINVAL
       	}
      -	n, uaddr, err := c.ReadFromUnix(b)
      -	return n, uaddr.toAddr(), err
      +	n, addr, err := c.ReadFromUnix(b)
      +	return n, addr.toAddr(), err
       }
      
      // ReadMsgUnix reads a packet from c, copying the payload into b and
      @@ -149,7 +151,9 @@ func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAdd
       	n, oobn, flags, sa, err := c.fd.ReadMsg(b, oob)
       	switch sa := sa.(type) {
       	case *syscall.SockaddrUnix:
      -		addr = &UnixAddr{sa.Name, sotypeToNet(c.fd.sotype)}
      +		if sa.Name != "" {
      +			addr = &UnixAddr{sa.Name, sotypeToNet(c.fd.sotype)}
      +		}
       	}
       	return
       }
      
    • ReadMsgUnix関数: ReadFromUnixと同様の変更が適用されています。
  3. src/pkg/syscall/syscall_bsd.go: BSD系OS向けのsyscall実装。
    --- a/src/pkg/syscall/syscall_bsd.go
    +++ b/src/pkg/syscall/syscall_bsd.go
    @@ -450,7 +450,9 @@ func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) {
     	if n, err = recvfrom(fd, p, flags, &rsa, &len); err != nil {
     		return
     	}
    -	from, err = anyToSockaddr(&rsa)
    +	if rsa.Addr.Family != AF_UNSPEC {
    +		from, err = anyToSockaddr(&rsa)
    +	}
     	return
     }
    
  4. src/pkg/syscall/syscall_linux.go: Linux系OS向けのsyscall実装。
    --- a/src/pkg/syscall/syscall_linux.go
    +++ b/src/pkg/syscall/syscall_linux.go
    @@ -574,7 +574,9 @@ func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) {
     	if n, err = recvfrom(fd, p, flags, &rsa, &len); err != nil {
     		return
     	}
    -	from, err = anyToSockaddr(&rsa)
    +	if rsa.Addr.Family != AF_UNSPEC {
    +		from, err = anyToSockaddr(&rsa)
    +	}
     	return
     }
    

コアとなるコードの解説

このコミットの主要な変更は、netパッケージとsyscallパッケージの両方で、受信したアドレス情報の有効性をより厳密にチェックするようになった点です。

  1. src/pkg/net/unixsock_posix.goにおける変更:

    • ReadFromUnixおよびReadMsgUnix関数では、syscall.SockaddrUnix型のアドレスが返された際に、sa.Name != ""という条件が追加されました。これは、Unixドメインソケットのアドレスがファイルシステム上のパス(名前)を持つことを前提としています。もしカーネルが名前のないソケットからのデータに対して空のNameを持つSockaddrUnixを返した場合、この条件によってaddr変数がnilのままになり、ReadFromの呼び出し元にnilのアドレスが正しく伝播されるようになります。これにより、名前のないソケットからのデータ受信時に、fromアドレスが予期せず非nilになる問題を解決します。
    • ReadFrom関数のシグネチャ変更は、Go 1のリリースに向けたAPIのクリーンアップの一環であり、機能的な挙動には影響しません。
  2. src/pkg/syscall/syscall_bsd.goおよびsrc/pkg/syscall/syscall_linux.goにおける変更:

    • Recvfrom関数では、anyToSockaddr(&rsa)を呼び出す前にrsa.Addr.Family != AF_UNSPECという条件が追加されました。AF_UNSPECはアドレスファミリーが指定されていないことを示す定数であり、これは通常、recvfromが有効なアドレス情報を取得できなかった場合に設定されます。このチェックにより、無効なアドレス情報からSockaddrオブジェクトを生成しようとすることを防ぎ、Recvfromfromアドレスとしてnilを返すことができるようになります。これは、特にゼロバイトバッファでrecvfromを呼び出した際に発生する可能性のあるエラー(issue 4352)や、カーネルがアドレスを返さない他のシナリオにおいて、Goのsyscall層がより堅牢に動作することを保証します。

これらの変更は、Goのネットワークスタックが、低レベルなシステムコールからの戻り値をより正確に解釈し、特にエッジケースにおけるアドレス情報の取り扱いを改善することで、全体的な信頼性と互換性を向上させるものです。新しく追加されたテストケースは、これらの修正が意図通りに機能することを検証しています。

関連リンク

参考にした情報源リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/6563d8623ddd3eb4e92563e8e76fc8f4829e6cfc
  • Go CL 7058062: https://golang.org/cl/7058062 (Goのコードレビューシステム)
  • Go Issue #4636 (直接的な情報は見つかりませんでしたが、コミットメッセージに記載されています)
  • Go Issue #4352 (直接的な情報は見つかりませんでしたが、コミットメッセージに記載されています)
  • recvfrom man page (Unix/Linux): man 2 recvfrom (一般的なシステムコールの挙動理解のため)
  • Unix Domain Sockets (Wikipediaなど): Unixドメインソケットの一般的な概念理解のため。