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

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

このコミットは、Go言語の標準ライブラリである net パッケージにおける以前の変更(コミットハッシュ 2518eee18c4f、Change List ID 6395055)を元に戻すものです。元の変更は、RemoteAddr.String メソッドチェーンが呼び出された際に発生する nil ポインタ参照解除(nil pointer dereference)の問題(Issue #3721)を修正することを目的としていましたが、その副作用としてTCPの自己接続(selfConnect)機能を破壊してしまいました。このコミットは、その破壊された機能を修復するために、元の変更をアンドゥ(元に戻す)しています。

コミット

commit e4389c008a223213fc3f506756f38d695cf60d40
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Wed Sep 19 01:33:03 2012 +0900

    undo CL 6395055 / 2518eee18c4f
    
    Broke TCP selfConnect
    
    ««« original CL description
    net: avoid nil pointer dereference when RemoteAddr.String method chain is called
    
    Fixes #3721.
    
    R=dave, rsc
    CC=golang-dev
    https://golang.org/cl/6395055
    »»»
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6533043

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

https://github.com/golang/go/commit/e4389c008a223213fc3f506756f38d695cf60d40

元コミット内容

このコミットは、以下のコミット(CL 6395055)を元に戻すものです。

commit 2518eee18c4f
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Tue Sep 18 00:40:00 2012 +0900

    net: avoid nil pointer dereference when RemoteAddr.String method chain is called
    
    Fixes #3721.
    
    R=dave, rsc
    CC=golang-dev
    https://golang.org/cl/6395055

元のコミットは、net パッケージにおいて RemoteAddr().String() のようなメソッドチェーンが呼び出された際に、RemoteAddr()nil を返す可能性がある場合に発生する nil ポインタ参照解除のクラッシュを回避することを目的としていました。これは、特に接続が確立されていないソケットや、エラー状態のソケットに対してアドレス情報を取得しようとした場合に問題となる可能性がありました。

変更の背景

元のコミット(CL 6395055)は、Goの net パッケージにおける特定の条件下での nil ポインタ参照解除のバグ(Issue #3721)を修正するために導入されました。このバグは、net.Conn インターフェースの RemoteAddr() メソッドが nil を返し、その結果として String() メソッドが呼び出された際にパニックを引き起こす可能性がありました。

しかし、この修正は意図しない副作用をもたらしました。具体的には、TCPの「自己接続(selfConnect)」機能が動作しなくなりました。自己接続とは、クライアントが自分自身(同じホスト、同じポート)に接続しようとするシナリオを指します。これは、特定のテストケースや、ループバックインターフェースを使用した特殊なネットワーク構成で発生する可能性があります。元の修正が、ソケットのアドレス解決ロジックを変更したことで、この自己接続の検出または確立が正しく行われなくなったと考えられます。

このコミットは、バグ修正によって導入されたこの新たな回帰(regression)を修正するために、元の変更を完全に元に戻すことを決定しました。これは、Goの標準ライブラリ開発における一般的なプラクティスであり、新たなバグを導入する可能性のある修正は、その影響を完全に理解し、より堅牢な解決策が見つかるまで元に戻されることがあります。

前提知識の解説

1. net パッケージ (Go言語)

Go言語の net パッケージは、ネットワークI/Oプリミティブへのポータブルインターフェースを提供します。TCP/IP、UDP、Unixドメインソケットなどのネットワークプロトコルを扱うための機能が含まれています。

  • net.Conn インターフェース: ネットワーク接続を表す汎用インターフェースです。Read, Write, Close, LocalAddr, RemoteAddr などのメソッドを持ちます。
  • net.Addr インターフェース: ネットワークエンドポイントアドレスを表す汎用インターフェースです。Network()String() メソッドを持ちます。TCPAddr, UDPAddr, IPAddr, UnixAddr など、具体的なアドレス型がこのインターフェースを実装します。
  • LocalAddr()RemoteAddr(): net.Conn インターフェースのメソッドで、それぞれローカルエンドポイントとリモートエンドポイントのネットワークアドレスを返します。

2. nil ポインタ参照解除 (Nil Pointer Dereference)

プログラミングにおいて、nil(または null)は「値がない」ことを示す特別な値です。nil ポインタ参照解除とは、nil 値を持つポインタが指すメモリ領域にアクセスしようとしたときに発生するエラーです。Go言語では、これは通常「パニック(panic)」を引き起こし、プログラムの実行を停止させます。

例:

var addr *net.TCPAddr // addr は nil
fmt.Println(addr.String()) // ここで nil ポインタ参照解除が発生し、パニックになる

3. TCP 自己接続 (TCP Self-Connect)

TCP自己接続とは、クライアントが自分自身(同じホスト、同じポート)に接続しようとするネットワークシナリオです。これは通常、ループバックインターフェース(例: 127.0.0.1::1)を使用して、クライアントとサーバーが同じプロセス内で動作している場合や、テスト目的で発生します。TCPプロトコルスタックは、このような自己接続を正しく処理できる必要があります。

4. Unix系システムコール (syscall パッケージ)

Go言語の syscall パッケージは、オペレーティングシステム(OS)の低レベルなシステムコールへのインターフェースを提供します。ネットワークプログラミングにおいては、ソケットの作成、バインド、接続、アドレス情報の取得などにこれらのシステムコールが使われます。

  • syscall.Getsockname(fd int): 指定されたファイルディスクリプタ fd に関連付けられたソケットのローカルアドレス(ソケット名)を取得します。
  • syscall.Getpeername(fd int): 指定されたファイルディスクリプタ fd に関連付けられたソケットのリモートアドレス(ピア名)を取得します。

これらのシステムコールは、ソケットが接続されている場合にのみ有効なアドレス情報を返します。接続されていないソケットに対して呼び出された場合、エラーを返すか、または意味のない値を返すことがあります。

5. ファイルディスクリプタ (File Descriptor, FD)

Unix系OSにおいて、ファイルディスクリプタは、プロセスが開いているファイルやソケットなどのI/Oリソースを識別するための整数値です。ネットワークプログラミングでは、ソケットが作成されると、それに対応するファイルディスクリプタが割り当てられ、そのディスクリプタを通じてソケットに対する操作が行われます。

技術的詳細

このコミットは、以前のコミットが導入した変更を元に戻すことで、net パッケージ内のソケットアドレス管理ロジックを以前の状態に戻しています。元のコミットは、netFD 構造体のアドレス設定方法を変更し、特に RemoteAddrnil になる可能性のあるケースで、String() メソッドが呼び出される前に nil チェックを強化しようとしました。しかし、この変更が syscall.Getsocknamesyscall.Getpeername の呼び出しタイミングや結果の解釈に影響を与え、TCP自己接続のシナリオで問題を引き起こしたと考えられます。

具体的には、元のコミットは localSockname および remoteSockname というヘルパー関数を導入し、ソケットのアドレス情報を取得する際に nil アドレスを特定のプロトコルに応じた nil アドレス型(例: (*TCPAddr)(nil))に変換するロジックを追加しました。これにより、nil ポインタ参照解除は回避されるものの、ソケットが実際に接続されていない場合や、自己接続のような特殊なケースで、期待されるアドレス情報が正しく設定されず、結果として接続が失敗する原因となりました。

このアンドゥコミットでは、これらのヘルパー関数を削除し、netFD のアドレス設定を直接 syscall.Getsocknamesyscall.Getpeername の結果に基づいて行うように戻しています。これにより、TCP自己接続が再び正しく機能するようになりますが、元のIssue #3721(nil ポインタ参照解除)は再発する可能性があります。これは、機能の破壊を伴うバグ修正よりも、既知のバグの再発の方が許容されるという判断があったことを示唆しています。

テストファイルの変更も重要です。ipraw_test.goudp_test.go から、IPConnAddrStringTestsudpConnAddrStringTests というテストケースが削除されています。これらのテストは、おそらく元のコミットで導入されたか、または元のコミットの変更によって失敗するようになったため、アンドゥの一環として削除されたと考えられます。これにより、自己接続の問題が修正されたことを確認するためのテストが一時的に失われた可能性があります。

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

このコミットは、主に以下のファイルと関数に変更を加えています。

  1. src/pkg/net/fd_unix.go

    • func (fd *netFD) accept(...) 関数内の netfd.setAddr の呼び出しが変更されています。
      • 変更前: netfd.setAddr(localSockname(fd, toAddr), toAddr(rsa))
      • 変更後: lsa, _ := syscall.Getsockname(netfd.sysfd); netfd.setAddr(toAddr(lsa), toAddr(rsa))
      • localSockname ヘルパー関数の使用を廃止し、直接 syscall.Getsockname を呼び出してローカルアドレスを取得するように戻しています。
  2. src/pkg/net/file_unix.go

    • func newFileFD(f *os.File) (*netFD, error) 関数内の netfd.setAddr の呼び出しが変更されています。
      • 変更前: netfd.setAddr(laddr, remoteSockname(netfd, toAddr))
      • 変更後: rsa, _ := syscall.Getpeername(fd); raddr := toAddr(rsa); netfd.setAddr(laddr, raddr)
      • remoteSockname ヘルパー関数の使用を廃止し、直接 syscall.Getpeername を呼び出してリモートアドレスを取得するように戻しています。
  3. src/pkg/net/sock_posix.go

    • func socket(...) 関数内の fd.setAddr の呼び出しが変更されています。
      • 変更前: fd.setAddr(laddr, remoteSockname(fd, toAddr))
      • 変更後: lsa, _ := syscall.Getsockname(s); ...; rsa, _ := syscall.Getpeername(s); raddr := toAddr(rsa); fd.setAddr(laddr, raddr)
      • ここでも localSocknameremoteSockname の使用を廃止し、直接 syscall.Getsocknamesyscall.Getpeername を呼び出すように戻しています。
    • localSocknameremoteSocknamenullProtocolAddr の各関数が完全に削除されています。 これらは元のコミットで導入されたヘルパー関数であり、このアンドゥコミットによって不要になったため削除されました。
  4. src/pkg/net/ipraw_test.go

    • ipConnAddrStringTests 変数と TestIPConnAddrString テスト関数が削除されています。
  5. src/pkg/net/udp_test.go

    • udpConnAddrStringTests 変数と TestUDPConnAddrString テスト関数が削除されています。

コアとなるコードの解説

このコミットの核心は、netFD 構造体(ネットワークファイルディスクリプタ)にローカルアドレスとリモートアドレスを設定するロジックを、元の状態に戻すことです。

元のコミット(CL 6395055)では、netFD.setAddr を呼び出す際に、localSocknameremoteSockname という新しいヘルパー関数を使用していました。これらのヘルパー関数は、syscall.Getsocknamesyscall.Getpeername の結果が nil であった場合に、特定のプロトコルに応じた nilnet.Addr 型(例: (*TCPAddr)(nil))を返すように設計されていました。これは、nil ポインタ参照解除を防ぐための試みでした。

しかし、このアプローチはTCP自己接続のシナリオで問題を引き起こしました。自己接続の場合、GetsocknameGetpeername が返すアドレス情報が、通常の接続とは異なる振る舞いをすることがあります。元の修正が、これらの特殊なケースを正しく扱えず、結果として自己接続が失敗する原因となりました。

このアンドゥコミットでは、localSocknameremoteSocknamenullProtocolAddr の各ヘルパー関数を完全に削除し、netFD.setAddr の呼び出し箇所で、直接 syscall.Getsocknamesyscall.Getpeername を呼び出してアドレス情報を取得するように戻しています。

例えば、src/pkg/net/fd_unix.goaccept 関数では、以下のように変更されました。

変更前(元のコミットによる変更):

	netfd.setAddr(localSockname(fd, toAddr), toAddr(rsa))

ここでは、localSocknamefd のローカルアドレスを取得し、toAddrnet.Addr 型に変換しています。

変更後(このアンドゥコミットによる変更):

	lsa, _ := syscall.Getsockname(netfd.sysfd)
	netfd.setAddr(toAddr(lsa), toAddr(rsa))

ここでは、syscall.Getsockname を直接呼び出してローカルソケットアドレス lsa を取得し、それを toAddrnet.Addr 型に変換して setAddr に渡しています。これにより、元の nil ポインタ参照解除の問題は再発する可能性がありますが、TCP自己接続の機能は回復します。

テストファイルの削除は、元の修正が導入したテストが、アンドゥによって再び失敗するようになったため、一時的に削除されたことを示唆しています。これは、問題の根本的な解決策が見つかるまで、機能の安定性を優先した結果と考えられます。

関連リンク

  • Go Issue #3721: net: RemoteAddr().String() panics if RemoteAddr() is nil
    • このコミットが元に戻した修正が対処しようとしていた問題です。
  • Go Change List 6395055: net: avoid nil pointer dereference when RemoteAddr.String method chain is called
    • このコミットが元に戻した元の変更です。
  • Go Change List 6533043: undo CL 6395055 / 2518eee18c4f
    • このコミット自体のChange Listです。

参考にした情報源リンク