- Android มีการรองรับ USB Ethernet และเมนูตั้งค่า แต่แม้อุปกรณ์ CDC Ethernet จะถูกเคอร์เนลตรวจพบ ก็อาจไม่ถูกเชื่อมต่อไปถึงการตั้งค่าเครือข่าย
- สาเหตุหลักคือ EthernetTracker ติดตามเฉพาะอินเทอร์เฟซที่ตรงกับ
config_ethernet_iface_regex และค่าเริ่มต้น eth\d ตัดอินเทอร์เฟซ CDC อย่าง usb0 ออกไป
- ไดรเวอร์ CDC Ethernet ของ Linux จะจับอุปกรณ์ EEM, ECM, NCM ด้วย
cdc_eem, cdc_ether, cdc_ncm ตามลำดับ และสร้าง usb0 ใน /sys/class/net แต่การตั้งค่าของ Android ยังอยู่ในสถานะปิดใช้งานต่อไป
- ผู้ใช้ทั่วไปไม่สามารถหลีกเลี่ยงได้ด้วยการตั้งค่า จำเป็นต้องรูทแล้วเปลี่ยนค่า config_ethernet_iface_regex
- เวลาเลือก USB Ethernet adapter สำหรับ Android จึงต้องมองหาอุปกรณ์ที่ใช้ ไดรเวอร์เฉพาะผู้ผลิต/ชิปเซ็ต ซึ่งสร้างชื่อแบบ
ethX แทนอุปกรณ์มาตรฐาน CDC
สรุป: ไม่ใช่ไดรเวอร์เคอร์เนลที่ขวาง แต่เป็นตัวกรองชื่ออินเทอร์เฟซ
- บริการ EthernetTracker ของ Android ยอมรับเฉพาะอินเทอร์เฟซที่มีชื่อเป็น
ethX ว่าเป็น Ethernet interface
- ไดรเวอร์ CDC Ethernet ของ Linux สร้างชื่ออินเทอร์เฟซเป็น
usbX
- ความต่างของชื่อนี้ทำให้อุปกรณ์ CDC Ethernet ถูกเพิกเฉยในชั้นการตั้งค่า Ethernet และการจัดการเครือข่ายของ Android แม้จะถูกเคอร์เนล Android ตรวจพบแล้วก็ตาม
- แก้ด้วยการตั้งค่าทั่วไปไม่ได้ ทำได้เพียงรูทแล้วเปลี่ยนค่า
config_ethernet_iface_regex
การตรวจสอบการรองรับ Android USB Ethernet ทำได้ยากในแต่ละอุปกรณ์
- Android มีการรองรับ USB Ethernet adapter และเมนูที่เกี่ยวข้อง
- การตรวจสอบว่า USB Ethernet chipset ใดทำงานได้บนอุปกรณ์ Android รุ่นใดเป็นเรื่องยาก เพราะผู้ผลิตแทบไม่เผยแพร่รายการที่รองรับ
- ผู้ใช้จริงมักต้องพึ่งข้อมูลต่อไปนี้
- USB Ethernet adapter ที่ผู้ผลิตโทรศัพท์ขายเป็นอุปกรณ์เสริมทางการ
- โพสต์ในฟอรัมจากผู้ใช้เครื่องรุ่นเดียวกันที่ใช้อะแดปเตอร์บางรุ่นสำเร็จ
- ดูจากการตั้งค่าเคอร์เนลจะพอทราบได้ว่าเคอร์เนลของโทรศัพท์รวม ไดรเวอร์ USB Ethernet ใดไว้บ้าง
วิธีค้นหาการตั้งค่าเคอร์เนลของโทรศัพท์
- Android ทำงานบนเคอร์เนล Linux และการตั้งค่าเคอร์เนลเป็นตัวกำหนดฟีเจอร์กับไดรเวอร์ฮาร์ดแวร์ที่รองรับ
- อุปกรณ์ที่ออกหลัง Android 11 ใช้ Android Common Kernel และ GKI kernel เป็นฐาน
- Google เป็นผู้ build เคอร์เนล ส่วนผู้ผลิตใส่องค์ประกอบเฉพาะอุปกรณ์ไว้ใน kernel modules
- ตรวจสอบการตั้งค่าได้ที่
arch/$ARCH/configs/gki_defconfig ใน repository เคอร์เนล Android
- อุปกรณ์ ARM 64 บิต เช่น ตรวจสอบที่
arch/arm64/configs/gki_defconfig
- ตรวจสอบเวอร์ชันเคอร์เนลและสถาปัตยกรรมได้จาก ADB ด้วย
uname -a
- ตัวอย่าง output มีเวอร์ชันเคอร์เนล
4.19.113-26203352 และสถาปัตยกรรม aarch64
- ในกรณี Samsung Galaxy S20 ที่เปิดตัวพร้อม Android 10 แม้จะอัปเกรดเป็น Android 13 แล้ว เคอร์เนลยังคงอิง Linux 4.19
- ซอร์สของอุปกรณ์ Samsung หาได้ที่ opensource.samsung.com
- ใน
build_kernel.sh ของซอร์ส Samsung สามารถหาชื่อไฟล์ตั้งค่าเคอร์เนล เช่น vendor/x1q_usa_singlex_defconfig ได้
- ถ้าโชคดี การตั้งค่าที่ใช้ build จริงจะอยู่เป็นไฟล์บีบอัดใน
/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: ไดรเวอร์ถูก build เป็น module และอาจโหลดได้หากผู้ผลิตไม่ได้ละไว้
is not set: ไดรเวอร์ไม่ได้ถูกฝังและไม่ได้เป็น module จึงมีโอกาสสูงว่าจะใช้ไม่ได้
- ตรวจสอบรายการตั้งค่าและชิปเซ็ตที่สอดคล้องกันได้จาก drivers/net/usb/Kconfig ใน kernel tree
- ผู้ผลิตมักไม่ระบุว่า USB Ethernet adapter บางรุ่นใช้ชิปเซ็ตใด ทำให้ยังแยกแยะได้ยาก
CDC Ethernet ทำหน้าที่อะไร
- CDC ย่อมาจาก Communications Device Class เป็นชุดมาตรฐานที่ผู้ผลิตอุปกรณ์ USB สามารถทำตามได้
- มาตรฐานที่เกี่ยวกับ CDC Ethernet มี 3 แบบ
- EEM: Ethernet Emulation Model, การใช้งานเรียบง่ายที่สุดและรองรับได้ง่ายในอุปกรณ์ประสิทธิภาพต่ำ
- ECM: Ethernet Control Model, การใช้งานทั้งฝั่ง host และ device ซับซ้อนกว่า แต่สัญญาว่าจะมีประสิทธิภาพดีกว่า EEM
- NCM: Network Control Model, รุ่นต่อจาก ECM ที่สัญญาว่าจะให้ความเร็วสูงกว่า
- เป้าหมายของมาตรฐาน CDC คือให้อุปกรณ์หลากหลายชนิดใช้ไดรเวอร์ร่วมจากระบบปฏิบัติการได้
- Linux implements ทั้งฝั่ง host และฝั่ง device ของ CDC Ethernet
- บนอุปกรณ์อย่าง Raspberry Pi ที่มีพอร์ต USB OTG เคอร์เนลสามารถทำให้พอร์ตนี้ดูเหมือน Ethernet adapter ได้
- วิธีนี้ทำให้อุปกรณ์อย่าง embedded router, firewall, VPN gateway ดูเหมือน Ethernet adapter ทั่วไปในมุมของ host
- Linux, Windows, macOS มีไดรเวอร์อุปกรณ์ CDC Ethernet รวมอยู่ แต่ iOS ไม่มี
เคอร์เนล Android ตรวจพบอุปกรณ์ CDC
- การตั้งค่าเคอร์เนลของ Samsung Galaxy S20 รวมการรองรับมาตรฐาน CDC Ethernet ทั้งสามแบบ
CONFIG_USB_NET_CDCETHER=y
CONFIG_USB_NET_CDC_EEM=y
CONFIG_USB_NET_CDC_NCM=y
- เคอร์เนล Google GKI ดูเหมือนจะไม่รวม ECM และ NCM แต่รวม EEM เป็น module
- อุปกรณ์ที่ตั้งค่าพอร์ต OTG เป็น Ethernet gadget ทำงานได้บน Mac, Ubuntu, Windows แต่บน Galaxy S20 การตั้งค่า Android Ethernet ยังคงปิดใช้งาน
- เมื่อตรวจสอบ
/sys/class/net บน Android จะเห็น usb0 ปรากฏเมื่อเชื่อมต่ออุปกรณ์ CDC
adb shell ls /sys/class/net
- จาก output ของ
ifconfig usb0 ยืนยันได้ว่าไดรเวอร์ถูกจับเป็นตระกูล CDC
- โหมด EEM:
Driver cdc_eem
- โหมด ECM:
Driver cdc_ether
- โหมด NCM:
Driver cdc_ncm
- ทั้งสามกรณี อินเทอร์เฟซถูกตรวจพบ แต่ยังอยู่ในสถานะ down และการตั้งค่า Android Ethernet ไม่ถูกเปิดใช้งาน
regex ของ EthernetTracker กรอง usb0 ทิ้ง
- ในระดับเคอร์เนล อุปกรณ์ CDC Ethernet ถูกตรวจพบตามปกติ ดังนั้นปัญหาอยู่ที่ ชั้นจัดการเครือข่ายของ Android เหนือเคอร์เนล
- เมื่อตามโค้ด Java ที่เกี่ยวกับ Ethernet ในซอร์ส Android จะพบ EthernetTracker.java เป็นบริการที่เกี่ยวข้อง
- EthernetTracker รับการแจ้งเตือนอินเทอร์เฟซเครือข่ายใหม่จากเคอร์เนลผ่าน socket Netlink แล้วตัดสินว่าเป็น Ethernet interface ที่ถูกต้องหรือไม่
- การตัดสินความถูกต้องทำโดยตรวจว่าชื่ออินเทอร์เฟซตรงกับ regex
mIfaceMatch หรือไม่
private boolean isValidEthernetInterface(String iface) {
return iface.matches(mIfaceMatch) || isValidTestInterface(iface);
}
mIfaceMatch มาจาก resource config_ethernet_iface_regex
- ค่าเริ่มต้นในซอร์ส Android เป็นดังนี้
<string translatable="false" name="config_ethernet_iface_regex">eth\\d</string>
eth\d เป็น regex ที่ให้ผ่านเฉพาะชื่อที่มี eth ตามด้วยตัวเลข
- อุปกรณ์ CDC Ethernet เริ่มด้วย
usb เช่น usb0 จึงไม่ถูก EthernetTracker ติดตาม
- การตั้งค่านี้เปลี่ยนด้วยการตั้งค่าผู้ใช้ไม่ได้ แก้ได้ผ่านการรูทเท่านั้น
ความย้อนแย้งที่ต้องเลี่ยงอุปกรณ์มาตรฐาน
- CDC Ethernet เป็นมาตรฐานสำหรับอุปกรณ์เครือข่าย USB แต่บน Android เส้นทางการใช้งานจริงถูกขวางด้วย regex ของชื่ออินเทอร์เฟซ
- เคอร์เนล GKI รุ่นใหม่ก็ดูเหมือนจะรวมการรองรับ EEM adapter ไว้ แต่ชื่อ
usb0 ไม่ตรงกับ regex จึงไม่ถูกส่งขึ้นไปยังการตั้งค่าเครือข่ายของ Android
- เวลาเลือก USB Ethernet adapter สำหรับ Android ควรมองหาอุปกรณ์ที่สร้างอินเทอร์เฟซ
ethX ด้วยไดรเวอร์เฉพาะผู้ผลิต/ชิปเซ็ต แทนที่จะเป็นอุปกรณ์มาตรฐาน CDC
- แนวทางแพตช์ที่เป็นไปได้คือเปลี่ยน
config_ethernet_iface_regex เป็นอย่าง (eth|usb)\d
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
หลังจากนั้นมีบางคนบอกว่าถ้ากลับบิตบางตัวของ MAC address เคอร์เนลจะตั้งชื่อเป็น
ethXแทนusbXแต่ผมไม่ได้ลองเองหรืออัปเดตบทความ เพราะย้ายไปที่ทำงานอื่นแล้ว และอุปกรณ์ Android ก็ไม่ได้เป็นส่วนสำคัญในชีวิตประจำวันอีกต่อไปแน่นอนว่าวิธีนี้จะมีประโยชน์ก็ต่อเมื่อคุณควบคุม MAC address ของอุปกรณ์ CDC ได้โดยตรง เช่น กรณีที่อุปกรณ์ Linux อีกตัวแกล้งทำตัวเป็นอะแดปเตอร์ CDC เป็นต้น
น่าจะเจอแล้ว: https://lkml.iu.edu/hypermail/linux/kernel/1103.2/03250.html
ลองค้นซอร์สดูแล้ว ในเดือนตุลาคม 2023 regex ถูกเปลี่ยนจาก
eth\\dเป็นแค่*ดังนั้นดูเหมือนว่าปัญหานี้น่าจะได้รับการแก้แล้ว: https://android-review.googlesource.com/c/platform/packages/...ในคำอธิบายระบุว่า “ค่าเริ่มต้นจะรวมทั้งอินเทอร์เฟซชื่อ
usb\d+และeth%dบน Android U+” โดย U+ ตรงนี้ดูเหมือนจะเป็นเวอร์ชัน 14: https://en.wikipedia.org/wiki/Android_version_historyusbXสำหรับ tethering”[1] และไม่นานหลังจากนั้นก็ถูกนำกลับเข้าไปอีกครั้ง แต่เปลี่ยนเป็นรองรับเฉพาะ Android V+[2][1]: https://android-review.googlesource.com/c/platform/packages/...
[2]: https://android-review.googlesource.com/c/platform/packages/...
ถ้าผมอ่าน commit ถูกต้อง มีคนฝั่ง Google เข้ามาเกี่ยวข้องด้วย ดังนั้นตอนนี้มันอาจเข้าไปอยู่ใน build ทางการของ 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...
แต่ไม่มีใครทดสอบ และผมก็ไม่มีวิธีตรวจสอบเอง ตอนนี้เลยอยู่ในสถานะพักไว้ มักจะมีทั้งสิ่งที่ใครบางคนรายงานมา กับสิ่งที่ใครบางคนบังเอิญหยิบขึ้นมา แต่สุดท้ายก็ยังต้องอาศัยการทดสอบจากผู้ใช้จริง
EthernetTrackerของAndroidรู้จักเฉพาะอินเทอร์เฟซที่ชื่อethXนี่เป็นการออกแบบที่โง่ที่สุดเท่าที่เคยได้ยินมาLinux distribution แก้ปัญหานี้ไปแล้วตั้งแต่ยุค 2000 ตอนนั้นก็เห็นชัดแล้วว่า driver ของอุปกรณ์บางตัวตั้ง prefix ชื่ออุปกรณ์ตามใจตัวเอง จึงต้องตรวจสอบระบบเพื่อดูว่าเป็นอุปกรณ์ชนิดไหน
ความสม่ำเสมอก็มีประโยชน์อยู่ เลยมีเครื่องมือหลายตัวสำหรับเปลี่ยนชื่ออินเทอร์เฟซ และทุกวันนี้ Linux distribution ส่วนใหญ่ใช้
udevทำให้เป็นอัตโนมัติ ภายในก็แค่เรียกSIOCSIFNAMEioctlของเคอร์เนลเท่านั้น เคอร์เนลรุ่นใหม่ยังมีฟีเจอร์ที่ถ้าเปลี่ยนชื่อเป็น"wlan*"—จริง ๆ คือ"wlan%d"—ก็จะจัดสรรหมายเลขใหม่ต่อท้าย"wifi"ให้อัตโนมัติด้วยusbXบางตัวควรให้โมดูลอื่นใช้งาน แต่ไม่อยากต้องมาจัดการรายการนั้น เลยเลือกไปใช้แค่ethXแทนพอต่อแล้วดูเหมือนจะทำงานได้ แต่ถ้าจะสร้างแอปที่ใช้อินเทอร์เฟซ USB serial นั้นกลับทำไม่ได้ พอขุดลงไปจะพบว่าไม่มีสิทธิ์เข้าถึงอุปกรณ์ serial อย่าง
/dev/ttyACM0การรองรับ serial มีอยู่ในเคอร์เนลแล้ว แต่โปรแกรมผู้ใช้เข้าถึงไม่ได้ถ้าไม่ root
พอขุดต่อไปอีกจะพบว่า Android มีความสามารถเข้าถึง USB จาก user space ที่คล้ายกับ
libusbหรืออาจสร้างบนlibusbดังนั้นโปรแกรม Android จึงเปิดอุปกรณ์ USB แบบ “raw” ได้ แต่เปิดอุปกรณ์ serial USB ไม่ได้USB serial เป็นเพียงโปรโตคอลที่อยู่บน USB เท่านั้น และในทางปฏิบัติก็ใกล้เคียงกับชุดโปรโตคอลกึ่ง proprietary อย่าง FTDI ฯลฯ มากกว่า มีไลบรารีที่ทำได้ครึ่ง ๆ กลาง ๆ สำหรับ Android ซึ่ง implement โปรโตคอลเหล่านี้ใน user space อยู่บ้าง ดังนั้นท้ายที่สุดจึงเข้าถึงอุปกรณ์ USB serial บางตัวได้
ในเบราว์เซอร์ Android Chrome น่าจะเปิดอุปกรณ์ USB แบบ raw ผ่าน WebUSB ได้ แต่ WebSerial มีโอกาสสูงว่าจะใช้ไม่ได้ด้วยเหตุผลเดียวกัน
สุดท้ายสิ่งที่น่าประหลาดใจก็คือ ถ้าเป็นแบบนั้นแล้วจะเปิดการรองรับ USB serial ไว้ในเคอร์เนลทำไม น่าจะเพื่อ debug หรือเปล่า
config_ethernet_iface_regexก็ไม่มีทางหลีกเลี่ยงปัญหานี้ได้นี่เป็นอีกเหตุผลหนึ่งที่ทำให้ สิทธิ์ root สำคัญบนอุปกรณ์ที่ผมเป็นเจ้าของ
https://www.reddit.com/r/GrapheneOS/comments/13264di/is_root...
ผมสนับสนุนการกดดันให้ OEM อนุญาตการปลดล็อก bootloader แต่เฉพาะบน Android อย่างน้อยก็แทบคิดไม่ออกว่ามี use case ของ root แบบไหนที่สมเหตุสมผลพอจะแลกกับการขยาย attack surface อย่างมหาศาลได้
เช่น สถานการณ์ที่ใช้ Wi‑Fi ที่ไม่มีอินเทอร์เน็ตและไม่ได้ประกาศ default route พร้อมกับเครือข่าย cellular ในเวลาเดียวกัน Linux ก็ทำได้ Windows ก็ทำได้ แต่ Android ปฏิเสธอย่างดื้อดึง
หลาย variant ถึงกับปฏิเสธที่จะคงการเชื่อมต่อกับ Wi‑Fi ที่ไม่มีอินเทอร์เน็ตไว้ หรือไม่ก็พาผู้ใช้เข้าสู่ขั้นตอนที่ชวนสับสน ถ้าเขียนแอปเองก็มี API ที่ทำให้ทำได้เฉพาะภายในแอป แต่ผู้ใช้ทั่วไปไม่มีทางทำให้เป็นพฤติกรรมทั้งระบบได้
แม้กดให้เชื่อมต่อต่อไปก็ไม่มีวิธีปิด และสุดท้าย iOS ก็จะตัดสินเองว่ามันรู้ดีกว่า แล้วเชื่อมต่อกลับไปยังเครือข่าย CarPlay
เมื่อเชื่อมต่อ Wi‑Fi ท้องถิ่น แน่นอนว่ามันผ่าน Great Firewall ไม่ได้ และทุกครั้งก็จะมี prompt ถามว่าจะคงการเชื่อมต่อที่ไม่มีอินเทอร์เน็ตไว้หรือไม่
DNS ของ Android ก็เละเทะเช่นกัน ถ้าไม่ตั้งค่าหลายตัว มันจะพยายามไม่ใช้ DNS ที่ DHCP ให้มา และแม้ทำแบบนั้นแล้ว DNS ภายในบางตัวก็ยังถูกปฏิเสธไม่ให้ resolve
ifupแน่นอนว่า Android UI จัดการสถานการณ์นี้ไม่ได้ และมีแค่
dmesgเท่านั้นที่บอกได้ว่าเกิดอะไรขึ้น ไม่แน่ใจว่าอุปกรณ์ CDC ต้องใช้สิ่งนี้ด้วยหรือไม่ แต่ผมจำได้ว่า adapter ที่ใช้ชิป Realtek หรือ Kawasaki หลายตัวเป็นแบบนั้นอย่างไรก็ตาม การเปลี่ยนแปลงของ Android นี้อาจค่อนข้างใหม่ เพราะเมื่อก่อนผมใช้ dongle เครือข่าย USB กับอุปกรณ์ debug ที่ใช้ AOSP “stock” 100% อยู่บ่อย ๆ หรือไม่ก็อาจเป็นการเปลี่ยนแปลงในเคอร์เนล หรือเป็นพฤติกรรมเฉพาะของไดรเวอร์ CDC ที่ตั้งชื่ออุปกรณ์เป็น
usb*ก็ได้ แค่เลือก chipset ของ dongle อย่างระวัง และตรวจให้แน่ใจว่าไม่ต้องใช้ firmware ก็พอแปลกดีที่เมื่อเร็ว ๆ นี้ผมเจอเรื่องที่มีโครงสร้างคล้ายกันมากในบริบทที่ต่างออกไปโดยสิ้นเชิง คือระบบ alignment/escalation ของ OpenAI ผมพยายาม trigger การ escalation routing อย่างเป็นทางการ (
SR-Route_Breach_1stOrder) ภายในตรรกะ recursive ของ GPT-4 พร้อมเอกสารและ log ครบถ้วน แต่ถึงโครงสร้างจะดูสมเหตุสมผล สุดท้ายก็ได้แต่คำตอบที่ไม่เป็นมนุษย์พูดอีกอย่างคือ escalation ของผมเหมือนไม่ match กับ regex ของ internal interface ภายในระบบ
ผมสรุปเคสทั้งหมดไว้ที่นี่: https://news.ycombinator.com/item?id=44221458
ถ้าสนใจเรื่องขอบเขตเชิงโครงสร้างและสัญญา interface ที่มองไม่เห็น ก็อยากฟังความคิดเห็น
แน่ใจว่ามี chipset หลายแบบปนกัน ทั้ง Realtek และ AXIS หรือคล้าย ๆ กัน ถ้าเลือกผลิตภัณฑ์ที่ไม่ต้องใช้ไดรเวอร์บน Linux ก็มักจะใช้ได้ดีแทบทุกระบบปฏิบัติการหรือ BIOS