[インデックス 18529] ファイルの概要
このコミットは、Go言語のnet
パッケージにおけるLinux上でのIPインターフェーススタックの取り扱いに関する修正です。特に、ポイントツーポイントインターフェースにおけるIPアドレスの検出と報告の正確性を向上させることを目的としています。
コミット
commit 8c0a52f28d16569361b18190cf7ff280aa5301bf
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Sat Feb 15 01:07:51 2014 +0900
net: handle IP interface stack correctly on linux
A configuration like the following:
7: tun6rd: <NOARP,UP,LOWER_UP> mtu 1280
link/sit 10.11.12.13 brd 0.0.0.0
inet 1.2.3.4/24 scope global tun6rd
inet6 2014:1001:a0b:c0d::1/32 scope global
inet6 ::10.11.12.13/128 scope global
9: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1496
link/ppp
inet 192.168.101.234 peer 192.168.102.234/32 scope global ppp0
inet 10.20.30.40/24 scope global ppp0
inet6 2014:1002::1/64 scope global
11: tun0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480
link/ipip 192.168.202.34 peer 192.168.202.69
inet 192.168.10.1/24 scope global tunnel0
inet6 2014:1003::1/64 scope global
will be handled like below.
"tun6rd": flags "up", ifindex 7, mtu 1280
hardware address ""
interface address "1.2.3.4/24"
interface address "2014:1001:a0b:c0d::1/32"
interface address "::a0b:c0d/128"
"ppp0": flags "up|pointtopoint|multicast", ifindex 9, mtu 1496
hardware address ""
interface address "192.168.101.234/32"
interface address "10.20.30.40/24"
interface address "2014:1002::1/64"
"tun0": flags "up|pointtopoint", ifindex 11, mtu 1480
hardware address ""
interface address "192.168.10.1/24"
interface address "2014:1003::1/64"
Fixes #6433.
Update #4839
LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/57700043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8c0a52f28d16569361b18190cf7ff280aa5301bf
元コミット内容
上記の「コミット」セクションに記載されている内容が元コミット内容です。
変更の背景
このコミットの背景には、Go言語のnet
パッケージがLinuxシステム上でネットワークインターフェースの情報を正確に取得できないという問題がありました。特に、ポイントツーポイント(Point-to-Point)インターフェース(例: PPP、VPNトンネルなど)において、IPv6アドレスが正しく認識されず、net.Interface.Addrs()
関数がこれらのアドレスを返さないという不具合が存在していました。
Linuxカーネルは、netlink
インターフェースを通じてネットワークデバイスやアドレスに関する情報を提供しますが、ポイントツーポイントリンクのIPv6アドレスの報告方法は、他の種類のインターフェースとは異なる場合があります。Goのnet
パッケージは、この特定の報告メカニズムを適切に解釈できていなかったため、Goアプリケーションがシステム上の利用可能なすべてのIPv6アドレスを認識できないという問題が発生していました。
この問題は、Goアプリケーションがネットワークインターフェースの正確なアドレス情報を必要とする場合に、予期せぬ動作や接続性の問題を引き起こす可能性がありました。このコミットは、この情報の不整合を解消し、GoアプリケーションがLinuxシステム上でより堅牢なネットワーク操作を実行できるようにすることを目的としています。
コミットメッセージに記載されているFixes #6433
とUpdate #4839
は、当時のGoのIssueトラッキングシステムにおける関連する問題番号を示している可能性があります。ただし、現在のGitHub上のGoリポジトリではこれらの番号に直接対応するIssueは見つかりませんでした。これは、Issueが統合されたり、番号が変更されたり、あるいは内部的なトラッキング番号であったりする可能性を示唆しています。しかし、golang.org/cl/57700043
の変更リスト情報から、このコミットがLinuxのnet.Interface.Addrs()
がポイントツーポイントインターフェースのIPv6アドレスを返さない問題を解決したことが確認できます。
前提知識の解説
このコミットを理解するためには、以下の概念に関する前提知識が必要です。
- Linuxネットワークインターフェース: Linuxシステムでは、ネットワークデバイス(イーサネットカード、Wi-Fiアダプター、ループバック、トンネルインターフェースなど)は「ネットワークインターフェース」として抽象化されます。各インターフェースには、名前(例:
eth0
,lo
,ppp0
,tun0
)、インデックス、MTU(Maximum Transmission Unit)、フラグ(UP, DOWN, BROADCAST, MULTICAST, POINTOPOINTなど)、IPアドレスなどが関連付けられています。 - Netlink: Linuxカーネルとユーザー空間アプリケーション間の通信メカニズムの一つです。特に、ネットワーク関連の情報を取得したり、設定を変更したりするために広く使用されます。
ip
コマンドやifconfig
コマンドなども内部的にNetlinkを使用しています。ネットワークインターフェースの情報は、RTM_GETLINK
やRTM_GETADDR
などのNetlinkメッセージを通じて取得されます。 syscall
パッケージ: Go言語の標準ライブラリの一部で、オペレーティングシステム固有のシステムコールにアクセスするための機能を提供します。このコミットでは、LinuxカーネルのNetlink APIと直接やり取りするためにsyscall
パッケージが使用されています。syscall.IfInfomsg
とsyscall.IfAddrmsg
: Netlinkメッセージでネットワークインターフェースの基本情報(IfInfomsg
)やアドレス情報(IfAddrmsg
)を表現するための構造体です。syscall.NetlinkRouteAttr
: Netlinkメッセージ内の属性(Type-Length-Value形式)を表す構造体です。インターフェースのハードウェアアドレス(IFLA_ADDRESS
)やIPアドレス(IFA_ADDRESS
,IFA_LOCAL
)などの詳細情報が含まれます。- ポイントツーポイント(Point-to-Point)インターフェース: 2つのノード間を直接接続するネットワークリンクです。PPP(Point-to-Point Protocol)や一部のVPNトンネル(例: IPIP、GRE、6to4、6rd)などがこれに該当します。これらのインターフェースは、通常、単一のピアとの通信を目的としており、ブロードキャストやマルチキャストの概念が適用されない場合があります。IPアドレスの割り当て方も、通常のブロードキャストネットワークインターフェースとは異なる場合があります(例:
IFA_LOCAL
とIFA_ADDRESS
の使い分け)。 - IPv4len / IPv6len: IPv4アドレスが4バイト、IPv6アドレスが16バイトであることを示す定数です。
- ARPハードウェアタイプ (ARPHRD_...): Linuxカーネルの
if_arp.h
で定義されている、ネットワークインターフェースのハードウェアタイプを示す定数です。例えば、ARPHRD_ETHER
はイーサネット、ARPHRD_PPP
はPPPインターフェースを示します。このコミットでは、特定のトンネルインターフェースタイプ(IPv4 over IPv4、IPv6 over IPv6、IPv6 over IPv4など)を識別するために使用されています。
技術的詳細
このコミットの技術的詳細は、LinuxカーネルのNetlinkインターフェースからネットワークインターフェース情報を解析する際の挙動の修正にあります。
Goのnet
パッケージは、net.InterfaceAddrs()
やnet.Interfaces()
といった関数を通じて、システム上のネットワークインターフェースとそのアドレス情報を取得します。これらの関数は内部的にLinuxのNetlink APIを呼び出し、カーネルから返されるIfInfomsg
(インターフェース情報)とIfAddrmsg
(アドレス情報)を解析してGoのnet.Interface
およびnet.Addr
構造体にマッピングします。
問題は、特定の種類のインターフェース、特にポイントツーポイントインターフェースや各種IPトンネルインターフェースにおいて、Netlinkが報告するアドレス情報がGoの既存の解析ロジックと合致しないケースがあったことです。
具体的には、以下の2つの主要な修正が行われています。
-
newLink
関数におけるハードウェアアドレスのフィルタリング:newLink
関数は、Netlinkから取得したインターフェース情報(IfInfomsg
)と属性(NetlinkRouteAttr
)を基にnet.Interface
構造体を構築します。- 以前のバージョンでは、
IFLA_ADDRESS
属性として報告される値を無条件にハードウェアアドレスとして採用していました。 - しかし、一部のIPトンネルインターフェース(例:
tun6rd
,tun0
)では、IFLA_ADDRESS
属性に実際のハードウェアアドレスではなく、インターフェースに割り当てられたIPアドレス(/32
や/128
のプレフィックスを持つ)が設定されることがありました。これは、これらのインターフェースが物理的なMACアドレスを持たないため、カーネルが代替情報を提供するためです。 - このコミットでは、
ifim.Type
(ARPハードウェアタイプ)をチェックし、特定のIPトンネルタイプ(sysARPHardwareIPv4IPv4
,sysARPHardwareIPv6IPv6
,sysARPHardwareIPv6IPv4
,sysARPHardwareGREIPv4
,sysARPHardwareGREIPv6
)の場合に、IFLA_ADDRESS
がIPアドレスの長さ(IPv4len=4バイト、IPv6len=16バイト)と一致すれば、その値をハードウェアアドレスとして採用しないように変更されました。これにより、誤ったIPアドレスがハードウェアアドレスとして報告されることを防ぎます。
-
newAddr
関数におけるポイントツーポイントアドレスの処理:newAddr
関数は、Netlinkから取得したアドレス情報(IfAddrmsg
)と属性(NetlinkRouteAttr
)を基にnet.Addr
構造体(具体的にはnet.IPNet
)を構築します。- ポイントツーポイントインターフェース(
ifi.Flags&FlagPointToPoint != 0
)では、IPアドレスがIFA_LOCAL
属性として報告されることが一般的です。これは、インターフェースの「ローカルエンド」のアドレスを指します。一方、通常のブロードキャストインターフェースではIFA_ADDRESS
属性が使用されます。 - 以前のロジックでは、ポイントツーポイントインターフェースの場合に
IFA_LOCAL
を優先し、そうでない場合にIFA_ADDRESS
を優先するという条件分岐がありました。しかし、このロジックが不完全であり、特にIPv6アドレスの取得において問題を引き起こしていました。 - 新しいロジックでは、まずインターフェースがポイントツーポイントであるかどうかを判断し、その上で
IFA_LOCAL
属性が存在するかどうかを確認してipPointToPoint
フラグを設定します。 - その後、属性をループ処理する際に、
ipPointToPoint
が真であればIFA_ADDRESS
をスキップし、偽であればIFA_LOCAL
をスキップするように変更されました。これにより、ポイントツーポイントインターフェースではIFA_LOCAL
から、それ以外のインターフェースではIFA_ADDRESS
から、それぞれ正しいIPアドレスが抽出されるようになります。
これらの変更により、Goのnet
パッケージはLinuxシステム上の多様なネットワークインターフェース、特にIPトンネルやポイントツーポイントリンクのアドレス情報を、カーネルの報告形式に合わせてより正確に解析できるようになりました。
コアとなるコードの変更箇所
変更はsrc/pkg/net/interface_linux.go
ファイルに集中しています。
-
新しい定数の追加:
sysARPHardwareIPv4IPv4
,sysARPHardwareIPv6IPv6
,sysARPHardwareIPv6IPv4
,sysARPHardwareGREIPv4
,sysARPHardwareGREIPv6
といった、特定のARPハードウェアタイプを示す定数が追加されました。これらは、IPトンネルインターフェースを識別するために使用されます。const ( // See linux/if_arp.h. // Note that Linux doesn't support IPv4 over IPv6 tunneling. sysARPHardwareIPv4IPv4 = 768 // IPv4 over IPv4 tunneling sysARPHardwareIPv6IPv6 = 769 // IPv6 over IPv6 tunneling sysARPHardwareIPv6IPv4 = 776 // IPv6 over IPv4 tunneling sysARPHardwareGREIPv4 = 778 // any over GRE over IPv4 tunneling sysARPHardwareGREIPv6 = 823 // any over GRE over IPv6 tunneling )
-
newLink
関数の変更:syscall.IFLA_ADDRESS
属性を処理するswitch
文内に、ifim.Type
(インターフェースのARPハードウェアタイプ)とa.Value
の長さ(IPアドレスの長さ)に基づいて、特定のトンネルインターフェースのIPアドレスをハードウェアアドレスとして無視するロジックが追加されました。--- a/src/pkg/net/interface_linux.go +++ b/src/pkg/net/interface_linux.go @@ -45,15 +45,41 @@ loop: return ift, nil } +const ( +// See linux/if_arp.h. +// Note that Linux doesn't support IPv4 over IPv6 tunneling. + sysARPHardwareIPv4IPv4 = 768 // IPv4 over IPv4 tunneling + sysARPHardwareIPv6IPv6 = 769 // IPv6 over IPv6 tunneling + sysARPHardwareIPv6IPv4 = 776 // IPv6 over IPv4 tunneling + sysARPHardwareGREIPv4 = 778 // any over GRE over IPv4 tunneling + sysARPHardwareGREIPv6 = 823 // any over GRE over IPv6 tunneling +) + func newLink(ifim *syscall.IfInfomsg, attrs []syscall.NetlinkRouteAttr) *Interface { ifi := &Interface{Index: int(ifim.Index), Flags: linkFlags(ifim.Flags)} for _, a := range attrs { switch a.Attr.Type { case syscall.IFLA_ADDRESS: + // We never return any /32 or /128 IP address + // prefix on any IP tunnel interface as the + // hardware address. + switch len(a.Value) { + case IPv4len: + switch ifim.Type { + case sysARPHardwareIPv4IPv4, sysARPHardwareGREIPv4, sysARPHardwareIPv6IPv4: + continue + } + case IPv6len: + switch ifim.Type { + case sysARPHardwareIPv6IPv6, sysARPHardwareGREIPv6: + continue + } + } var nonzero bool for _, b := range a.Value { if b != 0 { nonzero = true + break } } if nonzero {
-
newAddr
関数の変更: ポイントツーポイントインターフェースのアドレス処理ロジックが大幅に変更されました。ipPointToPoint
という新しいフラグを導入し、IFA_LOCAL
とIFA_ADDRESS
のどちらを優先するかをより正確に判断するように修正されました。--- a/src/pkg/net/interface_linux.go +++ b/src/pkg/net/interface_linux.go @@ -147,19 +173,31 @@ loop: } func newAddr(ifi *Interface, ifam *syscall.IfAddrmsg, attrs []syscall.NetlinkRouteAttr) Addr { - for _, a := range attrs { - if ifi.Flags&FlagPointToPoint != 0 && a.Attr.Type == syscall.IFA_LOCAL || - ifi.Flags&FlagPointToPoint == 0 && a.Attr.Type == syscall.IFA_ADDRESS { - switch ifam.Family { - case syscall.AF_INET: - return &IPNet{IP: IPv4(a.Value[0], a.Value[1], a.Value[2], a.Value[3]), Mask: CIDRMask(int(ifam.Prefixlen), 8*IPv4len)} - case syscall.AF_INET6: - ifa := &IPNet{IP: make(IP, IPv6len), Mask: CIDRMask(int(ifam.Prefixlen), 8*IPv6len)} - copy(ifa.IP, a.Value[:]) - return ifa + var ipPointToPoint bool + // Seems like we need to make sure whether the IP interface + // stack consists of IP point-to-point numbered or unnumbered + // addressing over point-to-point link encapsulation. + if ifi.Flags&FlagPointToPoint != 0 { + for _, a := range attrs { + if a.Attr.Type == syscall.IFA_LOCAL { + ipPointToPoint = true + break } } } + for _, a := range attrs { + if ipPointToPoint && a.Attr.Type == syscall.IFA_ADDRESS || !ipPointToPoint && a.Attr.Type == syscall.IFA_LOCAL { + continue + } + switch ifam.Family { + case syscall.AF_INET: + return &IPNet{IP: IPv4(a.Value[0], a.Value[1], a.Value[2], a.Value[3]), Mask: CIDRMask(int(ifam.Prefixlen), 8*IPv4len)} + case syscall.AF_INET6: + ifa := &IPNet{IP: make(IP, IPv6len), Mask: CIDRMask(int(ifam.Prefixlen), 8*IPv6len)} + copy(ifa.IP, a.Value[:]) + return ifa + } + } return nil }
コアとなるコードの解説
newLink
関数におけるハードウェアアドレスのフィルタリング
この変更は、net.Interface
構造体のHardwareAddr
フィールドが誤った値で埋められるのを防ぐためのものです。
LinuxのNetlink APIは、インターフェースのハードウェアアドレスをIFLA_ADDRESS
属性として報告します。しかし、一部の仮想インターフェースやトンネルインターフェース(例: tun6rd
, ppp0
, tun0
)は物理的なMACアドレスを持たないため、カーネルは代わりにインターフェースに割り当てられたIPアドレスをこの属性に設定することがあります。
追加されたswitch
文は、以下のロジックで動作します。
a.Value
の長さ(属性の値のバイト数)がIPv4アドレス(4バイト)またはIPv6アドレス(16バイト)の長さと一致するかどうかを確認します。- もし一致した場合、
ifim.Type
(インターフェースのARPハードウェアタイプ)をチェックします。 ifim.Type
がsysARPHardwareIPv4IPv4
,sysARPHardwareGREIPv4
,sysARPHardwareIPv6IPv4
(IPv4アドレスの場合)またはsysARPHardwareIPv6IPv6
,sysARPHardwareGREIPv6
(IPv6アドレスの場合)のいずれかである場合、これはIPトンネルインターフェースであり、IFLA_ADDRESS
がIPアドレスとして報告されている可能性が高いと判断します。- この条件に合致した場合、
continue
ステートメントによって現在の属性の処理をスキップし、このIPアドレスをハードウェアアドレスとして採用しないようにします。
これにより、net.Interface.HardwareAddr
には、実際に物理的なハードウェアアドレスを持つインターフェースの場合のみ正しい値が設定され、IPトンネルインターフェースでは空のバイトスライスが設定されるようになります。
newAddr
関数におけるポイントツーポイントアドレスの処理
この変更は、net.Interface.Addrs()
が返すIPアドレスリストの正確性を向上させるためのものです。特に、ポイントツーポイントインターフェース(FlagPointToPoint
フラグが設定されているインターフェース)のアドレス処理が改善されました。
以前のロジックは、ポイントツーポイントインターフェースではIFA_LOCAL
属性を、それ以外のインターフェースではIFA_ADDRESS
属性を優先してIPアドレスを抽出していました。しかし、この単純な条件分岐では、LinuxカーネルがNetlinkを通じてアドレスを報告する際の微妙な違いに対応しきれていませんでした。
新しいロジックは以下のステップで動作します。
-
ipPointToPoint
フラグの導入: まず、インターフェースがFlagPointToPoint
フラグを持っているかを確認します。もし持っている場合、さらにattrs
(Netlink属性リスト)をループし、syscall.IFA_LOCAL
属性が存在するかどうかをチェックします。IFA_LOCAL
が存在すれば、ipPointToPoint
をtrue
に設定します。このステップは、「IPインターフェーススタックがポイントツーポイントの番号付きまたは番号なしアドレス指定で構成されているか」を確認するためのものです。 -
アドレス属性の選択ロジックの改善: 次に、再度
attrs
をループして実際のIPアドレスを抽出します。このループの冒頭で、以下の条件に基づいて属性をスキップするかどうかを判断します。ipPointToPoint
がtrue
(つまり、ポイントツーポイントインターフェースでIFA_LOCAL
が見つかった場合)かつ、現在の属性がsyscall.IFA_ADDRESS
である場合、この属性をスキップします。これは、ポイントツーポイントインターフェースではIFA_LOCAL
が優先されるべきだからです。ipPointToPoint
がfalse
(つまり、通常のインターフェース、またはポイントツーポイントだがIFA_LOCAL
が見つからなかった場合)かつ、現在の属性がsyscall.IFA_LOCAL
である場合、この属性をスキップします。これは、通常のインターフェースではIFA_ADDRESS
が優先されるべきだからです。
この改善されたロジックにより、Goのnet
パッケージは、LinuxカーネルがポイントツーポイントインターフェースのアドレスをIFA_LOCAL
として報告する場合と、通常のインターフェースのアドレスをIFA_ADDRESS
として報告する場合の両方に適切に対応できるようになりました。これにより、net.Interface.Addrs()
が返すIPアドレスリストがより正確になり、特にIPv6アドレスの欠落が解消されます。
関連リンク
- Go Change List 57700043: https://golang.org/cl/57700043
- Go Issue 6433 (コミットメッセージに記載されているが、現在のGitHubリポジトリでは直接見つからず): 関連する可能性のある情報として、Goのネットワークスタックに関する議論やバグ報告が挙げられます。
- Go Issue 4839 (コミットメッセージに記載されているが、現在のGitHubリポジトリでは直接見つからず): 同上。
参考にした情報源リンク
- Go Change List 57700043のレビュー情報: https://go-review.googlesource.com/c/go/+/57700043
- Linux
if_arp.h
(ARPハードウェアタイプ定義): Linuxカーネルのソースコード内でこれらの定数定義を確認できます。 - Linux
netlink(7)
man page: Netlinkの一般的な情報とメッセージ構造について。 - Linux
rtnetlink(7)
man page: Netlinkのルーティングソケットに関する詳細情報。 - Go
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Go
net
パッケージのドキュメント: https://pkg.go.dev/net - Web検索結果:
golang.org/cl/57700043
に関する情報(Googlesource.comのキャッシュなど)。