はじめに
皆様、いかがお過ごしでしょうか。
SORACOM Discovery 2025のプロトタイピングコーナーでは「激安!かんたん!自作IoTボタン」という地味な展示をしていたT.A.C.です。
遅ればせながら、ようやく振り返りを兼ねてアーキテクチャーの詳細を解説します。
答えだけ欲しい人は「配布してるファーム焼いてください」以上
SORACOM Arcとは?
まず、SORACOM Arcとは何者か?を触れます。
以下にあるとおり、SORACOM Arcという魔法の実体はWireGuardそのものです。
そのため、WireGuardが動作する環境であれば、理屈の上ではどこからでもSORACOMに接続することができます。
SORACOM Arc は任意の IP ネットワークから SORACOM プラットフォームへのセキュアなリンクを提供するサービスです。 お客様は SORACOM Air だけでなく、Wi-Fi や有線通信、衛星通信といったあらゆる IP ネットワークから SORACOM の各種プラットフォームサービスをセキュアにご利用いただけます。
WireGuard を使用した VPN
デバイスと SORACOM プラットフォーム間でバーチャルネットワークを作成し、WiFi などの環境下でもセキュアに SORACOM プラットフォームを使用できます。
引用元:サービス紹介ページ
WireGuardの動作条件
例が多すぎて割愛しますが、WireGuardは様々な環境で動作します。
今回は、そのうちの1つ、lwIPを前提としたライブラリを使用します。
IwIPは組み込み向けのlightweightな実装です。名前そのまんまですね。
各種MicroPythonのポート(各ボード)でも使用されているものであり、これを前提とするのはうってつけというわけです。
そのため、SORACOM Advent Calendar 2021 7日目から、実に4年ぶりの?派生記事にあたります。
https://zenn.dev/ciniml/articles/wireguard-esp32
公開としてはM5Stamp C3Uでの動作確認をしています。
理屈の上では、他のポートでもLwIP(ESP-IDF)を使用していれば、同じ要領でカスタムファームを作成できるはずです。
実装方法
MicroPythonでは、C言語による MicroPython の拡張をサポートしています。
この拡張は、下記の種類があります。
-
mpyファイルへの埋め込み
ファームウェアとは関係無くあとから入れられる -
MicroPython 外部 C モジュール
ファームウェア内に含める
配布上は当然、前者のmpyファイルへの埋め込みが有利ですが、以下にあるとおりLwIPが明示的にエクスポートされていないと使用不能です。
エクスポートだけを改変してファームウェアを焼くのも、後者で外部Cモジュールを追加するのも大差ありません。むしろ、前者にするとファーム+mpyのインストールで2手順となります。
そのため、後者の外部Cモジュールで実装することにしました。
ファームウェアのビルド時に固定される```mp_fun_table```(```py/nativeglue.h```内)にある明示的にエクスポートされたシンボルテーブルにリンクされます。
※もし、エクスポートされてるじゃないか!と言うのがありましたら、教えてください。全部はソース読みきれなかったので当てずっぽうでincludeを試してみたがダメだったにすぎないので、記述の不一致などの可能性は多いにあります。
WireGuard-ESP32-Arduinoの調整
当時からESP-IDFのバージョンが上がり、4.1以降とそれ以前ではネットワーク周りが変わりました。
tcpip_adapter.hからesp_netif.hとesp_netif_net_stack.hに分かれて整理されてます。
そのため、前後で一応互換があるように、定義しつつパッチを当てます。
当てた結果はこちらのコミットを参照してください→4c8e967
※ほかにもあるかもですが、今回関係するのはバージョン前後合わせてこの3つ
https://docs.espressif.com/projects/esp-idf/en/v4.1/api-reference/network/tcpip_adapter_migration.html
その他、警告レベルも含め細かいのを当てて、それ以外は極力オリジナルに近い状態がmasterブランチです。
Micropython向け調整
Micropython向けに代表的な調整は以下の通りです。
- 内部的に呼び出す際、文字列ではなくnetifで行うように修正
- インターフェース指定をカスタムできるように関数分離
- pureCとして認識するようヘッダー指定を厳格化
- ロガーをArduinoのものからESP-IDFに定義されているものへ変更
- さらなる改善の余地あり
- 一応両用出来るようにFOR_ARDUINOの分岐追加
- 未検証
それらを行ったのがfuture_select_underlying_netifブランチです。
MicroPython外部Cモジュール化
ここまでで、ようやくWireGuardを入れるための準備ができました。
ライブラリとしては前述のWireGuard-ESP32-Arduinoを使用しますので、こちらではWrapperやbridgeとしての実装を行います。
具体的にはMicroPython側からどのように呼び出すか、あるいは、値を返すかの定義となります。
書き方についての詳細はリファレンスやこれ1本で記事になるぐらいながいので、同人誌「社畜の友 機関誌vol.4」を読んでください。
ファームのビルド
ファームのビルドはGithub Actionsを利用して行います。
Gitサブモジュールとして、以下の3つが必須になります。
- WireGuard-ESP32-Arduino
- ESP-IDF
- MicroPython
補足:actions/checkoutで個別に追加も一応可能です。ただ、開発中にLwIPやMicroPythonを参照する必要があるため、サブモジュールがよいのではないかと思います。
オリジナルのポートに対して、今回作成したWireGuardの外部Cモジュールを追加の操作(具体的には実行オプションの指定やLwIPのデバッグ設定を有効にしたりするのもワークフローの設定ファイルで行います。
具体的な内容はこちらを参照してください→build_firmware.yml
※だいたい10-15分ぐらい掛かる
使い方
そうして、できあがったファームウェアがこちらです。→ Releases / v0.1.0-alpha.1
焼き込み方は通常のMicroPythonと同じです。
あらかじめ、WireGuard の接続情報を取得しておいてください。
呼び出し方
- wifi接続
その他、SORACOMへリーチするネットワークに接続 - 時刻設定
NTPがお手軽 - WireGuard接続
ここだけ記事中では説明します。
import wireguard
wireguard.begin(local_ip=【①】,private_key【②】,remote_peer_address=【③】,remote_peer_public_key=【④】,remote_peer_port=【⑤】)
数が多いのでキーワード引数で指定します。一応番号も振ってます。
| キーワード引数 | WireGuard の接続情報 | 補足 | |
|---|---|---|---|
| ① | local_ip | Address | そのまま |
| ② | private_key | PrivateKey | そのまま ※base64 |
| ③ | remote_peer_address | Endpoint | 分解して「IP アドレスまたはホスト名」部分を使用 |
| ④ | remote_peer_public_key | PublicKey | そのまま ※base64 |
| ⑤ | remote_peer_port | Endpoint | 分解して「ポート番号」部分を使用 |
AllowedIPsは使用しません。全部SORACOM向きになります。
Arcを利用したボタンエミューレータ-にWifi接続も含めた記述があるので参考にしてください。こちらでは接続情報は設定ファイルとしています。
Arc-Button
余談
今後、WireGuardのLinux向け設定ファイルをそのまま読めるようにしたり、WANのSIM経由で設定したりのサポートができたらいいなとは思ってます。
便利機能系はPython側の土俵でやれるので、そこそこ便利にできるのでは無いかと!
つい最近、「MicroCat.1(プレリリース版)」というRaspberry Pi Pico 2のバリアントにLTE Cat.1を積んだボードがでたようです。
ファーム焼くのがめんどくさい!という方はこちらも検討してみるとよいのではないでしょうか
"W"と互換であれば、1つのハードで、太い通信はArc(Wifi)、細めやOTAはLTE経由なんてこともできそうだなとは思いましたが、Wifiは無いっぽいのでいい感じに使い分けましょう