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

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

このコミットは、Go言語の標準ライブラリであるnetパッケージにおいて、Windows環境でのTCP Keep-Alive期間を設定する機能を追加するものです。具体的には、net.TCPConnSetKeepAlivePeriodメソッドがWindows上で実際に機能するように、基盤となるシステムコール層の変更と、それを利用するネットワーク層のコードが追加されています。

コミット

commit 5277b90ec43e9b75f481ce737ade1d2a78bd32e1
Author: Nicholas Katsaros <nick@nickkatsaros.com>
Date:   Fri Jan 10 14:33:54 2014 +1100

    net: add SetKeepAlivePeriod for windows
    
    R=golang-codereviews, alex.brainman, bradfitz, mikioh.mikioh
    CC=golang-codereviews
    https://golang.org/cl/11393043

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

https://github.com/golang/go/commit/5277b90ec43e9b75f481ce737ade1d2a78bd32e1

元コミット内容

このコミットの元々の内容は、Go言語のnetパッケージにおいて、Windows環境でTCP Keep-Aliveの期間を設定する機能(SetKeepAlivePeriod)を実装することです。以前は、この機能はWindowsでは「未実装(no-op)」であり、設定しようとしても実際には何も行われませんでした。このコミットによって、Windows固有のシステムコールを利用して、この機能が有効化されます。

変更の背景

TCP Keep-Aliveは、アイドル状態のTCP接続がまだ有効であるかを確認するために、定期的に小さなパケットを送信するメカニズムです。これにより、ネットワーク機器(ルーター、ファイアウォールなど)がアイドル接続をタイムアウトで切断してしまうことを防ぎ、また、接続相手がクラッシュしたりネットワークから切断されたりした場合に、アプリケーションがそれを検知できるようになります。

Go言語のnetパッケージは、クロスプラットフォームで動作するように設計されていますが、特定のOS固有の機能については、それぞれのOSのAPIを利用して実装する必要があります。Windows環境において、SetKeepAlivePeriodが機能しないことは、長時間アイドル状態になる可能性のあるTCP接続を扱うアプリケーションにとって問題でした。例えば、データベース接続やメッセージキューへの接続など、常にオープンである必要があるが、データ転送が頻繁ではないアプリケーションでは、Keep-Aliveが適切に設定されていないと、意図せず接続が切断されてしまう可能性がありました。

このコミットは、WindowsのネットワークAPIであるWSAIoctlSIO_KEEPALIVE_VALSを利用して、この機能の欠落を解消し、GoアプリケーションがWindows上でも信頼性の高いTCP接続を維持できるようにすることを目的としています。

前提知識の解説

TCP Keep-Alive

TCP Keep-Aliveは、TCPプロトコルの一部として提供されるオプション機能です。これは、データが交換されていないアイドル状態のTCP接続が、まだ有効であるかどうかを確認するために使用されます。

  • 目的:
    1. アイドル接続の維持: ネットワーク機器(NATルーター、ファイアウォールなど)は、一定期間データが流れないアイドル接続を自動的に切断することがあります。Keep-Aliveパケットを定期的に送信することで、接続がアクティブであると認識させ、意図しない切断を防ぎます。
    2. ピアの生存確認: 接続相手のホストがクラッシュしたり、ネットワークから切断されたりした場合、アプリケーションは通常、データ送信を試みるまでその状態を知ることができません。Keep-Aliveパケットへの応答がない場合、接続が切断されたと判断でき、アプリケーションは適切なエラー処理を行うことができます。
  • 動作:
    • TCP Keep-Aliveは、設定されたアイドル期間が経過すると、小さな(通常は1バイトの)Keep-Aliveプローブパケットを相手に送信します。
    • 相手が応答すれば、接続はアクティブであると見なされ、タイマーがリセットされます。
    • 応答がない場合、設定された回数だけ再試行されます。
    • すべての再試行が失敗した場合、接続は切断されたと判断され、アプリケーションにエラーが通知されます。
  • 設定パラメータ:
    • Keep-Alive Time (KEEPALIVE_TIME): 接続がアイドル状態になってから最初のKeep-Aliveプローブを送信するまでの時間。
    • Keep-Alive Interval (KEEPALIVE_INTERVAL): 最初のプローブが応答されなかった場合に、後続のプローブを送信する間隔。
    • Keep-Alive Probes (KEEPALIVE_PROBES): 接続が切断されたと判断するまでに送信するプローブの最大回数。

Windows Sockets 2 (Winsock2) と WSAIoctl

Windows環境でのネットワークプログラミングは、Winsock2 APIを通じて行われます。WSAIoctl(Windows Socket Asynchronous I/O Control)は、Winsock2 APIの一部であり、ソケットに対して様々な制御操作を行うための汎用関数です。

  • WSAIoctlの役割: ソケットの動作を制御したり、拡張機能を取得したりするために使用されます。これは、Unix/Linuxにおけるioctlシステムコールに似ています。
  • 制御コード (Control Code): WSAIoctlは、実行する操作を指定するための制御コード(dwIoControlCodeパラメータ)を受け取ります。このコミットでは、SIO_KEEPALIVE_VALSという制御コードが使用されます。
  • 入力/出力バッファ: 制御コードに応じて、入力データや出力データを渡すためのバッファが指定されます。

SIO_KEEPALIVE_VALS

SIO_KEEPALIVE_VALSは、WSAIoctlで使用される特定の制御コードで、TCP Keep-Aliveのパラメータ(アイドル時間と再試行間隔)を設定するために使用されます。

  • この制御コードを使用する場合、TCPKeepalive構造体(または同等のデータ構造)を入力バッファとして渡します。
  • TCPKeepalive構造体には、Keep-Aliveの有効/無効、アイドル時間、再試行間隔がミリ秒単位で指定されます。

unsafe.Pointerunsafe.Sizeof

Go言語のunsafeパッケージは、型安全性をバイパスして低レベルのメモリ操作を可能にするためのものです。

  • unsafe.Pointer: 任意の型のポインタを保持できる汎用ポインタ型です。異なる型のポインタ間で変換を行う際に使用されます。C言語のvoid*に似ています。
  • unsafe.Sizeof: 式の評価結果の型のサイズ(バイト単位)を返します。コンパイル時に評価されます。 これらの機能は、Goの型システムを迂回するため、注意して使用する必要があります。通常、システムコールやC言語との相互運用など、特定の低レベルな操作でのみ必要とされます。

技術的詳細

このコミットの核心は、WindowsのTCP Keep-Alive設定をGoのnetパッケージから制御できるようにすることです。

  1. TCPKeepalive構造体の定義: Windows APIのTCP_KEEPALIVE構造体に対応するGoの構造体syscall.TCPKeepalivesrc/pkg/syscall/ztypes_windows.goに追加されます。この構造体は、Keep-Aliveの有効/無効(OnOff)、最初のプローブまでのアイドル時間(Time)、および後続のプローブ間隔(Interval)をミリ秒単位で保持します。

    type TCPKeepalive struct {
    	OnOff    uint32
    	Time     uint32
    	Interval uint32
    }
    
  2. SIO_KEEPALIVE_VALS定数の追加: WSAIoctl関数で使用される制御コードSIO_KEEPALIVE_VALSsrc/pkg/syscall/ztypes_windows.goに追加されます。この定数は、TCP Keep-Aliveパラメータを設定するための特定の操作を識別します。IOC_VENDORは、ベンダー固有のIOCTLコードであることを示します。

    const (
    	// ...
    	IOC_VENDOR                         = 0x18000000
    	// ...
    	SIO_KEEPALIVE_VALS                 = IOC_IN | IOC_VENDOR | 4
    )
    
  3. setKeepAlivePeriodの実装: src/pkg/net/tcpsockopt_windows.go内のsetKeepAlivePeriod関数が、実際のKeep-Alive設定ロジックを含むように変更されます。

    • Goのtime.Duration型の期間dを、Windowsが期待するミリ秒単位に変換します。この際、ミリ秒未満の端数は切り上げられます。
    • syscall.TCPKeepalive構造体のインスタンスkaを作成し、OnOff1(有効)、TimeIntervalを計算されたミリ秒値で設定します。
    • syscall.WSAIoctlを呼び出します。
      • fd.sysfd: ソケットのファイルディスクリプタ(Windowsではソケットハンドル)。
      • syscall.SIO_KEEPALIVE_VALS: 実行する操作(Keep-Aliveパラメータの設定)。
      • (*byte)(unsafe.Pointer(&ka)): TCPKeepalive構造体kaへのポインタをバイトポインタにキャストして入力バッファとして渡します。unsafe.Pointerがここで使用されます。
      • size: TCPKeepalive構造体のサイズ。unsafe.Sizeof(ka)で取得されます。
      • nil, 0: 出力バッファは不要なため。
      • &ret: 実際に転送されたバイト数を格納する変数。
      • nil, 0: オーバーラップI/O関連のパラメータ。
    • WSAIoctlが返すエラーは、os.NewSyscallErrorを使用してGoのエラー型に変換されます。

この変更により、GoアプリケーションはWindows上でもnet.TCPConn.SetKeepAlivePeriodを呼び出すことで、TCP Keep-Aliveの動作を細かく制御できるようになります。

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

src/pkg/net/tcpsockopt_windows.go

--- a/src/pkg/net/tcpsockopt_windows.go
+++ b/src/pkg/net/tcpsockopt_windows.go
@@ -7,7 +7,10 @@
 package net
 
 import (
+	"os"
+	"syscall"
 	"time"
+	"unsafe"
 )
 
 func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
@@ -16,6 +19,16 @@ func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
 	}
 	defer fd.decref()
 
-	// We can't actually set this per connection.  Act as a noop rather than an error.
-	return nil
+	// Windows expects milliseconds so round to next highest millisecond.
+	d += (time.Millisecond - time.Nanosecond)
+	millis := uint32(d / time.Millisecond)
+	ka := syscall.TCPKeepalive{
+		OnOff:    1,
+		Time:     millis,
+		Interval: millis,
+	}
+	ret := uint32(0)
+	size := uint32(unsafe.Sizeof(ka))
+	err := syscall.WSAIoctl(fd.sysfd, syscall.SIO_KEEPALIVE_VALS, (*byte)(unsafe.Pointer(&ka)), size, nil, 0, &ret, nil, 0)
+	return os.NewSyscallError("WSAIoctl", err)
 }

src/pkg/syscall/ztypes_windows.go

--- a/src/pkg/syscall/ztypes_windows.go
+++ b/src/pkg/syscall/ztypes_windows.go
@@ -512,9 +512,11 @@ const (
 
 	IOC_OUT                            = 0x40000000
 	IOC_IN                             = 0x80000000
+	IOC_VENDOR                         = 0x18000000
 	IOC_INOUT                          = IOC_IN | IOC_OUT
 	IOC_WS2                            = 0x08000000
 	SIO_GET_EXTENSION_FUNCTION_POINTER = IOC_INOUT | IOC_WS2 | 6
+	SIO_KEEPALIVE_VALS                 = IOC_IN | IOC_VENDOR | 4
 
 	// cf. http://support.microsoft.com/default.aspx?scid=kb;en-us;257460
 
@@ -1031,3 +1033,9 @@ type WSAProtocolChain struct {
 	ChainLen     int32
 	ChainEntries [MAX_PROTOCOL_CHAIN]uint32
 }\n
+type TCPKeepalive struct {
+	OnOff    uint32
+	Time     uint32
+	Interval uint32
+}

コアとなるコードの解説

src/pkg/net/tcpsockopt_windows.goの変更点

  • インポートの追加: os, syscall, unsafeパッケージが新しくインポートされています。これらは、システムコールへのアクセス、OS固有のエラー処理、および低レベルのメモリ操作のために必要です。
  • 以前のno-op実装の削除: 以前のsetKeepAlivePeriod関数は、単にnilを返していましたが、これが削除され、実際のWindows API呼び出しに置き換えられました。
  • 期間のミリ秒変換:
    d += (time.Millisecond - time.Nanosecond)
    millis := uint32(d / time.Millisecond)
    
    Goのtime.Durationはナノ秒単位ですが、Windows APIはミリ秒単位を期待します。time.Millisecond - time.Nanosecondを加算することで、ミリ秒未満の端数がある場合に切り上げが行われ、常に次のミリ秒に丸められます。例えば、1ms + 1ns は 2ms になります。これは、Keep-Alive期間が短すぎると意図しない挙動になるのを防ぐための安全策と考えられます。
  • syscall.TCPKeepalive構造体の初期化:
    ka := syscall.TCPKeepalive{
    	OnOff:    1,
    	Time:     millis,
    	Interval: millis,
    }
    
    TCPKeepalive構造体が初期化されます。OnOff: 1はKeep-Aliveを有効にすることを意味します。TimeIntervalには、計算されたミリ秒値が設定されます。この実装では、最初のプローブまでのアイドル時間と、その後のプローブ間隔が同じ値に設定されています。
  • syscall.WSAIoctlの呼び出し:
    err := syscall.WSAIoctl(fd.sysfd, syscall.SIO_KEEPALIVE_VALS, (*byte)(unsafe.Pointer(&ka)), size, nil, 0, &ret, nil, 0)
    
    これがWindows APIへの実際の呼び出しです。
    • fd.sysfd: ネットワーク接続の基盤となるソケットのシステムファイルディスクリプタ(Windowsではソケットハンドル)。
    • syscall.SIO_KEEPALIVE_VALS: TCP Keep-Aliveパラメータを設定するための制御コード。
    • (*byte)(unsafe.Pointer(&ka)): ka構造体のアドレスをunsafe.Pointerを介して*byteにキャストし、WSAIoctlの入力バッファとして渡します。これにより、Goの構造体からC言語スタイルのポインタを渡すことができます。
    • size: ka構造体のサイズ。unsafe.Sizeof(ka)で取得されます。
    • 残りの引数は、出力バッファやオーバーラップI/Oに関するもので、この操作では使用されないためnil0が渡されます。
  • エラーハンドリング:
    return os.NewSyscallError("WSAIoctl", err)
    
    WSAIoctlが返すエラーは、os.NewSyscallErrorによってGoの標準的なエラー型にラップされます。これにより、システムコールエラーがGoの慣用的な方法で扱えるようになります。

src/pkg/syscall/ztypes_windows.goの変更点

  • IOC_VENDOR定数の追加: IOC_VENDORは、IOCTLコードがベンダー固有の拡張であることを示すフラグです。SIO_KEEPALIVE_VALSが標準的なIOCTLではなく、Microsoft固有の拡張であることを示唆しています。
  • SIO_KEEPALIVE_VALS定数の追加: この定数は、WSAIoctl関数に渡される制御コードであり、TCP Keep-Aliveの設定操作を識別します。IOC_INは入力バッファがあることを、IOC_WS2はWinsock2関連のコードであることを示します。
  • TCPKeepalive構造体の追加: Windows APIのTCP_KEEPALIVE構造体に対応するGoの構造体です。この構造体は、WSAIoctlに渡すKeep-Aliveパラメータを定義します。

これらの変更により、GoのnetパッケージはWindows上でもTCP Keep-Alive機能を適切に利用できるようになり、クロスプラットフォームでのネットワークアプリケーション開発における一貫性が向上しました。

関連リンク

参考にした情報源リンク