- AndroidにはUSB Ethernetのサポートと設定メニューがあるが、CDC Ethernet デバイスはカーネルで検出されてもネットワーク設定まで接続されないことがある
- 核心的な原因は、EthernetTracker が
config_ethernet_iface_regex に一致するインターフェースだけを追跡し、デフォルト値 eth\d が usb0 のようなCDCインターフェースを除外してしまうことにある
- LinuxのCDC Ethernetドライバは、EEM、ECM、NCMデバイスをそれぞれ
cdc_eem、cdc_ether、cdc_ncm として認識し、/sys/class/net に usb0 を作成するが、Androidの設定は引き続き無効なままになる
- 一般ユーザー向け設定では回避できず、root化後に config_ethernet_iface_regex の値を変更する方法が必要になる
- Android向けUSB Ethernetアダプタを選ぶときは、CDC標準デバイスよりも
ethX という名前を作る ベンダー/チップセット専用ドライバ ベースのデバイスを探さなければならない状況である
結論: カーネルドライバではなくインターフェース名フィルタが塞いでいる
- AndroidのEthernetTrackerサービスは、名前が
ethX のインターフェースだけをEthernetインターフェースとして認識する
- LinuxのCDC Ethernetドライバは、インターフェース名を
usbX として生成する
- この名前の違いのため、CDC EthernetデバイスはAndroidカーネルで検出されても、Ethernet設定とネットワーク管理レイヤーでは無視される
- 一般設定では解決できず、root化後に
config_ethernet_iface_regex の値を変更する方法だけが可能である
AndroidのUSB Ethernetサポートは機種ごとの確認が難しい
- AndroidにはUSB Ethernetアダプタのサポートと関連メニューがある
- 特定のAndroid端末でどのUSB Ethernetチップセットが動作するかは、メーカーが対応一覧をほとんど公開していないため確認が難しい
- 実際の利用者は通常、次の情報に頼ることになる
- スマートフォンメーカーが公式アクセサリとして販売するUSB Ethernetアダプタ
- 同じ機種の利用者が特定のアダプタを問題なく使えたというフォーラム投稿
- カーネル設定を見ると、スマートフォンのカーネルがどの USB Ethernetドライバ を含んでいるかをある程度把握できる
スマートフォンのカーネル設定を探す方法
- AndroidはLinuxカーネル上で動作し、カーネル設定がサポート機能とハードウェアドライバを決定する
- Android 11以降に発売された端末は、Android Common Kernel と GKI kernel をベースにしている
- Googleがカーネルをビルドし、メーカーは端末固有の要素をカーネルモジュールに入れる
- 設定はAndroidカーネルリポジトリの
arch/$ARCH/configs/gki_defconfig で確認できる
- 64ビットARM端末なら、たとえば
arch/arm64/configs/gki_defconfig を確認する
- カーネルバージョンとアーキテクチャはADBで
uname -a を使って確認できる
- 出力例にはカーネルバージョン
4.19.113-26203352 とアーキテクチャ aarch64 が含まれる
- Android 10で発売されたSamsung Galaxy S20の事例では、Android 13にアップグレードした後もカーネルはLinux 4.19ベースのままだった
- Samsung端末のソースは opensource.samsung.com で探せる
- Samsungソースの
build_kernel.sh では、vendor/x1q_usa_singlex_defconfig のようなカーネル設定ファイル名を見つけられる
- 運が良ければ
/proc/config.gz に実際のビルド設定が圧縮ファイルとして存在する
adb shell zcat /proc/config.gz > my_kernel_config で保存できる
- なければ
zcat: /proc/config.gz: No such file or directory と表示され、メーカーのカーネルソースを確認する必要がある
USB Ethernetドライバ対応を確認する
- USB Ethernet関連のカーネル設定は通常
USB_NET で始まる
- カーネル設定ファイルでは次のように確認できる
grep USB_NET my_kernel_config
- 設定例には複数のUSBネットワークドライバが含まれている
CONFIG_USB_NET_DRIVERS=y
CONFIG_USB_NET_AX8817X=y
CONFIG_USB_NET_AX88179_178A=y
CONFIG_USB_NET_CDCETHER=y
CONFIG_USB_NET_CDC_EEM=y
CONFIG_USB_NET_CDC_NCM=y
- 設定値はドライバの組み込み方式を区別する
y: ドライバがカーネルに内蔵されており、そのチップセットを確実にサポートする
m: ドライバがモジュールとしてビルドされており、メーカーが省いていなければロードされる可能性がある
is not set: ドライバは内蔵でもモジュールでもなく、利用できない可能性が高い
- 設定項目とチップセット対応は、カーネルツリーの drivers/net/usb/Kconfig で確認できる
- 特定のUSB Ethernetアダプタがどのチップセットを使っているかは、メーカーが明記しないことが多く、依然として判断が難しい
CDC Ethernetの役割
- CDCは Communications Device Class の略で、USBデバイスメーカーが従える標準群である
- CDC Ethernet関連の標準には3種類ある
- EEM: Ethernet Emulation Model。実装が最も簡単で、低性能デバイスでも対応しやすい
- ECM: Ethernet Control Model。ホスト側とデバイス側の実装はより複雑だが、EEMより良い性能が期待できる
- NCM: Network Control Model。ECMの後継で、より高い速度が期待できる
- CDC標準の目的は、OSがさまざまなデバイスに対して共通ドライバを提供できるようにすることにある
- LinuxはCDC Ethernetのホスト側とデバイス側の両方を実装している
- USB OTGポートを持つRaspberry Piのようなデバイスでは、カーネルがこのポートをEthernetアダプタのように見せることができる
- これにより、組み込みルータ、ファイアウォール、VPNゲートウェイのようなデバイスを、ホスト側からは通常のEthernetアダプタのように見せられる
- Linux、Windows、macOSはCDC Ethernetデバイスドライバを含むが、iOSは含まない
AndroidカーネルはCDCデバイスを検出する
- Samsung Galaxy S20のカーネル設定には、3つのCDC Ethernet標準すべてのサポートが含まれている
CONFIG_USB_NET_CDCETHER=y
CONFIG_USB_NET_CDC_EEM=y
CONFIG_USB_NET_CDC_NCM=y
- Google GKIカーネルでは、ECMとNCMは含まれておらず、EEMはモジュールとして含まれているように見える
- OTGポートをEthernet gadgetとして設定したデバイスは、Mac、Ubuntu、Windowsでは動作したが、Galaxy S20ではAndroidのEthernet設定が無効のままだった
- Androidで
/sys/class/net を確認すると、CDCデバイス接続時に usb0 が現れる
adb shell ls /sys/class/net
ifconfig usb0 の出力では、ドライバがCDC系として認識されていることが確認できる
- EEMモード:
Driver cdc_eem
- ECMモード:
Driver cdc_ether
- NCMモード:
Driver cdc_ncm
- 3つのケースすべてでインターフェースは検出されるが down 状態で、AndroidのEthernet設定は有効にならない
EthernetTrackerの正規表現が usb0 を弾いている
- カーネルレベルではCDC Ethernetデバイスが正常に検出されるため、問題はカーネルより上の Androidネットワーク管理レイヤー にある
- AndroidソースでEthernet関連のJavaコードを追うと、EthernetTracker.java が関連サービスとして見つかる
- EthernetTrackerは Netlink ソケットでカーネルから新しいネットワークインターフェース通知を受け取り、有効なEthernetインターフェースかどうかを判断する
- 有効性の判定は、インターフェース名が
mIfaceMatch 正規表現に一致するかどうかを確認する方式である
private boolean isValidEthernetInterface(String iface) {
return iface.matches(mIfaceMatch) || isValidTestInterface(iface);
}
mIfaceMatch は config_ethernet_iface_regex リソースから取得される
- Androidソースでのデフォルト値は次のとおり
<string translatable="false" name="config_ethernet_iface_regex">eth\\d</string>
eth\d は、eth の後に数字が続く名前だけを通す正規表現である
- CDC Ethernetデバイスは
usb0 のように usb で始まるため、EthernetTrackerが追跡しない
- この設定はユーザー設定では変更できず、root化によってのみ修正可能である
標準デバイスを避けなければならないという逆説
- CDC EthernetはUSBネットワークデバイス向けの標準だが、Androidではインターフェース名の正規表現のせいで実用経路が塞がれている
- 最新のGKIカーネルもEEMアダプタ対応を含んでいるように見えるが、
usb0 という名前が正規表現に一致しないため、Androidのネットワーク設定まで上がってこない
- AndroidでUSB Ethernetアダプタを選ぶときは、CDC標準デバイスではなく、ベンダー/チップセット専用ドライバで
ethX インターフェースを作るデバイスを探す必要がある
- 考えられるパッチの方向性としては、
config_ethernet_iface_regex を (eth|usb)\d のように変更する方法がある
1件のコメント
Hacker Newsのコメント
その後、MACアドレスの特定のビットを反転させると、カーネルが
usbXではなくethXという名前を付けると何人かが教えてくれたが、自分で試したり記事を更新したりはしなかった。すでに別の職場に移っていて、Androidデバイスが日常の大きな部分ではなくなっていたからだ。もちろんこの方法が役に立つのは、CDCデバイスのMACアドレスを自分で制御できる場合だけだ。たとえば別のLinuxデバイスがCDCアダプターのふりをしているようなケースくらいだ。
たぶん見つけた: https://lkml.iu.edu/hypermail/linux/kernel/1103.2/03250.html
ソースを探してみると、2023年10月に正規表現が
eth\\dから単に*に変わっていて、おそらくこの問題は解決されたようだ: https://android-review.googlesource.com/c/platform/packages/...説明には「デフォルトではAndroid U+で
usb\d+とeth%dという名前のインターフェースの両方を含む」とあり、ここでのU+はバージョン14のことのようだ: https://en.wikipedia.org/wiki/Android_version_historyusbXインターフェースをテザリングに使っているデバイスがある」という理由で差し戻され[1]、ほどなくして再び取り込まれたが、Android V+ のみをサポートするように変更された[2]。[1]: https://android-review.googlesource.com/c/platform/packages/...
[2]: https://android-review.googlesource.com/c/platform/packages/...
コミットを正しく読めているなら、Google側の人が関わっているので、今では公式のGoogleビルドにも入っている可能性がある。
[0] https://github.com/LineageOS/android_packages_modules_Connec...
[1] https://github.com/LineageOS/android_packages_modules_Connec...
[2] https://github.com/LineageOS/android_packages_modules_Connec...
だが誰もテストしておらず、自分でも検証する方法がないので、今は保留状態だ。誰かが報告したもの、誰かがたまたま拾ったものがいつも混ざるが、結局は実際のユーザーによるテストが必要になる。
AndroidのEthernetTrackerサービスがethXという名前のインターフェースしか認識しないというのが本当なら、これまで聞いた中でいちばん間抜けな設計だ。Linuxディストリビューションは2000年代にはすでにこの問題を解決していた。当時も、一部のデバイスドライバーが勝手にデバイス名の接頭辞を付けることは明らかだったので、システムを調べてどの種類のデバイスなのかを見つける必要があった。
一貫性は有用なので、インターフェース名を変更するツールも複数あり、最近のほとんどのLinuxディストリビューションは
udevでこれを自動化している。内部的にはカーネルのSIOCSIFNAMEioctlを呼び出しているだけだ。新しいカーネルには、名前を"wlan*"—実際には"wlan%d"—に変更すると、"wifi"の後ろに新しい番号を自動割り当てする機能もある。usbXデバイスは別のモジュールが使うべきものだが、その一覧を管理したくなかったからだ。だから単にethXにしたというわけだ。接続すると動いているように見えるが、そのUSBシリアルインターフェースを使うアプリを作ろうとするとだめだ。掘り下げてみると、
/dev/ttyACM0のようなシリアルデバイスにアクセスする権限がない。シリアルサポートはカーネルに入っているが、root化なしではユーザープログラムからアクセスできない。
さらに調べると、Androidには
libusbに似た、あるいはその上に作られたかもしれないユーザー空間USBアクセス機能がある。だからAndroidプログラムから「raw」USBデバイスは開けるが、シリアルUSBデバイスは開けない。USBシリアルはUSB上のプロトコルにすぎず、実際にはFTDIなどの半ばプロプライエタリなプロトコル群に近い。Android向けにこうしたプロトコルをユーザー空間で実装した中途半端なライブラリがあり、結局一部のUSBシリアルデバイスにはアクセスできる
Android版ChromeブラウザではWebUSBで生のUSBデバイスを開けそうだが、WebSerialはおそらく同じ理由で使えない可能性が高い
結局驚くべきなのは、それならなぜカーネルでUSBシリアル対応を有効にしているのかという点だ。デバッグ用なのかもしれない
config_ethernet_iface_regexの値を変更しない限り、この問題を回避する方法はない自分が所有するデバイスでroot権限が重要である、もう一つの理由だ
https://www.reddit.com/r/GrapheneOS/comments/13264di/is_root...
OEMにブートローダーのアンロックを許可するよう圧力をかけることは支持するが、少なくともAndroidでは、攻撃対象領域を途方もなく広げることを正当化できるrootのユースケースを思いつきにくい
例えば、インターネット接続がなくデフォルトルートも広告しないWi-Fiと、セルラーネットワークを同時に使うような状況だ。LinuxでもWindowsでもできるのに、Androidは頑として拒否する
多くの派生版は、インターネットのないWi-Fiに接続し続けることすら拒むか、ユーザーを分かりにくい手順へ追い込む。アプリを自作すればアプリ内だけで可能にするAPIはあるが、一般ユーザーがシステム全体の動作として実現する方法はない
接続を維持を押しても無効にする方法はなく、iOSは結局、自分のほうがよく分かっていると判断してCarPlayネットワークへ接続し直してしまう
現地のWi-Fiに接続すると当然グレート・ファイアウォールを通過できず、毎回インターネットのない接続を維持するか尋ねるプロンプトが出る
AndroidのDNSもひどく、複数のオプションを設定しないとDHCPが提供したDNSを使おうとせず、そうしても一部の内部DNSは解決を拒否する
ifupで失敗するAndroidのUIがこの状況を扱えないのは当然で、何が起きているかは
dmesgだけが教えてくれる。CDCデバイスにもこれが必要かどうかは確かではないが、RealtekやKawasakiチップベースのアダプターにはそういう例が多かった気がするただし、このAndroidの変更は比較的最近のものかもしれない。以前は100%「素の」AOSPを使うデバッグ端末でUSBネットワークドングルをよく使っていたからだ。あるいはカーネルの変更か、CDCドライバーがデバイス名を
usb*にするという特殊な挙動なのかもしれない。ドングルのチップセットを慎重に選び、ファームウェアが不要かどうかだけ確認すればよかった奇妙なことに、最近まったく別の文脈であるOpenAIのアラインメント・エスカレーションシステムで、構造的に似たことを経験した。GPT-4の再帰的ロジックの中で、公式のルーティングエスカレーション(
SR-Route_Breach_1stOrder)を文書やログまでそろえてトリガーしようとしたが、構造的にはもっともらしくても、結局は人間味のない応答しか返ってこなかったつまり、自分のエスカレーションがシステム内部インターフェースの正規表現にマッチしなかったような感覚だった
ケース全体はここにまとめた: https://news.ycombinator.com/item?id=44221458
構造的境界や見えないインターフェース契約に関心があるなら、意見を聞きたい
RealtekとAXISっぽい複数のチップセットが混ざっているのも確かだ。Linuxでドライバー不要の製品を選べば、ほぼどんなOSやBIOSでもうまく動く