涅槃を目指す in はてな

人生に迷う様を書きます

KVMで無線LANとVMのブリッジができない問題に真剣に向き合った

TL;DR

  • Wifiに接続したマシンでKVM使うと、作成したVMがブリッジ使えなくなるよ
  • Linuxの仕様らしいので、自分で工夫するか、VirtualBoxのようなWifiブリッジできるプロダクト使ってね
  • ちなみにVirtualBoxMACアドレスを書き換えてるよ
  • Libvirtガチ勢はいい人が多いよ

どうした

前回の記事で

WiFiはレイヤー2転送に対応していない』

と書きました。

具体的にはWifi接続したUbuntukvmを入れて、作成したVMWifiにつないだNICとブリッジしようとしてもできませんでした。

なにをしたかったのか

イメージとしてはこういうことです。VMがホストマシンのブリッジを介して無線LAN NICを使って通信し、ホストマシンの同じL3ネットワークに所属させたかったのです。

ハイパーバイザにKVMVMの管理にはvirt-managerを使っていました。

f:id:ryo-plavi:20200711174557p:plain

なにができなかったのか

VMを作成してもネットワークの設定が有効になりません。ブリッジを介してDHCPサーバ(Wifiルータ)と通信できていないようでした。

f:id:ryo-plavi:20200711175026p:plain

ぐぐってみると、KVMの場合、ホストマシンがwifi接続をしている場合はVMとのブリッジ接続ができず、iptablesとかで工夫が必要なのがわかりました。

blog.geeko.jp

なるほどiptableとかで曲げないとダメなのか。

つぶやいたら神が降臨した

できないのは分かったんですが、調べた記事の中には『無線LANはL2レベルでの転送をサポートしてない』との書き込みあり、なんでだろなと気になってツイッターでつぶやいてみました。

すると!なんと!こんなリプが!

以前、Wi-Fi ブリッジ開発してました。Layer 2 的な unicast と multicast(含む broadcast) で取り扱いが異なるので面倒ですが、一般にはブリッジできます。

おおおおおー!神きたー!凄い!

確かにWifi中継機とかあるしな。うん。

技術的に不可能ではないですし、無線LAN NIC のドライバが未対応なのだと「予想」します。https://wiki.debian.org/BridgeNetworkConnections#Bridging_with_a_wireless_NIC

なるほど、ドライバの仕様かもしれないと。

AdHoc でなく AP/STA 構成の場合、無線区間中の AP->STA は 1:n の構成になり、以前の無線チップのファームウェアでは無線区間の ack を待たない(待てない)実装がほとんどでした。低い転送レート(bps)で到達確率上げてます。

AdHocていうのはアドホック・モードといい、無線LANアクセスポイントを経由せず、無線につないだ機器同士で直接通信する方式です。 APはWifiアクセスポイント(AccessPoint)のことで、STAはPCのような端末(Station)の事です。

無線通信は基本的に半二重で、APが複数のSTAとやり取りしてるようでも通信は1:1で行われて、他の人は空くのを待ちます。(シングルコアのCPUみたいな感じです)

加えて通信品質が悪いと転送レートを下げるので(≒速度低下)、相手の受信したよっていう返事(ACK)を待てないケースもあったりしたようで、苦労が伺われます。

無線区間中の STA->AP は 1:1 の構成なので無線区間の ack を待てますし、結果として高い転送レート(bps)で送信できます。無線区間中の STA 間通信は STA->AP->STA になるので、また面倒です。RFC ではなく IEEE の話題でしょうか、IEEE802.11bgani とか。

RFCでも調べてみようかと思ってたんですけど、IEEEではないのかというお話。深淵に入り込みそうだな…

libvirtメーリングリストで聞いてみた

それでもやはりWifi使うとVMがブリッジ使えなくなる理由が知りたくて色々とググってると、libvirtのドキュメントを見つけました。

wiki.libvirt.org

その中に

Important Note: Unfortunately, wireless interfaces cannot be attached to a Linux host bridge, so if your connection to the external network is via a wireless interface ("wlanX"), you will not be able to use this mode of networking for your guests.

『訳:悪いんだけど、無線インターフェースはLinuxホストのブリッジにアタッチできないぜ。だからもしお前が無線インターフェース(wlan的なやつ)越しに外部と通信したいなら、この方法(ブリッジ)をゲストOSに使うことはできないな』

その理由を知りたいんだよぉ。教えてほしいな。

なので勇気を出してlibvirtメーリングリストにメールで聞いてみることにしました。

勘違いして欲しくないんですが、私は決してGAFAみたいな外資系ITで働いてる人間でも、自己研鑽のためにTOEICの勉強をしてるような人間でもありません。高校生の時にとった英検2級をピークに惰眠を貪る毎日を過ごし、Google翻訳なしでは生きていけない人間です。

Linuxに関しても雰囲気で触ってる人間で、libvirtに関しても『kvmと一緒に入れるやつ?』くらいの認識でした。libvirtメーリングリストなんか絶対ガチ勢の集まりで、変なこと聞いたらF__KとかGIYF (Google Is Your Friendの略で日本語で言うググれカス)とか言われるんじゃないかと戦々恐々としておりました。

でも頑張って聞いてみました。こちらがそのアーカイブです。

www.redhat.com

ご覧ください紳士淑女の集まりです。むしろ優しい。

機械翻訳を褒めらてもらって、なぜか私まで嬉しいです。

何人かの神から回答を頂いたんですが、以下の回答がFAのようでした。

(私の文章)Is it a limitation of the NIC driver for the wireless LAN?

Not really.

(私の文章)Or is it a limitation of libvirt?

Definitely not. Completely out of libvirt's control.

It is a limitation of

1) the way that almost all wireless connections work (only traffic to/from a single MAC address is allowed on a particular wireless connection). (Yes, I know there are some wireless modes that allow multiple MAC addresses on a single association. But those modes aren't supported by most wireless APs or clients.)

2) Because of (1), the Linux kernel doesn't allow wireless network devices to be attached to a Linux host bridge device.

Some people have had success with IPv6 by enabling proxy ARP on the bridge device and wireless interface (without directly attaching them to each other, then manually adding a host route pointing to the guest's IP. It might be possible to do something similar for IPv4 using proxy ARP, but I personally haven't played with the idea.

意訳:

『それは、Wifiアダプタのドライバの制限でも、ましてやlibvirtの制限でもないよ、libvirtの管理外のところだ。

1)ほとんどの無線接続が使う動作方式っていうのがある(特に無線接続においては単一のMACアドレス間のみ許可される)。 (ああ、もちろん単一の接続で複数のMACアドレスを許可するモード(アドホックのこと?)があるのは知ってるよ。でもそのモードは大抵の無線LAN APやクライアントではサポートされていないんだ)

2)1)の理由から、Linuxカーネル無線LANアダプタがLinuxホストのブリッジデバイスにアタッチするのを許可してないんだ。

何人かの人はIPv6と、ブリッジデバイス無線LANアダプタでARPプロキシを有効にすることで成功してるみたいだ(それぞれアタッチせずに、ゲストOSへのルーティングを手動で設定しながらね)。似たようなことはIPv4でもARPプロキシを使えばできるだろうけど、個人的にそれを試したことはないよ。』

なるほど

さらにツイッターでリプくれた神はこうも言ってました。

無線でブリッジする時は、MAC Addr が 4つ登場します。無線区間の Src/Dst と、有線区間の Src/Dst これに対応してない場合が多いと言う意味だと思います。AP から見ると、どの STA の先に、どの有線MAC があるかも要管理です。別の STA 配下に有線端末がローミングすることも想定しますし大変です。

なるほど、その変換は大変そうだ。

そういえば、神が紹介してくれたDebianの資料にこういう一文がありました。

Just like you can bridge two wired ethernet interfaces, you can bridge between an ethernet interface and a wireless interface. However, most Access Points (APs) will reject frames that have a source address that didn’t authenticate with the AP. Since Linux does ethernet bridging transparently (doesn’t modify outgoing or incoming frames), we have to set up some rules to do this with a program called ebtables.

『訳:有線LAN同士をブリッジさせるような感じで、お前は有線LANと無線LANをブリッジできるぜ。でもよお、大抵のAPっていうのは認証してない送信元アドレスがあるフレームを拒否るんだわ。Linuxは透過的なブリッジをするから(行き/帰りどっちのフレームに対しても、変更するようなことはしないぜ)、どーしてもブリッジしてぇなら、「ebtables」っつープログラムを使っていくつかのルールを設定しなきゃダメだぜ。』

私の脳内で、ようやく話がつながってきました。

神々の教えを図にすると、こういうことでしょうか

f:id:ryo-plavi:20200711214358p:plain

Linuxカーネルは様々な用途で使用されるため、想定ケースが多くて大変なんでしょうね。

なので、基本的に無線ブリッジとして使うことはさせないけど、どうしてもやりたいなら自分で工夫してね。ということなんだと思います。

でも、VirtualBoxはできるんですよコレが。ではどういう工夫をしてるんでしょうか?

資料を見てみましょう

www.virtualbox.org

こうありますね

Bridging to a wireless interface is done differently from bridging to a wired interface, because most wireless adapters do not support promiscuous mode. All traffic has to use the MAC address of the host's wireless adapter, and therefore Oracle VM VirtualBox needs to replace the source MAC address in the Ethernet header of an outgoing packet to make sure the reply will be sent to the host interface.

『訳:ほとんどの無線LANアダプタはプロミスキャストモードをサポートしてないので、無線LANのブリッジは有線LANのブリッジとは違うんだ。(パケットを編集するために)すべてのトラフィックがホストの無線LANアダプタを経由させる必要があり、そのためVirtualBoxは戻りパケットがホストのNICに来るように、送信パケットのEthernetヘッダの送信元MACアドレスを(ホストの無線LANアダプタのMACアドレスに)書き換える必要があるんだぜ。』

なるほどー

じゃあ確かめてみよう!!

パケットキャプチャしてみる

VMからPING送信

VirtualBoxで作ったVMからPINGを送信してみます。

まずは仮想化ホスト(Ubuntu20.04)ののMACアドレスIPアドレスを確認

$ ip addr show wlp8s0
3: wlp8s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 08:d2:3e:a2:a2:29 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.8/24 brd 192.168.1.255 scope global dynamic noprefixroute wlp8s0
       valid_lft 85067sec preferred_lft 85067sec
    inet6 240d:1a:5af:9a00:9ed:344d:e634:58fe/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 7064sec preferred_lft 7064sec
    inet6 fe80::7293:7384:ea6c:ed7c/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

次にVM(CentOS8.2)のMACアドレスIPアドレスを確認

# ip addr show enp0s3
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:9a:8d:e0 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.10/24 brd 192.168.1.255 scope global dynamic noprefixroute enp0s3
       valid_lft 78571sec preferred_lft 78571sec

ホストマシンとVMIPアドレスMACアドレスは以下であることが確認できました

マシン種別 IPアドレス MACアドレス
ホストマシン 192.168.1.8 08:d2:3e:a2:a2:29
VM 192.168.1.10 08:00:27:9a:8d:e0

google.co.jpにIPv4PINGを打ちます

# ping -4 google.co.jp

ホスト側でキャプチャしたのが以下の画像です

f:id:ryo-plavi:20200711140149p:plain

青枠を見てもらうと分かると思うんですが、VMからの1回目は失敗していますが、2回目で送信元MACアドレスを書き換えて再送信してます。 戻りパケットはホストのMACアドレスです。

まさにこの文章と一致する動きなのが確認できました。

図解するとこんな感じでしょうか

f:id:ryo-plavi:20200711225430p:plain

まとめ

最後に

本当にありがとうございました!

最後までお読みいただきありがとうございました。