perf: 直接 ICMP の RTT 計測を同期 + カーネルタイムスタンプ化(Linux)#12
Merged
Conversation
pro-bing は echo 応答を別 goroutine の recvICMP で受け、channel 経由で run loop へ渡してから受信時刻(time.Now)を取る。そのため goroutine 起床+ channel ホップの遅延が各 RTT サンプルに混入し、静かな LAN/loopback では AVG/JIT が真のネットワーク値を大きく上回っていた(MIN は下限のまま肥大)。 Linux のみ、direct ICMP を pro-bing から「同期送受信 + SO_TIMESTAMPNS (カーネル軟件 RX タイムスタンプ)」方式へ置換する。TX=time.Now() / RX=カーネル時刻の単一 CLOCK_REALTIME ドメイン(iputils ping と同方式)。 - raw/datagram の選択は既存 useICMPPrivileged() を再利用し挙動不変 (非特権 LXC で datagram が塞がれ raw に倒れる経路も維持)。 - 応答照合: raw は fan-out のため id+seq+token+src、datagram は kernel demux 済みで id を書き換えられるため seq+token+src。 - 受信ループは絶対デッドラインから SO_RCVTIMEO を毎回再アームし、raw fan-out 下で非マッチパケットによる無限スピンを防ぐ。 - TTL/HopLimit は IP_RECVTTL/IPV6_RECVHOPLIMIT で取得(捕捉のみ・非表示)。 macOS/FreeBSD/Windows は pro-bing 据え置き(icmp_other.go, //go:build !linux)。 設定構文・CLI・権限要件は不変のため README 変更なし。 loopback 実測(非 root/datagram): avg 0.07ms(pro-bing) → 0.03ms (native ping ~0.02 並み。userspace フォールバックの ~0.05 より明確に低く、 カーネルタイムスタンプが効いていることを確認)。
codex レビューで挙がった同期 ICMP プローブの回帰を修正。
- F1 送信元インタフェース指定: SO_BINDTODEVICE を廃し IP_PKTINFO/IPV6_PKTINFO
(ipv4/ipv6.ControlMessage{IfIndex}.Marshal)へ。pro-bing と同じ無権限・全カーネル
方式に戻す(SO_BINDTODEVICE は <5.7 で CAP_NET_RAW を要求し source=eth0 が失敗)。
- F2 OS スレッド消費: ソケットを SOCK_NONBLOCK 化し os.NewFile+SyscallConn で
ランタイムポーラ経由に。応答待ちのプローブが OS スレッドを占有しなくなり、
大規模対象/障害時のスレッド増加(10000 上限)リスクを解消。SO_TIMESTAMPNS は維持。
- F3 IPv6 zone: net.ParseIP が捨てる %zone を netip.ParseAddr で保持し、
SockaddrInet6.ZoneId に設定(数値 scope/インタフェース名の両対応)。
- F4 broadcast/multicast: 死活監視は1行=1ホスト前提のため対象外と README に明記
(現状の X グリフで graceful degrade、コード変更なし)。
- F5 時計ステップ: RTT は monotonic な recvUser.Sub(tSend) を境界とし、カーネル ts は
[0, rttMono] のときのみ採用。負値クランプ(Min=0 恒久化)を撤廃。
回帰テスト追加(payload/match/zone/clock-step fallback/PKTINFO/timespec 検証)、
manual テストに source=interface/IP の egress 確認を追加。make check 通過。
- ctx キャンセルを受信待機に反映(契約整合・防御的): Send が受け取った ctx の取消で 待機中のプローブを即時中断する。絶対デッドライン(read+write 両方=SetDeadline)に 加え context.AfterFunc でデッドラインを now へ畳み、parked な send/recv を解除。 deadman の TUI は context.Background() を渡すため現状は発火しないが、ctx を引数に 取る Send 契約を尊重し pro-bing の RunWithContext と挙動を揃える。 - テストの 32-bit Linux 非互換を修正(実バグ): unix.Timespec.Nsec は linux/386・ linux/arm で int32 のため明示的 int64 リテラルはコンパイル不可。untyped 定数 nsPerSec に変更し全 linux arch で go test -c が通るようにする。 - 送受信のエラー分類を対称化(防御的): retryRecv を retryable に改名し sendProbe と recv で共有。EINTR/EAGAIN/EWOULDBLOCK を両経路で同一に扱う。送信前に write も含む デッドラインを張り、満杯送信バッファでのポーラ待機も有界化。 検証: make check 通過、4 OS + linux/arm + linux/386 クロスビルド、race クリーン、 manual(非 root datagram 経路)で ctx キャンセルが 100ms で復帰・source egress・ RTT カーネル ts・1s 有界を確認。raw/特権経路は要 root の実機 manual 検証(未実行)。
codex の指摘のうち、到達しうる2点を修正。raw BPF フィルタ(C1)は見送り(下記)。 - ENOBUFS を一時障害として再試行(回帰修正): 出力キュー一時満杯時の Sendmsg は ENOBUFS を返すが、即 Failed では輻輳で偽のダウン判定になっていた。retryableSend に ENOBUFS を含め、送信前に張る write 込みの絶対デッドラインまで再試行する (pro-bing も ENOBUFS を continue で再試行)。持続的輻輳はデッドラインで X に落ちる。 - TTL/HopLimit cmsg をネイティブエンディアンで読む: IP_TTL/IPV6_HOPLIMIT cmsg は ネイティブエンディアンの C int。c.Data[0] はビッグエンディアン(s390x/ppc64)で 0 に なる。出荷対象は全て LE だが binary.NativeEndian.Uint32 で全アーキ正しく読む。 raw ソケットへの BPF id フィルタ(pro-bing InstallICMPIDFilter 相当)は見送り。 正当性はユーザー空間の照合(id+seq+token+src)で既に担保され、フィルタは root+多対象+async での fan-out 時の CPU/rcvbuf 節約=スケール最適化にすぎない。 一方、誤った BPF が attach 成功すると全 raw プローブが自分の応答を落とし「全ダウン」 誤報になる(best-effort attach でも防げない)。非 root の手元で検証不能かつ誤ると 沈黙故障のため未実装。理由は openICMPSocket にコメント化(将来 root 検証付き PR で追加可)。 検証: make check 0 issues、7 ターゲットクロスビルド(linux amd64/386/arm/arm64・ darwin・windows・freebsd)、race クリーン、manual(非 root datagram)非回帰。
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
zoneIndex の strconv.Atoi は 64-bit int を返し、それが上限チェックなしで SockaddrInet6.ZoneId(uint32)へ変換されていた(%-1 や %4294967296 等の範囲外 zone で truncation/巨大値化)。設定由来で実害は小さいが CodeQL の指摘どおり修正: - zoneIndex: 数値 zone を [0, math.MaxInt32] に限定。範囲外は no-zone(0)へフォールバック。 - ipSockaddr: 変換直前にも同じ範囲ガードを置き int→uint32 を局所的に安全と証明可能に (//nolint:gosec を実ガードへ置換)。 - TestZoneIndex に範囲外(-1 / 2^32 / 超大)→0 のケースを追加。 make check 0 issues、linux 386/arm 含むクロスビルド維持。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景 / なぜ
Linux の直接 ICMP プローブの RTT が実体より大きく出ていた。原因は計測手法で、
pro-bingはecho reply を専用ゴルーチンで受け channel 経由で run ループへ渡してから 受信時刻
(
time.Now())を取るため、ゴルーチン起床 + channel ホップの遅延が毎 RTT に乗る。静かなLAN/loopback では AVG/JIT が真の RTT を大きく上回っていた(MIN は下限のまま膨張)。
何を
Linux のみ、直接 ICMP を pro-bing から 同期送受信 +
SO_TIMESTAMPNS(カーネルが RX softirqで押す軟件タイムスタンプ) へ置換。
TX=userspace CLOCK_REALTIME / RX=kernel softwareというiputils ping と同じ方式。非 Linux(macOS/FreeBSD/Windows)は pro-bing 据え置き(
icmp_other.go)。設定構文・CLI・権限要件は不変。
icmp.go: 共通の権限判定のみ残し、Linux パスから pro-bing 依存を除去icmp_linux.go(新規): 同期 + カーネルタイムスタンプのプローバicmp_other.go(新規,//go:build !linux): 従来 pro-bing 実装を退避//go:build manual実 ICMP)、README 1 段落設計の要点
Send)。setup は計測区間tSendより前なので RTT に含まれず、永続化しても精度は変わらない
SOCK_NONBLOCK+os.NewFile+SyscallConn)。応答待ちでパークしてもOS スレッドを占有しない(障害時に対象数分のスレッドが積み上がらない)
source=eth0は無権限・全カーネルで動作。SO_BINDTODEVICEは 5.7 未満で CAP_NET_RAW を要求するため不使用)カーネルが id を書換えるので seq+token+src)
[0, rttMono]のときのみ採用(REALTIME ステップで Min が 0 に固着しない)
レビュー対応(codex 3 ラウンド)
README 明記)、クロックステップ
int64Timespec)、送受信のエラー分類対称化
見送り: raw ソケットへの BPF id フィルタ(pro-bing
InstallICMPIDFilter相当)。正当性はユーザー空間の照合で既に担保され、フィルタは root+多対象 fan-out 時の CPU/rcvbuf 節約=スケール
最適化に過ぎない。一方、誤った BPF が attach 成功すると全 raw プローブが自応答を落として「全ダウン」
誤報になる(沈黙故障)。理由は
openICMPSocketにコメント化(将来 root 検証付きで追加可)。検証
make check(tidy/fmt/vet/lint/test)0 issues、race クリーンキャンセル、1s 有界
[raw=true]で SO_TIMESTAMPNS(v4 avg 0.0169ms /v6 0.0147ms)、IPv4 ヘッダストリップ + id 照合、ttl=64 取得、IPv6 raw 照合、source=lo(PKTINFO)/
source=IP(bind)、ctx キャンセル 100ms 復帰 — すべて成功