はじめに
aioice は、aiortcの作者、ジェレヌ・レネさん(って読み方であってると思うんですが...)による ICE(Interactive Connectivity Establishment) の python による実装です
examplesを見てて、この client と signaling server そのままで P2P の connection が作れそうな気がしてきたので試してみた所、掲題の件、確認できましたので僭越ながらその旨のご報告をさせていただきます次第です
前提知識の整理と、全体のまとめ
こちらの記事、WebRTCの基本とP2P通信が成立するまでを学ぶ
が大変わかりやすく解説してくださっています
本稿は、上記記事の構成要素の中で、以下を aioice 及びその example の実装を利用して RaspberryPi 同士の P2P 通信を試みるます
- ICE
- Signaling Server
本稿を一言で説明すると、Signaling Server を通じて両Raspberry Pi の ICE候補を交換し、その中で成功した接続を利用して P2P 通信をおこないます
ちなみに、以下は Google のサービスを利用します
- STUN
以下は利用しません
- TUNE
- WEB Browser 及び Web Server
また、WebRTC の data channel も利用していません
構成
STUN, TUNE
サンプルそのままなので STUN は stun.l.google.com、TUNE はナシです
Signaling Server
Digital OCEAN に Ubuntu 18.04.1 の python 3.6 で examples の signaling-server.py を起動しました
こちらは async def
を使っているので python 3.6 以上でないと動かないと思います
offer 側
2018年10月9日版 RASPBIAN STRETCH LITE 上で examples の ice-client.py を実行しました
offer 側は、USB 3G ドングルで、以下のように ppp でドコモの APN に接続しています
PPP 接続は こちらを利用して実現し、その歳に、soracom の SIM と DoCoMo L-05a を利用しました
pi@raspberrypi:~ $ ifconfig
eth0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
ether b8:27:eb:f4:79:d8 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 87 bytes 14662 (14.3 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 87 bytes 14662 (14.3 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
ppp0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1500
inet 10.199.235.70 netmask 255.255.255.255 destination 10.64.64.64
ppp txqueuelen 3 (Point-to-Point Protocol)
RX packets 109 bytes 16263 (15.8 KiB)
RX errors 5 dropped 0 overruns 0 frame 0
TX packets 106 bytes 16485 (16.0 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
answer 側
ソフトウェア構成は offer 側と同じです
ネットワークは ETHER と WLAN 経由で(なんで2個つけちゃったんだろ ^^;;;; カッコ悪い)OCNに接続しています
pi@raspberrypi:~/aioice/examples $ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.12 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::7a5d:edd6:e62e:b813 prefixlen 64 scopeid 0x20<link>
ether b8:27:eb:44:90:3e txqueuelen 1000 (Ethernet)
RX packets 823 bytes 110646 (108.0 KiB)
RX errors 0 dropped 4 overruns 0 frame 0
TX packets 440 bytes 75355 (73.5 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::ef62:2274:88bf:c84c prefixlen 64 scopeid 0x20<link>
ether b8:27:eb:11:c5:6b txqueuelen 1000 (Ethernet)
RX packets 356 bytes 80065 (78.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 53 bytes 9607 (9.3 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
実行結果
answer 側の実行結果(長いので途中省略しつつ、何をしているのかコメントを追加しました)
pi@raspberrypi:~/aioice/examples $ python3 ice-client.py answer
# ... 途中省略
# signaling server に接続
DEBUG:websockets.protocol:client - state = CONNECTING
DEBUG:websockets.protocol:client - event = connection_made(<_SelectorSocketTransport fd=8 read=idle write=<idle, bufsize=0>>)
DEBUG:websockets.protocol:client - state = OPEN
# ICE candidates の交換
DEBUG:websockets.protocol:client < Frame(fin=True, opcode=1, data=b'{"candidates": ["4f87e19c5641aa12ca08de45eceb9ab9 1 udp 2130706431 10.199.235.70 37527 typ host", "f8ea0185abe4b4573095996bcde68ab9 1 udp 1694498815 103.67.223.29 37527 typ srflx raddr 10.199.235.70 rport 37527"], "username": "FHqM", "password": "Q59D1k099V8joRgcD5lgYy"}', rsv1=False, rsv2=False, rsv3=False)
received offer {'password': 'Q59D1k099V8joRgcD5lgYy', 'candidates': ['4f87e19c5641aa12ca08de45eceb9ab9 1 udp 2130706431 10.199.235.70 37527 typ host', 'f8ea0185abe4b4573095996bcde68ab9 1 udp 1694498815 103.67.223.29 37527 typ srflx raddr 10.199.235.70 rport 37527'], 'username': 'FHqM'}
DEBUG:websockets.protocol:client > Frame(fin=True, opcode=1, data=b'{"password": "Hkx5hqTBu85lngdUfYGSyL", "candidates": ["65643b9f98192d70611f83879e2201cd 1 udp 2130706431 192.168.1.12 45479 typ host", "82106a16154025a8c79f9bf0de3a3f39 1 udp 2130706431 192.168.1.11 53408 typ host", "287b29102e73fb7f49e5fbccdf977807 1 udp 1694498815 123.218.7.133 53408 typ srflx raddr 192.168.1.11 rport 53408", "c7151c9e8b8c314d0e31a9180a96b358 1 udp 1694498815 123.218.7.133 45479 typ srflx raddr 192.168.1.12 rport 45479"], "username": "eEcp"}', rsv1=False, rsv2=False, rsv3=False)
DEBUG:websockets.protocol:client - state = CLOSING
# この後、Candidate Pair のチェックがしばらく続くので省略
# ...
# Peer がつながった!いぇい!
INFO:ice:Connection(0) Check CandidatePair(('192.168.1.12', 45479) -> ('103.67.223.29', 37527)) State.IN_PROGRESS -> State.SUCCEEDED
INFO:ice:Connection(0) ICE completed
connected
# テストとして offer に 'hello' メッセージを送る
sending b'hello' on component 1
# 略...
# offer から 'hello' を受信
received b'hello' on component 1
# 以下、略...
offer側
pi@raspberrypi:~/aioice/examples $ python3 ice-client.py offer
# ... 途中省略
# signaling server に接続
DEBUG:websockets.protocol:client - state = CONNECTING
DEBUG:websockets.protocol:client - event = connection_made(<_SelectorSocketTransport fd=7 read=idle write=<idle, bufsize=0>>)
DEBUG:websockets.protocol:client - state = OPEN
# ICE candidates の交換
DEBUG:websockets.protocol:client > Frame(fin=True, opcode=1, data=b'{"candidates": ["4f87e19c5641aa12ca08de45eceb9ab9 1 udp 2130706431 10.199.235.70 37527 typ host", "f8ea0185abe4b4573095996bcde68ab9 1 udp 1694498815 103.67.223.29 37527 typ srflx raddr 10.199.235.70 rport 37527"], "username": "FHqM", "password": "Q59D1k099V8joRgcD5lgYy"}', rsv1=False, rsv2=False, rsv3=False)
DEBUG:websockets.protocol:client < Frame(fin=True, opcode=1, data=b'{"password": "Hkx5hqTBu85lngdUfYGSyL", "candidates": ["65643b9f98192d70611f83879e2201cd 1 udp 2130706431 192.168.1.12 45479 typ host", "82106a16154025a8c79f9bf0de3a3f39 1 udp 2130706431 192.168.1.11 53408 typ host", "287b29102e73fb7f49e5fbccdf977807 1 udp 1694498815 123.218.7.133 53408 typ srflx raddr 192.168.1.11 rport 53408", "c7151c9e8b8c314d0e31a9180a96b358 1 udp 1694498815 123.218.7.133 45479 typ srflx raddr 192.168.1.12 rport 45479"], "username": "eEcp"}', rsv1=False, rsv2=False, rsv3=False)
received answer {'password': 'Hkx5hqTBu85lngdUfYGSyL', 'username': 'eEcp', 'candidates': ['65643b9f98192d70611f83879e2201cd 1 udp 2130706431 192.168.1.12 45479 typ host', '82106a16154025a8c79f9bf0de3a3f39 1 udp 2130706431 192.168.1.11 53408 typ host', '287b29102e73fb7f49e5fbccdf977807 1 udp 1694498815 123.218.7.133 53408 typ srflx raddr 192.168.1.11 rport 53408', 'c7151c9e8b8c314d0e31a9180a96b358 1 udp 1694498815 123.218.7.133 45479 typ srflx raddr 192.168.1.12 rport 45479']}
# この後、Candidate Pair のチェックがしばらく続くので省略
# ...
# Peer がつながった!いぇい!
INFO:ice:Connection(0) Check CandidatePair(('10.199.235.70', 37527) -> ('123.218.7.133', 45479)) State.IN_PROGRESS -> State.SUCCEEDED
INFO:ice:Connection(0) ICE completed
connected
# Anser に 'hello' を送信
sending b'hello' on component 1
# 略...
# Offer から 'hello' を受信
received b'hello' on component 1
# 以下、略...
無事につながって answer が送ってきた b'hello' を offer が受け取れてます、やったね!
感想
WEB とか関係なしで単にデバイス同士を直結させてセンサデータとかを送受信する時はwebrtc である必要性が限りなく希薄というか、動画だのファイル送信だのといったアプリケーションレイヤが必要にならない普通のデータ送受信なのにこの後、webrtc の data channel をつくらなくても、このまま ice が解決してくれた双方向の connection を生で使えばそれで十分な気がしますのですが、どうでしょうか?
蛇足
ぱっと見でこの signaling-server.py のコードがどう見てもチャットサーバにしか見えず、「多分、間違えて流用元のチャットサーバのコードを commit しちゃったんだろうな、そんな人って僕以外にもいるもんなんだなー」とか思ってその日は寝てしまったのですが、その夜、夢の中にレインボーマンのお師匠みたいな神様があらわれて
「N対Nのチャットサーバは、参加者が answert と offer だけだったら ICE Candidate の交換なのではないか」
とお告げをしてくれて、というのは嘘松で、本当は次の日の朝、朝ごはんを食べてて気がついたので今回の実験をしてみたのでした
朝ごはんはやっぱり食べたほうがいいみたいです
related works
-
トイドローン Tello をWebRTCでつなげてみた
- この記事で aiortc の存在をしりました、どうもありがとうございました
-
WebRTCの基本とP2P通信が成立するまでを学ぶ
- ICE, signaling, STUN, TUNE 等、webrtc の構成要素の役割と、P2P が成立するまでのプロセスをわかりやすく理解させていただきました。ありがとうございました