第650回 SORACOM公式ブログ

ソラコム公式ブログ

AI エージェントの VM から IoT デバイスは見えるか:Meta Muse と SORACOM Arc の検証記録

  • この記事をはてなブックマークに追加
  • 本文印刷

 本記事はソラコムが提供する「SORACOM公式ブログ」に掲載された「AI エージェントの VM から IoT デバイスは見えるか:Meta Muse と SORACOM Arc の検証記録」を再編集したものです。

はじめに ~ AIエージェント「Muse」

こんにちは、ソラコムの柴田(shibaken)です。普段はソリューションアーキテクトとしてSORACOMをお使いいただくお客様への技術的な支援を行い、休日はギターやシンセを演奏したり、キャンプをしたりしております。

さて、AIエージェントができることの範囲が爆発的に増え続ける昨今、24時間稼働するAI エージェントに IoT デバイスの面倒を見てもらえないか、と考えたことがある人はいらっしゃるのではないでしょうか?

今回は、Meta の AI エージェント Muse に SORACOM Arc を入れて、SORACOM の VPG(Virtual Private Gateway)/ SORACOM Gate 経由でデバイスと接続できるかどうか、を試した結果をまとめています。そこそこな長文ブログなので、適宜かいつまんでお読みいただければ幸いです。

先に結論を置いておくと、Muse の VM の通信ルールは、汎用の WireGuard クライアント(wireguard-go)の通信の仕方とあまり相性が良くありませんでした。 そのため、WireGuard で SORACOM につなぐ Arc も、公式の soratun のままではトンネルを張れませんでした。 wireguard-go に手を入れると、トンネルを張って維持するところまではできましたが、別のデバイスから Gate 経由で Muse に対する通信が成立したのはゆっくり送られた ping だけでした。 TCP も UDP も、アプリケーション同士の通信は成立せず、期待したIoTデバイス監視AIエージェント爆誕、とはなりませんでした。

うまくいきませんでした!というお話ですが、どの通信が通り、どの通信が通らないのかを、WireGuard クライアントの動き方の単位で切り分けられました。 Tailscale や Cloudflare WARP(WireGuard モード)など、WireGuard を使うツールを AI エージェントの環境で動かそうとしている方にも参考になると思うので、記録として残します。どなたかのお役に立てば幸いです。

本記事で使ったパッチ版の soratun は、筆者個人の検証用として公開しています。 ソラコムの公式ソフトウェアではなく、サポートの対象外です。 通常の Linux 環境では、公式の soratun を使ってください。 https://github.com/s8-ken/soratun-for-muse

1. Muse は Linux VM を持つ AI エージェント

Muse は Meta が提供する AI エージェントです。 チャットで依頼すると、Muse は自分専用の Linux VM の中でコマンドを実行し、ファイルを作り、ブラウザを操作して作業を進めます。 今回の検証で使った VM には root 権限があり、/dev/net/tun も使えました。

図0: Museに聞けば、Linux VM上で稼働することを普通に答えてくれます。

(余談ですが、Open AIのDotsにはクロヱ、という名前を付けています。)

ただし、この VM のネットワークは普通の Linux サーバーとは違います。 Web 系の通信(HTTP や HTTPS)は Muse の用意したプロキシを経由して外に出ます。 それ以外の「生の」ネットワーク通信は、Muse の設定画面の Direct network protocols(ダイレクトネットワークプロトコル)で管理されています。 SSH、SMTP、データベース接続、FTP、外部 DNS、その他の TCP、その他の UDP といった項目があり、初期状態ではどれも拒否(Deny)です。 項目ごとに Ask に切り替えると、宛先ごとに承認を求められる形で使えるようになります。

なお、本記事の内容は、2026 年 10 月時点で筆者が使った Muse の環境での観測結果です。 Muse の仕様として公開されているものではなく、今後変わる可能性があります。

図1:Muse の Linux VM とネットワークの関係

2. 期待したこと:Muse が IoT デバイスを見守る

SORACOM には API があり、MCP サーバーも用意されています。 これらをMuseから使えば、SIM の状態や通信量、Harvest に溜まったデータを見ることはできそうです。 ただ、これだけではデバイスの中までは見えません。 デバイスにログインしてログを見たり、設定を変えたりするには、デバイスへ IP で届く経路が必要です。

そこで考えたのが SORACOM Arc です。 SORACOM Arc は、セルラー回線を持たない機器を WireGuard のトンネルで SORACOM に接続するサービスです。 接続するとバーチャル SIM が発行され、IoT SIM と同じように SIM グループや VPG の設定を使えます。

Muse の VM を Arc でつなぎ、デバイスと同じ VPG に入れれば、SORACOM Gate のデバイス間通信(Gate D2D)で Muse からデバイスに直接届くはずです。 API で全体を見渡し、気になるデバイスには Gate 経由でログインして調べる。 そんな監視役の AI が、わりと簡単に作れるのではないかと期待しました。

図2:期待した構成(Muse が Gate 経由で IoT デバイスに直接接続できる)

3. Muse の VM で WireGuard クライアントを動かす(Arc 編)

ここからは手順を説明します。 途中で 3 回つまずき、そのたびに wireguard-go にちょっとした改造を加えました。 これらの壁は Arc に限った話ではなく、wireguard-go のような汎用の WireGuard クライアントを Muse の VM で動かすと起きる問題です。 SORACOM 側の設定は通常の Arc と同じです。 細かい話が続くので、通信試験の結果を記載した次の章まで飛ばしていただいてもかまいません。

3.1 SORACOM 側の準備

SORACOM 側の作業は、通常の Arc と変わりません。

  1. SIM グループを作り、VPG を設定する
     
  2. バーチャル SIM を作成し、そのグループに所属させる
     
  3. Muse の VM で WireGuard の鍵ペアを生成し、公開鍵をバーチャル SIM に登録する(秘密鍵を VM の外に出さないため)

今回は手軽に試すため、VPG にはソラコムが用意している共有 VPG の Public Gate を使いました。 Public Gate はすべての SORACOM アカウントで共有されるので、IP アドレスがわかれば他のアカウントのデバイスからも届きます。 検証が終わったら外すこと、継続して使うなら専用の VPG を作ることが公式に推奨されています。

バーチャル SIM で Gate D2D を使う場合は、soratun の設定ファイルの additionalAllowedIPs に、VPG のデバイスサブネット(デフォルトは 10.128.0.0/9)を指定します(公式ドキュメント)。

Arc には、1 アカウントあたりバーチャル SIM 1 契約分の無料利用枠があります。 Public Gate には VPG の利用料金がかかりません。

3.2 Muse 側の権限設定

Muse の設定画面で、Direct network protocols の「その他の UDP トラフィック」を Ask に切り替えます。 WireGuard は UDP で通信するので、これがないと何も送れません。
(Androidアプリには設定が現れず、現状はWebブラウザからアクセスして設定する必要があるようです。)

3.3 最初の壁:UDP の送信が EPERM になる

Muse の VM で公式の soratun(v1.2.8)を動かすと、インターフェース soratun0 は作られますが、ハンドシェイクが完了しません。 ログには次のエラーが出続けます。

sendmmsg: operation not permitted

UDP の権限を Ask に切り替えても、結果は変わりませんでした。 そこで、送信のしかたを変えながら試すと、次の法則が見つかりました。

送信のしかた 結果
connect() していないソケットから、毎回宛先を指定して送る(sendto / sendmmsg) 宛先を問わず即座に EPERM
先に connect() してから送る Ask の承認フローに乗り、承認後は送れる

DNSリクエストのUDPパケットは通るのにこちらのUDPパケットが通らないのはなんでだろう?と思ったのですが、これはglibc のリゾルバが connect() してから送るためでした。 一方、wireguard-go は connect() していないソケットから sendmmsg で送ります。 カーネル版の WireGuard に逃げる道もありません(VM にカーネルモジュールがなく、ip link add type wireguard が失敗します)。 connect() していないソケットから送る方式は、複数の相手とやり取りする UDP の VPN クライアントでは一般的です。 Muse の VM に自分で持ち込んだ WireGuard 系のクライアントは、同じ EPERM に当たる可能性があります。

そこで、wireguard-go の conn パッケージを次のように変えました。

  • 改造 1:送信前に宛先へ connect() し、Write() で送る。Arc の宛先は 1 つだけなので、ソケットを宛先に固定しても困らない。
     
  • 改造 2:受信したパケットの送信元を新しい宛先として採用する動作(ローミング)を止め、宛先を connect() 先に固定する。

改造 2 が必要なのは、この VM では応答パケットの送信元が Arc のサーバーではなく、Muse のゲートウェイのアドレスに書き換わって届くためです。 WireGuard の通常の動作では、その書き換わった送信元を新しい宛先として採用してしまい、次からの送信が Arc のサーバーに届かなくなります。

これでハンドシェイクが通り、トンネルが開通しました。

図3:UDP の送り方による違い(改造 1、2)

3.4 2 つ目の壁:約 2 分でトンネルが切れる

開通したトンネルは、約 2 分後に応答が返らなくなりました。 2 分は、WireGuard が鍵を更新する(rekey)タイミングです。 最初のハンドシェイクは通るのに、鍵の更新のハンドシェイクには応答が返ってきません。

UDP の試験用パケットで調べると、次の結果になりました。

  • 新しく作ったソケットで受け取る最初の応答:14 回中 14 回成功
     
  • 同じソケットで受け取る 2 回目以降の応答:20 回中 0 回成功(間隔を 1 秒にしても 20 秒にしても同じ)

成功率がここまで偏るので、ネットワークの不安定さではなく、Muse の UDP 中継が「1 つのソケットにつき、受け取れるのは最初の 1 パケットだけ」という振る舞いをしていると判断しました。 soratun を止めた状態でも再現するので、SORACOM 側の問題でもありません。

WireGuard は 1 本の UDP の流れで何百ものパケットをやり取りする前提で作られています。 最初のハンドシェイクは 1 往復で済むので通りますが、その後の鍵の更新で失敗します。

この制約を回避するため、wireguard-go に新しい Bind 実装を追加しました。

  • 改造 3(RotateBind):パケットを送るたびに、新しい connect() 済みソケットを作って送る。

新しいソケットには、まだ使っていない「受信 1 パケット分の枠」があります。 ここで助かったのが、Arc のサーバーが WireGuard の標準仕様どおりにローミングする点です。 Arc のサーバーは、最後にパケットが届いた送信元へ返信します。 つまり Muse 側から見ると、送るたびに新しい受信枠ができ、サーバーはその枠に返してくれる、という関係になります。 Arc 側には何も手を加えず、WireGuard のプロトコルの範囲内の動作だけで、Muse 側の制約を避けられます。

// 簡略化した擬似コード。実装は conn/bind_rotate.go を参照 func (b *RotateBind) sendOne(buf []byte, dst netip.AddrPort) error {     c, err := net.DialUDP("udp4", nil, net.UDPAddrFromAddrPort(dst)) // 送るたびに新しいソケット     if err != nil {         return err     }     go b.readOne(c) // このソケットで受け取れる 1 パケットを待ち、受け取ったら閉じる     _, err = c.Write(buf)     return err }

受信を待つ古いソケットは 45 秒後に閉じます。 サーバーが新しい送信元を学ぶ前に送ったパケットが、古いソケットに届くことがあるためです。

RotateBind に変えてからは、鍵の更新が約 2 分ごとに成功し続け、トンネルは 43 分間切れずに続きました(VM 自体の再起動で止まるまで)。

図4:1 ソケット 1 パケットの制約と RotateBind(改造 3)

3.5 3 つ目の壁:受信が詰まる

この改造は 4 章の検証中に追加したものですが、手順の一部なのでここで説明します。

RotateBind では、受信枠ができるのは Muse が何かを送ったときだけです。 Muse が受け取るパケットの数が送るパケットの数を上回ると、余ったパケットは捨てられます。 さらに Muse 側に送るものがないと、次の keepalive を送るまで、何も届かない状態が続きます。

そこで、次の 2 つを入れました。

  • 改造 4:wireguard-go の device 層で、認証済みのパケットを受け取るたびに keepalive を送る(40 ms に 1 回までに間引く)。受け取るたびに新しいソケットから送るので、受信枠が補充される。
     
  • 設定変更:PersistentKeepalive を 3 秒にする。詰まった状態から復旧するまでの時間が 3 秒以下になる。

keepalive の送信は、Bind(conn 層)ではなく device 層でしかできません。 keepalive は暗号化が必要で、送信済みのパケットを再送してもリプレイとして捨てられ、ローミングも起きないためです。

図5:受信が詰まる問題と、受信時の keepalive(改造 4)

この改造の後、4 章の試験では、外から送った TCP の SYN がすべて Muse の VM まで届くようになりました。

3.6 トンネル以外の原因で切れる場合

ここまでの改造で防げるのは、トンネル自体が切れる問題だけです。 Muse では、それ以外にも次の理由で接続が切れるケースがしばしば発生します。VM の再起動:Muse の VM はときどき再起動します。プロセスもインターフェースも消えるので、起動スクリプトで張り直します。

  • soratun プロセスの消失:短命な作業セッションから起動した soratun が、エラーを出さずに消えることがありました。Muse が会話のセッションから起動したプロセスは 89 分間動き続けたので、起動元のセッションが終わるとプロセスも回収されている可能性が高いと見ています(確定はしていません)。

Museが検知して自動復旧してくれるようにはできるのですが、そもそもこういった常時起動系のサービスを動かし続けることを想定されたLinux VMではなさそうです。TUN を作る権限が足りず、systemd のサービスにはできませんでした。

3.7 パッチ版 soratun のビルド

パッチ一式は次のリポジトリで公開しています。

https://github.com/s8-ken/soratun-for-muse

soratun 側の変更は go.mod の replace 1 行だけで、パッチはすべて wireguard-go 側にあります。

cd third_party/soratun go build -trimpath -o soratun ./cmd/soratun sudo ./soratun up --config ./arc.json

4. 別の端末から Gate 経由でアクセスした結果

さて、ようやくWireGuardのトンネルが維持できるようになったところで、デバイス側から Muse に対し、通信が無事届くかを試しました。

4.1 試験の構成

  • デバイス側:plan-D の IoT SIM を挿した Raspberry Pi。SIM は Public Gate を設定した SIM グループに所属させる。
     
  • Muse 側:Arc のバーチャル SIM。同じく Public Gate に所属。
     
  • 待ち受け:Muse の VM で、TCP 22 番(テスト用の SSH サーバー)、TCP 9000 番(python3 -m http.server)、UDP 5300 番(受け取った内容を返すだけのエコーサーバー)を待ち受ける。

Raspberry Pi の SIM と Muse のバーチャル SIM は、別々の SORACOM アカウントに属しています。 Public Gate は全アカウントで共有されているので、アカウントが違っても同じネットワークに入れます。

Raspberry Pi 側では tcpdump で、Muse 側ではソケットとカウンタの観測で、パケットの行方を両側から確認しました。

図6:試験の構成

4.2 ping:ゆっくり送れば届く

1.5〜2 秒間隔で ping を送ると、30 回中 30 回とも応答が返りました。 往復時間は 250〜450 ms です。

0.5 秒間隔で送ると、大半が失われました(10 回中 0 回、6 回中 2 回など)。 連続して送った後は、しばらく届かない時間が続きました。 これは 3.4 の改造の時点の結果で、受け取る数が送る数を上回ると受信枠が足りなくなる、という説明と合います。

4.3 TCP:閉じたポートは RST、待ち受け中のポートは無応答

TCP では、ポートの状態によって結果が分かれました。

宛先ポート Muse 側の状態 Raspberry Pi が受け取ったもの
9999、113、3389、445、587、5900 など 待ち受けなし RST(接続拒否)
22 テスト用の SSH サーバーが待ち受け中 何も返らない
9000 http.server が待ち受け中(VM 内からの接続では HTTP 200 を確認) 何も返らない

待ち受けていないポートに RST が返るので、Raspberry Pi から Muse の VM のカーネルまでは届いており、カーネルの応答も戻ってきています。 それなのに、待ち受けているポートだけが応答しません。

Muse 側の観測では、22 番と 9000 番のどちらでも、次のように動いていました。

  • 5 回送った SYN(再送を含む)は、5 回とも soratun0 に届いている
  • TcpInSegs は 5 増えている(TCP の処理まで届いている)
  • ListenOverflows と ListenDrops は 0 のまま
  • 半開きの接続(SYN-RECV 状態のソケット)は一度も作られない
  • soratun0 への書き戻し(送信)は 0 で、SYN-ACK は一度も出ていない
  • iptables と nftables にはルールが 1 つもない

SYN は TCP の処理まで届いているのに、接続要求として扱われず、何も起きずに消えています。 待ち受けているプログラムを Python の単純なサーバーに変えても同じだったので、特定のサーバーソフトの問題ではありません。

図7:TCP の SYN の行方

4.4 UDP:応答は返らない

UDP 5300 番にデータグラムを 1 つ送りましたが、エコーの応答は返りませんでした。

4.5 Muse から始める通信

逆向きも確認しました。 Muse の VM から、トンネルの先のアドレス(10.0.0.0/8 など)へ TCP の connect() を試みると、すぐに EPERM になります。 ping pong.soracom.io のような ICMP での疎通確認も、Muse の VM からはできませんでした。

5. 通る通信と通らない通信

ここまでの結果をまとめます。

通信 結果 必要なもの
Arc トンネルの確立と維持(ハンドシェイク、鍵の更新) ○(改造 1〜4 が必要) soratun プロセス
外から Muse への ping(ゆっくり送る場合) ○ カーネルの応答だけ
外から Muse の閉じたポートへの TCP(RST が返る) ○ カーネルの応答だけ
外から Muse の待ち受けポートへの TCP ✕(SYN は届くが、接続にならない) アプリの待ち受けソケット
外から Muse の待ち受けポートへの UDP ✕(応答なし。原因は 4.4 を参照) アプリの待ち受けソケット
Muse からトンネルの先への通信(TCP、ICMP) ✕(EPERM) アプリのソケット

通るのは、カーネルが自分で返す応答(ICMP の echo 応答や RST)と、soratun 自身の通信だけです。 アプリケーションのソケットが関わる通信は、送る向きも受ける向きも成立しませんでした。

これらを踏まえて、Muse の VM には、プライベートアドレス(10.0.0.0/8)との通信をソケット単位で制限する仕組みがあると考えています。 エージェントが社内ネットワークなどに勝手にアクセスしないための、意図した安全機構である、という推定です。 ただし、これは観測結果からの推定で、Muse の仕様として確認したものではありません。

図8:通る通信と通らない通信

補足:WireGuard 系のツールを Muse で使うときの参考

今回の壁を層ごとに分けると、次のようになります。

  • 送信:connect() していないソケットからの送信が EPERM になる。自分で持ち込んだ UDP の VPN クライアントに当てはまる。
     
  • 受信:1 つのソケットで受け取れるのは最初の 1 パケットだけ。1 本のソケットで長く通信する UDP のプロトコル全般に当てはまる。
     
  • トンネルの中:アプリのソケットとプライベートアドレスの通信が通らない。今回の観測の範囲では、トンネルの方式によらない。

一方で、Muse によると、VM には Tailscale クライアントが最初から入っていて、外向きの通信は許可されているそうです。 Muse の VM は WireGuard を一律に止めているのではなく、プラットフォームが用意したクライアントかどうかや通信の向きなどで、もっと細かく制御しているようです。 ただし、これは Muse の回答によるもので、筆者は Tailscale の挙動を検証していません。 Muse で WireGuard 系の接続が必要な場合は、まず VM に用意されている仕組みで足りるかを確認すると早いと思います。

6. まとめ

Meta の AI エージェント Muse に SORACOM Arc を入れ、VPG(Public Gate)経由で IoT デバイスとつながるかを試しました。

  • Muse の VM の通信ルールは、汎用の WireGuard クライアント(wireguard-go)の送り方・受け方と相性が悪く、公式の soratun のままでは Arc のトンネルを張れなかった。
  • wireguard-go に 4 つの改造(connect() 化、ローミングの無効化、RotateBind、受信時の keepalive)を入れると、トンネルを張って維持できた。Arc 側は WireGuard の標準仕様どおりに動き、変更は不要だった。
  • 別アカウントの Raspberry Pi から Gate 経由で届くのは、ゆっくり送った ping と、閉じたポートからの RST だけだった。
  • 待ち受けソケット宛ての TCP と UDP、Muse から始める通信は、どれも成立しなかった。Muse の VM 側で、トンネルの方式に関係なく、プライベートアドレスとのアプリケーションの通信が制限されていると考えられる。
  • Muse によると、Tailscale は最初から入っていて外向きの通信は許可されているので、WireGuard を一律に止めているわけではなさそうだ(筆者は未検証)。

今回の環境では、「Muse が Gate 経由でデバイスにログインして調べる」構成は実現できませんでした。 一方で、トンネルの確立と維持はできているので、Muse 側で、持ち込んだトンネル内のアプリケーションの通信が許可されれば、VPG や Gate と組み合わせてデバイスを見守る AI を作る道は開けます。 そのときは、今回のパッチと切り分けの結果が役に立つはずです。

なお、デバイスのデータを見守るだけなら、Arc を使わない構成でも実現できそうです。 デバイスから SORACOM Harvest Data にデータを送り、Muse には SORACOM API で Harvest Data を読んでもらう形です。 この通信は SORACOM の HTTPS エンドポイント宛てなので、今回の制限(プライベートアドレスとの通信)には当たらないと考えられます(本記事では未検証です)。 Muse に API を使わせる場合は、Harvest Data の読み取りだけを許可した SAM ユーザーの認証キーを渡すと安全です。

図9:Arc を使わない構成例(Harvest Data に溜めて Muse に読んでもらう)


本記事の作業は 2026 年 10 月 27 日に実施しました。Muse のネットワークの挙動や SORACOM のサービス仕様は変わる可能性があります。

― ソラコム柴田 (shibaken)

投稿 AI エージェントの VM から IoT デバイスは見えるか:Meta Muse と SORACOM Arc の検証記録 は SORACOM公式ブログ に最初に表示されました。

本記事はアフィリエイトプログラムによる収益を得ている場合があります

過去記事アーカイブ

2026年
01月
02月
03月
04月
05月
06月
07月
08月
09月
10月
2025年
01月
02月
03月
04月
05月
06月
07月
08月
09月
10月
11月
12月
2024年
01月
02月
03月
04月
05月
06月
07月
08月
09月
10月
11月
12月
2023年
01月
02月
03月
04月
05月
06月
07月
08月
09月
10月
11月
12月
2022年
01月
02月
03月
04月
05月
06月
07月
08月
09月
10月
11月
12月
2021年
01月
02月
03月
04月
05月
06月
07月
08月
09月
10月
11月
12月