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

[インデックス 17361] ファイルの概要

このコミットは、Go言語のnetパッケージにおいて、インターネットプロトコル番号に関する最小限の情報を追加するものです。これは、完全なプロトコル番号リストを持たないプラットフォームをサポートすることを目的としています。具体的には、主要なプロトコル(ICMP, IGMP, TCP, UDP, IPv6-ICMP)の名称と番号の対応をハードコードしたマップを導入し、既存のプロトコル読み込みロジック(特にUnix系システムにおける/etc/protocolsの読み込み)と統合することで、より堅牢なプロトコル解決メカニズムを提供します。

コミット

commit fd58320f32a1b8f329e741ab10f1a552a06ede4a
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Thu Aug 22 10:33:37 2013 +0900

    net: add minimal internet protocol number information base
    
    This CL adds minimal information for supporting platforms that don't
    have a complete list of internet protocol numbers.
    
    Fixes #5344.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12898045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/fd58320f32a1b8f329e741ab10f1a552a06ede4a

元コミット内容

net: add minimal internet protocol number information base

This CL adds minimal information for supporting platforms that don't
have a complete list of internet protocol numbers.

Fixes #5344.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12898045

変更の背景

Go言語のnetパッケージは、ネットワークI/Oを扱うための移植性の高いインターフェースを提供します。これには、TCP/IP、UDP、IPネットワークなどが含まれます。ネットワークプロトコルを識別するために、通常はプロトコル名(例: "tcp", "udp")または対応するインターネットプロトコル番号(例: TCPは6、UDPは17)が使用されます。

しかし、一部のプラットフォームでは、システムが提供するプロトコル番号のリストが不完全であるか、あるいは全く提供されていない場合があります。このような環境では、Goのnetパッケージがプロトコル名を正しく解決できず、ネットワーク通信に問題が生じる可能性がありました。

このコミットは、この問題を解決するために導入されました。主要なインターネットプロトコル(ICMP, IGMP, TCP, UDP, IPv6-ICMP)について、Go自身が最小限のプロトコル名と番号の対応表を持つことで、システムに依存せずにこれらの基本的なプロトコルを確実に識別できるようにすることが目的です。これにより、Goアプリケーションの移植性と堅牢性が向上します。コミットメッセージにあるFixes #5344は、この変更が特定の課題(Issue 5344)を解決することを示唆しています。

前提知識の解説

インターネットプロトコル番号 (IANA Protocol Numbers)

インターネットプロトコル番号は、Internet Assigned Numbers Authority (IANA) によって管理される8ビットの数値で、IPパケットのIPヘッダ(IPv4のProtocolフィールドまたはIPv6のNext Headerフィールド)にカプセル化される次のレベルのプロトコルを識別するために使用されます。これにより、ネットワークデバイスはIPヘッダの後に続くデータを正しく解釈できます。

  • 目的: IPパケットが運ぶペイロードがどのプロトコル(例: TCP, UDP, ICMP)のものであるかを識別します。
  • 範囲: 0から255までの256個の番号があります。
  • 主要なプロトコル番号の例:
    • 1: ICMP (Internet Control Message Protocol)
    • 2: IGMP (Internet Group Management Protocol)
    • 6: TCP (Transmission Control Protocol)
    • 17: UDP (User Datagram Protocol)
    • 58: ICMPv6 (ICMP for IPv6)
  • IANAの役割: IANAは、これらのプロトコル番号の公式なレジストリを維持しており、www.iana.org/assignments/protocol-numbersで公開されています。

Go言語のnetパッケージとプロトコル解決

Goのnetパッケージは、ネットワークアドレスやプロトコル名を解決する機能を提供します。例えば、net.Dialnet.ListenIPのような関数では、ネットワークタイプを指定する際にプロトコル名(例: "tcp")またはプロトコル番号(例: "6")を使用できます。

通常、Goはシステムが提供する情報源(Unix系システムでは/etc/protocolsファイルなど)からプロトコル名と番号の対応を読み込みます。しかし、前述の通り、これらの情報源が不完全な場合があるため、Go自身が基本的なプロトコル情報を内部に持つことで、より信頼性の高い解決を可能にします。

/etc/protocolsファイル

Unix系オペレーティングシステムでは、/etc/protocolsファイルは、インターネットプロトコル名とその対応するプロトコル番号、およびオプションのエイリアスを定義するテキストファイルです。このファイルは、システム上のネットワークアプリケーションがプロトコル名を数値に変換するために使用されます。

例:

# /etc/protocols:
# $Id: protocols,v 1.1 2000/07/11 19:00:00 lukem Exp $
#
# Internet (IP) protocols
#
ip      0       IP              # internet protocol, pseudo protocol number
icmp    1       ICMP            # internet control message protocol
igmp    2       IGMP            # internet group management protocol
ggp     3       GGP             # gateway-gateway protocol
ipencap 4       IP-ENCAP        # IP encapsulated in IP (RFC2003)
st      5       ST              # ST datagram mode
tcp     6       TCP             # transmission control protocol
egp     8       EGP             # exterior gateway protocol
pup     12      PUP             # PARC universal packet protocol
udp     17      UDP             # user datagram protocol
hmp     20      HMP             # host monitoring protocol
xns-idp 22      XNS-IDP         # Xerox NS IDP
rdp     27      RDP             # "reliable datagram" protocol
ipv6    41      IPv6            # Internet Protocol, Version 6
ipv6-route 43   IPv6-Route      # Routing Header for IPv6
ipv6-frag 44    IPv6-Frag       # Fragment Header for IPv6
gre     47      GRE             # Generic Routing Encapsulation
esp     50      ESP             # Encap Security Payload
ah      51      AH              # Authentication Header
ipv6-icmp 58    IPv6-ICMP       # ICMP for IPv6
ipv6-nonxt 59   IPv6-NoNxt      # No Next Header for IPv6
ipv6-opts 60    IPv6-Opts       # Destination Options for IPv6

Goのnetパッケージは、このファイルを読み込んでプロトコル情報を取得しますが、このコミットにより、ファイルが存在しない、または不完全な場合でも基本的なプロトコルが認識されるようになります。

技術的詳細

このコミットの主要な技術的アプローチは以下の通りです。

  1. ハードコードされたプロトコルマップの導入: src/pkg/net/lookup.goprotocolsというグローバルマップを導入し、主要なインターネットプロトコル名とその番号の対応を初期値として設定します。これにより、システムが提供する情報に依存せず、Go自身が基本的なプロトコル情報を常に利用できるようになります。

    • icmp: 1
    • igmp: 2
    • tcp: 6
    • udp: 17
    • ipv6-icmp: 58
    • これらのプロトコル名は大文字・小文字を区別しないように、両方のケースがマップに含まれています(例: "icmp"と"ICMP")。
  2. 既存のプロトコル読み込みロジックの変更:

    • Unix系システム (src/pkg/net/lookup_unix.go): 以前は/etc/protocolsを読み込む際にprotocolsマップをゼロから作成していましたが、この変更により、新しく導入されたグローバルなprotocolsマップに、/etc/protocolsから読み込んだ情報を追加する形になります。これにより、ハードコードされた情報が優先され、システムファイルの情報がそれにマージされる形になります。具体的には、/etc/protocolsから読み込んだプロトコル名が既にprotocolsマップに存在しない場合にのみ、その情報を追加します。これにより、Goが提供する基本的な情報が上書きされることを防ぎます。
    • Windows系システム (src/pkg/net/lookup_windows.go): Windowsでは、lookupProtocol関数がシステムコールを使用してプロトコル情報を取得します。このコミットでは、システムコールが失敗した場合(r.err != nil)、新しく導入されたグローバルなprotocolsマップをフォールバックとして参照するように変更されました。これにより、システムがプロトコル情報を解決できない場合でも、Goが持つ最小限の情報で対応できるようになります。

このアプローチにより、Goのnetパッケージは、様々なプラットフォームにおいて、より堅牢かつ一貫したプロトコル解決能力を持つことになります。

コアとなるコードの変更箇所

このコミットでは、以下の3つのファイルが変更されています。

  1. src/pkg/net/lookup.go:

    • protocolsというmap[string]int型のグローバル変数が追加され、主要なインターネットプロトコル名とその番号の初期値が設定されています。
    --- a/src/pkg/net/lookup.go
    +++ b/src/pkg/net/lookup.go
    @@ -8,6 +8,19 @@ import (
      	"time"
      )
      
    +// protocols contains minimal mappings between internet protocol
    +// names and numbers for platforms that don't have a complete list of
    +// protocol numbers.
    +//
    +// See http://www.iana.org/assignments/protocol-numbers
    +var protocols = map[string]int{
    +	"icmp": 1, "ICMP": 1,
    +	"igmp": 2, "IGMP": 2,
    +	"tcp": 6, "TCP": 6,
    +	"udp": 17, "UDP": 17,
    +	"ipv6-icmp": 58, "IPV6-ICMP": 58, "IPv6-ICMP": 58,
    +}
    +
     var lookupGroup singleflight
      
     // lookupHostMerge wraps lookupHost, but makes sure that for any given
    
  2. src/pkg/net/lookup_unix.go:

    • protocols変数の宣言が削除され、グローバルなprotocolsマップを使用するように変更されています。
    • readProtocols関数内で、/etc/protocolsから読み込んだプロトコル情報を既存のprotocolsマップにマージするロジックが追加されています。具体的には、読み込んだプロトコル名がマップに存在しない場合にのみ追加されます。
    --- a/src/pkg/net/lookup_unix.go
    +++ b/src/pkg/net/lookup_unix.go
    @@ -11,15 +11,11 @@ import (
      	"sync"
      )
      
    -var (
    -	protocols         map[string]int
    -	onceReadProtocols sync.Once
    -)
    +var onceReadProtocols sync.Once
      
     // readProtocols loads contents of /etc/protocols into protocols map
     // for quick access.
     func readProtocols() {
    -	protocols = make(map[string]int)
      	if file, err := open("/etc/protocols"); err == nil {
      		for line, ok := file.readLine(); ok; line, ok = file.readLine() {
      			// tcp    6   TCP    # transmission control protocol
    @@ -31,9 +27,13 @@ func readProtocols() {
      			}
      			if proto, _, ok := dtoi(f[1], 0); ok {
    -				protocols[f[0]] = proto
    +				if _, ok := protocols[f[0]]; !ok {
    +					protocols[f[0]] = proto
    +				}
      				for _, alias := range f[2:] {
    -					protocols[alias] = proto
    +					if _, ok := protocols[alias]; !ok {
    +						protocols[alias] = proto
    +					}
      				}
      			}
      		}
    
  3. src/pkg/net/lookup_windows.go:

    • lookupProtocol関数内で、システムコールがエラーを返した場合に、新しく導入されたグローバルなprotocolsマップをフォールバックとして参照するロジックが追加されています。
    --- a/src/pkg/net/lookup_windows.go
    +++ b/src/pkg/net/lookup_windows.go
    @@ -42,6 +42,11 @@ func lookupProtocol(name string) (proto int, err error) {
      	ch <- result{proto: proto, err: err}
      }()
      r := <-ch
    +	if r.err != nil {
    +		if proto, ok := protocols[name]; ok {
    +			return proto, nil
    +		}
    +	}
      return r.proto, r.err
     }
      
    

コアとなるコードの解説

src/pkg/net/lookup.go

  • var protocols = map[string]int{...}:
    • この行は、Goのnetパッケージが内部的に使用するプロトコル名と番号の対応マップを定義しています。
    • icmp, igmp, tcp, udp, ipv6-icmpといった基本的なプロトコルが、そのIANA割り当て番号と共に初期化されています。
    • 大文字と小文字の両方のプロトコル名が登録されているのは、プロトコル名の検索時に大文字・小文字を区別しない柔軟性を提供するためです。
    • このマップは、システムが提供するプロトコル情報が不完全な場合や利用できない場合のフォールバックとして機能します。

src/pkg/net/lookup_unix.go

  • var onceReadProtocols sync.Once:
    • sync.Onceは、readProtocols関数が複数回呼び出されても、その中の処理(/etc/protocolsの読み込み)が一度だけ実行されることを保証するためのGoの標準ライブラリの機能です。これにより、リソースの無駄な消費を防ぎ、初期化処理の安全性を確保します。
  • func readProtocols() { ... }:
    • この関数は、Unix系システムで/etc/protocolsファイルを読み込み、その内容をprotocolsマップに反映させる役割を担います。
    • 変更点: 以前はprotocols = make(map[string]int)でマップを初期化していましたが、この行が削除されました。これにより、src/pkg/net/lookup.goで定義されたグローバルなprotocolsマップがそのまま使用されるようになります。
    • マージロジック:
      if proto, _, ok := dtoi(f[1], 0); ok {
          if _, ok := protocols[f[0]]; !ok {
              protocols[f[0]] = proto
          }
          for _, alias := range f[2:] {
              if _, ok := protocols[alias]; !ok {
                  protocols[alias] = proto
              }
          }
      }
      
      • この部分が、/etc/protocolsから読み込んだプロトコル情報(f[0]はプロトコル名、f[1]は番号、f[2:]はエイリアス)をprotocolsマップに追加するロジックです。
      • if _, ok := protocols[f[0]]; !okという条件は、「もしprotocolsマップにf[0](プロトコル名)がまだ存在しないならば」という意味です。これにより、src/pkg/net/lookup.goでハードコードされた基本的なプロトコル情報が、/etc/protocolsの内容によって上書きされることを防ぎます。システムファイルに同じプロトコルが定義されていても、Goが持つ情報が優先されます。
      • エイリアスについても同様のチェックが行われ、既存のエイリアスが上書きされないように配慮されています。

src/pkg/net/lookup_windows.go

  • func lookupProtocol(name string) (proto int, err error) { ... }:
    • この関数は、Windowsシステムでプロトコル名を解決するために使用されます。通常、システムコール(lookupProtocolの内部で呼び出されるlookup関数)を通じてプロトコル情報を取得します。
    • 変更点:
      if r.err != nil {
          if proto, ok := protocols[name]; ok {
              return proto, nil
          }
      }
      
      • r.err != nilは、システムコールによるプロトコル解決がエラーを返した場合(つまり、システムが指定されたプロトコル名を解決できなかった場合)を意味します。
      • この場合、if proto, ok := protocols[name]; okという条件で、src/pkg/net/lookup.goで定義されたグローバルなprotocolsマップに、要求されたプロトコル名(name)が存在するかどうかを確認します。
      • もし存在すれば、そのハードコードされたプロトコル番号(proto)を返し、エラーはnilとすることで、解決が成功したと見なします。
      • これにより、Windowsシステムがプロトコル名を解決できない場合でも、Goが持つ最小限のプロトコル情報で対応できるようになり、堅牢性が向上します。

これらの変更により、Goのnetパッケージは、OSのプロトコル情報に完全に依存することなく、主要なプロトコルを確実に識別できるようになり、異なるプラットフォームでの動作の安定性が向上しました。

関連リンク

  • Go Issue #5344: コミットメッセージにFixes #5344とありますが、Goの公式Issueトラッカーでこの番号のIssueを直接特定することはできませんでした。しかし、このコミットが「完全なプロトコル番号リストを持たないプラットフォームをサポートする」という目的を明記していることから、特定の環境でのプロトコル解決に関する問題が背景にあったと考えられます。
  • IANA Protocol Numbers Registry: https://www.iana.org/assignments/protocol-numbers - インターネットプロトコル番号の公式なレジストリ。

参考にした情報源リンク