Java × Linux ネットワークプログラミング:複数インターフェース・ポートバインドを制御する方法

はじめに

Java でネットワークプログラミングを行う際、ServerSocketDatagramSocket を使ってポートをバインドするのは基本的な操作です。
しかし、Linux 環境で複数のネットワークインターフェース(NIC)を持つサーバでは、「どのインターフェースにバインドするか」を制御することが重要になります。

例えば以下のようなケースです。

  • 同一サーバ上で内外ネットワークを分離して運用したい
  • ロードバランサや仮想ネットワーク上で特定の IP にのみ応答したい
  • UDP ベースのアプリケーションで、送信インターフェースを明示したい

本記事では、Java で複数インターフェースやポートのバインドを制御する方法を、Linux ネットワークの観点から詳しく解説します。


1. Linux 環境におけるネットワークインターフェースの確認

まず、Linux で利用可能なインターフェース一覧を確認します。

$ ip addr show

出力例:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536
    inet 127.0.0.1/8 scope host lo
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    inet 10.0.0.10/24 brd 10.0.0.255 scope global eth1

このように、eth0(社内LAN)と eth1(外部用)といった複数NICがある場合、JavaアプリがどちらのIPを利用するかを明示することが可能です。


2. ServerSocketで特定のIPにバインドする

通常のServerSocket(全インターフェースにバインド)

ServerSocket server = new ServerSocket(8080);

上記のようにIPを指定しない場合、0.0.0.0:8080 にバインドされ、全インターフェースで待受けが行われます。

特定インターフェースへのバインド

InetAddress bindAddr = InetAddress.getByName("192.168.1.10");
ServerSocket server = new ServerSocket(8080, 50, bindAddr);

これにより、192.168.1.10(eth0)にのみバインドされます。
つまり、eth1(10.0.0.10)経由のアクセスは拒否されます。


3. DatagramSocket(UDP)の場合

UDP でも同様に、InetAddressを指定してバインドできます。

InetAddress localAddr = InetAddress.getByName("10.0.0.10");
DatagramSocket socket = new DatagramSocket(new InetSocketAddress(localAddr, 9999));

これで eth1(10.0.0.10)上のポート9999で受信します。

送信インターフェースを明示的に指定する(NetworkInterface)

送信時に特定インターフェースを使用したい場合:

NetworkInterface ni = NetworkInterface.getByName("eth1");
socket.setNetworkInterface(ni);

これにより、パケット送信が指定インターフェースから行われます。


4. NIOを使った複数ポート・インターフェース制御

NIO(java.nio.channels)を使うと、非同期・マルチポートでの待受けが容易になります。

try (Selector selector = Selector.open()) {
    InetSocketAddress address1 = new InetSocketAddress("192.168.1.10", 8080);
    InetSocketAddress address2 = new InetSocketAddress("10.0.0.10", 9090);

    ServerSocketChannel server1 = ServerSocketChannel.open();
    server1.bind(address1);
    server1.configureBlocking(false);
    server1.register(selector, SelectionKey.OP_ACCEPT);

    ServerSocketChannel server2 = ServerSocketChannel.open();
    server2.bind(address2);
    server2.configureBlocking(false);
    server2.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
        selector.select();
        for (SelectionKey key : selector.selectedKeys()) {
            if (key.isAcceptable()) {
                ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                SocketChannel client = channel.accept();
                System.out.println("接続: " + client.getRemoteAddress());
            }
        }
        selector.selectedKeys().clear();
    }
}

このコードでは、複数のインターフェース・ポートを同時に待受けSelectorでイベント駆動的に処理します。


5. ポート競合・バインドエラー対策

複数プロセスや同一アプリ内でバインド競合が起こると、以下の例外が発生します

java.net.BindException: Address already in use

この場合の対策は:

  • 別ポートを使用する
  • プロセスを確認して停止する $ sudo lsof -i :8080
  • SO_REUSEADDRを設定する(TCP再利用)
ServerSocket socket = new ServerSocket();
socket.setReuseAddress(true);
socket.bind(new InetSocketAddress("192.168.1.10", 8080));

6. IPv6 対応と dual stack の挙動

Java では、デフォルトで IPv4/IPv6 のデュアルスタックが有効です。
IPv6 アドレスでバインドする場合は

InetAddress addr6 = InetAddress.getByName("fe80::1");
ServerSocket server = new ServerSocket(8080, 50, addr6);

必要に応じて java.net.preferIPv4Stack プロパティで制御可能です

-Djava.net.preferIPv4Stack=true

7. まとめ

要件使用クラス設定例
TCPで特定IPにバインドServerSocketnew ServerSocket(port, backlog, bindAddr)
UDPで特定NICから送受信DatagramSocket + setNetworkInterface()
複数ポート・非同期通信ServerSocketChannel + Selector
IPv6対応InetAddress (IPv6指定)

Linuxサーバ上でJavaを運用する際、インターフェースやポートのバインド制御を正しく行うことで、
セキュリティ・性能・柔軟性の高いネットワークアプリケーションを構築できます。

タイトルとURLをコピーしました