プロンプトとコンテキスト
あるサービスが設定からホストとポートを読み取り、fmt.Sprintf("%s:%d", host, port) を net.Dial に渡しています。IPv4 のテストはパスしますが、本番環境では IPv6、リンクローカルアドレス、および不正なポートのパースが露見します。Go 1.25 で実装を修正し、net.JoinHostPort、SplitHostPort、ゾーン識別子、入力検証の境界を説明し、新しい go vet hostport アナライザーについて議論してください。コアとなるスキルは移植性の高いネットワークプログラミングであるため、これは coding の質問です。
面接官が評価するポイント
第一に、IPv6 テキストには複数のコロンが含まれており、2001:db8::1:443 のような単純な文字列連結が曖昧になることを認識しているかどうか。
第二に、手書きの分岐ではなく、ネットワークアドレス構文に従って角括弧を追加するために標準ライブラリを使用しているかどうか。
第三に、角括弧やパース処理が重複しないように、ホスト文字列、ゾーン識別子、ポート文字列、完全なオーソリティを区別しているかどうか。
第四に、テストとエラー処理が IPv4、IPv6、%zone、空のホスト、無効なポートをカバーしているかどうか。
第五に、コードレビューや実際の接続テストで修正を検証しつつ、go vet をシグナルとして活用しているかどうか。
最初に確認すべき明確化の質問
hostは単純な IP、DNS 名、角括弧で囲まれたオーソリティ、あるいはそれらの混在ですか?- ポートは整数、文字列、サービス名のいずれですか?
0は許可されますか? - IPv6 リンクローカルアドレスや
%eth0などのゾーンをサポートする必要がありますか? - ネットワークは TCP、UDP、それとも Unix ソケットですか?
- ユーザー入力内の角括弧や制御文字は拒否すべきですか?
- CI 環境で IPv6 ループバックリスナーをバインドできますか?
30秒での回答
「単純な文字列連結では IPv6 ホストとポートの境界を表現できないため、2001:db8::1:443 は曖昧になります。私はポートを検証した上で net.JoinHostPort(host, strconv.Itoa(port)) を呼び出します。標準ライブラリが IPv6 に対して [host]:port を出力し、ゾーンも保持します。手動で角括弧を追加することはありません。完全なオーソリティを受け入れる場合は、再構築する前に net.SplitHostPort でパースします。テストは IPv4、IPv6、%zone、DNS、空入力、無効なポート、およびラウンドトリップパースを網羅します。Go 1.25 の go vet hostport アナライザーは静的ゲートであり、ネットワークテストの代わりにはなりません。」
詳細な解決策
ステップ 1: 失敗の原因を説明する
IPv4 アドレスにはホストとポートを区切るコロンが1つあります。一方、IPv6 アドレス自体にコロンが含まれています。そのため、IPv6 ネットワークテキストでは [2001:db8::1]:443 のようにホストを角括弧で囲みます。単純な文字列連結では境界が提供されないため、パーサーは最後のコロンがポートの区切り文字であるかどうかを確実に判断できません。
ステップ 2: 標準ライブラリを使用して構築する
ポートを文字列に変換し、net.JoinHostPort を呼び出します。ホストにコロンが含まれている場合、角括弧が追加されます。DNS 名と IPv4 は通常の形式のまま残ります。また、ゾーン識別子も保持されるため、呼び出す前に角括弧を追加したりテキストをエスケープしたりしないでください。
func dialAddress(host string, port int) (string, error) {
if port < 1 || port > 65535 {
return "", fmt.Errorf("port out of range: %d", port)
}
return net.JoinHostPort(host, strconv.Itoa(port)), nil
}ステップ 3: 入力の形状を定義する
API は、すでにポートが含まれているオーソリティではなく、単体のホストを受け入れるべきです。完全なアドレスをサポートする必要がある場合は、まず net.SplitHostPort を呼び出し、各要素を正規化してから JoinHostPort で再構築します。文字列置換は IPv6 やゾーンに対して安全ではありません。
ステップ 4: ゾーン識別子を処理する
IPv6 リンクローカルアドレスは fe80::1%eth0 となる場合があり、[fe80::1%eth0]:443 を生成します。ゾーンはインターフェーススコープを保持しており、削除してはなりません。ログやダウンストリームの設定に制御文字や予期しないオーソリティテキストが侵入しないよう、許可された文字と長さを検証してください。
ステップ 5: 完全なアドレスを検証およびパースする
構築後、net.SplitHostPort を呼び出して可逆性を確認し、ホストとポートが入力と一致していることをアサートします。これにはポートを含むネットワークアドレスが必要です。ポートの欠落、余分なコロン、不一致の角括弧は、実用的なエラーを返す必要があります。
ステップ 6: go vet hostport を理解する
Go 1.25 では hostport アナライザーが追加され、net.Dial のアドレス構築に使用される fmt.Sprintf("%s:%d", host, port) パターンにフラグが立てられます。これはよくあるミスを検出しますが、設定コントラクト、ゾーン検証、ポート範囲の確認、実際のネットワークスタックテストの代わりにはなりません。
ステップ 7: テストマトリクスを構築する
127.0.0.1:443、[2001:db8::1]:443、[fe80::1%eth0]:443、DNS 名、空のホスト、ポート 0、65536、負の値、およびすでに角括弧で囲まれた入力をカバーします。サポートされている環境では、IPv4 および IPv6 ループバックリスナーに接続します。それ以外の場合は、純粋な構築および分割のテストを維持し、CI の機能を文書化してください。
質の高い模範解答
「欠陥は Dial ではなくアドレス構文にあります。IPv6 ホストにはコロンが含まれるため、単純な文字列連結ではポートを区切ることができません。私は API を単体ホストと整数ポートとして定義し、1..65535 をチェックして、net.JoinHostPort(host, strconv.Itoa(port)) を呼び出します。決して手動で角括弧を書くことはありません。完全なオーソリティを受け入れる場合は、まずそれを分割し、正規化されたホストとポートから再構築します。リンクローカルアドレスのために %eth0 を保持および検証します。テストは IPv4、IPv6、ゾーン、DNS、無効なポート、および可逆的なパースを網羅します。Go 1.25 の go vet hostport は静的ゲートであり、IPv4/IPv6 ループバック接続によって統合カバレッジを提供します。」
よくある間違い
fmt.Sprintf("%s:%d", ...)を使い続ける → IPv6 ホストとポートの境界が曖昧になる →net.JoinHostPortを使用する。- すべてのホストに手動で角括弧を追加する → IPv4 および DNS のフォーマットが不正になる → 標準ライブラリに判断させる。
%zoneを削除する → リンクローカル IPv6 がルーティングできなくなる可能性がある → ゾーンを保持し検証する。- 完全なオーソリティを単体のホストとして扱う → ポートや角括弧が重複する → 入力コントラクトを定義し、必要に応じて分割する。
- IPv4 のみをテストする → 本番環境での障害パスがカバーされないままになる → IPv6、ゾーン、ループバックテストを含める。
- go vet のみに依存する → カスタム入力や実行時エラーが残る → 単体テスト、分割テスト、接続テストを組み合わせる。
- ポートの検証をスキップする → 負の値やオーバーフローした値が設定に入り込む → 構築前に範囲を確認する。
- 未検証の生アドレスをログに記録する → 制御文字や機密設定が漏洩する可能性がある → 正規化して構造化フィールドとしてログに記録する。
フォローアップ質問
フォローアップ 1: JoinHostPort はホストが有効な IP であることを検証しますか?
これはネットワークアドレス構文に従ってホストとポートを結合するだけであり、DNS 解決や完全なビジネス検証ではありません。ドメイン、ゾーン、または特殊文字が許可されるかどうかは、入力コントラクトとリゾルバーパスに属します。
フォローアップ 2: なぜ単純に最後のコロンを使用しないのですか?
IPv6 の圧縮表記、ゾーン、ポートの欠落により、文字列ベースのルールは脆弱になります。標準の Join/Split のペアはアドレス構文に従い、角括弧やポートのエラーを明示的に報告します。
フォローアップ 3: ポート文字列を直接渡すことはできますか?
はい、JoinHostPort は文字列のポートを受け入れます。ビジネスロジックの入力が整数の場合は、範囲を検証し strconv.Itoa で変換することで、負の値やオーバーフローした値がアドレスにフォーマットされないようにしてください。
フォローアップ 4: ゾーンはどのようにサニタイズすべきですか?
インターフェースに必要なゾーンのセマンティクスを維持しつつ、使用可能な文字セットと長さを制限します。% を二重エンコードしないでください。可能な場合は、ホストとゾーンを個別の構造化フィールドとしてログに記録します。
フォローアップ 5: 代わりに url.URL を使用すべきなのはどのような場合ですか?
スキーム、オーソリティ、パスを含む HTTP または HTTPS の URI を構築する場合は url.URL を使用します。net.Dial に渡す host:port には JoinHostPort を使用してください。URI を手動で文字列連結しないでください。
フォローアップ 6: 修正が実際の IPv6 で機能することをどのように証明しますか?
IPv6 対応の CI または専用環境で [::1]:0 リスナーをバインドし、割り当てられたポートを読み取って、同じコンストラクタ経由で接続します。IPv6 のない環境向けには純粋関数テストを維持し、その違いを文書化してください。