[インデックス 17056] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet
およびsyscall
パッケージにおける、FreeBSD環境でのIPv6ネットワークマスクの取り扱いに関するバグ修正を目的としています。具体的には、FreeBSDの最新バージョン(特にFreeBSD 9.1)において、ルーティングテーブルメッセージ(RTM_GETなどの応答)から取得されるネットワークマスク情報にアドレスファミリー識別子(AF_UNSPEC)が正しく設定されない問題に対応しています。
コミット
commit 262d6f58c78d53d2e85957075de0f817861cc23c
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Aug 7 00:25:23 2013 +0900
syscall: fix IPv6 wrong network mask on latest FreeBSD
Looks like latest FreeBSD doesn't set address family identifer
for RTAX_NETMASK stuff; probably RTAX_GENMASK too, not confirmed.
This CL tries to identify address families by using the length of
each socket address if possible.
The issue is confirmed on FreeBSD 9.1.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12332043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/262d6f58c78d53d2e85957075de0f817861cc23c
元コミット内容
このコミットの元の内容は、FreeBSDの最新バージョン(特に9.1)において、ルーティングテーブルメッセージ内のRTAX_NETMASK
(およびおそらくRTAX_GENMASK
)フィールドにアドレスファミリー識別子(AF_UNSPEC
)が設定されない問題を修正することです。この問題により、Goのsyscall
パッケージがネットワークマスクを正しく解釈できず、結果としてIPv6アドレスのネットワークマスクが誤って認識される可能性がありました。
この修正は、ソケットアドレスの長さ(rsa.Len
)に基づいてアドレスファミリーを推測することで、この問題を回避しようとします。具体的には、SizeofSockaddrInet4
(IPv4ソケットアドレスのサイズ)であればAF_INET
、SizeofSockaddrInet6
(IPv6ソケットアドレスのサイズ)であればAF_INET6
と判断し、それ以外の場合は従来のAF_INET
と見なすように変更されています。
また、net/interface_test.go
には、IPNet
型のアドレスが正しく処理されているかを確認するためのテストケースが追加されています。これは、IPv4およびIPv6アドレスのプレフィックス長がそれぞれ8*IPv4len
および8*IPv6len
と一致するかを検証するものです。
変更の背景
この変更の背景には、FreeBSDのルーティングテーブルメッセージの構造に関する特定の挙動の変化があります。従来のFreeBSDでは、ルーティングテーブルメッセージ内のソケットアドレス構造体(sockaddr
)において、RTAX_NETMASK
フィールドにネットワークマスクが格納される際に、そのアドレスファミリー(IPv4かIPv6か)を示す識別子(sa_family
)が適切に設定されていました。しかし、FreeBSD 9.1以降のバージョンでは、このsa_family
フィールドがAF_UNSPEC
(未指定)として返されるようになりました。
Goのsyscall
パッケージは、これらのルーティングテーブルメッセージをパースしてネットワークインターフェース情報を取得します。AF_UNSPEC
が設定されている場合、GoのコードはデフォルトでIPv4(AF_INET
)と解釈していました。この挙動はIPv4アドレスのネットワークマスクには問題ありませんでしたが、IPv6アドレスのネットワークマスクに対しては誤った解釈を引き起こし、結果としてIPv6のネットワーク設定が正しく行われない、あるいは誤った情報が取得されるというバグが発生していました。
このコミットは、このFreeBSDの挙動変更に対応し、AF_UNSPEC
が設定されている場合でも、ソケットアドレスの実際の長さからアドレスファミリーを推測することで、IPv6ネットワークマスクを正しく識別できるようにするためのものです。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
1. ルーティングテーブルとルーティングテーブルメッセージ (RTM)
オペレーティングシステムは、ネットワークトラフィックをどこに転送するかを決定するために「ルーティングテーブル」を保持しています。このテーブルには、宛先ネットワーク、ネクストホップルーター、使用するネットワークインターフェースなどの情報が含まれています。
FreeBSDのようなBSD系のシステムでは、カーネルとユーザーランドのプロセス間でルーティング情報をやり取りするために「ルーティングソケット」と「ルーティングテーブルメッセージ (RTM)」が使用されます。ユーザーランドのプログラムはルーティングソケットを通じてカーネルにルーティング情報の変更を要求したり、カーネルからルーティングイベント(例: ルートの追加/削除、インターフェースの状態変更)の通知を受け取ったりします。
RTMは、ルーティング情報をカプセル化するための構造化されたメッセージです。これらのメッセージは、rt_msghdr
というヘッダと、それに続く複数のソケットアドレス構造体(sockaddr
)で構成されます。
2. sockaddr
構造体とアドレスファミリー
sockaddr
は、ソケット通信においてアドレス情報を表現するための汎用的な構造体です。様々な種類のアドレス(IPv4、IPv6、Unixドメインなど)を扱うために、sockaddr
の後に特定のアドレスファミリーに応じた具体的な構造体(例: sockaddr_in
、sockaddr_in6
)が続きます。
sockaddr
構造体には、sa_family
というフィールドがあり、これはアドレスの種類(アドレスファミリー)を示します。一般的なアドレスファミリーには以下のようなものがあります。
AF_INET
: IPv4アドレスAF_INET6
: IPv6アドレスAF_UNSPEC
: 未指定、または不明なアドレスファミリー
3. RTAX_NETMASK
と RTAX_GENMASK
RTMのメッセージボディには、ルーティングエントリの様々な属性が格納されます。これらの属性は、rt_msghdr
のrtm_addrs
フィールドによってビットマスク形式で示され、それぞれのビットが特定のソケットアドレス構造体の存在を示します。
RTAX_NETMASK
: ネットワークマスク(サブネットマスク)を示すソケットアドレスがメッセージに含まれていることを示します。RTAX_GENMASK
: 一般的なマスク(例: ルートのスコープマスク)を示すソケットアドレスがメッセージに含まれていることを示します。
これらのフィールドに対応するsockaddr
構造体は、ルーティングテーブルエントリのネットワークアドレスやマスク情報を提供します。
4. Go言語の net
および syscall
パッケージ
net
パッケージ: Go言語の標準ネットワークインターフェースを提供するパッケージです。IPアドレス、ネットワークインターフェース、TCP/UDP接続などを抽象化して扱います。net.IPAddr
はIPアドレスを、net.IPNet
はIPアドレスとネットワークマスクの組(CIDR表記など)を表します。syscall
パッケージ: オペレーティングシステムの低レベルなシステムコールにアクセスするためのパッケージです。このコミットでは、FreeBSDカーネルからルーティング情報を取得するために、syscall
パッケージがルーティングソケットからのメッセージをパースする部分が関係しています。
5. IPAddr
, IPNet
, IPv4len
, IPv6len
net.IPAddr
: 単一のIPアドレスを表す型。net.IPNet
: IPネットワークを表す型。IPアドレスとネットワークマスク(IPMask
)のペアで構成されます。IPMask
はビットマスクとして表現され、Size()
メソッドでプレフィックス長(例:/24
の24)を取得できます。net.IPv4len
: IPv4アドレスのバイト長(4バイト)。net.IPv6len
: IPv6アドレスのバイト長(16バイト)。
6. SizeofSockaddrInet4
と SizeofSockaddrInet6
これらは、それぞれIPv4ソケットアドレス(sockaddr_in
)とIPv6ソケットアドレス(sockaddr_in6
)の構造体のバイトサイズを表す定数です。これらのサイズは、特定のアドレスファミリーのソケットアドレスがメモリ上でどれくらいの領域を占めるかを決定します。
技術的詳細
このコミットの技術的な核心は、FreeBSDのルーティングテーブルメッセージのパースロジックにおけるAF_UNSPEC
の取り扱いを改善することにあります。
FreeBSD 9.1以降では、RTAX_NETMASK
フィールドに対応するsockaddr
構造体のsa_family
がAF_UNSPEC
として返されるようになりました。これは、カーネルがネットワークマスクのアドレスファミリーを明示的に設定しないという最適化、あるいは変更によるものと考えられます。Goの既存のsyscall
パッケージでは、RTAX_NETMASK
のsa_family
がAF_UNSPEC
の場合、歴史的な理由からデフォルトでAF_INET
(IPv4)と解釈していました。
このデフォルトの解釈は、IPv4のネットワークマスクには問題ありませんが、IPv6のネットワークマスクに対しては誤ったプレフィックス長を導き出す原因となります。例えば、IPv6アドレスのネットワークマスクがAF_UNSPEC
として渡された場合、GoのコードはそれをIPv4のマスクとして処理しようとし、結果として不正確なネットワーク情報が生成されていました。
このコミットでは、src/pkg/syscall/route_bsd.go
内のInterfaceAddrMessage.sockaddr()
メソッドに修正が加えられました。このメソッドは、ルーティングテーブルメッセージからソケットアドレスを抽出し、GoのSockaddr
型に変換する役割を担っています。
修正前は、RTAX_NETMASK
のrsa.Family
がAF_UNSPEC
の場合、無条件にrsa.Family = AF_INET
と設定していました。
修正後は、AF_UNSPEC
の場合に、rsa.Len
(ソケットアドレスのバイト長)をチェックするswitch
文が導入されました。
rsa.Len
がSizeofSockaddrInet4
(IPv4ソケットアドレスのサイズ)と一致する場合、rsa.Family
をAF_INET
に設定します。rsa.Len
がSizeofSockaddrInet6
(IPv6ソケットアドレスのサイズ)と一致する場合、rsa.Family
をAF_INET6
に設定します。- 上記以外の場合(不明なサイズ、または従来の挙動を維持する場合)、
rsa.Family
をAF_INET
に設定します。
この変更により、AF_UNSPEC
が設定されたネットワークマスクであっても、そのバイト長から正確なアドレスファミリーを推測し、IPv6のネットワークマスクを正しく処理できるようになりました。
また、src/pkg/net/interface_test.go
には、IPNet
型のアドレスが正しくパースされているかを検証するためのテストロジックが追加されました。これは、IPNet
のIP
がIPv4であればプレフィックス長が8*IPv4len
(32ビット)であること、IPv6であればプレフィックス長が8*IPv6len
(128ビット)であることを確認するものです。これにより、ネットワークマスクの解釈が正しく行われていることを保証します。
コアとなるコードの変更箇所
src/pkg/net/interface_test.go
--- a/src/pkg/net/interface_test.go
+++ b/src/pkg/net/interface_test.go
@@ -108,12 +108,23 @@ func testInterfaceMulticastAddrs(t *testing.T, ifi *Interface) {
func testAddrs(t *testing.T, ifat []Addr) {
for _, ifa := range ifat {
switch ifa := ifa.(type) {
- case *IPAddr, *IPNet:
+ case *IPAddr:
if ifa == nil {
t.Errorf("\tunexpected value: %v", ifa)
} else {
t.Logf("\tinterface address %q", ifa.String())
}
+ case *IPNet:
+ if ifa == nil {
+ t.Errorf("\tunexpected value: %v", ifa)
+ } else {
+ _, prefixLen := ifa.Mask.Size()
+ if ifa.IP.To4() != nil && prefixLen != 8*IPv4len || ifa.IP.To16() != nil && ifa.IP.To4() == nil && prefixLen != 8*IPv6len {
+ t.Errorf("\tunexpected value: %v, %v, %v, %v", ifa, ifa.IP, ifa.Mask, prefixLen)
+ } else {
+ t.Logf("\tinterface address %q", ifa.String())
+ }
+ }
default:
t.Errorf("\tunexpected type: %T", ifa)
}
src/pkg/syscall/route_bsd.go
--- a/src/pkg/syscall/route_bsd.go
+++ b/src/pkg/syscall/route_bsd.go
@@ -156,7 +156,14 @@ func (m *InterfaceAddrMessage) sockaddr() (sas []Sockaddr) {
case RTAX_NETMASK:
if rsa.Family == AF_UNSPEC {
- rsa.Family = AF_INET // an old fasion, AF_UNSPEC means AF_INET
+ switch rsa.Len {
+ case SizeofSockaddrInet4:
+ rsa.Family = AF_INET
+ case SizeofSockaddrInet6:
+ rsa.Family = AF_INET6
+ default:
+ rsa.Family = AF_INET // an old fasion, AF_UNSPEC means AF_INET
+ }
}
sa, err := anyToSockaddr((*RawSockaddrAny)(unsafe.Pointer(rsa)))
if err != nil {
コアとなるコードの解説
src/pkg/net/interface_test.go
の変更
このファイルでは、testAddrs
関数内のswitch
文が変更されています。
- 変更前は、
*IPAddr
と*IPNet
の両方をまとめてcase *IPAddr, *IPNet:
で処理していました。 - 変更後は、
*IPAddr
と*IPNet
を個別のcase
で処理するように分離されました。 - 特に
case *IPNet:
ブロックが追加され、IPNet
型のアドレスに対する詳細な検証ロジックが導入されました。ifa.Mask.Size()
を呼び出してプレフィックス長を取得します。ifa.IP.To4() != nil && prefixLen != 8*IPv4len
:もしIPアドレスがIPv4であり、かつプレフィックス長がIPv4のビット長(32ビット)と異なる場合、エラーを報告します。ifa.IP.To16() != nil && ifa.IP.To4() == nil && prefixLen != 8*IPv6len
:もしIPアドレスがIPv6であり(かつIPv4ではない)、かつプレフィックス長がIPv6のビット長(128ビット)と異なる場合、エラーを報告します。- このテストは、
syscall
パッケージがルーティングメッセージから取得したIPNet
情報が、IPアドレスのバージョンに応じた正しいプレフィックス長を持っていることを保証します。これにより、ネットワークマスクのパースが正しく行われているかを確認できます。
src/pkg/syscall/route_bsd.go
の変更
このファイルでは、InterfaceAddrMessage
構造体のsockaddr()
メソッド内のcase RTAX_NETMASK:
ブロックが変更されています。
if rsa.Family == AF_UNSPEC
:ルーティングメッセージから取得したソケットアドレス(rsa
)のアドレスファミリーがAF_UNSPEC
(未指定)である場合に、以下のロジックが適用されます。switch rsa.Len
:AF_UNSPEC
の場合、ソケットアドレスの実際のバイト長(rsa.Len
)に基づいてアドレスファミリーを推測します。case SizeofSockaddrInet4:
:もし長さがIPv4ソケットアドレスのサイズと一致すれば、rsa.Family
をAF_INET
(IPv4)に設定します。case SizeofSockaddrInet6:
:もし長さがIPv6ソケットアドレスのサイズと一致すれば、rsa.Family
をAF_INET6
(IPv6)に設定します。default:
:上記いずれにも該当しない場合、または不明な場合は、従来の挙動に従いrsa.Family
をAF_INET
(IPv4)に設定します。これは、古いFreeBSDバージョンや、IPv4のネットワークマスクがAF_UNSPEC
で返される可能性のあるケースに対応するためです。
この変更により、FreeBSD 9.1以降で発生していたIPv6ネットワークマスクの誤認識問題が解決され、Goのnet
パッケージがシステムから正確なネットワークインターフェース情報を取得できるようになります。
関連リンク
- Go CL 12332043: https://golang.org/cl/12332043
参考にした情報源リンク
- FreeBSD man pages (rtm_addrs, sockaddr, routing socketなど):
- Go言語の
net
パッケージドキュメント: https://pkg.go.dev/net - Go言語の
syscall
パッケージドキュメント: https://pkg.go.dev/syscall - BSD系のルーティングソケットに関する一般的な情報源 (例: "UNIX Network Programming, Volume 1, Third Edition: The Sockets Networking API" by W. Richard Stevens, Bill Fenner, Andrew M. Rudoff)
- FreeBSDのルーティングテーブルメッセージの構造に関する情報 (例: カーネルソースコードの
sys/route.h
など) - IPv4とIPv6のアドレス構造に関する一般的なネットワーク知識。
[インデックス 17056] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet
およびsyscall
パッケージにおける、FreeBSD環境でのIPv6ネットワークマスクの取り扱いに関するバグ修正を目的としています。具体的には、FreeBSDの最新バージョン(特にFreeBSD 9.1)において、ルーティングテーブルメッセージ(RTM_GETなどの応答)から取得されるネットワークマスク情報にアドレスファミリー識別子(AF_UNSPEC)が正しく設定されない問題に対応しています。
コミット
commit 262d6f58c78d53d2e85957075de0f817861cc23c
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Aug 7 00:25:23 2013 +0900
syscall: fix IPv6 wrong network mask on latest FreeBSD
Looks like latest FreeBSD doesn't set address family identifer
for RTAX_NETMASK stuff; probably RTAX_GENMASK too, not confirmed.
This CL tries to identify address families by using the length of
each socket address if possible.
The issue is confirmed on FreeBSD 9.1.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12332043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/262d6f58c78d53d2e85957075de0f817861cc23c
元コミット内容
このコミットの元の内容は、FreeBSDの最新バージョン(特に9.1)において、ルーティングテーブルメッセージ内のRTAX_NETMASK
(およびおそらくRTAX_GENMASK
)フィールドにアドレスファミリー識別子(AF_UNSPEC
)が設定されない問題を修正することです。この問題により、Goのsyscall
パッケージがネットワークマスクを正しく解釈できず、結果としてIPv6アドレスのネットワークマスクが誤って認識される可能性がありました。
この修正は、ソケットアドレスの長さ(rsa.Len
)に基づいてアドレスファミリーを推測することで、この問題を回避しようとします。具体的には、SizeofSockaddrInet4
(IPv4ソケットアドレスのサイズ)であればAF_INET
、SizeofSockaddrInet6
(IPv6ソケットアドレスのサイズ)であればAF_INET6
と判断し、それ以外の場合は従来のAF_INET
と見なすように変更されています。
また、net/interface_test.go
には、IPNet
型のアドレスが正しく処理されているかを確認するためのテストケースが追加されています。これは、IPv4およびIPv6アドレスのプレフィックス長がそれぞれ8*IPv4len
および8*IPv6len
と一致するかを検証するものです。
変更の背景
この変更の背景には、FreeBSDのルーティングテーブルメッセージの構造に関する特定の挙動の変化があります。従来のFreeBSDでは、ルーティングテーブルメッセージ内のソケットアドレス構造体(sockaddr
)において、RTAX_NETMASK
フィールドにネットワークマスクが格納される際に、そのアドレスファミリー(IPv4かIPv6か)を示す識別子(sa_family
)が適切に設定されていました。しかし、FreeBSD 9.1以降のバージョンでは、このsa_family
フィールドがAF_UNSPEC
(未指定)として返されるようになりました。
Goのsyscall
パッケージは、これらのルーティングテーブルメッセージをパースしてネットワークインターフェース情報を取得します。AF_UNSPEC
が設定されている場合、GoのコードはデフォルトでIPv4(AF_INET
)と解釈していました。この挙動はIPv4アドレスのネットワークマスクには問題ありませんでしたが、IPv6アドレスのネットワークマスクに対しては誤った解釈を引き起こし、結果としてIPv6のネットワーク設定が正しく行われない、あるいは誤った情報が取得されるというバグが発生していました。
このコミットは、このFreeBSDの挙動変更に対応し、AF_UNSPEC
が設定されている場合でも、ソケットアドレスの実際の長さからアドレスファミリーを推測することで、IPv6ネットワークマスクを正しく識別できるようにするためのものです。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
1. ルーティングテーブルとルーティングテーブルメッセージ (RTM)
オペレーティングシステムは、ネットワークトラフィックをどこに転送するかを決定するために「ルーティングテーブル」を保持しています。このテーブルには、宛先ネットワーク、ネクストホップルーター、使用するネットワークインターフェースなどの情報が含まれています。
FreeBSDのようなBSD系のシステムでは、カーネルとユーザーランドのプロセス間でルーティング情報をやり取りするために「ルーティングソケット」と「ルーティングテーブルメッセージ (RTM)」が使用されます。ユーザーランドのプログラムはルーティングソケットを通じてカーネルにルーティング情報の変更を要求したり、カーネルからルーティングイベント(例: ルートの追加/削除、インターフェースの状態変更)の通知を受け取ったりします。
RTMは、ルーティング情報をカプセル化するための構造化されたメッセージです。これらのメッセージは、rt_msghdr
というヘッダと、それに続く複数のソケットアドレス構造体(sockaddr
)で構成されます。
2. sockaddr
構造体とアドレスファミリー
sockaddr
は、ソケット通信においてアドレス情報を表現するための汎用的な構造体です。様々な種類のアドレス(IPv4、IPv6、Unixドメインなど)を扱うために、sockaddr
の後に特定のアドレスファミリーに応じた具体的な構造体(例: sockaddr_in
、sockaddr_in6
)が続きます。
sockaddr
構造体には、sa_family
というフィールドがあり、これはアドレスの種類(アドレスファミリー)を示します。一般的なアドレスファミリーには以下のようなものがあります。
AF_INET
: IPv4アドレスAF_INET6
: IPv6アドレスAF_UNSPEC
: 未指定、または不明なアドレスファミリー
3. RTAX_NETMASK
と RTAX_GENMASK
RTMのメッセージボディには、ルーティングエントリの様々な属性が格納されます。これらの属性は、rt_msghdr
のrtm_addrs
フィールドによってビットマスク形式で示され、それぞれのビットが特定のソケットアドレス構造体の存在を示します。
RTAX_NETMASK
: ネットワークマスク(サブネットマスク)を示すソケットアドレスがメッセージに含まれていることを示します。RTAX_GENMASK
: 一般的なマスク(例: ルートのスコープマスク)を示すソケットアドレスがメッセージに含まれていることを示します。
これらのフィールドに対応するsockaddr
構造体は、ルーティングテーブルエントリのネットワークアドレスやマスク情報を提供します。
4. Go言語の net
および syscall
パッケージ
net
パッケージ: Go言語の標準ネットワークインターフェースを提供するパッケージです。IPアドレス、ネットワークインターフェース、TCP/UDP接続などを抽象化して扱います。net.IPAddr
はIPアドレスを、net.IPNet
はIPアドレスとネットワークマスクの組(CIDR表記など)を表します。syscall
パッケージ: オペレーティングシステムの低レベルなシステムコールにアクセスするためのパッケージです。このコミットでは、FreeBSDカーネルからルーティング情報を取得するために、syscall
パッケージがルーティングソケットからのメッセージをパースする部分が関係しています。
5. IPAddr
, IPNet
, IPv4len
, IPv6len
net.IPAddr
: 単一のIPアドレスを表す型。net.IPNet
: IPネットワークを表す型。IPアドレスとネットワークマスク(IPMask
)のペアで構成されます。IPMask
はビットマスクとして表現され、Size()
メソッドでプレフィックス長(例:/24
の24)を取得できます。net.IPv4len
: IPv4アドレスのバイト長(4バイト)。net.IPv6len
: IPv6アドレスのバイト長(16バイト)。
6. SizeofSockaddrInet4
と SizeofSockaddrInet6
これらは、それぞれIPv4ソケットアドレス(sockaddr_in
)とIPv6ソケットアドレス(sockaddr_in6
)の構造体のバイトサイズを表す定数です。これらのサイズは、特定のアドレスファミリーのソケットアドレスがメモリ上でどれくらいの領域を占めるかを決定します。
技術的詳細
このコミットの技術的な核心は、FreeBSDのルーティングテーブルメッセージのパースロジックにおけるAF_UNSPEC
の取り扱いを改善することにあります。
FreeBSD 9.1以降では、RTAX_NETMASK
フィールドに対応するsockaddr
構造体のsa_family
がAF_UNSPEC
として返されるようになりました。これは、カーネルがネットワークマスクのアドレスファミリーを明示的に設定しないという最適化、あるいは変更によるものと考えられます。Goの既存のsyscall
パッケージでは、RTAX_NETMASK
のsa_family
がAF_UNSPEC
の場合、歴史的な理由からデフォルトでAF_INET
(IPv4)と解釈していました。
このデフォルトの解釈は、IPv4のネットワークマスクには問題ありませんが、IPv6のネットワークマスクに対しては誤ったプレフィックス長を導き出す原因となります。例えば、IPv6アドレスのネットワークマスクがAF_UNSPEC
として渡された場合、GoのコードはそれをIPv4のマスクとして処理しようとし、結果として不正確なネットワーク情報が生成されていました。
このコミットでは、src/pkg/syscall/route_bsd.go
内のInterfaceAddrMessage.sockaddr()
メソッドに修正が加えられました。このメソッドは、ルーティングテーブルメッセージからソケットアドレスを抽出し、GoのSockaddr
型に変換する役割を担っています。
修正前は、RTAX_NETMASK
のrsa.Family
がAF_UNSPEC
の場合、無条件にrsa.Family = AF_INET
と設定していました。
修正後は、AF_UNSPEC
の場合に、rsa.Len
(ソケットアドレスのバイト長)をチェックするswitch
文が導入されました。
rsa.Len
がSizeofSockaddrInet4
(IPv4ソケットアドレスのサイズ)と一致する場合、rsa.Family
をAF_INET
に設定します。rsa.Len
がSizeofSockaddrInet6
(IPv6ソケットアドレスのサイズ)と一致する場合、rsa.Family
をAF_INET6
に設定します。- 上記以外の場合(不明なサイズ、または従来の挙動を維持する場合)、
rsa.Family
をAF_INET
に設定します。
この変更により、AF_UNSPEC
が設定されたネットワークマスクであっても、そのバイト長から正確なアドレスファミリーを推測し、IPv6のネットワークマスクを正しく処理できるようになりました。
また、src/pkg/net/interface_test.go
には、IPNet
型のアドレスが正しくパースされているかを検証するためのテストロジックが追加されました。これは、IPNet
のIP
がIPv4であればプレフィックス長が8*IPv4len
(32ビット)であること、IPv6であればプレフィックス長が8*IPv6len
(128ビット)であることを確認するものです。これにより、ネットワークマスクの解釈が正しく行われていることを保証します。
コアとなるコードの変更箇所
src/pkg/net/interface_test.go
--- a/src/pkg/net/interface_test.go
+++ b/src/pkg/net/interface_test.go
@@ -108,12 +108,23 @@ func testInterfaceMulticastAddrs(t *testing.T, ifi *Interface) {
func testAddrs(t *testing.T, ifat []Addr) {
for _, ifa := range ifat {
switch ifa := ifa.(type) {
- case *IPAddr, *IPNet:
+ case *IPAddr:
if ifa == nil {
t.Errorf("\tunexpected value: %v", ifa)
} else {
t.Logf("\tinterface address %q", ifa.String())
}
+ case *IPNet:
+ if ifa == nil {
+ t.Errorf("\tunexpected value: %v", ifa)
+ } else {
+ _, prefixLen := ifa.Mask.Size()
+ if ifa.IP.To4() != nil && prefixLen != 8*IPv4len || ifa.IP.To16() != nil && ifa.IP.To4() == nil && prefixLen != 8*IPv6len {
+ t.Errorf("\tunexpected value: %v, %v, %v, %v", ifa, ifa.IP, ifa.Mask, prefixLen)
+ } else {
+ t.Logf("\tinterface address %q", ifa.String())
+ }
+ }
default:
t.Errorf("\tunexpected type: %T", ifa)
}
src/pkg/syscall/route_bsd.go
--- a/src/pkg/syscall/route_bsd.go
+++ b/src/pkg/syscall/route_bsd.go
@@ -156,7 +156,14 @@ func (m *InterfaceAddrMessage) sockaddr() (sas []Sockaddr) {
case RTAX_NETMASK:
if rsa.Family == AF_UNSPEC {
- rsa.Family = AF_INET // an old fasion, AF_UNSPEC means AF_INET
+ switch rsa.Len {
+ case SizeofSockaddrInet4:
+ rsa.Family = AF_INET
+ case SizeofSockaddrInet6:
+ rsa.Family = AF_INET6
+ default:
+ rsa.Family = AF_INET // an old fasion, AF_UNSPEC means AF_INET
+ }
}
sa, err := anyToSockaddr((*RawSockaddrAny)(unsafe.Pointer(rsa)))
if err != nil {
コアとなるコードの解説
src/pkg/net/interface_test.go
の変更
このファイルでは、testAddrs
関数内のswitch
文が変更されています。
- 変更前は、
*IPAddr
と*IPNet
の両方をまとめてcase *IPAddr, *IPNet:
で処理していました。 - 変更後は、
*IPAddr
と*IPNet
を個別のcase
で処理するように分離されました。 - 特に
case *IPNet:
ブロックが追加され、IPNet
型のアドレスに対する詳細な検証ロジックが導入されました。ifa.Mask.Size()
を呼び出してプレフィックス長を取得します。ifa.IP.To4() != nil && prefixLen != 8*IPv4len
:もしIPアドレスがIPv4であり、かつプレフィックス長がIPv4のビット長(32ビット)と異なる場合、エラーを報告します。ifa.IP.To16() != nil && ifa.IP.To4() == nil && prefixLen != 8*IPv6len
:もしIPアドレスがIPv6であり(かつIPv4ではない)、かつプレフィックス長がIPv6のビット長(128ビット)と異なる場合、エラーを報告します。- このテストは、
syscall
パッケージがルーティングメッセージから取得したIPNet
情報が、IPアドレスのバージョンに応じた正しいプレフィックス長を持っていることを保証します。これにより、ネットワークマスクのパースが正しく行われているかを確認できます。
src/pkg/syscall/route_bsd.go
の変更
このファイルでは、InterfaceAddrMessage
構造体のsockaddr()
メソッド内のcase RTAX_NETMASK:
ブロックが変更されています。
if rsa.Family == AF_UNSPEC
:ルーティングメッセージから取得したソケットアドレス(rsa
)のアドレスファミリーがAF_UNSPEC
(未指定)である場合に、以下のロジックが適用されます。switch rsa.Len
:AF_UNSPEC
の場合、ソケットアドレスの実際のバイト長(rsa.Len
)に基づいてアドレスファミリーを推測します。case SizeofSockaddrInet4:
:もし長さがIPv4ソケットアドレスのサイズと一致すれば、rsa.Family
をAF_INET
(IPv4)に設定します。case SizeofSockaddrInet6:
:もし長さがIPv6ソケットアドレスのサイズと一致すれば、rsa.Family
をAF_INET6
(IPv6)に設定します。default:
:上記いずれにも該当しない場合、または不明な場合は、従来の挙動に従いrsa.Family
をAF_INET
(IPv4)に設定します。これは、古いFreeBSDバージョンや、IPv4のネットワークマスクがAF_UNSPEC
で返される可能性のあるケースに対応するためです。
この変更により、FreeBSD 9.1以降で発生していたIPv6ネットワークマスクの誤認識問題が解決され、Goのnet
パッケージがシステムから正確なネットワークインターフェース情報を取得できるようになります。
関連リンク
- Go CL 12332043: https://golang.org/cl/12332043
参考にした情報源リンク
- FreeBSD man pages (rtm_addrs, sockaddr, routing socketなど):
- Go言語の
net
パッケージドキュメント: https://pkg.go.dev/net - Go言語の
syscall
パッケージドキュメント: https://pkg.go.dev/syscall - BSD系のルーティングソケットに関する一般的な情報源 (例: "UNIX Network Programming, Volume 1, Third Edition: The Sockets Networking API" by W. Richard Stevens, Bill Fenner, Andrew M. Rudoff)
- FreeBSDのルーティングテーブルメッセージの構造に関する情報 (例: カーネルソースコードの
sys/route.h
など) - IPv4とIPv6のアドレス構造に関する一般的なネットワーク知識。