> For the complete documentation index, see [llms.txt](https://nazenani-stun.firefirestyle.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://nazenani-stun.firefirestyle.net/nattraversal.md).

# Nat越えしてみよう

実際にNat越えしてみましょう。徐々に拡張しながら、STUNへ近づけていきます。

## \[1] サーバーに一時的に配布されたIPとPortを教えてもらう

一時的に配布されているにはどうすれば良いでしょうか? 「実際にサーバーに接続してみて、そのサーバーに、一時的に配布されているIPアドレスとPortが何か教えてもらう。」というのが、STUNの基本的な戦略です。

インターネットに利用している端末は、IPで管理されているのでした。我々はサーバーのIPアドレスを知っているからサーバーへ接続できます。どうように、サーバーも利用者のIPアドレスを知っているから、データを配信できるのです。 このサーバーが認識している、IPアドレスを教えてもらいます。

### Dartで書いてみよう

#### Server側のコード

```
import 'dart:io';
import 'dart:convert';

main(List<String> args) async {
  String svAddr = args[0];
  int svPort = int.parse(args[1]);
  startUDPServer(svAddr, svPort);
  startTCPServer(svAddr, svPort);
}

startUDPServer(String svAddr, int svPort) async {
  RawDatagramSocket socket = await RawDatagramSocket.bind(svAddr, svPort, reuseAddress: true);
  socket.listen((RawSocketEvent event) {
    if (event == RawSocketEvent.READ) {
      Datagram dg = socket.receive();
      String content = "${dg.address.address},${dg.port}\n";
      socket.send(UTF8.encode(content), dg.address, dg.port);
    }
  });
}

startTCPServer(String host, int port) async {
  ServerSocket server = await ServerSocket.bind(host, port);
  server.listen((Socket socket) {
    String content = "${socket.remoteAddress.address},${socket.remotePort}\n";
    socket.add(UTF8.encode(content));
  });
}
```

これは、サーバーにアクセスしてきた、端末のIPアドレスとPORT番号を返すプログラムです。30行程度ですが、十分機能します。

#### Client側のコード

```
import 'dart:io';
import 'dart:convert';

main(List<String> args) async {
  String clAddr = args[0];
  int clPort = int.parse(args[1]);
  String svAddr = args[2];
  int svPort = int.parse(args[3]);

  startUDPClient(clAddr, clPort, svAddr, svPort);
  startTCPClient(svAddr, svPort);
}

startUDPClient(String clAddr, int clPort, String svAddr, int svPort) async {
  RawDatagramSocket socket = await RawDatagramSocket.bind(clAddr, clPort, reuseAddress: true);
  socket.listen((RawSocketEvent event) {
    if (event == RawSocketEvent.READ) {
      Datagram dg = socket.receive();
      print("--");
      print("  [receive udp] ${dg.address.address} ${dg.port}");
      print("  ${UTF8.decode(dg.data,allowMalformed:true)}");
      print("--");
    }
  });
  socket.send(UTF8.encode("test"), new InternetAddress(svAddr), svPort);
}

startTCPClient(String svAddr, int svPort) async {
  Socket socket = await Socket.connect(svAddr, svPort);
  socket.listen((List<int> data) {
    print("--");
    print("  [receive tcp]");
    print("  ${UTF8.decode(data,allowMalformed:true)}");
    print("--");
  });
  socket.add(UTF8.encode(""));
}
```

client側のコードも30行程度ですね!!

### TCP よりも UDPの方がP2Pに向いている

はい、UDPもTCPもIPアドレスを特定する事ができます。しかし、TCPだと、どのようなPort番号が設定されているかを確認する方法がないですね。

```
RawDatagramSocket socket = await RawDatagramSocket.bind(clAddr, clPort, reuseAddress: true);
socket.send(UTF8.encode("test"), new InternetAddress(svAddr), svPort);
```

```
  Socket socket = await Socket.connect(svAddr, svPort);
  socket.add(UTF8.encode(""));
```

UDPで、サーバーに接続する場合は、サーバーとして動作しているSocketを利用して接続できます。しかし、TCPでは、サーバーに接続する場合は、サーバーへ接続専用のSocketを利用しています。

このため、TCPは、サーバーとして待ち受けしているSocketのIPとPortが実際に何を指しているかを、判定できません。

P2Pアプリを実現する場合、UDPを使用した方が、より高い確率でP2P接続する事ができるようになります。

## \[2] 他のサーバーからもアクセスしてもらう

実際に利用してみると、\[1]の方法では上手くいかない場合があります。上手くいかなかったあなた。おめでとうございます。自分で作成した仕組みを色々試していると、上手くいかないものです。 上手くいかないといのは、改善のチャンスですね。ついていますね。ルーターによっては、一時的に配布されるIPが毎回変わったり。Port番号の対応付け方法が、毎回変わる場合があります。

### 一時的なIPが変わる場合でも、P2P接続できる

それでも、相手を選べば、P2P接続可能です。自分自信には接続してもらえなくても、自分から接続しに行けば、つながります。 通信相手が、Nat越え可能な場合は、こちらから接続しに行くという方法が残されています。

| \|Nat越えできる | Nat越えできない |           |
| ---------- | --------- | --------- |
| Nat越えできる   | P2P接続できる  | P2P接続できる  |
| Nat越えできない  | P2P接続できる  | P2P接続できない |

つまり、その端末のIPの状態がわかると、効率良くP2P接続ができます。

### 前もって試しておこう!!

STUNでは、別IP、別PORTから、接続する機能を持っています。前もってIPの状態を調べておくという訳ですね。

#### Server側

```
import 'dart:io';
import 'dart:convert';

main(List<String> args) async {
  String primaryAddr = args[0];
  int primaryPort = int.parse(args[1]);
  String secondaryAddr = args[2];
  int secondaryPort = int.parse(args[3]);
  startUDPServer(primaryAddr, primaryPort, secondaryAddr, secondaryPort);
}

startUDPServer(String primaryAddr, int primaryPort, String secondaryAddr, int secondaryPort) async {
  RawDatagramSocket ppSocket = await RawDatagramSocket.bind(primaryAddr, primaryPort, reuseAddress: true);
  RawDatagramSocket psSocket = await RawDatagramSocket.bind(primaryAddr, secondaryPort, reuseAddress: true);
  RawDatagramSocket spSocket = await RawDatagramSocket.bind(secondaryAddr, primaryPort, reuseAddress: true);
  RawDatagramSocket ssSocket = await RawDatagramSocket.bind(secondaryAddr, secondaryPort, reuseAddress: true);
  Map sockets = {"PP": ppSocket, "PS": psSocket, "SP": spSocket, "SS": ssSocket};
  ppSocket.listen((RawSocketEvent event) {
    if (event == RawSocketEvent.READ) {
      try {
        Datagram dg = ppSocket.receive();
        String request = UTF8.decode(dg.data);
        String content = "${dg.address.address},${dg.port}\n";
        print("udp: ${request}");
        RawDatagramSocket socket = sockets[request];
        socket.send(UTF8.encode(content), dg.address, dg.port);
      } catch (e) {}
    }
  });
}
```

#### Client側

```
import 'dart:io';
import 'dart:convert';

main(List<String> args) async {
  String clAddr = args[0];
  int clPort = int.parse(args[1]);
  String svAddr = args[2];
  int svPort = int.parse(args[3]);
  String type = args[4];
  startUDPClient(clAddr, clPort, svAddr, svPort, type);
}

startUDPClient(String clAddr, int clPort, String svAddr, int svPort, String type) async {
  RawDatagramSocket socket = await RawDatagramSocket.bind(clAddr, clPort, reuseAddress: true);
  socket.listen((RawSocketEvent event) {
    if (event == RawSocketEvent.READ) {
      Datagram dg = socket.receive();
      print("--");
      print("  [receive udp] ${dg.address.address} ${dg.port}");
      print("  ${UTF8.decode(dg.data,allowMalformed:true)}");
      print("--");
    }
  });
  print("##send ${type}");
  socket.send(UTF8.encode("${type}"), new InternetAddress(svAddr), svPort);
}
```

どうでしょうか。30行程度のコードでした。

### STUN の分類方法

STUNでは、もう少し細かく分類してくれます。

| \|PP                   | PS | SP | SS |   |                                                              |
| ---------------------- | -- | -- | -- | - | ------------------------------------------------------------ |
| UDP Blocked            | x  | -  | -  | - |                                                              |
| Open Internet          | o  | -  | -  | o | ifconfigのIPとPORTがSTUNサーバーから受け取ったIPが同じ                        |
| Symmetric UDP Firewall | o  | -  | -  | x | ifconfigのIPとPORTがSTUNサーバーから受け取ったIPが同じ                        |
| Full Cone              | o  | -  | -  | o | ifconfigのIPとSTUNサーバーから受け取ったIPとPORTが違う                        |
| Symmetric NAT          | o  | -  | -  | x | ifconfigのIPとSTUNサーバーから受け取ったIPとPORTが違う。 複数回試しても、異なるIPとPORTが返る |
| Restricted             | o  | o  | -  | x | ifconfigのIPとSTUNサーバーから受け取ったIPとPORTが違う。 複数回試しても、同じIPとPORTが返る  |
| Port Restricted        | o  | x  | -  | x | ifconfigのIPとSTUNサーバーから受け取ったIPとPORTが違う。 複数回試しても、同じIPとPORTが返る  |

といった感じです。サーバー機能を利用して待ち受けできるのは、"Open Internet" と "Full Cone" のみとなります。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://nazenani-stun.firefirestyle.net/nattraversal.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
