IO-Data製ルーターWN-7T94XRでVRChat(オンラインゲーム)が快適に遊べないことがある
整理
サポートに投げたが、少し情報が散らばっていたり記載していない事もあるため整理する。
- 今回確認している事象はVRChatのプレイ時にUDPにてphotonサーバーとの通信が失敗すること。
- 本事象はAtermルーターでは確認できなかった事象であり、WN-7T94XRの再起動や初期化では改善しなかった
- WN-7T94XRのセキュリティ機能は全てOFFにしている。ちなみに接続方式はIPv4 over IPv6(MAP-E)のIPv6ブリッジ
- 事象最初期はVRChat/photon側を疑ったが、現在その可能性は低いと考えている。この理由は類似報告を発見できなかったことや、後のEC2をphotonサーバーと見立ててたテストにてルーターで挙動の差を確認できたためである。
- ルーター側の挙動で確認できている事象としてはPC側から模擬photonサーバーへのUDP通信で、WN-7T94XR側が失敗の数具体的にはインターネットに出ていない件数が有意(ぽい感じ)に大きいことである。これの理由は趣味の範囲で調査している私では追求できない部分と判断。ただし、推測としてCE内のMAP(NAPT)やセキュリティ等が干渉している可能性を指摘している。
- 特に私のPCでは、VRChatをプレイしている際の通信をWiresharkで覗いていると、PCからphtonマスターサーバーへのUDP通信で用いられるSourcePortが6万台になると失敗している様な印象である。恐らくVRChat側でPCからの通信でSourcePortを制御していない(推定OS制御)ため、本来使えるポートからの通信をルーター側で弾いているのではないかというのが考えている事である。
- ただソフトウェア的な原因追及には限界があり、pcとルーター間、ルーターとONU間に観測機器とかいれたりルーター内のログを見たりは私ではできないので結構お手上げという状況
- PC→L2スイッチ(多分)→ラズパイ模擬Photonサーバーでのテスト結果として、成功数がWN-7T94XRよりも大きい事がわかる。このことからWindowsからパケットが出ていない事象は考慮しなくても良い
注意
取り扱っている問題が長期化しており、文面が安定していません。
閲覧にはご注意ください。
起きてるトラブル

Connection画面で止まります。
そしてこうなります

切断されました。
リトライを繰り返すと入れたりするので、完全に切れてるわけでもないし、1度入ると基本的にはじかれたりしないため、接続の際どこかとのやりとりが失敗していそうです。
WN-7T94XRって?
IO-DATA発売のちょっと高価なルーターです。
高価なルーターなのでトラブルがあってもそうそうと買い換えれません。
VRChatのログを見る

というわけでVRChat側のログを見ていきます。
関係ありそうなログを見つけることが出来ました。
2024.07.17 21:50:43 Error - Connection lost. OnStatusChanged to TimeoutDisconnect. Client state was: ConnectingToMasterServer. SCS v0 UDP SocketErrorCode: 0 AppOutOfFocus WinSock
2024.07.17 21:50:43 Log - [Behaviour] OnDisconnected: ClientTimeout
2024.07.17 21:50:43 Error - [Behaviour] {{ NameServerAddress: 'ns.photonengine.io:5058', MasterServerAddress: '7F9F9FE877148C1A1A3D601BE243D675.exitgames.com:5055', GameServerAddress: '', Code: 'ClientTimeout', State: 'Disconnected', Time: '0' }}
1行目のエラーがSocket通信をマスターサーバーと行おうとしたが、失敗していること。その際に用いられているプロトコルはUDPな事。
3行目のエラーは接続先の情報です。
これらのことから情以下の情報を得られました。
- VRChatはns.photonengine.ioというサービスを用いている。https://www.photonengine.com/ja-jp マルチプレイ用のサービスを提供しているようです。
- Socket通信かそれに近い領域で失敗している。
よって以下の推測が出来ます。
- Socket通信の最初が失敗している。接続後は安定することからSocket通信が開始した後は影響ない事からそう考えられる。
- photonとの接続で失敗している。
情報を集める
Photonの公式Doc
日本語版で恐縮ですが、以下のサイトが見つかりました。
Pun 2 接続が切断された場合の調査 | Photon Engine
内容から
別のサーバーまたはリージョンを使用
は少なくともVRChatのワールド生成で他のリージョンを選ぶという事では回避出来ない事がわかりました。
他のWifiやルーターなどを試してみてください。
やりたくないです……。せめてVRChatにバグ報告か、ルーターの仕様が悪いのかの確認をしたいです。
Wireshark
Wiresharkを用いたデバッグ方法が提案されています。これはやってみましょう。
ただ私の知見がないため、少し後回し。
VRChat Doc
World Debug Views | VRChat Creation
起動時オプションに「--enable-debug-gui」を設定して起動するとデバッグモードが解禁されるとのことです。ワクワクしますね。

ただ、あまりログで出力する内容と差はありませんでした。ワールド作成用のデバッグモードのためさもありなん。
VRChat Feedback
Connecting to a Discord Voice Chat Can Sometimes Cause a Disconnect.
がDiscordを使用している場合類似のエラーを報告しています。
が、私と全く同じような報告は見つけられませんでした。
(検索条件: WinSock,photon)
このことからルーター固有の挙動が疑われます。
調査方法
Wiresharkを用いる方法で継続。
VRChatはログレベルを--enable-sdk-log-levelsを追加する事で引き上げることは可能だがそちらでは有益な情報は見られなかった。
被疑箇所の明示

現状の私の理解だと、VRChatのサーバーといっても大きく2つ存在している。
1つが画像上のVRChat自体が持っているAPI系の処理であったり資源を格納しているAPIサーバー、こちらは推定だが問題ないと考えている。単純にエラーログでphoton関係のサーバーが疑われる。
ルーターを変える前は問題なかったので、ルーターがphotonが使用しているPortか何かを弾いているのではないかと考えているため、それをWiresharkで確認したいです。
ただ、UDPおよびWebSocketはTCPと異なり応答確認の様な分かりやすいパケットがなかった気がする………
少しそれぞれの仕様を確認する時間が必要。
調査メモ
photonの利用Port番号: https://doc.photonengine.com/ja-jp/pun/v1/connection-and-authentication/tcp-and-udp-port-numbers
どれかルーター弾いてるんじゃないかな?リトライで成功するから、全部は弾いていないけど特定のPortがDenyな挙動似思える。 宛先Portで明確に弾く物はなさそう。以降のServerスクリプト書いてるときに確認した気がする。
この場合、プロトコルの仕様を確認しなくても、最悪沢山パケットキャプチャしてゲームサーバーからの応答で見られないポート番号を探したりという方向性もある。
ただ、これならIO-DATAとかに「IPv6+のMAPEで明確に弾くポートない?」って聞いたら答えてくれないかなぁ
Port Memo
ポート番号 プロトコル 目的
5058または27000 UDP クライアントからネームサーバー (UDP)
5055または27001 UDP クライアントからマスターサーバー (UDP)
5056または27002 UDP クライアントからゲームサーバー (UDP)
4533 TCP クライアントからネームサーバー (TCP)
4530 TCP クライアントからマスターサーバー (TCP)
4531 TCP クライアントからゲームサーバー (TCP)
9090 TCP クライアントからマスターサーバー (WebSocket)
9091 TCP クライアントからゲームサーバー (WebSocket)
9093 TCP クライアントからネームサーバー (WebSocket)
19090 TCP クライアントからマスターサーバー (安全なWebSocket)
19091 TCP クライアントからゲームサーバー (安全なWebSocket)
19093 TCP クライアントからネームサーバー(安全なWebSockets)
左SourceがローカルIP
- 63664->5055 NG
- 49534->5055 OK,Source対応が逆の物を確認。その後5056と56032でやり取り。
- 64299->5056 NG
読む: RFC 7597
RFCを読んで
関係ありそうなところだけ読んだ

ルーターの挙動について
ポートが開いているUDP APIのIPv4のサーバーを1台こっそり立ててそこにリクエストを送ってどうなるか調べたらいいじゃないか。
というわけでUDPサーバーをEC2に建てて通信が失敗する事はあるか確認してみる。
サーバー側は以下のスクリプトにてphoton社のサーバーと同様のポートが開いている様にする。
なぜこのポートかは調査メモを参照
import socket
import threading
def log_connection(server_addr, server_port, client_addr, client_port, protocol, message=""):
with open('connections.txt', 'a') as f:
f.write(f"Protocol: {protocol}, ServerIP: {server_addr}, ServerPort: {server_port}, ClientIP: {client_addr}, ClientPort: {client_port}, Message: {message}\n")
# TCP server functions
def handle_tcp_connection(client_socket, client_address, server_port):
message = client_socket.recv(1024).decode('utf-8')
log_connection('0.0.0.0', server_port, client_address[0], client_address[1], "TCP", message)
client_socket.sendall(f"Received on port {server_port}".encode('utf-8'))
client_socket.close()
def start_tcp_server(port):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('0.0.0.0', port))
server_socket.listen(5)
print(f"TCP server listening on port {port}")
while True:
client_socket, client_address = server_socket.accept()
threading.Thread(target=handle_tcp_connection, args=(client_socket, client_address, port)).start()
# UDP server functions
def handle_udp_connection(server_socket, port):
while True:
message, client_address = server_socket.recvfrom(1024)
message = message.decode('utf-8')
log_connection('0.0.0.0', port, client_address[0], client_address[1], "UDP", message)
server_socket.sendto(f"Received on port {port}".encode('utf-8'), client_address)
def start_udp_server(port):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('0.0.0.0', port))
print(f"UDP server listening on port {port}")
threading.Thread(target=handle_udp_connection, args=(server_socket, port)).start()
if __name__ == "__main__":
tcp_ports = [4533, 4530, 4531, 9090, 9091, 9093, 19090, 19091, 19093]
udp_ports = [5058, 5055, 5056, 27000, 27001, 27002]
for port in tcp_ports:
threading.Thread(target=start_tcp_server, args=(port,)).start()
for port in udp_ports:
threading.Thread(target=start_udp_server, args=(port,)).start()
クライアント側は任意のIPに対して、送信元portと送信先portを指定したTCPコネクション、UDPのパケットを送る機能をもったスクリプトを準備。
送信元portは範囲していして順番に送れる。
import socket
import argparse
import time
def log_result(server_ip, port, success, protocol, response="", local_port=None):
filename = f'{protocol}_results.txt'
with open(filename, 'a') as f:
log_entry = f"ServerIP: {server_ip}, Port: {port}, Success: {success}, Response: {response}"
if local_port:
log_entry += f", LocalPort: {local_port}"
log_entry += "\n"
f.write(log_entry)
def udp_connect(server_ip, target_port, local_port, timeout):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
if timeout is not None:
sock.settimeout(timeout)
if local_port:
sock.bind(('', local_port))
message = "To:{local_port}"
sock.sendto(message.encode('utf-8'), (server_ip, target_port))
response, _ = sock.recvfrom(1024)
log_result(server_ip, target_port, "Success", "UDP", response.decode("utf8"), local_port)
except OSError as e:
log_result(server_ip, target_port, f"Failed to bind local port {local_port} ({e})", "UDP", local_port=local_port)
except Exception as e:
log_result(server_ip, target_port, f"Failed ({e})", "UDP", local_port=local_port)
finally:
sock.close()
def tcp_connect(server_ip, target_port, local_port, timeout):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
if timeout is not None:
sock.settimeout(timeout)
if local_port:
sock.bind(('', local_port))
sock.connect((server_ip, target_port))
response = sock.recv(1024)
log_result(server_ip, target_port, "Success", "TCP", response.decode("utf8"), local_port)
except OSError as e:
log_result(server_ip, target_port, f"Failed OSError{local_port} ({e})", "TCP", local_port=local_port)
except Exception as e:
log_result(server_ip, target_port, f"Failed ({e})", "TCP", local_port=local_port)
finally:
sock.close()
def scan_ports(server_ip, target_port, protocol, start_port=None, end_port=None, timeout=None):
if start_port is None or end_port is None:
print("Error: Both start_port and end_port must be specified.")
return
for local_port in range(start_port, end_port + 1):
if protocol == "UDP":
udp_connect(server_ip, target_port, local_port, timeout)
elif protocol == "TCP":
tcp_connect(server_ip, target_port, local_port, timeout)
else:
print("Unsupported protocol. Please use TCP or UDP.")
# if local_port % 5 == 0:
# time.sleep(1) # 短い待機時間を追加してリソースの開放を促進
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="TCP/UDP Client")
parser.add_argument('server_ip', type=str, help='対象サーバーのIP')
parser.add_argument('target_port', type=int, help='対象のポート')
parser.add_argument('protocol', type=str, choices=['TCP', 'UDP'], help='プロトコル')
parser.add_argument('--start_port', type=int, help='開始ポート')
parser.add_argument('--end_port', type=int, help='終了ポート')
parser.add_argument('--timeout', type=float, help='タイムアウト時間(秒)', default=1)
args = parser.parse_args()
if (args.start_port is None) != (args.end_port is None):
print("Error: Both start_port and end_port must be specified together.")
else:
scan_ports(args.server_ip, args.target_port, args.protocol, args.start_port, args.end_port, args.timeout)
スクリプトは大体ChatGPTに書いて貰いました。
WN-7T94XRでお手製photonサーバーに接続してみる
python .\client.py ${EC2_IP} 5055 UDP --start_port 49152 --end_port 65535 --timeout 0.5
でお手製photonサーバーの5055ポートにたいして、クライアント側は動的ポート(IANA定義)全てで1回ずつ送受信できるかのチェックを行った。
結果のログはそこそこ大きいので省略するが、クライアント側で返答まで確認できたのは418回に留まった。サーバー側に到達しているのも同程度。(どちらかログ消し忘れてからテスト開始してしまったのか、数が一致していない。要調査。一旦クライアント側の件数全てをサーバー側で処理できているとする)
通常、動的ポートは他の機器やアプリも用いているため送信元ポートにしていると(通常は指定しないでOSやライブラリ側でポート指定して貰っているという理解)失敗する事もあるとは思っていたが、1万5千回近い送信に対して送受信確認できたのが3%程度となったのは予想に反する。
この理由が不明である。
ちなみに、同時にWiresharkも動かしているので送信元ポートのままPCを出ていることは確認できている。(WiresharkからPCを出るまでに送信元Portが変更されていなければ)
例えば以下はクライアントスクリプトを実行したWindowsで使えるUDPのポート範囲を見るが、今回のスクリプトの引数範囲と同様であるように見える。
netsh int ipv4 show dynamicport udp
プロトコル udp の動的ポートの範囲
---------------------------------
開始ポート : 49152
ポート数 : 16384
このことから以下の予測ができる
- PCからの出力には問題無いが、ルーター側の出力時弾いている
- PCからの出力にも問題が無く、ルーター側でも問題無いが経路で消えている
- 〃サーバー側で弾いている(ただ、これは気にしない。通信先がデモサーバーであるため)
- 〃サーバー側でも問題無く、応答をルーター側で弾いている。
- 〃、応答はルーターも通っているが、PCで弾いている(これも気にしない。ブロックする要素が重い足らない)
この整理からルーターを変えた場合どうなるのかが気になる。また、もう一度テストしてちゃんとクライアントで送受信成功している件数とサーバー側で記録している数が一致するか確認したい。
テストは時間かかって面倒なので、とりあえずルーターを変えてテストしてみる。
Aterm WG2600HS2との比較
同じ事をしてみた。
ちゃんと見てないけど、クライアント側の成功数が以下。
上のwc -lがAtermで成功した回数で下がWN-7T94XRの成功回数。
注意としては、それぞれの実施時間は異なるし、EC2は一回落としてるから宛先のIPも変わってるという変数が沢山あること。
ただ、それにしても有意に成功数に差が出ているように見える。
一応、図上の変更点はルーターのみでありPC側でSourcePortとして正常に動かないPortが一定数あるにしてもそれが時間で3000も増減するかというとNOというが普通だと思われる。
quaile@DESKTOP:/mnt/c/Users/quaile/develop/script$ grep -v "timed out" UDP_results.txt | wc -l
3585
quaile@DESKTOP:/mnt/c/Users/quaile/develop/script$ grep -v "timed out" 0724/UDP_results.txt | wc -l
419
ちなみにAtermだとやはりVRChatで同事象は起きていないように見える。
サーバー側の成功数も以下。上下関係は同じ
[ec2-user@ip- ~]$ wc -l connections.txt
3605 connections.txt
[ec2-user@ip- ~]$ wc -l 0724/connections.txt
304 0724/connections.txt
推論
WN-7T94XR側のファイアウォール設定、あるいはポートフォワーディング機能が干渉あるいは。NAPTテーブルなどPC側SourcePortによる振り分けや干渉が過剰?性能不足?
Wiresharkでボーッと確認した所感ではあるが、VRChat->WN-7T94XR->phton serverでUDP使用とするとき、VRChatがSourcePortで5万後半か6万台のPortを割り当てられて通信を試みる事が多いのだが、この6万台だと上手く接続できていない気がする。
これ以上は状況証拠固めしかできないため、雑多ではあるがサポートへ連携する。
ちなみにUDP通信に対して失敗と言ってるのは応答の通信がないと言うことをさしてます。
サポートからこれで情報出てこなかったら同製品1台+他のIO製品+NW観測できる機器かって検証で遊ぼう
専門外の領域だけどこういうの楽しい
課題
結局今回の問題の調査に時間がかかった要因として最も大きいのは、PC→WN-7T94XR→インターネットのどこで止まっているかがわからない事である。
今からでも正直Windowsやオメーの環境が悪いよってなる可能性もある。 Raspberry Piでのテストでクライアント(送信元)が悪い可能性は限りなく減った
なのでその部分を私の環境で追求する方法を検討する。
- リクエスト元(PC)の環境を変える。Windowsは悪くありませんと言えるようになります。
- サーバーをローカルエリアNWの中に作成し、リクエスト元とリクエスト先の間にルーターを通さないようにする。L3スイッチかPC→Serverを直結させる事で実現できる。(L3スイッチ持ってないけど欲しい。……L3スイッチてUDPの中身見るっけ?だめならルーティング機能だけのルーター用意かな。)サーバーはラズパイ4Bが1台ある。
Raspberry Piで遊ぶ
もしルーターを経由しない場合模擬サーバーと通信できるのか確認するため、Raspberry PiでサーバーをローカルNWに建てます。
※航続手順の関係で実態はWifi設定なし



PCとRaspberry PiをETQG-ESH05(スイッチングハブ)経由で接続する
LAN接続のみを許可しているラズパイとPCを同じスイッチに接続する。


こんな感じでラズパイ側からPC側のアプリケーションも見れるし、

PC側からtracerouteしても直接接続しているのが判る。
ヨシ
という事で、ルーターを通してやっていた事をローカルネットワーク内でやってみる。
リクエスト元はWindowsPCで、宛先は同Subnet内のラズパイサーバー

爆速。あまりに早すぎて実行するスクリプト間違えたかと思った。1万5千リクエストくらいならこのL2スイッチはちゃんと捌ききってくれました。偉いぞ。
結果をみると明らかに成功数が増加している。
今回失敗したポートは他に使用されていたりして利用できないと思われる。実施時間的にタイムアウトの時間も含まれていないのが多いという推測ができる。
このことからWN-7T94XRで失敗していたパケットの多くはWindowsから出ているのはわかる。
Windowsが被疑箇所にはなりえない。
時間あるときに調べる
OpenWrt
WN-7T94XRの下にETQG-ESH05(スイッチングハブ)を置いて、そこにPCとiPad接続したらルーターの挙動が安定しない。具体的には192.169.0.1へのアクセスが処理落ちしているような挙動をして有線インターネットが使えない。WI-FIは使える。なるほーど?
IO-dataの製品ダメなんじゃないかな。どっちも家庭用ならそこそこの機器だぞ
ちなみにまだ解決していない。
原因はこのルーターではあるが調査進んでいないとのこと。
新しいルーター買って改善したので一旦ここは閉じる。