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

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

このコミットは、Go言語のsyscallパッケージにおけるOpenBSD 5.2でのUnixドメインソケットに対するgetsocknameシステムコールの挙動に関するバグ修正です。具体的には、OpenBSD 5.2でバインドされていないUnixドメインソケットに対してgetsocknameを呼び出した際に発生する、アドレスファミリー(AF)が未設定で長さがゼロとして返される問題へのワークアラウンドを実装しています。これにより、TestPassFDテストがOpenBSD 5.2で正しく動作するようになります。

コミット

commit 1b36bcc3b59a3e123c5b1ac2617cadcb69d0e94a
Author: Joel Sing <jsing@google.com>
Date:   Tue Mar 5 21:40:37 2013 +1100

    syscall: handle getsockname for unix sockets on openbsd 5.2
    
    On OpenBSD 5.2, calling getsockname on an unbound Unix domain socket
    results in a successful syscall, however the AF is unset and the length
    is returned as zero. This has been changed to more portable behaviour,
    which will be included in the OpenBSD 5.3 release.
    
    For now, work around this by treating a successful getsockname() call
    that returns a family of AF_UNSPEC and length of zero as a AF_UNIX
    socket.
    
    Makes TestPassFD work on OpenBSD 5.2.
    
    Fixes #4956.
    
    R=golang-dev, minux.ma, rsc, mikioh.mikioh
    CC=golang-dev
    https://golang.org/cl/7449046

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

https://github.com/golang/go/commit/1b36bcc3b59a3e123c5b1ac2617cadcb69d0e94a

元コミット内容

このコミットは、OpenBSD 5.2におけるUnixドメインソケットのgetsocknameの挙動を修正します。OpenBSD 5.2では、バインドされていないUnixドメインソケットに対してgetsocknameを呼び出すと、システムコール自体は成功するものの、アドレスファミリー(AF)が未設定(AF_UNSPEC)で、長さがゼロとして返されるという問題がありました。この挙動はOpenBSD 5.3でよりポータブルな挙動に変更される予定でしたが、それまでの間、Goのsyscallパッケージでこの特定のケース(AF_UNSPECかつ長さゼロ)をAF_UNIXソケットとして扱うことでワークアラウンドを適用しています。これにより、TestPassFDテストがOpenBSD 5.2で正しく動作するようになります。

変更の背景

この変更の背景には、OpenBSD 5.2の特定のバージョンにおけるgetsocknameシステムコールの非標準的な挙動があります。通常、getsocknameはソケットにバインドされたローカルアドレスを返すために使用されます。Unixドメインソケットの場合、これはファイルシステム上のパス、または抽象的な名前空間内の名前になります。

OpenBSD 5.2では、ソケットがまだbind()されていない状態でgetsockname()を呼び出すと、システムコール自体はエラーを返さずに成功します。しかし、返されるsockaddr構造体のアドレスファミリーフィールド(sa_family)がAF_UNSPEC(未指定)となり、アドレスの長さ(sa_len)がゼロとして返されるという、予期せぬ状態になっていました。これは、他の多くのUNIX系システムや、OpenBSDの将来のバージョン(5.3以降)での標準的な挙動とは異なっていました。

この非標準的な挙動が、Go言語のsyscallパッケージ内のTestPassFDテストの失敗を引き起こしていました。TestPassFDはファイルディスクリプタの受け渡しをテストするもので、内部的にUnixドメインソケットを使用しています。このテストがOpenBSD 5.2で失敗するのは、getsocknameがソケットの正しい情報を返さないため、ソケットのタイプを正しく識別できないことが原因でした。

この問題を解決するため、OpenBSD 5.3で導入される予定の「よりポータブルな挙動」が適用されるまでの間、Go側でこの特定のケースを検出して補正するワークアラウンドが必要となりました。

前提知識の解説

1. システムコール (System Call)

システムコールは、ユーザー空間のプログラムがオペレーティングシステム(OS)のカーネル空間の機能にアクセスするためのインターフェースです。ファイルI/O、プロセス管理、ネットワーク通信など、OSが提供する低レベルな操作を実行するために使用されます。getsocknameもその一つで、ソケットに関する情報を取得するためのシステムコールです。

2. ソケット (Socket)

ソケットは、ネットワーク通信やプロセス間通信(IPC)のエンドポイントとして機能する抽象化された概念です。プログラムはソケットを通じてデータを送受信します。ソケットには様々な種類があり、通信プロトコルやアドレスファミリーによって分類されます。

3. Unixドメインソケット (Unix Domain Socket / UDS)

Unixドメインソケットは、同じホスト上のプロセス間で通信を行うためのソケットの一種です。TCP/IPソケットがネットワークを介した通信に使用されるのに対し、Unixドメインソケットはファイルシステム上のパス(例: /tmp/my_socket)をアドレスとして使用し、カーネル内で直接データをやり取りするため、ネットワークソケットよりも高速でオーバーヘッドが少ないという特徴があります。

4. getsockname() システムコール

getsockname()は、指定されたソケットのローカルアドレス(ソケットがバインドされているアドレス)を取得するためのシステムコールです。例えば、TCPソケットであればローカルIPアドレスとポート番号、Unixドメインソケットであればバインドされているファイルパスなどが返されます。

5. sockaddr 構造体

sockaddrは、ソケットアドレスを表現するための汎用的な構造体です。異なる種類のアドレスファミリー(IPv4、IPv6、Unixドメインなど)に対応するために、通常はsockaddr_in(IPv4)、sockaddr_in6(IPv6)、sockaddr_un(Unixドメイン)などの具体的な構造体にキャストして使用されます。

  • sa_family: アドレスファミリー(例: AF_INETAF_INET6AF_UNIXAF_UNSPEC)。
  • sa_len: アドレスの長さ(OpenBSDなど一部のBSD系システムで使用される)。

6. AF_UNSPECAF_UNIX

  • AF_UNSPEC: アドレスファミリーが未指定であることを示す定数です。通常、ソケットがまだアドレスにバインドされていない場合や、アドレスファミリーが不明な場合に使用されます。
  • AF_UNIX: Unixドメインソケットのアドレスファミリーを示す定数です。

7. TestPassFD

Go言語のテストスイートに含まれる特定のテストケースで、ファイルディスクリプタ(FD)のプロセス間での受け渡し機能が正しく動作するかどうかを検証します。この機能は、UnixドメインソケットのSCM_RIGHTSメッセージを介して実現されます。

8. OpenBSD

OpenBSDは、セキュリティとコードの品質に重点を置いたUNIX系オペレーティングシステムです。厳格な開発プロセスとコードレビューで知られています。OSのバージョンアップに伴い、システムコールの挙動が改善されることがあります。

技術的詳細

このコミットは、OpenBSD 5.2におけるgetsocknameシステムコールの特定の挙動に対するGo言語のsyscallパッケージの適応を示しています。

通常、getsocknameはソケットのローカルアドレス情報をsockaddr構造体として返します。Unixドメインソケットの場合、この情報はsockaddr_un構造体として解釈され、sun_familyフィールドにはAF_UNIXが、sun_pathフィールドにはバインドされたパスが格納されます。

しかし、OpenBSD 5.2では、バインドされていないUnixドメインソケットに対してgetsocknameを呼び出すと、以下の問題が発生しました。

  1. システムコール自体は成功する(エラーが返されない)。
  2. 返されるsockaddr構造体のsa_familyフィールドがAF_UNSPEC(未指定)となる。
  3. 返されるsockaddr構造体のsa_lenフィールドが0となる。

この挙動は、Goのsyscallパッケージがソケットのタイプを正しく識別するのを妨げ、特にTestPassFDのようなUnixドメインソケットの機能に依存するテストが失敗する原因となっていました。

OpenBSDの開発者はこの挙動を認識しており、OpenBSD 5.3でよりポータブルな(他のUNIX系システムと互換性のある)挙動に変更することを決定していました。このコミットは、OpenBSD 5.3が広く利用可能になるまでの間、OpenBSD 5.2での互換性を確保するためのワークアラウンドです。

ワークアラウンドのロジックは以下の通りです。

  • getsocknameシステムコールが成功したにもかかわらず、
  • 返されたsockaddr構造体のアドレスファミリーがAF_UNSPECであり、
  • かつ、アドレスの長さが0である場合、
  • そのソケットはOpenBSD 5.2のバインドされていないUnixドメインソケットであると判断し、
  • 強制的にアドレスファミリーをAF_UNIXに、アドレスの長さをSizeofSockaddrUnix(Unixドメインソケットアドレスの標準的なサイズ)に設定し直します。

これにより、Goのsyscallパッケージは、OpenBSD 5.2の非標準的なgetsocknameの戻り値でも、Unixドメインソケットを正しく認識し、関連する機能(TestPassFDなど)が期待通りに動作するようになります。

コミットメッセージには「TODO(jsing): Remove after OpenBSD 5.4 is released (see issue 3349).」というコメントがあり、これはこのワークアラウンドが一時的なものであることを示しています。OpenBSD 5.4以降では、この特定のコードは不要になる予定でした。

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

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

  1. src/pkg/syscall/passfd_test.go

    • TestPassFD関数から、OpenBSDに関する特定のスキップロジックが削除されました。
    • 変更前:
      if runtime.GOOS == "openbsd" {
      	t.Skip("issue 4956")
      }
      
    • 変更後: このブロックが完全に削除されています。
  2. src/pkg/syscall/syscall_bsd.go

    • Getsockname関数内に、OpenBSD 5.2の挙動を補正するロジックが追加されました。
    • 変更前:
      func Getsockname(fd int) (sa Sockaddr, err error) {
      	var rsa RawSockaddrAny
      	var len _Socklen
      	if err = getsockname(fd, &rsa, &len); err != nil {
      		return
      	}
      	return anyToSockaddr(&rsa)
      }
      
    • 変更後:
      func Getsockname(fd int) (sa Sockaddr, err error) {
      	var rsa RawSockaddrAny
      	var len _Socklen
      	if err = getsockname(fd, &rsa, &len); err != nil {
      		return
      	}
      	// TODO(jsing): Remove after OpenBSD 5.4 is released (see issue 3349).
      	if runtime.GOOS == "openbsd" && rsa.Addr.Family == AF_UNSPEC && rsa.Addr.Len == 0 {
      		rsa.Addr.Family = AF_UNIX
      		rsa.Addr.Len = SizeofSockaddrUnix
      	}
      	return anyToSockaddr(&rsa)
      }
      

コアとなるコードの解説

src/pkg/syscall/passfd_test.go の変更

TestPassFD関数は、ファイルディスクリプタの受け渡し機能(UnixドメインソケットのSCM_RIGHTSメッセージを介して実現される)をテストするためのものです。以前は、OpenBSD上でこのテストが失敗する既知の問題(issue 4956)があったため、runtime.GOOS == "openbsd"の場合にテストをスキップするロジックが含まれていました。

このコミットでsyscall_bsd.goに修正が加えられたことにより、OpenBSD 5.2でもgetsocknameが正しく動作するようになったため、このスキップロジックは不要となり削除されました。これにより、TestPassFDがOpenBSD 5.2でも実行され、機能が正しく動作することが検証されるようになりました。

src/pkg/syscall/syscall_bsd.go の変更

このファイルは、BSD系のオペレーティングシステム(OpenBSDを含む)向けのシステムコール関連の定義と実装を含んでいます。

Getsockname関数は、Goのsyscallパッケージが提供するgetsocknameシステムコールのラッパーです。この関数は、ソケットディスクリプタfdを受け取り、そのソケットのローカルアドレスをSockaddrインターフェースとして返します。

追加されたコードブロックは以下の通りです。

	// TODO(jsing): Remove after OpenBSD 5.4 is released (see issue 3349).
	if runtime.GOOS == "openbsd" && rsa.Addr.Family == AF_UNSPEC && rsa.Addr.Len == 0 {
		rsa.Addr.Family = AF_UNIX
		rsa.Addr.Len = SizeofSockaddrUnix
	}
  • runtime.GOOS == "openbsd": この条件は、現在の実行環境がOpenBSDである場合にのみ、以下のロジックが適用されることを保証します。これはOS固有のワークアラウンドであるため、他のOSには影響を与えません。
  • rsa.Addr.Family == AF_UNSPEC: getsocknameが返したsockaddr構造体のアドレスファミリーがAF_UNSPEC(未指定)であるかどうかをチェックします。これはOpenBSD 5.2の非標準的な挙動の特徴です。
  • rsa.Addr.Len == 0: getsocknameが返したsockaddr構造体のアドレスの長さが0であるかどうかをチェックします。これもOpenBSD 5.2の非標準的な挙動の特徴です。

これら3つの条件がすべて真である場合、GoランタイムはOpenBSD 5.2のバインドされていないUnixドメインソケットの特殊なケースであると判断します。

  • rsa.Addr.Family = AF_UNIX: sockaddr構造体のアドレスファミリーをAF_UNIXに強制的に設定し直します。これにより、GoのsyscallパッケージがこのソケットをUnixドメインソケットとして正しく認識できるようになります。
  • rsa.Addr.Len = SizeofSockaddrUnix: sockaddr構造体のアドレスの長さをSizeofSockaddrUnixに設定し直します。SizeofSockaddrUnixは、Unixドメインソケットのアドレス構造体(sockaddr_un)の標準的なサイズを示す定数です。これにより、アドレスの長さも正しい値に補正されます。

この修正により、OpenBSD 5.2上でもGetsockname関数がUnixドメインソケットに対して期待される正しい情報を返すようになり、TestPassFDを含む関連する機能が正常に動作するようになりました。TODOコメントは、このワークアラウンドがOpenBSDの将来のバージョンで不要になることを示唆しており、コードの保守性に関する考慮がなされています。

関連リンク

参考にした情報源リンク