2014年1月8日水曜日

P2P探訪 STUNでNAT越え その2

中継サーバーを間におく事で、お互いのアドレスとポート番号を特定する事ができまはた。UDPをSocketを使用して、通信してみましょう。しかし、残念ながら、多くの皆さんは通信に失敗する事でしょう。

<後述するような中継サーバー(P2PTracker)を試してみましょう>

ルータの制限

なぜだ? UDPならばできそうなものだが?

「アドレスとポートから送り先をたどれない」ならば、そもそもUDPでの通信ができないではないか?
 そもそも、UDPはTCPと違いコネクションを持ちません。UDPは通信相手もUDPパケットに含まれるアドレスとポート番号を頼りにして相手と通信をします。
 ですから、「UDPパケットを受け取ったサーバー」と「そのサーバーからパケット情報をもらったクライアント」では、差が無いように思えます。


残念ながら、ルータによって制限がかけられています。

残念ながらルータには制限がかけられている場合があります。UDPの通信の仕組みはどうあれ、「UDPで送ったパケットを返信するのは、送った先の端末から」なのです。
ルータ制作者の立場にたってみれば、それ以外を想定する必要はないでしょう。

 
例えば、通信した「相手のアドレス」意外は不正な通信の可能性が高いとみなして、パケットを破棄する。(制限付きNAT)
例えば、「通信した相手のポート番号」意外は不正な通信の可能性が高いと見なして、パケットを破棄する。
※ 制限している訳では無くて、効率の問題でそうなっているだけかも知れません。

そもそも、UDPの本来の目的を満たすだけならば、送信相手によって、アドレスとポートを変えても良いでしょう。(シンメトリックNAT)

まとめ/次回予告

UDPに制限がある事を理解して頂けたことでしょう。 次回は、「制限を突破する方法」または、「制限の種類を特定する方法」について解説して行きたいと思います。



サンプルコード

○中継サーバー

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.LinkedList;


public class P2PTracker {

    public static void main(String[] args) {
        P2PTracker tracker;
        try {
            tracker = new P2PTracker();
            tracker.run();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private LinkedList mPeerList = new LinkedList<>();
    private DatagramSocket mSocket= null;

    public P2PTracker() throws IOException {
        mSocket = new DatagramSocket(8080);
    }

    public void run() {
        try {
            do {
                DatagramPacket packet = receive();
                requestPacket(packet.getAddress().getHostAddress(), packet.getPort());
            } while(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public DatagramPacket receive() throws IOException {
            byte[] buf = new byte[1024];
            DatagramPacket packet= new DatagramPacket(buf,buf.length);
            mSocket.receive(packet);
            PeerInfo info = new PeerInfo();
            info.mHost = packet.getAddress().getHostName();
            info.mPort = packet.getPort();
            if(!mPeerList.contains(info)) {
                mPeerList.addFirst(info);
                if(mPeerList.size() > 50) {
                    mPeerList.removeLast();
                }
            }
            return packet;
    }

    public void requestPacket(String host, int port) throws UnknownHostException, IOException {
        byte[] buf = getPeerInfoList().toString().getBytes();
        DatagramPacket packet= new DatagramPacket(
                buf, buf.length,
                Inet4Address.getByName(host), port);
        mSocket.send(packet);
    }

    public String getPeerInfoList() {
        StringBuilder buider = new StringBuilder();
        for(PeerInfo info : mPeerList) {
            buider.append(""+info.mHost+":"+info.mPort+",");
        }

        System.out.println("#s#"+buider.toString());
        return buider.toString();
    }

    public static class PeerInfo {
        public String mHost = "";
        public int mPort = 0;

        @Override
        public boolean equals(Object obj) {
            if(!(obj instanceof PeerInfo)) {
                return false;
            }
            PeerInfo target = (PeerInfo)obj;
            if(target.mHost.equals(mHost) && target.mPort == mPort) {
                return true;
            } else {
                return false;
            }
        }
    }
}



○クライアントの中継サーバと通信する部分

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;


public class P2PClient {

    public static void main(String[] args) {
        P2PClient client = new P2PClient("xx.xx.xx.xx");
        client.run(8081);
    }

    private String mHost = "127.0.0.1";
        public P2PClient(String host) {
        mHost = host;
    }

    public void run(int port) {
        try {
            clientAction(port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void clientAction(int port) throws IOException {
        DatagramSocket socket = new DatagramSocket(port);
        byte[] buf = new byte[1024];
        DatagramPacket sendPacket= new DatagramPacket(
          buf,buf.length,
          Inet4Address.getByName(mHost), 
          8080);
        socket.send(sendPacket);

        DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
        socket.receive(receivePacket);
        System.out.println("#cl#"+new String(receivePacket.getData()));
        socket.close();
    }
}


....
...
..


0 件のコメント:

コメントを投稿

mbedtls dart の 開発を始めた web wasm ffi io flutter

C言語 で開発した機能を、Dart をターゲットとして、Web でも サーバーでも、そして Flutter  でも使えるようにしたい。 そこで、mbedtls の 暗号化の部分を Dart 向けのPackageを作りながら、 実現方法を試行錯誤する事にした。 dart...