HOWTO · Java

Java.Net.SocketException: Permission Denied in Java を修正する

このチュートリアルでは、Java での java.net.SocketException: Permission denied エラーについて説明します。

このページの内容

java.net.SocketException: Permission denied は、ソケット操作がオペレーティングシステムまたはセキュリティ層によって拒否されたことを示します。このメッセージだけでは、どの層が、なぜ拒否したのかは分かりません。まずスタックトレースと、プログラムが実行しようとしていた操作を確認し、その操作に適用されるポリシーを調べてください。サーバーの bind 失敗とクライアントの接続失敗では、考えられる原因も対処方法も異なります。

java.net.SocketException: Permission denied の意味

Java のソケット呼び出しが成功するには、ホスト環境が要求された操作を許可する必要があります。拒否の原因としては、オペレーティングシステムの権限、ファイアウォールやエンドポイントセキュリティのポリシー、コンテナーやクラスターのポリシー、ランタイムやライブラリの制限などが考えられます。これらは調査すべき可能性であり、例外メッセージだけで原因が確定するわけではありません。

このメッセージは、ポートがすでに使用中の場合とも異なります。ポートの競合では通常、アドレスがすでに使用中であることを示す別のメッセージが表示されます。権限拒否は操作が拒否されたことを示すだけで、ポートが予約済みであること、ネットワークが接続を拒否したこと、Java が古すぎることを証明するものではありません。どの制限が有効なのか分かりにくくなるため、複数の設定を一度に変更しないでください。

ソケット操作が失敗する箇所を特定する

まず、プログラムが待ち受けソケットを開こうとしているのか、別のホストへの接続を試みているのかを確認します。サーバーは bind を呼び出してローカルのアドレスとポートを確保します。クライアントは通常、リモートの接続先に到達するために connect を呼び出します。スタックトレースも手掛かりになります。Net.bind、ServerSocketChannel.bind、またはサーバーコネクターの起動処理などのフレームがあれば、ローカルで bind を試みている可能性があります。これらは操作を特定する手掛かりであり、特定のポリシーや原因を証明するものではありません。

たとえば、Tomcat の起動時のスタックトレースに Net.bind と NioEndpoint.bind が含まれていれば、起動処理でローカルコネクターを bind していたことが分かります。Tomcat に設定されたアドレスとポートを確認し、失敗した時刻をオペレーティングシステムやコンテナーのログと照合してください。トレースがクライアント接続を示している場合は、接続先のホストとポートを記録し、送信方向のポリシー、ルーティング、プロキシ設定を調べます。TLS の設定中に失敗しても、それだけで証明書がソケット拒否の原因とは言えません。証明書の設定を変更する前に、失敗したソケット呼び出しを特定してください。

サーバーの bind 権限エラーを修正する

番号の小さいポートが制限されていることは、サーバーが bind できない原因の一つです。しきい値や規則はオペレーティングシステムとその設定によって異なるため、ポート番号が 1024 未満というだけで、すべてのホストでこれが原因だとは判断できません。実際のコネクターポートと、ホストの権限またはケイパビリティのポリシーを確認してください。開発用または内部向けのサービスなら、権限を必要としないポートを選ぶのが最小限の対処となることがよくあります。それに合わせてクライアントの URL またはリバースプロキシの設定も更新してください。本番環境では、必要に応じてプラットフォームがサポートするサービス設定やポート転送を使用します。

Tomcat や Java プロセス全体を、日常的に root または Administrator で起動するのは避けてください。権限を高めると広範なアクセスが許され、アプリケーションの欠陥がホスト全体のリスクにつながる可能性があります。特権ポートでの待ち受けが必要な場合は、オペレーティングシステムがサポートするサービスマネージャー、ケイパビリティ、またはフロントエンドプロキシを最小限の権限で設定し、サービスに必要なアクセスだけを許可してください。

OpenShift などのコンテナープラットフォームでは、開発者のマシンで使えるポートが、ワークロードのセキュリティコンテキストや割り当てられたユーザーによって禁止されることがあります。デプロイメントに設定されたコンテナーポート、実効ユーザーとセキュリティコンテキスト、起動時のプラットフォームイベントと Pod のログを確認してください。プラットフォームのポリシーで許可されたポートと ID を使用します。ワークロードのポリシーを弱めたり、証拠なしにクラスターのファイアウォールが原因だと決めつけたりしないでください。

ファイアウォールとランタイムの制限を確認する

失敗した呼び出しが connect なら、Java プロセスと接続先に適用される送信方向のファイアウォール規則やエンドポイントセキュリティ規則を確認します。bind の失敗なら、ローカルの bind ポリシーと、待ち受けソケットを制限するアプリケーション制御規則を確認してください。該当する拒否ログを調べ、必要なアドレス、ポート、通信方向、プロセスに対して最小限のポリシー変更を依頼するか、変更を加えます。診断のためにファイアウォールやウイルス対策製品を無効にするのは安全な対処ではありません。環境で定められた通常の管理手順に従い、ログを調べるか、対象を限定した一時的な規則を使ってください。

アプリケーションがコンテナー、サンドボックス、マネージドホスティングサービス、またはネットワークを制限するセキュリティポリシーの下で実行されているかも確認してください。実効ユーザー、ケイパビリティ、環境、ポリシーを、動作しているデプロイメントと比較します。サードパーティ製ライブラリに独自の設定要件がある場合もありますが、依存関係を変更する前に、そのドキュメントと失敗した呼び出しを確認してください。サポート対象バージョンの使用やセキュリティ上の理由から Java の更新が適切なことはありますが、エラーメッセージだけでは古いランタイムが拒否の原因だとは分かりません。

システムプロパティ -Djava.net.preferIPv4Stack=true は、IPv4 と IPv6 のスタック選択が関係すると見られる場合に使う、Java 7 時代の古い診断オプションです。bind や connect の権限を与えるものではなく、一般的な解決策として扱うべきではありません。ランタイムと観測されたアドレスファミリーの動作から関連性がある場合に限って使用してください。このオプションを使うために古い Java を導入するのではなく、サポート対象の Java ランタイムを優先してください。

修正を検証する

変更を加える前に、操作、ローカルまたはリモートのアドレス、ポート、ランタイムの実行ユーザー、関連するポリシーを記録してください。原因に対応した変更を一つだけ適用し、同じ操作を再実行して同じログを調べます。サーバーの場合は、意図したアドレスとポートで待ち受けていること、およびサービスが正常に起動することを確認します。クライアントの場合は、意図した接続先に到達し、リモートサービスが接続を受け付けることを確認します。拒否が続くなら、最初に疑った原因への対処だけでは不十分です。広範な権限を追加するのではなく、新しいスタックトレースと拒否記録を使って調査を続けてください。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

public class SocketBindCheck {
    public static void main(String[] args) {
        try (java.net.ServerSocket socket = new java.net.ServerSocket(0)) {
            int assignedPort = socket.getLocalPort();
            if (assignedPort > 0 && assignedPort <= 65535) {
                System.out.println("Ephemeral port assigned");
            } else {
                System.out.println("Unexpected port assignment");
            }
        } catch (java.io.IOException problem) {
            System.err.println("Socket could not open: " + problem);
            System.exit(1);
        }
    }
}
Ephemeral port assigned

要求されたポート 0 は、使用可能なエフェメラルポートをオペレーティングシステムに選ばせます。この検査では、割り当てられたポートが TCP/UDP の有効なポート範囲内にあることを確認し、try-with-resources ブロックによって待ち受けソケットを閉じます。成功時の実行では Ephemeral port assigned と表示され、ステータス 0 で終了します。ソケットのセットアップに失敗した場合は、プログラムが IOException を表示して 0 以外のステータスで終了するため、自動処理でセットアップ失敗と成功を区別できます。原因を特定するには、ホストのログとセキュリティポリシーを確認してください。この検査では、特定の設定済みポート、リモート接続、Tomcat、OpenShift のポリシーは検証しません。

範囲外のポートは別の境界ケースです。Java はオペレーティングシステムに bind を依頼する前にこれを拒否するため、権限ポリシーの診断にはなりません。次の簡単な検査では、この引数レベルのエラーを捕捉し、正常に終了します。

public class SocketPortBoundaryCheck {
    public static void main(String[] args) {
        try {
            new java.net.ServerSocket(65536).close();
            System.out.println("Unexpectedly accepted port");
        } catch (IllegalArgumentException problem) {
            System.out.println("Port rejected before bind");
        } catch (java.io.IOException problem) {
            System.out.println("Socket setup failed");
        }
    }
}