[インデックス 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アドレス処理に関連する部分)