【解決】UTMが原因でAIネットワークが不安定になった話

未分類

突然発生したネットワークの謎の不調

ある日突然、愛用のM1 Macのネットワークが極めて不安定になりました。症状は以下の通り:

  • Claude Code: 起動はするものの、すべてのプロンプトでエラー
  • Cursor IDE: GPT-5が途中まで動作して突然停止
  • 一般的なWeb閲覧: 正常
  • 動画や音楽再生: 正常 当然、最初は一般的なネットワークトラブルを疑いました。

試したこと(効果なし)

  • Mac本体の再起動 ✗
  • Wi-Fiルーターの再起動 ✗
  • Wi-Fiルーターの設定見直し – 問題なし
  • ONUの電源入れ直し ✗
  • Cursorの再起動 ✗

主にAI利用が制限されているため、AI使いすぎでプロバイダ制限されたのか?と疑心暗鬼になりましたが杞憂でした。

原因は意外なところに…UTM!

一晩寝て冷静になって考えてみると、「いつもと違うこと」を思い出しました。

マクロ付きExcelファイルをどうしても開く必要があり、数年ぶりにUTMでWindows 11を起動していた

しかも、Windows Updateを全適用するために長時間起動したままにしていました。

ifconfigでMacのIPアドレスを確認してみると見慣れないサブネットマスクの192.168.1.10になっていることを確認。

問題の核心:UTMの共有ネットワークモード

UTMのネットワーク設定を確認すると「共有モード(NAT/SLIRP)」になっていました。これをブリッジモードに変更したところ、すべての問題が即座に解決!

なぜ共有モードが問題を引き起こすのか?

1. 二重NAT地獄

インターネット
    ↓
ルーター (192.168.1.1)
    ↓ NAT変換 #1
Mac (192.168.1.10)
    ↓ NAT変換 #2 ← ここが問題!
UTM内部ネットワーク (192.168.64.*/24)
    ↓
Windows 11 (192.168.64.5)

パケットが2回もアドレス変換されることで:

  • 遅延が倍増
  • パケットロスが発生しやすい
  • 接続が不安定になる

2. UTMのNATエンジンの制限(SLIRP)の詳細

共有モードで使用されるSLIRPエンジンの技術的制限:

  • 同時接続数の上限:

    • ARPテーブル:16エントリまで(デフォルト設定)
    • NDPテーブル:16エントリまで(IPv6用)
    • 実質的に同時接続できるデバイス数が極めて限定的
  • 小さなバッファサイズ:

    • mbuf(メッセージバッファ)構造による制限
    • パケット断片化処理での脆弱性
    • 大量データ転送時に頻繁にバッファオーバーフロー
  • プロトコル制限:

    • ICMPサポート無し(pingが使えない)
    • IPv6ポートフォワーディング未対応
    • WebSocketなど長時間接続に最適化されていない

3. M1 Mac特有の問題の詳細

  • ARM版UTMのNAT実装の最適化不足:

    • x86_64エミュレーション時は「非常に遅い」と公式に記載
    • I/O性能が特に問題(ネットワークI/Oを含む)
    • Hypervisor Frameworkのネットワーク部分が未最適化
  • macOS Sequoiaとの相性問題:

    • Sequoia実行時に追加ドライバーが必要(MobileDevice.pkg等)
    • v4.6.5でカーネルパニック報告により変更が戻された経緯
    • macOS 12以降でUSB共有、動的解像度に制限
  • : Rosetta 2は無関係(UTMはネイティブARMアプリ)

ブリッジモードが安定する理由

ブリッジモードでは、仮想マシンが物理ネットワークに直接参加します:

インターネット
    ↓
ルーター (192.168.1.1)
    ├─ Mac (192.168.1.10)
    └─ Windows 11 (192.168.1.20) ← 同じネットワークセグメント!

メリット

  • NATを経由しない直接通信
  • 実機と同等のネットワーク性能
  • レイテンシが50-70%削減
  • スループットが2-3倍向上

なぜWeb閲覧・動画は正常でAI系サービスだけエラーになるのか?

この現象には技術的な理由があります。

AI系サービスの特殊な接続要件

1. WebSocket長時間接続

一般的なWebサービス:HTTPSの短時間接続(数秒)
AI系サービス:WebSocketで数分〜数十分の継続接続

Claude CodeやCursor(GPT-5)は、リアルタイムのやり取りのためにWebSocketを使用します。

WebSocketとは?通常のHTTPとの違い

通常のHTTP通信(リクエスト・レスポンス型)

[クライアント] → リクエスト送信 → [サーバー]
[クライアント] ← レスポンス返却 ← [サーバー]
接続終了
  • クライアントからのリクエストが必要
  • サーバーから自発的にデータを送れない
  • 毎回新しい接続を確立(オーバーヘッド大)

WebSocket通信(双方向リアルタイム型)

[接続確立フェーズ]
クライアント → HTTPアップグレード要求 → サーバー
クライアント ← 101 Switching Protocols ← サーバー

[データ通信フェーズ]
クライアント ⇄ 双方向データ送受信 ⇄ サーバー
(接続を維持したまま何度でもやり取り可能)
WebSocketの技術的な仕組み
  1. ハンドシェイク(接続確立)
GET /chat HTTP/1.1
Host: api.openai.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
  1. プロトコル切り替え
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  1. データフレーム構造
  • 最小2バイトのヘッダー(効率的)
  • ペイロード長の動的調整
  • マスキングによるセキュリティ
  • Ping/Pongによる接続維持
AI系サービスがWebSocketを必要とする理由

これらの特徴により:

  • 双方向通信: サーバーからクライアントへのプッシュ通信が必要
  • ステートフル接続: 会話の文脈を保持する長時間接続
  • 低レイテンシ要求: 230ms以下のレスポンスタイム
  • ストリーミング: トークンを生成次第、即座に送信

2. 大量データのストリーミング転送

YouTube/SoundCloud:バッファリング可能な一方向ストリーム
AI系サービス:リアルタイム双方向の大量トークン転送
  • GPT-5: 156〜180トークン/秒の高速ストリーミング
  • Claude: 数千トークンの応答を連続送信
  • バッファリング不可(リアルタイム応答が必須)

3. SLIRPの制限がAIサービスに致命的な理由

接続数の枯渇:

ARPテーブル:16エントリ制限
→ AI APIの複数並行ストリーム(モデル選択、トークンカウント、本体通信など)で即座に枯渇

バッファオーバーフロー:

YouTube:動画データを事前バッファリング可能
AI応答:リアルタイムストリーミングでバッファ不可
→ mbufバッファが即座にオーバーフロー

NAT変換の遅延累積:

通常のWeb:1往復で完了
AI通信:数百〜数千回の双方向やり取り
→ 各往復で10-20msの遅延が累積し、タイムアウト

具体的な影響の違い

サービス接続タイプNAT影響結果
YouTubeHTTP/単方向✅ 正常
SoundCloudHTTP/単方向✅ 正常
一般WebHTTP/短時間✅ 正常
Claude CodeWebSocket/双方向❌ エラー
Cursor GPT-5WebSocket/双方向❌ 途中停止

解決方法:ブリッジモードへの変更手順

1. UTMの設定変更

1. 仮想マシンを停止
2. 設定 → ネットワーク
3. ネットワークモード:「ブリッジ」を選択
4. ブリッジインターフェース:「en0」(Wi-Fi)を選択

2. Windows 11側の確認

ブリッジモード変更後、Windows 11は自動的にDHCPから新しいIPアドレス(192.168.1.*/24)を取得します。

3. 動作確認

# Windows 11で実行
ipconfig /all
# 192.168.1.* のIPアドレスが割り当てられていることを確認

まとめ:仮想マシンのネットワーク設定は要注意!

今回の教訓:

  1. 仮想マシンのネットワーク設定は、ホスト全体に影響を与える可能性がある
  2. 共有モード(NAT)、パフォーマンスと安定性に問題がある
  3. ブリッジモードは圧倒的に安定する

もしあなたもMacでUTMを使っていて、ネットワークが不安定になったら、まずネットワーク設定を確認してみてください。意外とこれが原因かもしれません。

環境情報

  • Mac: M1 MacBook Pro
  • OS: macOS Sequoia 15.6
  • UTM: Version 4.5.3
  • 仮想OS: Windows 11 Pro

この記事が同じ問題で悩んでいる方の助けになれば幸いです!

コメント

");const o=le?le.createHTML(e):e;if(ot===nt)try{t=(new Y).parseFromString(o,ut)}catch(e){}if(!t||!t.documentElement){t=se.createDocument(ot,"template",null);try{t.documentElement.innerHTML=rt?ce:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),ot===nt?pe.call(t,ze?"html":"body")[0]:ze?t.documentElement:i},St=function(e){return ue.call(e.ownerDocument||e,e,B.SHOW_ELEMENT|B.SHOW_COMMENT|B.SHOW_TEXT|B.SHOW_PROCESSING_INSTRUCTION|B.SHOW_CDATA_SECTION,null)},bt=function(e){return e instanceof G&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof W)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},Nt=function(e){return"function"==typeof R&&e instanceof R};function Rt(e,t,n){u(e,(e=>{e.call(o,t,n,ft)}))}const wt=function(e){let t=null;if(Rt(de.beforeSanitizeElements,e,null),bt(e))return Et(e),!0;const n=pt(e.nodeName);if(Rt(de.uponSanitizeElement,e,{tagName:n,allowedTags:Ne}),Ue&&e.hasChildNodes()&&!Nt(e.firstElementChild)&&S(/<[/\w!]/g,e.innerHTML)&&S(/<[/\w!]/g,e.textContent))return Et(e),!0;if(e.nodeType===ee)return Et(e),!0;if(Ue&&e.nodeType===te&&S(/<[/\w]/g,e.data))return Et(e),!0;if(!Ne[n]||ve[n]){if(!ve[n]&&Dt(n)){if(De.tagNameCheck instanceof RegExp&&S(De.tagNameCheck,n))return!1;if(De.tagNameCheck instanceof Function&&De.tagNameCheck(n))return!1}if(je&&!$e[n]){const t=ae(e)||e.parentNode,n=ie(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=$(n[o],!0);r.__removalCount=(e.__removalCount||0)+1,t.insertBefore(r,re(e))}}}return Et(e),!0}return e instanceof O&&!function(e){let t=ae(e);t&&t.tagName||(t={namespaceURI:ot,tagName:"template"});const n=h(e.tagName),o=h(t.tagName);return!!it[e.namespaceURI]&&(e.namespaceURI===tt?t.namespaceURI===nt?"svg"===n:t.namespaceURI===et?"svg"===n&&("annotation-xml"===o||lt[o]):Boolean(Tt[n]):e.namespaceURI===et?t.namespaceURI===nt?"math"===n:t.namespaceURI===tt?"math"===n&&ct[o]:Boolean(yt[n]):e.namespaceURI===nt?!(t.namespaceURI===tt&&!ct[o])&&!(t.namespaceURI===et&&!lt[o])&&!yt[n]&&(st[n]||!Tt[n]):!("application/xhtml+xml"!==ut||!it[e.namespaceURI]))}(e)?(Et(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!S(/<\/no(script|embed|frames)/i,e.innerHTML)?(Me&&e.nodeType===Q&&(t=e.textContent,u([he,ge,Te],(e=>{t=y(t,e," ")})),e.textContent!==t&&(f(o.removed,{element:e.cloneNode()}),e.textContent=t)),Rt(de.afterSanitizeElements,e,null),!1):(Et(e),!0)},Ot=function(e,t,n){if(Ge&&("id"===t||"name"===t)&&(n in r||n in dt))return!1;if(Ce&&!Le[t]&&S(ye,t));else if(xe&&S(Ee,t));else if(!we[t]||Le[t]){if(!(Dt(e)&&(De.tagNameCheck instanceof RegExp&&S(De.tagNameCheck,e)||De.tagNameCheck instanceof Function&&De.tagNameCheck(e))&&(De.attributeNameCheck instanceof RegExp&&S(De.attributeNameCheck,t)||De.attributeNameCheck instanceof Function&&De.attributeNameCheck(t))||"is"===t&&De.allowCustomizedBuiltInElements&&(De.tagNameCheck instanceof RegExp&&S(De.tagNameCheck,n)||De.tagNameCheck instanceof Function&&De.tagNameCheck(n))))return!1}else if(Je[t]);else if(S(be,y(n,_e,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==E(n,"data:")||!Ve[e]){if(ke&&!S(Ae,y(n,_e,"")));else if(n)return!1}return!0},Dt=function(e){return"annotation-xml"!==e&&T(e,Se)},vt=function(e){Rt(de.beforeSanitizeAttributes,e,null);const{attributes:t}=e;if(!t||bt(e))return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:we,forceKeepAttr:void 0};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=pt(a),m=c;let f="value"===a?m:A(m);if(n.attrName=s,n.attrValue=f,n.keepAttr=!0,n.forceKeepAttr=void 0,Rt(de.uponSanitizeAttribute,e,n),f=n.attrValue,!Ye||"id"!==s&&"name"!==s||(At(a,e),f="user-content-"+f),Ue&&S(/((--!?|])>)|<\/(style|title)/i,f)){At(a,e);continue}if("attributename"===s&&T(f,"href")){At(a,e);continue}if(n.forceKeepAttr)continue;if(!n.keepAttr){At(a,e);continue}if(!Ie&&S(/\/>/i,f)){At(a,e);continue}Me&&u([he,ge,Te],(e=>{f=y(f,e," ")}));const d=pt(e.nodeName);if(Ot(d,s,f)){if(le&&"object"==typeof j&&"function"==typeof j.getAttributeType)if(l);else switch(j.getAttributeType(d,s)){case"TrustedHTML":f=le.createHTML(f);break;case"TrustedScriptURL":f=le.createScriptURL(f)}if(f!==m)try{l?e.setAttributeNS(l,a,f):e.setAttribute(a,f),bt(e)?Et(e):p(o.removed)}catch(t){At(a,e)}}else At(a,e)}Rt(de.afterSanitizeAttributes,e,null)},Lt=function e(t){let n=null;const o=St(t);for(Rt(de.beforeSanitizeShadowDOM,t,null);n=o.nextNode();)Rt(de.uponSanitizeShadowNode,n,null),wt(n),vt(n),n.content instanceof s&&e(n.content);Rt(de.afterSanitizeShadowDOM,t,null)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,i=null,l=null;if(rt=!e,rt&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Nt(e)){if("function"!=typeof e.toString)throw b("toString is not a function");if("string"!=typeof(e=e.toString()))throw b("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Pe||gt(t),o.removed=[],"string"==typeof e&&(Xe=!1),Xe){if(e.nodeName){const t=pt(e.nodeName);if(!Ne[t]||ve[t])throw b("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof R)n=_t("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===J&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!Fe&&!Me&&!ze&&-1===e.indexOf("<"))return le&&We?le.createHTML(e):e;if(n=_t(e),!n)return Fe?null:We?ce:""}n&&He&&Et(n.firstChild);const c=St(Xe?e:n);for(;i=c.nextNode();)wt(i),vt(i),i.content instanceof s&&Lt(i.content);if(Xe)return e;if(Fe){if(Be)for(l=me.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(we.shadowroot||we.shadowrootmode)&&(l=fe.call(a,l,!0)),l}let m=ze?n.outerHTML:n.innerHTML;return ze&&Ne["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&S(K,n.ownerDocument.doctype.name)&&(m="\n"+m),Me&&u([he,ge,Te],(e=>{m=y(m,e," ")})),le&&We?le.createHTML(m):m},o.setConfig=function(){gt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),Pe=!0},o.clearConfig=function(){ft=null,Pe=!1},o.isValidAttribute=function(e,t,n){ft||gt({});const o=pt(e),r=pt(t);return Ot(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&f(de[e],t)},o.removeHook=function(e,t){if(void 0!==t){const n=m(de[e],t);return-1===n?void 0:d(de[e],n,1)[0]}return p(de[e])},o.removeHooks=function(e){de[e]=[]},o.removeAllHooks=function(){de={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},o}();return re}))
タイトルとURLをコピーしました