Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 #6433Update #4839は、当時のGoのIssueトラッキングシステムにおける関連する問題番号を示している可能性があります。ただし、現在のGitHub上のGoリポジトリではこれらの番号に直接対応するIssueは見つかりませんでした。これは、Issueが統合されたり、番号が変更されたり、あるいは内部的なトラッキング番号であったりする可能性を示唆しています。しかし、golang.org/cl/57700043の変更リスト情報から、このコミットがLinuxのnet.Interface.Addrs()がポイントツーポイントインターフェースのIPv6アドレスを返さない問題を解決したことが確認できます。

前提知識の解説

このコミットを理解するためには、以下の概念に関する前提知識が必要です。

  1. Linuxネットワークインターフェース: Linuxシステムでは、ネットワークデバイス(イーサネットカード、Wi-Fiアダプター、ループバック、トンネルインターフェースなど)は「ネットワークインターフェース」として抽象化されます。各インターフェースには、名前(例: eth0, lo, ppp0, tun0)、インデックス、MTU(Maximum Transmission Unit)、フラグ(UP, DOWN, BROADCAST, MULTICAST, POINTOPOINTなど)、IPアドレスなどが関連付けられています。
  2. Netlink: Linuxカーネルとユーザー空間アプリケーション間の通信メカニズムの一つです。特に、ネットワーク関連の情報を取得したり、設定を変更したりするために広く使用されます。ipコマンドやifconfigコマンドなども内部的にNetlinkを使用しています。ネットワークインターフェースの情報は、RTM_GETLINKRTM_GETADDRなどのNetlinkメッセージを通じて取得されます。
  3. syscallパッケージ: Go言語の標準ライブラリの一部で、オペレーティングシステム固有のシステムコールにアクセスするための機能を提供します。このコミットでは、LinuxカーネルのNetlink APIと直接やり取りするためにsyscallパッケージが使用されています。
  4. syscall.IfInfomsgsyscall.IfAddrmsg: Netlinkメッセージでネットワークインターフェースの基本情報(IfInfomsg)やアドレス情報(IfAddrmsg)を表現するための構造体です。
  5. syscall.NetlinkRouteAttr: Netlinkメッセージ内の属性(Type-Length-Value形式)を表す構造体です。インターフェースのハードウェアアドレス(IFLA_ADDRESS)やIPアドレス(IFA_ADDRESS, IFA_LOCAL)などの詳細情報が含まれます。
  6. ポイントツーポイント(Point-to-Point)インターフェース: 2つのノード間を直接接続するネットワークリンクです。PPP(Point-to-Point Protocol)や一部のVPNトンネル(例: IPIP、GRE、6to4、6rd)などがこれに該当します。これらのインターフェースは、通常、単一のピアとの通信を目的としており、ブロードキャストやマルチキャストの概念が適用されない場合があります。IPアドレスの割り当て方も、通常のブロードキャストネットワークインターフェースとは異なる場合があります(例: IFA_LOCALIFA_ADDRESSの使い分け)。
  7. IPv4len / IPv6len: IPv4アドレスが4バイト、IPv6アドレスが16バイトであることを示す定数です。
  8. 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つの主要な修正が行われています。

  1. 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アドレスがハードウェアアドレスとして報告されることを防ぎます。
  2. 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ファイルに集中しています。

  1. 新しい定数の追加: 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
    )
    
  2. 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 {
    
  3. newAddr関数の変更: ポイントツーポイントインターフェースのアドレス処理ロジックが大幅に変更されました。ipPointToPointという新しいフラグを導入し、IFA_LOCALIFA_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文は、以下のロジックで動作します。

  1. a.Valueの長さ(属性の値のバイト数)がIPv4アドレス(4バイト)またはIPv6アドレス(16バイト)の長さと一致するかどうかを確認します。
  2. もし一致した場合、ifim.Type(インターフェースのARPハードウェアタイプ)をチェックします。
  3. ifim.TypesysARPHardwareIPv4IPv4, sysARPHardwareGREIPv4, sysARPHardwareIPv6IPv4(IPv4アドレスの場合)またはsysARPHardwareIPv6IPv6, sysARPHardwareGREIPv6(IPv6アドレスの場合)のいずれかである場合、これはIPトンネルインターフェースであり、IFLA_ADDRESSがIPアドレスとして報告されている可能性が高いと判断します。
  4. この条件に合致した場合、continueステートメントによって現在の属性の処理をスキップし、このIPアドレスをハードウェアアドレスとして採用しないようにします。

これにより、net.Interface.HardwareAddrには、実際に物理的なハードウェアアドレスを持つインターフェースの場合のみ正しい値が設定され、IPトンネルインターフェースでは空のバイトスライスが設定されるようになります。

newAddr関数におけるポイントツーポイントアドレスの処理

この変更は、net.Interface.Addrs()が返すIPアドレスリストの正確性を向上させるためのものです。特に、ポイントツーポイントインターフェース(FlagPointToPointフラグが設定されているインターフェース)のアドレス処理が改善されました。

以前のロジックは、ポイントツーポイントインターフェースではIFA_LOCAL属性を、それ以外のインターフェースではIFA_ADDRESS属性を優先してIPアドレスを抽出していました。しかし、この単純な条件分岐では、LinuxカーネルがNetlinkを通じてアドレスを報告する際の微妙な違いに対応しきれていませんでした。

新しいロジックは以下のステップで動作します。

  1. ipPointToPointフラグの導入: まず、インターフェースがFlagPointToPointフラグを持っているかを確認します。もし持っている場合、さらにattrs(Netlink属性リスト)をループし、syscall.IFA_LOCAL属性が存在するかどうかをチェックします。IFA_LOCALが存在すれば、ipPointToPointtrueに設定します。このステップは、「IPインターフェーススタックがポイントツーポイントの番号付きまたは番号なしアドレス指定で構成されているか」を確認するためのものです。

  2. アドレス属性の選択ロジックの改善: 次に、再度attrsをループして実際のIPアドレスを抽出します。このループの冒頭で、以下の条件に基づいて属性をスキップするかどうかを判断します。

    • ipPointToPointtrue(つまり、ポイントツーポイントインターフェースでIFA_LOCALが見つかった場合)かつ、現在の属性がsyscall.IFA_ADDRESSである場合、この属性をスキップします。これは、ポイントツーポイントインターフェースではIFA_LOCALが優先されるべきだからです。
    • ipPointToPointfalse(つまり、通常のインターフェース、またはポイントツーポイントだが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のキャッシュなど)。