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

[インデックス 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に移動することで、コードベースの品質と保守性を向上させることを目的としています。

前提知識の解説

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

  1. ネットワークポート (Network Port):

    • TCP/IPネットワークにおいて、アプリケーションが通信を行うための論理的な終点です。0から65535までの数値で表され、特に0から1023までは「ウェルノウンポート (Well-Known Ports)」と呼ばれ、HTTP (80), HTTPS (443), SSH (22) など、特定のサービスに予約されています。
    • ポート番号は、IPアドレスと組み合わせて、特定のホスト上の特定のアプリケーションプロセスを識別するために使用されます。
  2. /etc/services ファイル:

    • Unix系オペレーティングシステムにおいて、サービス名とそれに対応するポート番号およびプロトコル(TCP/UDP)のマッピングを定義するテキストファイルです。
    • 例えば、http 80/tcpのようなエントリが含まれており、これによりアプリケーションはサービス名(例: "http")からポート番号(例: 80)を解決できます。
    • GoのnetパッケージのLookupPort関数は、このファイルや他のシステムメカニズムを利用してサービス名をポート番号に変換します。
  3. Go言語のnetパッケージ:

    • Go言語の標準ライブラリの一部で、ネットワークI/O機能を提供します。TCP/UDP接続、IPアドレスの解決、DNSルックアップなど、様々なネットワーク関連の操作をサポートします。
    • net.Dial, net.Listen, net.ResolveTCPAddrなどの関数が含まれます。
  4. dtoi関数 (Decimal To Integer):

    • Go言語の内部関数で、文字列の先頭から10進数として解釈できる部分を整数に変換します。
    • dtoi(s string, i int)のようなシグネチャを持ち、si番目以降から数値の解析を開始し、解析された数値、解析が終了したインデックス、および成功/失敗を示すブール値を返します。
  5. LookupPort関数:

    • netパッケージの関数で、指定されたネットワークタイプ(例: "tcp", "udp")とサービス名(例: "http", "ssh")に基づいて、対応するポート番号をルックアップします。
    • システムの設定(例: /etc/services)を参照してポート番号を解決します。
  6. ビルドタグ (+build):

    • Go言語のソースファイルに記述される特殊なコメントで、そのファイルが特定の環境(OS、アーキテクチャなど)でのみコンパイルされるように指定します。
    • 例: // +build darwin freebsd linux netbsd openbsd は、このファイルが指定されたUnix系OSでのみビルドされることを意味します。これにより、プラットフォーム固有のコードを分離できます。

技術的詳細

このコミットは、Go言語のnetパッケージにおけるポート番号の処理方法を根本的に改善しています。主要な変更点は以下の通りです。

  1. parsePort関数の導入と集約:

    • 以前はipsock.go内で直接行われていたポート文字列の数値変換とバリデーション(0-65535の範囲チェック)ロジックが、net/port.goに新しく定義されたparsePort(net, port string) (int, error)関数に抽出されました。
    • この関数は、まずdtoiを使ってポート文字列を直接数値に変換しようと試みます。
    • もし文字列が純粋な数値でない場合(例: "http"のようなサービス名の場合)、LookupPort関数を呼び出してサービス名からポート番号を解決します。
    • 最終的に、解析されたポート番号が有効な範囲(0から65535)にあるかをチェックし、エラーがなければポート番号を返します。
    • これにより、ポート解析ロジックが一元化され、ipsock.goのような他のファイルからはparsePortを呼び出すだけでよくなりました。
  2. プラットフォーム固有コードの分離 (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系システムでは、これらの関数はコンパイルされず、異なるポートルックアップメカニズムが使用されることになります。
  3. port.goの役割の変更:

    • port.goは、以前は/etc/servicesの読み込みロジックを含んでいましたが、このコミットによりその役割が変更されました。
    • 新しいport.goは、parsePort関数という、ネットワークサービスポート番号を解析するための汎用的なロジックのみを提供します。これにより、ファイル名とその内容がより一致するようになりました。

これらの変更は、Goのnetパッケージの内部構造を改善し、コードの重複を排除し、プラットフォーム固有の依存関係を明確に分離することで、長期的な保守性と拡張性を高めるものです。

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

このコミットによる主要なコード変更は以下の3つのファイルにわたります。

  1. 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
    -
     }
    
  2. src/pkg/net/port.go:

    • /etc/services関連のコード(services変数、servicesError変数、onceReadServicesreadServices関数、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 }
    
  3. src/pkg/net/port_unix.go (新規ファイル):

    • port.goから削除された/etc/services関連のコード(services変数、servicesError変数、onceReadServicesreadServices関数、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型)とエラーを返します。
      1. まず、dtoi(port, 0)を呼び出して、ポート文字列が直接数値として解析できるか試みます。
      2. dtoiが失敗した場合(つまり、ポートが数値ではなくサービス名である可能性が高い場合)、LookupPort(net, port)を呼び出して、システムに登録されたサービス名からポート番号をルックアップします。
      3. 最終的に、解析されたポート番号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パッケージは、ポート解析ロジックの共通化と、プラットフォーム固有のサービスルックアップメカニズムの明確な分離を実現し、コードベースの構造と保守性を大幅に向上させています。

関連リンク

参考にした情報源リンク