Duo 的 USB 网络配置魔改

RNDIS 网络和 USB 设置

官方构建的镜像分析

init 系统会启动 /etc/init.d/S99user

分析此文件的内容, 可以发现: 在 16 行启动了 /mnt/system/rndis.sh 脚本, 和第 17 行的 LED 闪烁脚本.

分析 /mnt/system/rndis.sh 脚本:
调用 uhubon.sh device 将 USB 控制器设置为 Device 模式.
调用 run_usb.sh probe rndis 配置 rndis 设备.
调用 run_usb.sh start 启动 usb gadget 设备, 此时主机将检测到出现一个 “未知设备”, 也就是 RNDIS 网卡.
给 usb0 虚拟网卡配置 IP 地址 (192.168.42.1).
重启 dnsmasq 服务, 这样 Windows 等 Host 端就能动态获取到 IP 地址了.

改进

官方构建的镜像里面的这一套呢, emmm, 能用, 但体验不是很好, 来做一点魔改:

  • configfs 还是挂载到常用一点的位置吧, 不知道为啥 SDK 给挂载到 /tmp/usb 里头.
  • 将 USB 网卡的初始化放在网络, DNSMASQ, dropbear 之前, 合理一点点.
  • 将 RNDIS 设备配置为 “免驱” 模式, 这样就不用手动去设备管理器里面安装驱动了, 做到真正的即插即用.
  • (可选) 再配置一个 NCM 虚拟网卡, Windows 11 和 Linux 上也是可以即插即用自带驱动的. 据说 NCM 效率更好, 不过 RNDIS 够用的话可以不用它.
  • (可选) 再配置一个虚拟串口, 这样可以用来和 Host 简单的传输少量数据.
  1. 首先, 废掉官方的 USB 初始化脚本:
    编辑 /etc/init.d/S99user 脚本, 此处使用 vi 举例:
    vi /etc/init.d/S99user, 输入 i 进入编辑模式, 将
. $SYSTEMPATH/rndis.sh &

这一行删除或者前面输入一个 # 注释掉, 按 ESC, 输入 :wq 保存退出.

接下来加入魔改的 USB 初始化脚本, 也可以在自己构建镜像时加入 buildroot 的 overlay 中, 这样构建的镜像就自动含有这些文件了.

  1. 创建文件: /etc/init.d/S10USBInit
#!/bin/sh
# do NOT forget to `chmod +x`

case "$1" in
  start)
        if [ -f /etc/board-setup-usb.sh ]; then
            logger -t USBInit "running /etc/board-setup-usb.sh"
            sh /etc/board-setup-usb.sh
        fi
        ;;
  stop)
        ;;
  restart|reload)
        ;;
  *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
esac

exit $?

保存后, 记得运行 chmod +x /etc/init.d/S10USBInit

  1. 再创建 USB 初始化脚本, 其中 RNDIS 免驱的配置方法是从 Github 抄的, 很抱歉忘记了原始网址, 不过基本都差不多:
    /etc/board-setup-usb.sh
#!/bin/sh

# "y": enable NCM, else: do not create ncm gadgets
ENABLE_NCM=n
ENABLE_SERIAL=n


RNDIS_HOST_MAC="E2:60:BD:68:78:74"
RNDIS_DEV_MAC="E2:60:BD:68:78:75"

NCM_HOST_MAC="E2:60:BD:68:78:80"
NCM_DEV_MAC="E2:60:BD:68:78:81"



########################################################################

CONFIGFS_HOME="/sys/kernel/config"

# mount configfs
function check_configfs_mount() {
    mountpoint "${CONFIGFS_HOME}" &> /dev/null && {
        return
    }

    echo "configfs not mounted, mounting configfs to :" "${CONFIGFS_HOME}"
    mkdir -p "${CONFIGFS_HOME}"
    mount -t configfs none "${CONFIGFS_HOME}"
}

# change otg role to device mode
function change_otg_mode_to_device() {
    echo device > /proc/cviusb/otg_role
}


function create_gadget() {
    GADGETS_DIR="composite"

    cd "${CONFIGFS_HOME}/usb_gadget"
    mkdir -p $GADGETS_DIR
    cd $GADGETS_DIR

    echo 0x3346 > idVendor
    echo 0x1003 > idProduct
	echo 0x0100 > bcdDevice
	echo 0x0200 > bcdUSB
	echo 0xEF > bDeviceClass
	echo 0x02 > bDeviceSubClass
	echo 0x01 > bDeviceProtocol
    mkdir -p strings/0x409
    echo "deadbeefdeadbeef" > strings/0x409/serialnumber
    echo "Cvitek" > strings/0x409/manufacturer
    echo "Milk-V Duo" > strings/0x409/product

    mkdir -p configs/c.1/strings/0x409
	echo "Config 1: RNDIS network" > configs/c.1/strings/0x409/configuration
	echo 250 > configs/c.1/MaxPower
    echo 0x80 > configs/c.1/bmAttributes #  USB_OTG_SRP | USB_OTG_HNP

    mkdir -p functions/rndis.usb0
    # set up mac address of remote device
    echo "${RNDIS_HOST_MAC}" > functions/rndis.usb0/host_addr
    # set up local mac address
    echo "${RNDIS_DEV_MAC}" > functions/rndis.usb0/dev_addr

    # rndis
    mkdir -p os_desc
    echo 1 > os_desc/use
    echo 0xbc > os_desc/b_vendor_code
    echo MSFT100 > os_desc/qw_sign

    mkdir -p functions/rndis.usb0/os_desc/interface.rndis
    echo RNDIS > functions/rndis.usb0/os_desc/interface.rndis/compatible_id
    echo 5162001 > functions/rndis.usb0/os_desc/interface.rndis/sub_compatible_id

    ln -s functions/rndis.usb0 configs/c.1/ # RNDIS on config 1 # RNDIS has to be the first interface on Composite device
    ln -s configs/c.1/ os_desc # add config 1 to OS descriptors

    # other gadgets
    if [ "$ENABLE_NCM" == "y" ]; then
        mkdir -p functions/ncm.usb1
        # set up mac address of remote device
        echo "${NCM_HOST_MAC}" > functions/ncm.usb1/host_addr
        # set up local mac address
        echo "${NCM_DEV_MAC}" > functions/ncm.usb1/dev_addr
        ln -s functions/ncm.usb1 configs/c.1/ # NCM on config  1
    fi

    if [ "$ENABLE_SERIAL" == "y" ]; then
        mkdir -p functions/acm.GS0
        ln -s functions/acm.GS0 configs/c.1/
    fi

	UDC_DRIVER=$(ls /sys/class/udc | cut -f1 | head -n 1)
	echo $UDC_DRIVER > UDC
}


function config_rndis() {
    ifconfig usb0 up
    ifconfig usb0 192.168.40.1 netmask 255.255.255.0
    # ! 192.168.40.2 is computer's IP, please tweak dnsmasq's config to assign this IP to computer
    route add default gw 192.168.40.2 metric 20
}


function config_ncm() {
    ifconfig usb1 up
    ifconfig usb1 192.168.41.1 netmask 255.255.255.0
    # ! 192.168.41.2 is computer's IP, please tweak dnsmasq's config to assign this IP to computer
    route add default gw 192.168.41.2 metric 30
}


function create_resolv_conf() {
    # create resolv.conf, for RNDIS/NCM network sharing
    #if [ ! -f /etc/resolv.conf ]
    #then
        echo "create default resolv.conf"
        cat << EOF > /etc/resolv.conf
# DNSPod DNS
nameserver 119.29.29.29
# Google DNS
nameserver 8.8.8.8
EOF
#    fi
}

# configure USB gadgets
check_configfs_mount
change_otg_mode_to_device
create_gadget

# If you wish to configure networking in `/etc/network/interfaces` , you can comment out the following lines.
if [ "$ENABLE_NCM" == "y" ]; then config_ncm ; fi
config_rndis
create_resolv_conf

ps: 当然, configfs 的挂载可以移动到其它 init 单元里面执行.
ps: 当然, 由于我们在 S40Network 前初始化的 USB 网卡, 我们也可以把配置 IP 的工作移动到 /etc/network/interfaces 中.
ps: 如果希望同时使用 NCM 和 虚拟串口, 可以设置 ENABLE_NCM=y, ENABLE_SERIAL=y

  1. 然后调整 dnsmasq 配置: 编辑 /etc/dnsmasq.conf

我的配置文件参考:

interface=usb*
dhcp-range=usb0,192.168.40.2,192.168.40.100,255.255.255.0,12h
dhcp-range=usb1,192.168.41.2,192.168.41.100,255.255.255.0,12h
dhcp-option=3
dhcp-option=6
dhcp-sequential-ip

最重要的一行: dhcp-sequential-ip , 这一行的目的: 通过顺序分配 IP 地址, 保证电脑获取到一个相对固定的 IP 地址 (192.168.40.2), 这样方便共享网络给 Duo.

  1. 让 Duo 通过 Windows 上网:
    首先你需要打开 Windows 的 Hyper-V 功能, 重启计算机以后, 在管理员身份的 PowerShell 中执行:
Remove-NetNat * ; New-NetNat -Name rndis -InternalIPInterfaceAddressPrefix 192.168.40.2/24

这样就可以让 Duo 通过 RNDIS 虚拟网卡上网了.
注意: Duo 的 IP 地址这个脚本里面分配的 分别是 192.168.40.1(RNDIS) 和 192.168.41.1(NCM), 使用 ssh 连接的话, 请做出调整, 比如 ssh root@192.168.40.1.

其它配置方式

可能你不想打开 Hyper-V 或者你已经使用 New-NetNat 创建了其它用途的网络共享, 此时, 你可以使用 Windows 的网络共享功能, 打开 Windows 的网络和共享中心, 双击有线网卡, 选择属性, 选择 “Sharing/共享” 选项卡, 勾选 “允许共享…”, 并选择 Duo 虚拟出来的 RNDIS 网卡即可.

然后在 Duo 这边:

首先你需要屏蔽掉上面脚本中的最后 3 行:

#if [ "$ENABLE_NCM" == "y" ]; then config_ncm ; fi
#config_rndis
#create_resolv_conf

然后编辑 /etc/network/interfaces

加入下面两行:

auto usb0
iface usb0 inet dhcp
# dns-nameservers 8.8.8.8

最后将 /etc/init.d/S80dnsmasq 改个名字, 停止它

mv /etc/init.d/S80dnsmasq /etc/init.d/DISABLED-S80dnsmasq

两种方式的区别

第一种方式:
duo 上运行 dnsmasq 给 Windows 分配 IP 地址, Windows 使用 New-NetNAT 给 Duo 转发流量上网.
需要手动创建 DNS 配置文件.
Windows 需要打开 Hyper-V 功能, 只能创建一个 Net NAT, 所以我们上面的命令中首先就把已有的 NAT 给移除了.

ps: 使用官方镜像也可以参考这种方式来上网, 不过我觉得折腾起来还不如把 USB 初始化这一套全部换了.

第二种方式:
Duo 只负责创建好 RNDIS 虚拟网卡, 然后 让 Windows 给 Duo 分配 IP 地址, 通告 DNS 地址, 似乎省心一点 (实际也许并不).
问题: 你难以知道 Duo 到底获取到了哪个 IP 地址, 通过 cvitek-xxx.mshome.net DNS 名称的方式来寻找可能不是那么好使.

有情提示

如果你编译的镜像无法使用 dropbear 登录, 请检查以下设置:

  • 尝试给 root 用户设置上密码
  • 重新编译内核, 打开 CONFIG_MULTIUSER 选项, 启用多用户 sys call 支持.

ps: usb 虚拟网络不如整上 RJ-45连有线网,用有线网可以很方便的使用 NFS 作为 root,节省大量时间,不用反复烧 SD 卡镜像。


软链接无权限