1 ポイント 投稿者 GN⁺ 2025-06-09 | 1件のコメント | WhatsAppで共有
  • AndroidにはUSB Ethernetのサポートと設定メニューがあるが、CDC Ethernet デバイスはカーネルで検出されてもネットワーク設定まで接続されないことがある
  • 核心的な原因は、EthernetTrackerconfig_ethernet_iface_regex に一致するインターフェースだけを追跡し、デフォルト値 eth\dusb0 のようなCDCインターフェースを除外してしまうことにある
  • LinuxのCDC Ethernetドライバは、EEM、ECM、NCMデバイスをそれぞれ cdc_eemcdc_ethercdc_ncm として認識し、/sys/class/netusb0 を作成するが、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 KernelGKI 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);
}
  • mIfaceMatchconfig_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件のコメント

 
GN⁺ 2025-06-09
Hacker Newsのコメント
  • 以前の職場で AndroidデバイスとCDC Ethernetアダプターを接続するのに苦労した週のあと、この文章を書いた。
    その後、MACアドレスの特定のビットを反転させると、カーネルがusbXではなくethXという名前を付けると何人かが教えてくれたが、自分で試したり記事を更新したりはしなかった。すでに別の職場に移っていて、Androidデバイスが日常の大きな部分ではなくなっていたからだ。
    もちろんこの方法が役に立つのは、CDCデバイスのMACアドレスを自分で制御できる場合だけだ。たとえば別のLinuxデバイスがCDCアダプターのふりをしているようなケースくらいだ。
  • 面白い詳細な分析記事だ。
    ソースを探してみると、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_history
  • LineageOSのコミット履歴を見ると、この問題はいったん修正され[0]、互換性の問題で差し戻され[1]、その差し戻しが再度取り消されたものの[2]、最新のAndroidバージョンにのみ適用されたようだ。
    コミットを正しく読めているなら、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...
    • Lineage基準では、しばらく前にそれを見つけて https://review.lineageos.org/c/LineageOS/android_packages_mo... を作った。
      だが誰もテストしておらず、自分でも検証する方法がないので、今は保留状態だ。誰かが報告したもの、誰かがたまたま拾ったものがいつも混ざるが、結局は実際のユーザーによるテストが必要になる。
  • AndroidEthernetTrackerサービスがethXという名前のインターフェースしか認識しないというのが本当なら、これまで聞いた中でいちばん間抜けな設計だ。
    Linuxディストリビューションは2000年代にはすでにこの問題を解決していた。当時も、一部のデバイスドライバーが勝手にデバイス名の接頭辞を付けることは明らかだったので、システムを調べてどの種類のデバイスなのかを見つける必要があった。
    一貫性は有用なので、インターフェース名を変更するツールも複数あり、最近のほとんどのLinuxディストリビューションはudevでこれを自動化している。内部的にはカーネルのSIOCSIFNAME ioctlを呼び出しているだけだ。新しいカーネルには、名前を"wlan*"—実際には"wlan%d"—に変更すると、"wifi"の後ろに新しい番号を自動割り当てする機能もある。
    • AndroidでNetworkManagerを使い、Androidの無線設定UIがNetworkManagerのGUIのように動作するようにしたら筋が通るのか気になる。
    • 一部のusbXデバイスは別のモジュールが使うべきものだが、その一覧を管理したくなかったからだ。だから単にethXにしたというわけだ。
  • AndroidスマートフォンにUSBシリアルデバイスを接続しようとするときも、似たような間抜けなことが起きる。
    接続すると動いているように見えるが、そのUSBシリアルインターフェースを使うアプリを作ろうとするとだめだ。掘り下げてみると、/dev/ttyACM0のようなシリアルデバイスにアクセスする権限がない。
    シリアルサポートはカーネルに入っているが、root化なしではユーザープログラムからアクセスできない。
    さらに調べると、Androidにはlibusbに似た、あるいはその上に作られたかもしれないユーザー空間USBアクセス機能がある。だからAndroidプログラムから「raw」USBデバイスは開けるが、シリアルUSBデバイスは開けない。

USBシリアルはUSB上のプロトコルにすぎず、実際にはFTDIなどの半ばプロプライエタリなプロトコル群に近い。Android向けにこうしたプロトコルをユーザー空間で実装した中途半端なライブラリがあり、結局一部のUSBシリアルデバイスにはアクセスできる
Android版ChromeブラウザではWebUSBで生のUSBデバイスを開けそうだが、WebSerialはおそらく同じ理由で使えない可能性が高い
結局驚くべきなのは、それならなぜカーネルでUSBシリアル対応を有効にしているのかという点だ。デバッグ用なのかもしれない

  • 端末をroot化してconfig_ethernet_iface_regexの値を変更しない限り、この問題を回避する方法はない
    自分が所有するデバイスでroot権限が重要である、もう一つの理由だ
    • ただし「root化」はAndroidの多くのセキュリティ機能を取り除く。アプリが必要な権限だけを持つ代わりに、rootとしてすべての権限を持ててしまうため、巨大なセキュリティ脆弱性になる
      https://www.reddit.com/r/GrapheneOS/comments/13264di/is_root...
    • ネットワークトラフィックを自由に迂回・切り替えできるというのは、ユーザー空間にスーパーユーザー権限を置くべきではない最大の理由かもしれない
      OEMにブートローダーのアンロックを許可するよう圧力をかけることは支持するが、少なくともAndroidでは、攻撃対象領域を途方もなく広げることを正当化できるrootのユースケースを思いつきにくい
  • Androidは腹立たしいことに、複数のネットワークへ同時に接続できない
    例えば、インターネット接続がなくデフォルトルートも広告しないWi-Fiと、セルラーネットワークを同時に使うような状況だ。LinuxでもWindowsでもできるのに、Androidは頑として拒否する
    多くの派生版は、インターネットのないWi-Fiに接続し続けることすら拒むか、ユーザーを分かりにくい手順へ追い込む。アプリを自作すればアプリ内だけで可能にするAPIはあるが、一般ユーザーがシステム全体の動作として実現する方法はない
    • iOSも同じだ。ドライブレコーダーに接続して映像をダウンロードしていると、しばらくして「インターネットが検出されません。セルラーに切り替えますか?」のようなポップアップが出る
      接続を維持を押しても無効にする方法はなく、iOSは結局、自分のほうがよく分かっていると判断してCarPlayネットワークへ接続し直してしまう
    • 西側諸国向けのAndroidスマートフォンを持って中国本土へ行くと、さらに面倒だ。インターネット接続の有無をGoogleサービスへの接続試行で判断するためだ
      現地のWi-Fiに接続すると当然グレート・ファイアウォールを通過できず、毎回インターネットのない接続を維持するか尋ねるプロンプトが出る
    • インターネットが切れると、スマートフォンで診断できなくて非常に腹立たしい。インターネットのないWi-Fiに接続し続けないからだ
      AndroidのDNSもひどく、複数のオプションを設定しないとDHCPが提供したDNSを使おうとせず、そうしても一部の内部DNSは解決を拒否する
    • Windowsもできないのではないかと思う。Windowsで無線アダプターが2つあっても、GUIでは別々のWi-Fiネットワーク2つに接続できなかった。ターミナルでは試していない
  • ファームウェア要件も確認する必要がある。デバイスによっては列挙はされるが、必要なファームウェアがないと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
    構造的境界や見えないインターフェース契約に関心があるなら、意見を聞きたい
  • 本当に不思議だ。USB Ethernetアダプターを15個ほど持っているが、全部問題なく動作する
    RealtekとAXISっぽい複数のチップセットが混ざっているのも確かだ。Linuxでドライバー不要の製品を選べば、ほぼどんなOSやBIOSでもうまく動く