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

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

コミット

commit 6a6224c88dfbe450179230ed2b1819a41391963b
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Thu Mar 13 14:45:50 2014 +0900

    net: fix TCP keepalive on dragonfly
    
    Fixes #7528.
    
    LGTM=jsing
    R=jsing
    CC=golang-codereviews
    https://golang.org/cl/75140045

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

https://github.com/golang/go/commit/6a6224c88dfbe450179230ed2b1819a41391963b

元コミット内容

net: fix TCP keepalive on dragonfly

このコミットは、Go言語のネットワークパッケージにおいて、DragonFly BSDオペレーティングシステム上でのTCPキープアライブ機能の不具合を修正するものです。関連するIssueは #7528 です。

変更の背景

このコミットの背景には、Go言語のネットワークスタックが様々なオペレーティングシステムで正しく動作することを目指すという目標があります。TCPキープアライブは、アイドル状態のTCP接続が切断されずに維持されているかを確認するための重要なメカニズムです。これにより、ネットワーク機器のタイムアウトや、一方のピアが予期せずクラッシュした場合に、もう一方のピアがそれを検出し、リソースを解放することができます。

Go言語のnetパッケージは、OSに依存しない抽象化を提供しつつも、内部的には各OSのシステムコールを利用しています。TCPキープアライブの設定も例外ではなく、setsockoptシステムコールを通じて行われますが、このシステムコールの引数や挙動はOSによって微妙に異なる場合があります。

DragonFly BSDは、FreeBSDからフォークしたオープンソースのUnix系オペレーティングシステムであり、独自のカーネル開発が行われています。そのため、他のUnix系OS(Linux, FreeBSDなど)とは異なるTCPソケットオプションの実装や定数を持つ可能性があります。

このコミット以前は、GoのnetパッケージがDragonFly BSD上でTCPキープアライブを正しく設定できていなかったと考えられます。これは、おそらく他のUnix系OS向けに共通化されたコードが、DragonFly BSDの特定の要件を満たしていなかったためです。Issue #7528 がこの問題の存在を示唆しています。

前提知識の解説

TCPキープアライブ (TCP Keepalive)

TCPキープアライブは、TCP接続が長時間アイドル状態にある場合に、その接続がまだ有効であるかどうかを確認するために使用されるメカニズムです。これは、以下のような目的で利用されます。

  1. デッドピアの検出: 接続の片側がクラッシュしたり、ネットワークから切断されたりした場合に、もう一方の側がそれを検出し、リソースを解放できるようにします。
  2. NAT/ファイアウォールのタイムアウト防止: 多くのNATデバイスやファイアウォールは、一定時間アイドル状態の接続をタイムアウトさせて切断します。キープアライブパケットを定期的に送信することで、接続をアクティブに保ち、これらのデバイスによる意図しない切断を防ぎます。

TCPキープアライブは、通常、ソケットオプションを通じて設定されます。主要な設定項目は以下の通りです。

  • TCP_KEEPIDLE (または SO_KEEPALIVE_TIME): 接続がアイドル状態になってから、最初のキープアライブプローブを送信するまでの時間(秒)。
  • TCP_KEEPINTVL (または SO_KEEPALIVE_INTERVAL): 最初のプローブが応答されなかった場合に、後続のキープアライブプローブを送信する間隔(秒)。
  • TCP_KEEPCNT (または SO_KEEPALIVE_PROBES): 接続が切断されたと判断するまでに送信するキープアライブプローブの最大数。

これらの値はOSによってデフォルト値が異なり、また設定方法もシステムコールレベルで差異がある場合があります。

setsockopt システムコール

setsockoptは、ソケットのオプションを設定するためのシステムコールです。ソケットの挙動を制御するために使用され、TCPキープアライブの設定もこれを通じて行われます。 一般的な形式は以下の通りです。 int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

  • sockfd: オプションを設定するソケットのファイルディスクリプタ。
  • level: オプションが適用されるプロトコルレベル(例: SOL_SOCKETはソケットレベル、IPPROTO_TCPはTCPレベル)。
  • optname: 設定するオプションの名前(例: SO_KEEPALIVE, TCP_KEEPIDLE, TCP_KEEPINTVL)。
  • optval: オプションの値を指すポインタ。
  • optlen: optvalのサイズ。

DragonFly BSD

DragonFly BSDは、FreeBSD 4.8からフォークしたオープンソースのUnix系オペレーティングシステムです。FreeBSDとは異なるカーネル設計思想を持ち、特にスケーラビリティと並列処理に重点を置いています。そのため、ネットワークスタックやシステムコールの実装において、FreeBSDやLinuxとは異なる独自の挙動や定数を持つことがあります。この差異が、GoのnetパッケージがDragonFly BSD上でTCPキープアライブを正しく設定できない原因となっていた可能性があります。

技術的詳細

このコミットの技術的詳細は、Go言語のnetパッケージがTCPキープアライブを設定する際に、DragonFly BSDの特定の要件に対応するためのものです。

Goのnetパッケージは、netFDという内部構造体を通じてファイルディスクリプタ(sysfd)を管理し、ソケット操作を行います。TCPキープアライブの設定は、setKeepAlivePeriod関数によって行われます。この関数は、指定された期間(d)に基づいて、TCP_KEEPINTVLTCP_KEEPIDLEという2つのTCPソケットオプションを設定します。

TCP_KEEPINTVLTCP_KEEPIDLE

これらのソケットオプションは、TCPキープアライブの動作を制御します。

  • TCP_KEEPINTVL: キープアライブプローブの送信間隔を設定します。
  • TCP_KEEPIDLE: 接続がアイドル状態になってから、最初のキープアライブプローブを送信するまでの時間を設定します。

多くのUnix系システムでは、これらのオプションの値は秒単位で指定されますが、カーネルによってはミリ秒単位を期待するものもあります。コミットのコードを見ると、Goのtime.Durationをミリ秒に変換していることから、DragonFly BSDのカーネルがミリ秒単位を期待していることが示唆されます。

OS固有の実装

Go言語では、異なるOSやアーキテクチャに特化したコードをコンパイル時に選択するために、ビルドタグ(+buildディレクティブ)を使用します。 このコミットでは、src/pkg/net/tcpsockopt_dragonfly.goという新しいファイルが追加されています。このファイルは、// +build dragonflyというビルドタグを持つことで、DragonFly BSD環境でのみコンパイルされるようになります。これにより、DragonFly BSDに特有のsetKeepAlivePeriodの実装を提供し、他のUnix系OS(Linux, FreeBSD, NetBSDなど)とは異なる挙動を吸収しています。

具体的には、tcpsockopt_unix.goからdragonflyビルドタグが削除され、tcpsockopt_dragonfly.goに移動しています。これは、tcpsockopt_unix.goが提供する汎用的なUnix向けの実装がDragonFly BSDでは不十分であったため、DragonFly BSD専用のより正確な実装が必要になったことを意味します。

新しいtcpsockopt_dragonfly.go内のsetKeepAlivePeriod関数では、time.Duration型の期間dをミリ秒に変換しています。 d += (time.Millisecond - time.Nanosecond) msecs := int(time.Duration(d.Nanoseconds()) / time.Millisecond) この計算は、dを最も近いミリ秒に切り上げるためのものです。例えば、1ナノ秒でもミリ秒の端数があれば、次のミリ秒に繰り上げられます。これは、カーネルがミリ秒単位の整数値を期待しているため、Goのtime.Durationの精度を調整していると考えられます。

その後、syscall.SetsockoptIntを使用して、TCP_KEEPINTVLTCP_KEEPIDLEの両方に同じミリ秒値を設定しています。これは、DragonFly BSDにおいて、キープアライブのアイドル時間とプローブ間隔の両方を同じ値に設定することが適切である、またはそのように動作することが期待されていることを示唆しています。

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

このコミットによる主要なコード変更は以下の2つのファイルにあります。

  1. src/pkg/net/tcpsockopt_dragonfly.go: 新規作成されたファイル。
  2. src/pkg/net/tcpsockopt_unix.go: 既存ファイルの変更。

src/pkg/net/tcpsockopt_dragonfly.go (新規ファイル)

// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net

import (
	"os"
	"syscall"
	"time"
)

// Set keep alive period.
func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
	if err := fd.incref(); err != nil {
		return err
	}
	defer fd.decref()

	// The kernel expects milliseconds so round to next highest millisecond.
	d += (time.Millisecond - time.Nanosecond)
	msecs := int(time.Duration(d.Nanoseconds()) / time.Millisecond)

	err := os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, msecs))
	if err != nil {
		return err
	}
	return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, msecs))
}

src/pkg/net/tcpsockopt_unix.go (変更箇所)

--- a/src/pkg/net/tcpsockopt_unix.go
+++ b/src/pkg/net/tcpsockopt_unix.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build dragonfly freebsd linux nacl netbsd
+// +build freebsd linux nacl netbsd
 
 package net

コアとなるコードの解説

src/pkg/net/tcpsockopt_dragonfly.go

このファイルは、DragonFly BSDに特化したTCPキープアライブ期間の設定ロジックを実装しています。

  • // +build dragonfly: この行はビルドタグです。Goコンパイラは、このファイルがdragonflyというタグが指定されたビルドでのみコンパイルされるように指示します。これにより、OS固有のコードを分離し、クロスプラットフォーム対応を実現しています。
  • package net: このファイルがnetパッケージの一部であることを示します。
  • import ("os", "syscall", "time"): 必要な標準ライブラリパッケージをインポートしています。
    • os: オペレーティングシステムとの相互作用のための機能を提供します。os.NewSyscallErrorはシステムコールエラーをラップするために使用されます。
    • syscall: 低レベルのシステムコールインターフェースを提供します。syscall.SetsockoptIntはソケットオプションを設定するために使用されます。
    • time: 時間の測定と表示のための機能を提供します。time.Durationは期間を表すために使用されます。
  • func setKeepAlivePeriod(fd *netFD, d time.Duration) error:
    • この関数は、netFD型のファイルディスクリプタfdと、キープアライブ期間を表すtime.Duration型のdを受け取ります。
    • fd.incref()defer fd.decref(): netFDの参照カウントを増減させることで、ファイルディスクリプタのライフサイクルを適切に管理しています。これにより、ソケットが使用中に閉じられることを防ぎます。
    • d += (time.Millisecond - time.Nanosecond): この行は、dを最も近いミリ秒に切り上げるためのトリックです。例えば、d10s 1nsの場合、time.Millisecond - time.Nanosecond(約1ミリ秒)が加算されることで、10s 1msに切り上げられます。これは、カーネルがミリ秒単位の整数値を期待しているため、Goのtime.Durationのナノ秒精度をミリ秒精度に調整しています。
    • msecs := int(time.Duration(d.Nanoseconds()) / time.Millisecond): dをナノ秒に変換し、それをミリ秒で割ることで、ミリ秒単位の整数値msecsを計算します。
    • err := os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, msecs)):
      • syscall.SetsockoptInt: ソケットオプションを設定するシステムコールを呼び出します。
      • fd.sysfd: ソケットのファイルディスクリプタ。
      • syscall.IPPROTO_TCP: TCPプロトコルレベルのオプションであることを指定します。
      • syscall.TCP_KEEPINTVL: キープアライブプローブの送信間隔を設定するオプション名。
      • msecs: 設定するミリ秒単位の値。
      • os.NewSyscallError: システムコールがエラーを返した場合に、Goのエラー型に変換します。
    • if err != nil { return err }: TCP_KEEPINTVLの設定でエラーが発生した場合、すぐにエラーを返します。
    • return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, msecs)):
      • 同様に、TCP_KEEPIDLEオプションを設定します。
      • syscall.TCP_KEEPIDLE: 接続がアイドル状態になってから最初のキープアライブプローブを送信するまでの時間を設定するオプション名。
      • TCP_KEEPINTVLTCP_KEEPIDLEの両方に同じmsecs値を設定している点が重要です。これはDragonFly BSDの特定の挙動に合わせたものと考えられます。

src/pkg/net/tcpsockopt_unix.go

このファイルは、一般的なUnix系OS(FreeBSD, Linux, NetBSD, NaCl)向けのTCPソケットオプション設定ロジックを含んでいます。

  • - // +build dragonfly freebsd linux nacl netbsd: 変更前は、このファイルがDragonFly BSDを含む複数のOSでコンパイルされるように指定されていました。
  • + // +build freebsd linux nacl netbsd: 変更後、dragonflyタグが削除されました。これは、DragonFly BSD向けのsetKeepAlivePeriodの実装がtcpsockopt_dragonfly.goに分離されたため、このファイルからは除外されたことを意味します。これにより、各OSの特性に合わせたより正確なキープアライブ設定が可能になります。

関連リンク

参考にした情報源リンク