[インデックス 13180] ファイルの概要
このコミットは、Go言語の標準ライブラリnet
パッケージにおけるポート番号の解析ロジックを改善するものです。具体的には、ポート番号を文字列から数値に変換する処理をparsePort
という独立した関数として抽出し、コードの再利用性、可読性、および保守性を向上させています。また、/etc/services
ファイルからサービスポート情報を読み込むロジックをport.go
からport_unix.go
という新しいファイルに分離し、Unix系システムに特化した実装であることを明確にしています。
コミット
net: make parsePort as a function
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6256059
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ac486ab15c47edc31a5b2898bf79d26b9a9c939a
元コミット内容
commit ac486ab15c47edc31a5b2898bf79d26b9a9c939a
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue May 29 06:12:06 2012 +0900
net: make parsePort as a function
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6256059
変更の背景
このコミットの主な背景は、Go言語のnet
パッケージ内で重複していたポート番号解析ロジックの整理と共通化です。以前は、ipsock.go
のような複数の場所で、ポート番号の文字列を数値に変換し、それが有効なポート範囲内にあるかを確認する類似のコードが散在していました。このような重複は、コードの保守を困難にし、将来的な変更やバグ修正の際に一貫性を保つことを難しくします。
また、/etc/services
ファイルからサービス名に対応するポート番号をルックアップする機能(LookupPort
)の実装が、port.go
という汎用的なファイルに置かれていましたが、この機能は主にUnix系システムに依存するものでした。このため、プラットフォーム固有のコードを分離し、よりクリーンなアーキテクチャにする必要がありました。
これらの課題を解決するため、ポート解析ロジックをparsePort
という単一の関数に集約し、プラットフォーム固有のサービスルックアップロジックをport_unix.go
に移動することで、コードベースの品質と保守性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
ネットワークポート (Network Port):
- TCP/IPネットワークにおいて、アプリケーションが通信を行うための論理的な終点です。0から65535までの数値で表され、特に0から1023までは「ウェルノウンポート (Well-Known Ports)」と呼ばれ、HTTP (80), HTTPS (443), SSH (22) など、特定のサービスに予約されています。
- ポート番号は、IPアドレスと組み合わせて、特定のホスト上の特定のアプリケーションプロセスを識別するために使用されます。
-
/etc/services ファイル:
- Unix系オペレーティングシステムにおいて、サービス名とそれに対応するポート番号およびプロトコル(TCP/UDP)のマッピングを定義するテキストファイルです。
- 例えば、
http 80/tcp
のようなエントリが含まれており、これによりアプリケーションはサービス名(例: "http")からポート番号(例: 80)を解決できます。 - Goの
net
パッケージのLookupPort
関数は、このファイルや他のシステムメカニズムを利用してサービス名をポート番号に変換します。
-
Go言語の
net
パッケージ:- Go言語の標準ライブラリの一部で、ネットワークI/O機能を提供します。TCP/UDP接続、IPアドレスの解決、DNSルックアップなど、様々なネットワーク関連の操作をサポートします。
net.Dial
,net.Listen
,net.ResolveTCPAddr
などの関数が含まれます。
-
dtoi
関数 (Decimal To Integer):- Go言語の内部関数で、文字列の先頭から10進数として解釈できる部分を整数に変換します。
dtoi(s string, i int)
のようなシグネチャを持ち、s
のi
番目以降から数値の解析を開始し、解析された数値、解析が終了したインデックス、および成功/失敗を示すブール値を返します。
-
LookupPort
関数:net
パッケージの関数で、指定されたネットワークタイプ(例: "tcp", "udp")とサービス名(例: "http", "ssh")に基づいて、対応するポート番号をルックアップします。- システムの設定(例:
/etc/services
)を参照してポート番号を解決します。
-
ビルドタグ (
+build
):- Go言語のソースファイルに記述される特殊なコメントで、そのファイルが特定の環境(OS、アーキテクチャなど)でのみコンパイルされるように指定します。
- 例:
// +build darwin freebsd linux netbsd openbsd
は、このファイルが指定されたUnix系OSでのみビルドされることを意味します。これにより、プラットフォーム固有のコードを分離できます。
技術的詳細
このコミットは、Go言語のnet
パッケージにおけるポート番号の処理方法を根本的に改善しています。主要な変更点は以下の通りです。
-
parsePort
関数の導入と集約:- 以前は
ipsock.go
内で直接行われていたポート文字列の数値変換とバリデーション(0-65535の範囲チェック)ロジックが、net/port.go
に新しく定義されたparsePort(net, port string) (int, error)
関数に抽出されました。 - この関数は、まず
dtoi
を使ってポート文字列を直接数値に変換しようと試みます。 - もし文字列が純粋な数値でない場合(例: "http"のようなサービス名の場合)、
LookupPort
関数を呼び出してサービス名からポート番号を解決します。 - 最終的に、解析されたポート番号が有効な範囲(0から65535)にあるかをチェックし、エラーがなければポート番号を返します。
- これにより、ポート解析ロジックが一元化され、
ipsock.go
のような他のファイルからはparsePort
を呼び出すだけでよくなりました。
- 以前は
-
プラットフォーム固有コードの分離 (
port_unix.go
の新規作成):net/port.go
に存在していた/etc/services
ファイルを読み込み、サービス名からポート番号をルックアップするreadServices
関数とgoLookupPort
関数が、src/pkg/net/port_unix.go
という新しいファイルに移動されました。port_unix.go
には// +build darwin freebsd linux netbsd openbsd
というビルドタグが追加されており、これによりこれらの関数がUnix系システムでのみコンパイルされることが保証されます。- この分離により、
port.go
はより汎用的なポート操作ロジックのみを保持し、コードベースのモジュール性が向上しました。Windowsなどの非Unix系システムでは、これらの関数はコンパイルされず、異なるポートルックアップメカニズムが使用されることになります。
-
port.go
の役割の変更:port.go
は、以前は/etc/services
の読み込みロジックを含んでいましたが、このコミットによりその役割が変更されました。- 新しい
port.go
は、parsePort
関数という、ネットワークサービスポート番号を解析するための汎用的なロジックのみを提供します。これにより、ファイル名とその内容がより一致するようになりました。
これらの変更は、Goのnet
パッケージの内部構造を改善し、コードの重複を排除し、プラットフォーム固有の依存関係を明確に分離することで、長期的な保守性と拡張性を高めるものです。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の3つのファイルにわたります。
-
src/pkg/net/ipsock.go
:hostPortToIP
関数内のポート解析ロジックが、新しく導入されたparsePort
関数への呼び出しに置き換えられました。
--- a/src/pkg/net/ipsock.go +++ b/src/pkg/net/ipsock.go @@ -129,17 +129,10 @@ func hostPortToIP(net, hostport string) (ip IP, iport int, err error) { } } - p, i, ok := dtoi(port, 0) - if !ok || i != len(port) { - p, err = LookupPort(net, port) - if err != nil { - return nil, 0, err - } - } - if p < 0 || p > 0xFFFF { - return nil, 0, &AddrError{"invalid port", port} + p, err := parsePort(net, port) + if err != nil { + return nil, 0, err } return addr, p, nil - }
-
src/pkg/net/port.go
:/etc/services
関連のコード(services
変数、servicesError
変数、onceReadServices
、readServices
関数、goLookupPort
関数)が削除されました。- 代わりに、
parsePort
関数が新しく定義されました。
--- a/src/pkg/net/port.go +++ b/src/pkg/net/port.go @@ -1,69 +1,24 @@ // Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style +// Copyright 2012 The Go Authors. All rights reserved. // license that can be found in the LICENSE file. -// +build darwin freebsd linux netbsd openbsd - -// Read system port mappings from /etc/services +// Network service port manipulations package net -import "sync" - -var services map[string]map[string]int -var servicesError error -var onceReadServices sync.Once - -func readServices() { - services = make(map[string]map[string]int) - var file *file - if file, servicesError = open("/etc/services"); servicesError != nil { - return - } - for line, ok := file.readLine(); ok; line, ok = file.readLine() { - // "http 80/tcp www www-http # World Wide Web HTTP" - if i := byteIndex(line, '#'); i >= 0 { - line = line[0:i] - } - f := getFields(line) - if len(f) < 2 { - continue - } - portnet := f[1] // "tcp/80" - port, j, ok := dtoi(portnet, 0) - if !ok || port <= 0 || j >= len(portnet) || portnet[j] != '/' { - continue - } - netw := portnet[j+1:] // "tcp" - m, ok1 := services[netw] - if !ok1 { - m = make(map[string]int) - services[netw] = m - } - for i := 0; i < len(f); i++ { - if i != 1 { // f[1] was port/net - m[f[i]] = port - } - } - } - file.close() -} - -// goLookupPort is the native Go implementation of LookupPort. -func goLookupPort(network, service string) (port int, err error) { - onceReadServices.Do(readServices) - - switch network { - case "tcp4", "tcp6": - network = "tcp" - case "udp4", "udp6": - network = "udp" - } - - if m, ok := services[network]; ok { - if port, ok = m[service]; ok { - return - } - } - return 0, &AddrError{"unknown port", network + "/" + service} +\n// parsePort parses port as a network service port number for both\n// TCP and UDP.\n+func parsePort(net, port string) (int, error) {\n+\tp, i, ok := dtoi(port, 0)\n+\tif !ok || i != len(port) {\n+\t\tvar err error\n+\t\tp, err = LookupPort(net, port)\n+\t\tif err != nil {\n+\t\t\treturn 0, err\n+\t\t}\n+\t}\n+\tif p < 0 || p > 0xFFFF {\n+\t\treturn 0, &AddrError{\"invalid port\", port}\n+\t}\n+\treturn p, nil\n }
-
src/pkg/net/port_unix.go
(新規ファイル):port.go
から削除された/etc/services
関連のコード(services
変数、servicesError
変数、onceReadServices
、readServices
関数、goLookupPort
関数)がこのファイルに移動され、新規作成されました。- ファイル冒頭にUnix系OS向けのビルドタグが追加されています。
--- /dev/null +++ b/src/pkg/net/port_unix.go @@ -0,0 +1,69 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd linux netbsd openbsd + +// Read system port mappings from /etc/services + +package net + +import "sync" + +var services map[string]map[string]int +var servicesError error +var onceReadServices sync.Once + +func readServices() { + services = make(map[string]map[string]int) + var file *file + if file, servicesError = open("/etc/services"); servicesError != nil { + return + } + for line, ok := file.readLine(); ok; line, ok = file.readLine() { + // "http 80/tcp www www-http # World Wide Web HTTP" + if i := byteIndex(line, '#'); i >= 0 { + line = line[0:i] + } + f := getFields(line) + if len(f) < 2 { + continue + } + portnet := f[1] // "tcp/80" + port, j, ok := dtoi(portnet, 0) + if !ok || port <= 0 || j >= len(portnet) || portnet[j] != '/' { + continue + } + netw := portnet[j+1:] // "tcp" + m, ok1 := services[netw] + if !ok1 { + m = make(map[string]int) + services[netw] = m + } + for i := 0; i < len(f); i++ { + if i != 1 { // f[1] was port/net + m[f[i]] = port + } + } + } + file.close() +} + +// goLookupPort is the native Go implementation of LookupPort. +func goLookupPort(network, service string) (port int, err error) { + onceReadServices.Do(readServices) + + switch network { + case "tcp4", "tcp6": + network = "tcp" + case "udp4", "udp6": + network = "udp" + } + + if m, ok := services[network]; ok { + if port, ok = m[service]; ok { + return + } + } + return 0, &AddrError{"unknown port", network + "/" + service} +}
コアとなるコードの解説
src/pkg/net/ipsock.go
の変更
- 変更前:
hostPortToIP
関数内で、ポート文字列を数値に変換するロジックが直接記述されていました。これにはdtoi
関数による数値変換の試みと、失敗した場合のLookupPort
によるサービス名解決、そしてポート範囲のバリデーションが含まれていました。 - 変更後: この複雑なロジックが
parsePort(net, port)
という新しい関数呼び出しに置き換えられました。これにより、ipsock.go
のコードは大幅に簡潔になり、ポート解析の詳細を知る必要がなくなりました。エラーハンドリングもparsePort
関数が返すエラーをそのまま返す形になり、よりクリーンになりました。
src/pkg/net/port.go
の変更
- 変更前: このファイルは、汎用的なポート関連の機能に加えて、
/etc/services
ファイルを読み込むreadServices
関数や、それを利用してサービス名をポート番号に変換するgoLookupPort
関数(LookupPort
のGoネイティブ実装)を含んでいました。これらの関数はUnix系システムに特化したものでした。 - 変更後:
parsePort
関数の新規定義: この関数は、ポート文字列(例: "80"や"http")を受け取り、対応するポート番号(int
型)とエラーを返します。- まず、
dtoi(port, 0)
を呼び出して、ポート文字列が直接数値として解析できるか試みます。 dtoi
が失敗した場合(つまり、ポートが数値ではなくサービス名である可能性が高い場合)、LookupPort(net, port)
を呼び出して、システムに登録されたサービス名からポート番号をルックアップします。- 最終的に、解析されたポート番号
p
が有効なポート範囲(0から65535、つまり0xFFFF
)内にあるかを確認します。範囲外であればAddrError
を返します。
- まず、
/etc/services
関連のコードはすべて削除され、このファイルはparsePort
という汎用的なポート解析ロジックのみを持つようになりました。これにより、port.go
の役割がより明確になり、プラットフォーム非依存のポート処理の共通ロジックを担うようになりました。
src/pkg/net/port_unix.go
の新規作成
- このファイルは、
port.go
から移動された/etc/services
関連のコードを格納するために新しく作成されました。 // +build darwin freebsd linux netbsd openbsd
: このビルドタグにより、このファイル内のコードは指定されたUnix系オペレーティングシステムでのみコンパイルされます。これにより、/etc/services
のようなUnix固有のメカニズムに依存するコードが、他のOS(例: Windows)のビルドプロセスに影響を与えないように分離されます。readServices()
:/etc/services
ファイルを読み込み、サービス名とポート番号のマッピングをservices
マップに格納します。このマップは、goLookupPort
関数によって利用されます。goLookupPort(network, service string) (port int, err error)
:LookupPort
関数のGoネイティブ実装であり、readServices
によって読み込まれたservices
マップを使用して、指定されたネットワークタイプとサービス名に対応するポート番号を検索します。
これらの変更により、Goのnet
パッケージは、ポート解析ロジックの共通化と、プラットフォーム固有のサービスルックアップメカニズムの明確な分離を実現し、コードベースの構造と保守性を大幅に向上させています。
関連リンク
- Go言語の
net
パッケージのドキュメント: https://pkg.go.dev/net - Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/6256059
参考にした情報源リンク
/etc/services
ファイルに関する情報 (Wikipedia): https://ja.wikipedia.org/wiki//etc/services- TCP/UDPポート番号に関する情報 (Wikipedia): https://ja.wikipedia.org/wiki/TCP/UDP%E3%83%9D%E3%83%BC%E3%83%88%E4%B8%80%E8%A6%A7
- Go言語のビルドタグに関する公式ドキュメント: https://go.dev/cmd/go/#hdr-Build_constraints
- Go言語の
dtoi
関数に関する議論 (Go Issues): https://github.com/golang/go/issues/10000 (直接的なドキュメントではないが、内部関数の利用例として参考になる) - Go言語の
LookupPort
関数に関するドキュメント: https://pkg.go.dev/net#LookupPort - Go言語の
net
パッケージのソースコード (GitHub): https://github.com/golang/go/tree/master/src/netipsock.go
: https://github.com/golang/go/blob/master/src/net/ipsock.goport.go
: https://github.com/golang/go/blob/master/src/net/port.goport_unix.go
: https://github.com/golang/go/blob/master/src/net/port_unix.go (これらのリンクはコミット時点のものではなく、現在のmasterブランチのものです。しかし、関連するコードのコンテキストを理解するのに役立ちます。)