[インデックス 15317] ファイルの概要
このコミットは、Go言語の標準ライブラリnet
パッケージ内のsrc/pkg/net/interface_linux.go
ファイルに対する変更です。このファイルは、Linuxシステムにおけるネットワークインターフェースのアドレス情報を取得するロジックを扱っています。具体的には、netlink
ソケットを通じてカーネルからネットワークインターフェースのアドレス情報を取得し、Goのnet.Addr
型に変換する処理が含まれています。
コミット
このコミットは、Linux上のポイントツーポイントインターフェースにおいて、正しいインターフェースアドレスを返すようにnet
パッケージの動作を修正します。以前の実装では、ポイントツーポイントインターフェースのピアアドレスを誤ってインターフェースアドレスとして扱っていた問題を解決しています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e4890e57e1bf24bd6e2671cfb133bdc49ce0da71
元コミット内容
commit e4890e57e1bf24bd6e2671cfb133bdc49ce0da71
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Feb 20 07:31:44 2013 +0900
net: return correct point-to-point interface address on linux
On Linux point-to-point interface an IFA_ADDRESS attribute
represents a peer address. For a correct interface address
we should take an IFA_LOCAL attribute instead.
Fixes #4839.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/7352045
変更の背景
この変更は、Go言語のnet
パッケージがLinux上のポイントツーポイントインターフェース(例: PPP、VPNトンネルなど)のアドレスを誤って解釈していた問題(Issue #4839)を修正するために行われました。
Linuxのnetlink
インターフェースを通じてネットワークアドレス情報を取得する際、IFA_ADDRESS
属性は通常、インターフェースのプライマリIPアドレスを示します。しかし、ポイントツーポイントインターフェースの場合、IFA_ADDRESS
は「ピアアドレス」、つまり通信相手のIPアドレスを指すという特殊な振る舞いをします。このため、Goのnet
パッケージがIFA_ADDRESS
を無条件にインターフェース自身のアドレスとして扱ってしまうと、誤った情報が返されることになります。
正しいインターフェース自身のアドレスを取得するためには、ポイントツーポイントインターフェースの場合に限り、IFA_LOCAL
属性を参照する必要がありました。このコミットは、このLinuxカーネルのnetlink
の挙動の違いを考慮に入れ、net
パッケージが常に正しいインターフェースアドレスを返すように修正することを目的としています。
前提知識の解説
1. Linux Netlink
Netlink
は、Linuxカーネルとユーザー空間のプロセス間で情報を交換するためのソケットベースのIPC(プロセス間通信)メカニズムです。ネットワーク関連の情報を取得・設定する際に広く使用されます。ネットワークインターフェース、ルーティングテーブル、ファイアウォールルールなどの管理に利用されます。
2. syscall.NetlinkMessage
とsyscall.IfAddrmsg
syscall.NetlinkMessage
:netlink
ソケットを通じて送受信されるメッセージの基本構造です。メッセージタイプ、フラグ、シーケンス番号、ペイロード(データ)などを含みます。syscall.IfAddrmsg
:netlink
メッセージのペイロードとして、インターフェースアドレス情報(RTM_NEWADDR
メッセージなど)を運ぶ構造体です。インターフェースのインデックス、アドレスファミリー(IPv4/IPv6)、プレフィックス長などの情報が含まれます。
3. syscall.NetlinkRouteAttr
NetlinkRouteAttr
は、netlink
メッセージ内の属性(TLV: Type-Length-Value)を表現する構造体です。IfAddrmsg
に付随して、実際のIPアドレスなどの詳細情報がこれらの属性として格納されます。
4. IFA_ADDRESS
とIFA_LOCAL
これらはnetlink
のインターフェースアドレス属性のタイプです。
IFA_ADDRESS
: 通常のインターフェース(イーサネットなど)では、この属性がインターフェースに割り当てられたIPアドレスを示します。しかし、ポイントツーポイントインターフェースでは、これはピア(相手側)のIPアドレスを示します。IFA_LOCAL
: ポイントツーポイントインターフェースにおいて、この属性がインターフェース自身に割り当てられたIPアドレスを示します。通常のインターフェースでは、IFA_ADDRESS
とIFA_LOCAL
は同じ値を指すことが多いですが、ポイントツーポイントインターフェースでは明確に区別されます。
5. ポイントツーポイントインターフェース (FlagPointToPoint
)
ポイントツーポイントインターフェースは、2つのノード間でのみ通信が行われるネットワークリンクを指します。例えば、PPP (Point-to-Point Protocol) を使用したダイヤルアップ接続や、VPNトンネルなどがこれに該当します。これらのインターフェースは、通常、IFF_POINTOPOINT
フラグ(Goではnet.FlagPointToPoint
)が設定されています。
技術的詳細
このコミットの核心は、netlink
から取得したアドレス属性を解釈する際に、対象のインターフェースがポイントツーポイントであるかどうかを判別し、それに応じてIFA_ADDRESS
とIFA_LOCAL
のどちらの属性をインターフェースのIPアドレスとして採用するかを切り替える点にあります。
変更前は、newAddr
関数内でsyscall.IFA_ADDRESS
属性のみを無条件にインターフェースのIPアドレスとして扱っていました。これにより、ポイントツーポイントインターフェースではピアアドレスが誤って自身のIPアドレスとして認識されていました。
変更後は、newAddr
関数に*Interface
型の引数ifi
と*syscall.IfAddrmsg
型の引数ifam
が追加されました。これにより、newAddr
関数内でインターフェースのフラグ(ifi.Flags
)をチェックし、FlagPointToPoint
が設定されているかどうかを判別できるようになりました。
新しいロジックは以下のようになります。
- インターフェースがポイントツーポイント (
ifi.Flags&FlagPointToPoint != 0
) の場合:syscall.IFA_LOCAL
属性をインターフェースのIPアドレスとして採用します。 - インターフェースがポイントツーポイントではない (
ifi.Flags&FlagPointToPoint == 0
) 場合: 従来通りsyscall.IFA_ADDRESS
属性をインターフェースのIPアドレスとして採用します。
また、addrTable
関数内でnewAddr
を呼び出す際に、InterfaceByIndex
を呼び出してインターフェース情報を取得し、その結果をnewAddr
に渡すように変更されています。これにより、newAddr
関数が必要なインターフェースフラグ情報にアクセスできるようになりました。
さらに、IPアドレスのプレフィックス長(Prefixlen
)も、以前はnewAddr
関数の引数として渡されていたpfxlen
を使用していたのに対し、変更後はifam.Prefixlen
(syscall.IfAddrmsg
から取得)を使用するように修正されています。これは、ifam
がnetlink
メッセージから直接取得されたアドレス情報を含んでいるため、より正確な情報源となります。
コアとなるコードの変更箇所
--- a/src/pkg/net/interface_linux.go
+++ b/src/pkg/net/interface_linux.go
@@ -119,12 +119,16 @@ func addrTable(msgs []syscall.NetlinkMessage, ifindex int) ([]Addr, error) {
case syscall.RTM_NEWADDR:
ifam := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0]))
ifi, err := InterfaceByIndex(int(ifam.Index))
if err != nil {
return nil, err
}
if ifindex == 0 || ifindex == int(ifam.Index) {
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
if err != nil {
return nil, os.NewSyscallError("netlink routeattr", err)
}
- ifat = append(ifat, newAddr(attrs, int(ifam.Family), int(ifam.Prefixlen)))
+ ifat = append(ifat, newAddr(attrs, ifi, ifam))
}
}
}
@@ -132,19 +136,19 @@ done:
return ifat, nil
}
-func newAddr(attrs []syscall.NetlinkRouteAttr, family, pfxlen int) Addr {
+func newAddr(attrs []syscall.NetlinkRouteAttr, ifi *Interface, ifam *syscall.IfAddrmsg) Addr {
ifa := &IPNet{}
for _, a := range attrs {
- switch a.Attr.Type {
- case syscall.IFA_ADDRESS:
- switch family {
+ 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:
ifa.IP = IPv4(a.Value[0], a.Value[1], a.Value[2], a.Value[3])
- ifa.Mask = CIDRMask(pfxlen, 8*IPv4len)
+ ifa.Mask = CIDRMask(int(ifam.Prefixlen), 8*IPv4len)
case syscall.AF_INET6:
ifa.IP = make(IP, IPv6len)
copy(ifa.IP, a.Value[:])
- ifa.Mask = CIDRMask(pfxlen, 8*IPv6len)
+ ifa.Mask = CIDRMask(int(ifam.Prefixlen), 8*IPv6len)
}
}
}
コアとなるコードの解説
addrTable
関数の変更
- 変更前:
ifat = append(ifat, newAddr(attrs, int(ifam.Family), int(ifam.Prefixlen)))
newAddr
関数にifam.Family
とifam.Prefixlen
を直接渡していました。インターフェースのフラグ情報は渡されていませんでした。 - 変更後:
ifi, err := InterfaceByIndex(int(ifam.Index)) if err != nil { return nil, err } ifat = append(ifat, newAddr(attrs, ifi, ifam))
syscall.IfAddrmsg
から取得したインターフェースインデックス(ifam.Index
)を使ってInterfaceByIndex
を呼び出し、対応する*Interface
構造体(ifi
)を取得しています。このifi
には、インターフェースのフラグ(FlagPointToPoint
など)が含まれています。そして、newAddr
関数にこのifi
と元のifam
を渡すように変更されました。これにより、newAddr
関数がインターフェースのタイプに基づいて適切なアドレス属性を選択できるようになります。
newAddr
関数の変更
-
関数シグネチャの変更:
- 変更前:
func newAddr(attrs []syscall.NetlinkRouteAttr, family, pfxlen int) Addr
- 変更後:
func newAddr(attrs []syscall.NetlinkRouteAttr, ifi *Interface, ifam *syscall.IfAddrmsg) Addr
引数がfamily
とpfxlen
から、*Interface
型のifi
と*syscall.IfAddrmsg
型のifam
に変更されました。これにより、newAddr
関数内でインターフェースのフラグや、netlink
メッセージから直接取得したプレフィックス長にアクセスできるようになります。
- 変更前:
-
アドレス属性選択ロジックの変更:
- 変更前:
switch a.Attr.Type { case syscall.IFA_ADDRESS: // ... IPアドレスとマスクを設定 ... }
syscall.IFA_ADDRESS
属性のみを無条件に処理していました。 - 変更後:
このif ifi.Flags&FlagPointToPoint != 0 && a.Attr.Type == syscall.IFA_LOCAL || ifi.Flags&FlagPointToPoint == 0 && a.Attr.Type == syscall.IFA_ADDRESS { // ... IPアドレスとマスクを設定 ... }
if
文が、ポイントツーポイントインターフェースの特殊な挙動を考慮した新しいロジックです。ifi.Flags&FlagPointToPoint != 0
: インターフェースがポイントツーポイントである場合。a.Attr.Type == syscall.IFA_LOCAL
: この場合、IFA_LOCAL
属性がインターフェース自身のIPアドレスを示します。ifi.Flags&FlagPointToPoint == 0
: インターフェースがポイントツーポイントではない場合。a.Attr.Type == syscall.IFA_ADDRESS
: この場合、IFA_ADDRESS
属性がインターフェース自身のIPアドレスを示します。 この条件により、インターフェースのタイプに応じて適切なnetlink
属性からIPアドレスを取得するようになります。
- 変更前:
-
プレフィックス長の設定の変更:
- 変更前:
ifa.Mask = CIDRMask(pfxlen, ...)
- 変更後:
ifa.Mask = CIDRMask(int(ifam.Prefixlen), ...)
プレフィックス長をnewAddr
の引数pfxlen
からではなく、ifam.Prefixlen
(syscall.IfAddrmsg
から取得した元の値)を使用するように変更されました。これは、ifam
がnetlink
メッセージから直接取得されたアドレス情報を含んでいるため、より正確な情報源となります。
- 変更前:
これらの変更により、Goのnet
パッケージはLinuxシステム上で、通常のインターフェースとポイントツーポイントインターフェースの両方に対して、正確なIPアドレス情報を取得できるようになりました。
関連リンク
- Go CL: https://golang.org/cl/7352045
- Go Issue #4839: https://github.com/golang/go/issues/4839
参考にした情報源リンク
- Linux
netlink(7)
man page (特にRTM_NEWADDR
メッセージとIFA_ADDRESS
,IFA_LOCAL
属性に関する記述): https://man7.org/linux/man-pages/man7/netlink.7.html - Linux
rtnetlink(7)
man page (特にIFA_ADDRESS
とIFA_LOCAL
の区別について): https://man7.org/linux/man-pages/man7/rtnetlink.7.html - Go
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Go
net
パッケージのドキュメント: https://pkg.go.dev/net - RFC 3442 - The Classless Static Route Option for Dynamic Host Configuration Protocol (DHCP) version 4 (IFA_LOCALとIFA_ADDRESSの関連性について間接的に触れられている場合がありますが、直接的な情報源ではありません)
- Linuxカーネルのソースコード (net/ipv4/fib_frontend.c, net/ipv6/addrconf.c など、netlinkアドレス処理に関連する部分)