├── asuswrt-usb-network.service ├── install_router.sh ├── LICENSE ├── install_pi.sh ├── README.md └── asuswrt-usb-network.sh /asuswrt-usb-network.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Enable USB network when connected to Asus router 3 | After=systemd-modules-load.service 4 | 5 | # These delay the boot process but make sure that services 6 | # depending on "network-online.target" will not break. 7 | # If this will not be the only network then you can safely remove these. 8 | Before=network-online.target 9 | Wants=network-online.target 10 | 11 | [Service] 12 | Type=oneshot 13 | RemainAfterExit=yes 14 | ExecStart=/usr/local/sbin/asuswrt-usb-network start 15 | ExecStop=/usr/local/sbin/asuswrt-usb-network stop 16 | 17 | [Install] 18 | WantedBy=sysinit.target 19 | -------------------------------------------------------------------------------- /install_router.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | [ -f "/rom/jffs.json" ] || { echo "This script must run on the Asus router!"; exit 1; } 4 | 5 | set -e 6 | 7 | echo "Installing required scripts..." 8 | 9 | if [ ! -f /jffs/scripts/jas.sh ]; then 10 | curl -fsS "https://raw.githubusercontent.com/jacklul/asuswrt-scripts/master/install.sh" | sh 11 | /bin/sh /jffs/scripts/jas.sh setup 12 | fi 13 | 14 | /jffs/scripts/jas.sh install usb-network service-event hotplug-event 15 | 16 | echo "Starting..." 17 | /jffs/scripts/jas.sh usb-network start 18 | /jffs/scripts/jas.sh service-event start 19 | /jffs/scripts/jas.sh hotplug-event start 20 | 21 | echo "Finished!" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2025 Jack'lul 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /install_pi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | [ -f "/rom/jffs.json" ] && { echo "This script must run on the Raspberry Pi!"; exit 1; } 4 | [ "$UID" -eq 0 ] || { echo "This script must run as root!"; exit 1; } 5 | command -v debugfs >/dev/null 2>&1 || { echo "This script requires 'debugfs' command - install it with \"apt-get install e2fsprogs\"."; exit 1; } 6 | 7 | SPATH=$(dirname "$0") 8 | REQUIRED_FILES=( asuswrt-usb-network.sh asuswrt-usb-network.service ) 9 | DOWNLOAD_PATH=/tmp/asuswrt-usb-network 10 | DOWNLOAD_URL=https://raw.githubusercontent.com/jacklul/asuswrt-usb-raspberry-pi/master 11 | 12 | set -e 13 | 14 | MISSING_FILES=0 15 | for FILE in "${REQUIRED_FILES[@]}"; do 16 | [ ! -f "$SPATH/$FILE" ] && MISSING_FILES=$((MISSING_FILES+1)) 17 | done 18 | 19 | if [ "$MISSING_FILES" -gt 0 ]; then 20 | if [ "$MISSING_FILES" = "${#REQUIRED_FILES[@]}" ]; then 21 | mkdir -pv "$DOWNLOAD_PATH" 22 | SPATH="$DOWNLOAD_PATH" 23 | fi 24 | 25 | for FILE in "${REQUIRED_FILES[@]}"; do 26 | [ ! -f "$SPATH/$FILE" ] && wget -nv -O "$SPATH/$FILE" "$DOWNLOAD_URL/$FILE" 27 | done 28 | fi 29 | 30 | for FILE in "${REQUIRED_FILES[@]}"; do 31 | [ ! -f "$SPATH/$FILE" ] && { echo "Missing required file for installation: $FILE"; exit 1; } 32 | done 33 | 34 | cp -v "$SPATH/asuswrt-usb-network.sh" /usr/local/sbin/asuswrt-usb-network && chmod 755 /usr/local/sbin/asuswrt-usb-network 35 | cp -v "$SPATH/asuswrt-usb-network.service" /etc/systemd/system && chmod 644 /etc/systemd/system/asuswrt-usb-network.service 36 | 37 | command -v dos2unix >/dev/null 2>&1 && dos2unix /usr/local/sbin/asuswrt-usb-network 38 | 39 | echo "To enable the service run \"sudo systemctl enable asuswrt-usb-network.service\" command." 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asus Router <=> USB <=> Raspberry Pi 2 | ### Connecting Raspberry Pi to LAN through USB port on Asus router 3 | 4 | This makes any Raspberry Pi capable of becoming USB Gadget to connect to LAN network through router's USB port. 5 | 6 | Great way to run [Pi-hole](https://pi-hole.net) in your network on a budget Raspberry Pi Zero! 7 | 8 | > [!WARNING] 9 | > This cannot be used together with Optware / Asus Download Master on stock firmware. 10 | 11 | ## How it works 12 | 13 | Asus routers have the capability to run a script when USB storage device is mounted. 14 | 15 | This is how this magic is happening: 16 | 17 | - Router is booting, at one point USB port gets powered and Pi starts booting as well 18 | - Pi pretends to be USB storage device, router mounts it and triggers the script 19 | - The script on the router writes a file to the mass storage device 20 | - The script on the Pi detects that and transforms itself into USB Ethernet gadget 21 | - The script on the router waits for the new network interface to become available and then enables it and adds it to the LAN bridge interface 22 | - The Pi is now a member of your LAN network 23 | 24 | The script on the router also is monitoring for the interface changes in case the Raspberry Pi reboots. 25 | 26 | ## Installation 27 | 28 | ### On the Raspberry Pi: 29 | 30 | > [!IMPORTANT] 31 | > Make sure you have `debugfs` command available - if not install it with `apt-get install e2fsprogs`. 32 | 33 | Add `dtoverlay=dwc2` to **/boot/config.txt** and `modules-load=dwc2` to **/boot/cmdline.txt** after `rootwait`. 34 | 35 | Install `asuswrt-usb-network` script: 36 | 37 | ```bash 38 | wget -O - "https://raw.githubusercontent.com/jacklul/asuswrt-usb-raspberry-pi/master/install_pi.sh" | sudo bash 39 | ``` 40 | 41 | Then enable it: 42 | 43 | ```bash 44 | sudo systemctl enable asuswrt-usb-network.service 45 | ``` 46 | 47 | Modify configuration - `sudo nano /etc/asuswrt-usb-network.conf`: 48 | 49 | - If you're running [Asuswrt-Merlin](https://www.asuswrt-merlin.net) set `SKIP_MASS_STORAGE=true` 50 | - _We are using `services-start` script on the router side - no need to use command startup method_ 51 | 52 | - If you're running official firmware in most cases you will need to set `FAKE_ASUS_OPTWARE=true` 53 | - _Newer firmware versions dropped support for `script_usbmount` NVRAM variable so we need a workaround_ 54 | - You might also need to change `ASUS_OPTWARE_ARCH` to reflect architecture of the router (set to `arm` by default) 55 | - By default `/bin/sh /jffs/scripts/usb-mount-script` command is executed on the router - you can change this with `FAKE_ASUS_OPTWARE_CMD` variable 56 | 57 | For the full list of configuration variables - [look below](#configuration). 58 | 59 | ### On the Asus router: 60 | 61 | Enable the SSH access in the router, connect to it and then execute this command to install required scripts: 62 | 63 | ```sh 64 | curl -fsSL "https://raw.githubusercontent.com/jacklul/asuswrt-usb-raspberry-pi/master/install_router.sh" | sh 65 | ``` 66 | 67 | _This command will install required scripts from [jacklul/asuswrt-scripts](https://github.com/jacklul/asuswrt-scripts) repository._ 68 | 69 | _On Asuswrt-Merlin `/jffs/scripts/services-start` will be used instead of `/jffs/scripts/usb-mount-script`._ 70 | 71 | ### Finish 72 | 73 | Power off the router, connect your Pi to the router's USB port and then turn it on - in a few minutes it should all be working smoothly! 74 | 75 | If it does not work and you're running stock firmware then make sure you are using build-in workaround - [see "Modify configuration" step above](#on-the-raspberry-pi). 76 | 77 | ## Configuration 78 | 79 | You can set configuration variables in `/etc/asuswrt-usb-network.conf`. 80 | 81 | | Variable | Default | Description | 82 | | --- | --- | --- | 83 | | NETWORK_FUNCTION | "ecm" | Network gadget function to use
Supported values are: `rndis, ecm (recommended), eem, ncm` | 84 | | VERIFY_CONNECTION | true | Verify that we can reach gateway after enabling network gadget?
Recommended if using services depending on systemd's `network-online.target` | 85 | | SKIP_MASS_STORAGE | false | Skip adding initial mass storage gadget - instead setup network gadget right away?
This is only useful on Asuswrt-Merlin firmware | 86 | | FAKE_ASUS_OPTWARE | false | Launch startup command through fake Asus' Optware installation?
(requires `SKIP_MASS_STORAGE=false`) | 87 | | FAKE_ASUS_OPTWARE_ARCH | "arm" | Optware architecture supported by the router
Known values are: `arm, mipsbig, mipsel` | 88 | | FAKE_ASUS_OPTWARE_CMD | "/bin/sh /jffs/scripts/usb-mount-script" | Command to execute when fake Asus' Optware starts | 89 | | TEMP_IMAGE_FILE | "/tmp/asuswrt-usb-network.img" | Temporary image file that will be created | 90 | | TEMP_IMAGE_SIZE | 1 | Image size in MB, might need to be increased in case your router doesn't want to mount the storage due to partition size errors | 91 | | TEMP_IMAGE_FS | "ext2" | Filesystem to use, must be supported by `mkfs.` command and the router, `ext2` should be fine in most cases | 92 | | TEMP_IMAGE_DELETE | true | Delete temporary image after it is no longer useful? | 93 | | WAIT_TIMEOUT | 90 | Maximum seconds to wait for the router to write to the storage image file
After this time is reached the script will continue as normal | 94 | | WAIT_RETRY | 0 | How many seconds to wait before recreating the gadget device
Must be set to at least 10 and lower than `WAIT_TIMEOUT` to work
Gadget restart can happen multiple times if `WAIT_TIMEOUT / WAIT_RETRY` is 2 or bigger | 95 | | WAIT_SLEEP | 1 | Time to sleep between each image contents checks, in seconds | 96 | | VERIFY_TIMEOUT | 60 | Maximum seconds to wait for the connection check | 97 | | VERIFY_SLEEP | 1 | Time to sleep between each gateway ping, in seconds | 98 | | GADGET_ID | "usbnet" | Gadget ID used in configfs path `/sys/kernel/config/usb_gadget/[ID]` | 99 | | GADGET_PRODUCT | `(generated)` | Product name, for example: "Raspberry Pi Zero W USB Gadget"
(generated from `/sys/firmware/devicetree/base/model`) | 100 | | GADGET_MANUFACTURER | "Raspberry Pi Foundation" | Product manufacturer | 101 | | GADGET_SERIAL | `(generated)` | Device serial number, by default uses CPU serial
(generated from `/proc/cpuinfo`) | 102 | | GADGET_VENDOR_ID | "0x1d6b" | `0x1d6b` = Linux Foundation | 103 | | GADGET_PRODUCT_ID | "0x0104" | `0x0104` = Multifunction Composite Gadget | 104 | | GADGET_USB_VERSION | "0x0200" | `0x0200` = USB 2.0, should be left unchanged | 105 | | GADGET_DEVICE_VERSION | "0x0100" | Should be incremented every time you change your setup
This only matters for Windows, no need to change it when plugging into Linux machines | 106 | | GADGET_DEVICE_CLASS | "0xef" | `0xef` = Multi-interface device
see https://www.usb.org/defined-class-codes | 107 | | GADGET_DEVICE_SUBCLASS | "0x02" | `0x02` = Interface Association Descriptor sub class | 108 | | GADGET_DEVICE_PROTOCOL | "0x01" | `0x01` = Interface Association Descriptor protocol | 109 | | GADGET_MAX_PACKET_SIZE | "0x40" | Declare max packet size, decimal or hex | 110 | | GADGET_MAX_POWER | "250" | Declare max power usage, decimal or hex | 111 | | GADGET_ATTRIBUTES | "0x80" | `0xc0` = self powered, `0x80` = bus powered, should be left as bus powered | 112 | | GADGET_MAC_VENDOR | "B8:27:EB" | Vendor MAC prefix to use in generated MAC address (`B8:27:EB` = Raspberry Pi Foundation) | 113 | | GADGET_MAC_HOST | " " | Host MAC address, if empty - MAC address is generated from `GADGET_MAC_VENDOR` and CPU serial | 114 | | GADGET_MAC_DEVICE | " " | Device MAC address, if empty - MAC address is generated from CPU serial with `02:` prefix | 115 | | GADGET_STORAGE_FILE | " " | Path to the image file that will be mounted as mass storage together with network function | 116 | | GADGET_STORAGE_FILE_CHECK | true | Whenever to run **e2fsck** (check and repair) on image file with each mount | 117 | | GADGET_STORAGE_STALL | " " | Change value of `stall` option, empty means system default | 118 | | GADGET_STORAGE_REMOVABLE | " " | Change value of `removable` option, empty means system default
Automatically set to 1 when attaching image file | 119 | | GADGET_STORAGE_CDROM | " " | Change value of `cdrom` option, empty means system default | 120 | | GADGET_STORAGE_RO | " " | Change value of `ro` option, empty means system default | 121 | | GADGET_STORAGE_NOFUA | " " | Change value of `nofua` option, empty means system default | 122 | | GADGET_STORAGE_INQUIRY_STRING | " " | Change value of `inquiry_string`, empty means system default
Must be in this format: `vendor(len 8) + model(len 16) + rev(len 4)` | 123 | | GADGET_SCRIPT | " " | Run custom script just before gadget creation, must be a valid path to executable script file, receives argument with device's `configfs` path | 124 | 125 | ## Setup for Pi-hole on a Pi (Zero) 126 | 127 | Install [`force-dns.sh`](https://github.com/jacklul/asuswrt-scripts#user-content-force-dnssh) script to force LAN and Guest WiFi clients to use the Pi-hole: 128 | 129 | ```sh 130 | curl -fsSL "https://raw.githubusercontent.com/jacklul/asuswrt-scripts/master/scripts/force-dns.sh" -o /jffs/scripts/force-dns.sh 131 | chmod +x /jffs/scripts/force-dns.sh 132 | ``` 133 | 134 | Edit `/jffs/scripts/force-dns.conf` and paste the following: 135 | 136 | ``` 137 | PERMIT_MAC="01:02:03:04:05:06" 138 | #PERMIT_IP="192.168.1.251-192.168.1.254" 139 | REQUIRE_INTERFACE="usb*" 140 | BLOCK_ROUTER_DNS=true 141 | #FALLBACK_DNS_SERVER="9.9.9.9" 142 | ``` 143 | 144 | Replace `01:02:03:04:05:06` with the MAC address of the `usb0` interface on the Pi - to grab it execute the following command on the Pi: 145 | 146 | ```bash 147 | sudo asuswrt-usb-network status 148 | # the `Host MAC` is the value you want to pick 149 | ``` 150 | 151 | You can add IPs or IP ranges to `PERMIT_IP` variable to prevent that IPs from having their DNS server forced. 152 | Use `FALLBACK_DNS_SERVER` in case the Pi disconnects from the router, it can also be set to the router's IP address. 153 | 154 | **When running Pi-hole on the Pi it will be beneficial to run `force-dns.sh` right after Pi connect to the router** - edit `/jffs/scripts/usb-network.conf` and paste the following: 155 | 156 | ``` 157 | EXECUTE_COMMAND="/jffs/scripts/force-dns.sh run" 158 | ``` 159 | 160 | ## Running Entware 161 | 162 | Create an image that will serve as storage: 163 | 164 | ```bash 165 | sudo dd if=/dev/zero of=/mass_storage.img bs=1M count=1024 166 | # OR use fallocate which is faster 167 | sudo fallocate -l 1G /mass_storage.img 168 | 169 | # format as ext2 for compatibility 170 | sudo mkfs.ext2 /mass_storage.img 171 | ``` 172 | 173 | Modify the configuration in `/etc/asuswrt-usb-network.conf`: 174 | 175 | ``` 176 | GADGET_STORAGE_FILE="/mass_storage.img" 177 | ``` 178 | 179 | Then you will need to install few scripts from [jacklul/asuswrt-scripts repository](https://github.com/jacklul/asuswrt-scripts) on the router: 180 | 181 | ```bash 182 | curl -fsSL "https://raw.githubusercontent.com/jacklul/asuswrt-scripts/master/scripts/usb-mount.sh" -o /jffs/scripts/usb-mount.sh 183 | curl -fsSL "https://raw.githubusercontent.com/jacklul/asuswrt-scripts/master/scripts/entware.sh" -o /jffs/scripts/entware.sh 184 | chmod +x /jffs/scripts/usb-mount.sh /jffs/scripts/entware.sh 185 | ``` 186 | 187 | Reboot the Pi, wait for the storage to be mounted by `usb-mount.sh` script then install Entware by using this command: 188 | 189 | ```bash 190 | /jffs/scripts/entware.sh install 191 | ``` 192 | 193 | It will now automatically mount and boot Entware after scripts are started. 194 | -------------------------------------------------------------------------------- /asuswrt-usb-network.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Made by Jack'lul 3 | # 4 | # This script allows connecting Raspberry Pi to stock 5 | # Asus router using USB Ethernet Gadget 6 | # 7 | # For more information, see: 8 | # https://github.com/jacklul/asuswrt-usb-raspberry-pi 9 | # 10 | 11 | # shellcheck disable=2155,1090 12 | 13 | # Configuration variables 14 | NETWORK_FUNCTION="ecm" 15 | VERIFY_CONNECTION=true 16 | SKIP_MASS_STORAGE=false 17 | FAKE_ASUS_OPTWARE=false 18 | FAKE_ASUS_OPTWARE_ARCH="arm" 19 | FAKE_ASUS_OPTWARE_CMD="/bin/sh /jffs/scripts/usb-mount-script" 20 | TEMP_IMAGE_FILE="/tmp/asuswrt-usb-network.img" 21 | TEMP_IMAGE_SIZE=1 22 | TEMP_IMAGE_FS="ext2" 23 | TEMP_IMAGE_DELETE=true 24 | WAIT_TIMEOUT=90 25 | WAIT_RETRY=0 26 | WAIT_SLEEP=1 27 | VERIFY_TIMEOUT=60 28 | VERIFY_SLEEP=1 29 | GADGET_ID="usbnet" 30 | GADGET_PRODUCT="$(tr -d '\0' < /sys/firmware/devicetree/base/model) USB Gadget" 31 | GADGET_MANUFACTURER="Raspberry Pi Foundation" 32 | GADGET_SERIAL="$(grep Serial /proc/cpuinfo | sed 's/Serial\s*: 0000\(\w*\)/\1/')" 33 | GADGET_VENDOR_ID="0x1d6b" 34 | GADGET_PRODUCT_ID="0x0104" 35 | GADGET_USB_VERSION="0x0200" 36 | GADGET_DEVICE_VERSION="0x0100" 37 | GADGET_DEVICE_CLASS="0xef" 38 | GADGET_DEVICE_SUBCLASS="0x02" 39 | GADGET_DEVICE_PROTOCOL="0x01" 40 | GADGET_MAX_PACKET_SIZE="0x40" 41 | GADGET_MAX_POWER="250" 42 | GADGET_ATTRIBUTES="0x80" 43 | GADGET_MAC_VENDOR="B8:27:EB" 44 | GADGET_MAC_HOST="" 45 | GADGET_MAC_DEVICE="" 46 | GADGET_STORAGE_FILE="" 47 | GADGET_STORAGE_FILE_CHECK=true 48 | GADGET_STORAGE_STALL="" 49 | GADGET_STORAGE_REMOVABLE="" 50 | GADGET_STORAGE_CDROM="" 51 | GADGET_STORAGE_RO="" 52 | GADGET_STORAGE_NOFUA="" 53 | GADGET_STORAGE_INQUIRY_STRING="" 54 | GADGET_SCRIPT="" 55 | 56 | readonly CONFIG_FILE="/etc/asuswrt-usb-network.conf" 57 | if [ -f "$CONFIG_FILE" ]; then 58 | [ ! -r "$CONFIG_FILE" ] && { echo "Unable to read $CONFIG_FILE"; exit 1; } 59 | 60 | . "$CONFIG_FILE" 61 | fi 62 | 63 | readonly CONFIGFS_DEVICE_PATH="/sys/kernel/config/usb_gadget/$GADGET_ID" 64 | 65 | ################################################## 66 | 67 | require_root() { 68 | [ "$UID" -eq 0 ] || { echo "This script must run as root!"; exit 1; } 69 | } 70 | 71 | init_gadget() { 72 | local CONFIG="$1" 73 | 74 | if [ -d "$CONFIGFS_DEVICE_PATH" ]; then 75 | [ -n "$(cat "$CONFIGFS_DEVICE_PATH/UDC")" ] && { echo "Gadget \"$GADGET_ID\" is already up"; exit 16; } 76 | 77 | echo "Cleaning up old gadget \"$GADGET_ID\"..."; 78 | gadget_down silent && gadget_cleanup silent 79 | fi 80 | 81 | modprobe libcomposite 82 | 83 | mkdir "$CONFIGFS_DEVICE_PATH" 84 | 85 | echo "$GADGET_VENDOR_ID" > "$CONFIGFS_DEVICE_PATH/idVendor" 86 | echo "$GADGET_PRODUCT_ID" > "$CONFIGFS_DEVICE_PATH/idProduct" 87 | echo "$GADGET_USB_VERSION" > "$CONFIGFS_DEVICE_PATH/bcdUSB" 88 | echo "$GADGET_DEVICE_VERSION" > "$CONFIGFS_DEVICE_PATH/bcdDevice" 89 | echo "$GADGET_DEVICE_CLASS" > "$CONFIGFS_DEVICE_PATH/bDeviceClass" 90 | echo "$GADGET_DEVICE_SUBCLASS" > "$CONFIGFS_DEVICE_PATH/bDeviceSubClass" 91 | echo "$GADGET_DEVICE_PROTOCOL" > "$CONFIGFS_DEVICE_PATH/bDeviceProtocol" 92 | echo "$GADGET_MAX_PACKET_SIZE" > "$CONFIGFS_DEVICE_PATH/bMaxPacketSize0" 93 | 94 | # 0x409 = english 95 | mkdir "$CONFIGFS_DEVICE_PATH/strings/0x409" 96 | echo "$GADGET_PRODUCT" > "$CONFIGFS_DEVICE_PATH/strings/0x409/product" 97 | echo "$GADGET_MANUFACTURER" > "$CONFIGFS_DEVICE_PATH/strings/0x409/manufacturer" 98 | echo "$GADGET_SERIAL" > "$CONFIGFS_DEVICE_PATH/strings/0x409/serialnumber" 99 | 100 | mkdir "$CONFIGFS_DEVICE_PATH/configs/$CONFIG" 101 | echo "$GADGET_MAX_POWER" > "$CONFIGFS_DEVICE_PATH/configs/$CONFIG/MaxPower" 102 | echo "$GADGET_ATTRIBUTES" > "$CONFIGFS_DEVICE_PATH/configs/$CONFIG/bmAttributes" 103 | mkdir "$CONFIGFS_DEVICE_PATH/configs/$CONFIG/strings/0x409" 104 | } 105 | 106 | set_configuration_string() { 107 | local CONFIG="$1" 108 | local STRING="$2" 109 | local CURRENT="$(cat "$CONFIGFS_DEVICE_PATH/configs/$CONFIG/strings/0x409/configuration")" 110 | 111 | if [ ! -f "$CONFIGFS_DEVICE_PATH/configs/$CONFIG/strings/0x409/configuration" ] || [ -z "$CURRENT" ]; then 112 | echo "$STRING" > "$CONFIGFS_DEVICE_PATH/configs/$CONFIG/strings/0x409/configuration" 113 | else 114 | echo "$CURRENT + $STRING" > "$CONFIGFS_DEVICE_PATH/configs/$CONFIG/strings/0x409/configuration" 115 | fi 116 | } 117 | 118 | add_function() { 119 | local FUNCTION="${1,,}" 120 | local CONFIG="c.1" 121 | local INSTANCE="0" 122 | local LUN_INSTANCE="0" 123 | local ARGUMENT="$2" 124 | 125 | if [ ! -d "$CONFIGFS_DEVICE_PATH/functions" ]; then 126 | init_gadget "$CONFIG" 127 | fi 128 | 129 | case "$FUNCTION" in 130 | "ecm"|"rndis"|"eem"|"ncm") 131 | mkdir "$CONFIGFS_DEVICE_PATH/functions/$FUNCTION.$INSTANCE" 132 | 133 | generate_mac_addresses 134 | 135 | echo "$GADGET_MAC_HOST" > "$CONFIGFS_DEVICE_PATH/functions/$FUNCTION.$INSTANCE/dev_addr" 136 | echo "$GADGET_MAC_DEVICE" > "$CONFIGFS_DEVICE_PATH/functions/$FUNCTION.$INSTANCE/host_addr" 137 | 138 | set_configuration_string "$CONFIG" "${FUNCTION^^}" 139 | 140 | ln -s "$CONFIGFS_DEVICE_PATH/functions/$FUNCTION.$INSTANCE" "$CONFIGFS_DEVICE_PATH/configs/$CONFIG" 141 | ;; 142 | "mass_storage") 143 | [ -z "$ARGUMENT" ] && { echo "Image file not provided"; exit 22; } 144 | [ ! -f "$ARGUMENT" ] && { echo "Image file does not exist: $ARGUMENT"; exit 2; } 145 | 146 | mkdir "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE" 147 | 148 | [ -n "$GADGET_STORAGE_STALL" ] && echo "$GADGET_STORAGE_STALL" > "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE/stall" 149 | 150 | [ ! -d "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE/lun.$LUN_INSTANCE" ] && mkdir "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE/lun.$LUN_INSTANCE" 151 | 152 | echo "$ARGUMENT" > "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE/lun.$LUN_INSTANCE/file" 153 | 154 | [ -n "$GADGET_STORAGE_REMOVABLE" ] && echo "$GADGET_STORAGE_REMOVABLE" > "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE/lun.$LUN_INSTANCE/removable" 155 | [ -n "$GADGET_STORAGE_CDROM" ] && echo "$GADGET_STORAGE_CDROM" > "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE/lun.$LUN_INSTANCE/cdrom" 156 | [ -n "$GADGET_STORAGE_RO" ] && echo "$GADGET_STORAGE_RO" > "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE/lun.$LUN_INSTANCE/ro" 157 | [ -n "$GADGET_STORAGE_NOFUA" ] && echo "$GADGET_STORAGE_NOFUA" > "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE/lun.$LUN_INSTANCE/nofua" 158 | [ -n "$GADGET_STORAGE_REMOVABLE" ] && echo "$GADGET_STORAGE_INQUIRY_STRING" > "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE/lun.$LUN_INSTANCE/inquiry_string" 159 | 160 | set_configuration_string "$CONFIG" "Mass Storage" 161 | 162 | ln -s "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$INSTANCE" "$CONFIGFS_DEVICE_PATH/configs/$CONFIG" 163 | ;; 164 | *) 165 | echo "Invalid function specified: $FUNCTION" 166 | exit 22 167 | ;; 168 | esac 169 | } 170 | 171 | get_network_instance() { 172 | local INSTANCE_PATH=$(find $CONFIGFS_DEVICE_PATH/functions/ -maxdepth 2 -name "ifname" || echo "") 173 | 174 | if [ -n "$INSTANCE_PATH" ]; then 175 | basename "$(dirname "$INSTANCE_PATH")" 176 | fi 177 | } 178 | 179 | gadget_up() { 180 | udevadm settle -t 5 || : 181 | ls /sys/class/udc > "$CONFIGFS_DEVICE_PATH/UDC" 182 | 183 | local NET_INSTANCE="$(get_network_instance)" 184 | 185 | if [ -n "$NET_INSTANCE" ]; then 186 | local INTERFACE="$(cat "$CONFIGFS_DEVICE_PATH/functions/$NET_INSTANCE/ifname")" 187 | 188 | ifconfig "$INTERFACE" up 189 | fi 190 | } 191 | 192 | gadget_down() { 193 | if [ -d "$CONFIGFS_DEVICE_PATH" ]; then 194 | [ "$1" != "silent" ] && echo "Taking down gadget \"$GADGET_ID\"..."; 195 | 196 | [ -n "$(cat "$CONFIGFS_DEVICE_PATH/UDC")" ] && echo "" > "$CONFIGFS_DEVICE_PATH/UDC" 197 | 198 | local NET_INSTANCE="$(get_network_instance)" 199 | 200 | if [ -n "$NET_INSTANCE" ]; then 201 | local INTERFACE="$(cat "$CONFIGFS_DEVICE_PATH/functions/$NET_INSTANCE/ifname")" 202 | 203 | [ -d "/sys/class/net/$INTERFACE" ] && ifconfig "$INTERFACE" down 204 | fi 205 | else 206 | echo "Gadget \"$GADGET_ID\" is already down" 207 | return 19 208 | fi 209 | } 210 | 211 | gadget_cleanup() { 212 | if [ -d "$CONFIGFS_DEVICE_PATH" ]; then 213 | [ "$1" != "silent" ] && echo "Cleaning up gadget \"$GADGET_ID\"..."; 214 | 215 | find $CONFIGFS_DEVICE_PATH/configs/*/* -maxdepth 0 -type l -exec rm {} \; 2> /dev/null || true 216 | find $CONFIGFS_DEVICE_PATH/configs/*/strings/* -maxdepth 0 -type d -exec rmdir {} \; 2> /dev/null || true 217 | find $CONFIGFS_DEVICE_PATH/os_desc/* -maxdepth 0 -type l -exec rm {} \; 2> /dev/null || true 218 | find $CONFIGFS_DEVICE_PATH/functions/* -maxdepth 0 -type d -exec rmdir {} \; 2> /dev/null || true 219 | find $CONFIGFS_DEVICE_PATH/strings/* -maxdepth 0 -type d -exec rmdir {} \; 2> /dev/null || true 220 | find $CONFIGFS_DEVICE_PATH/configs/* -maxdepth 0 -type d -exec rmdir {} \; 2> /dev/null || true 221 | 222 | rmdir "$CONFIGFS_DEVICE_PATH" 2> /dev/null 223 | fi 224 | } 225 | 226 | generate_mac_addresses() { 227 | local GADGET_SERIAL="${GADGET_SERIAL^^}" 228 | 229 | [ -z "$GADGET_MAC_DEVICE" ] && GADGET_MAC_DEVICE="02:$(echo "$GADGET_SERIAL" | sed 's/\(\w\w\)/:\1/g' | cut -b 5-)" 230 | 231 | if [ "$(echo "$GADGET_MAC_DEVICE" | awk -F":" '{print NF-1}')" != "5" ]; then 232 | echo "Invalid device MAC address: $GADGET_MAC_DEVICE" 233 | exit 22 234 | fi 235 | 236 | if [ -z "$GADGET_MAC_HOST" ]; then 237 | if [ "$(echo "$GADGET_MAC_VENDOR" | awk -F":" '{print NF-1}')" != "2" ]; then 238 | echo "Invalid value for \"GADGET_MAC_VENDOR\" variable!" 239 | exit 22 240 | fi 241 | 242 | GADGET_MAC_HOST="${GADGET_MAC_VENDOR^^}:$(echo "$GADGET_SERIAL" | sed 's/\(\w\w\)/:\1/g' | cut -b 11-)" 243 | fi 244 | 245 | if [ "$(echo "$GADGET_MAC_HOST" | awk -F":" '{print NF-1}')" != "5" ]; then 246 | echo "Invalid host MAC address: $GADGET_MAC_HOST" 247 | exit 22 248 | fi 249 | } 250 | 251 | is_started() { 252 | if [ -d "$CONFIGFS_DEVICE_PATH" ]; then 253 | local NET_INSTANCE="$(get_network_instance)" 254 | 255 | [ -n "$NET_INSTANCE" ] && return 0 256 | fi 257 | 258 | return 1 259 | } 260 | 261 | create_image() { 262 | local FILE="$1" 263 | local SIZE="$2" 264 | local FILESYSTEM="$3" 265 | 266 | command -v "mkfs.$FILESYSTEM" >/dev/null 2>&1 || { echo "Function \"mkfs.$FILESYSTEM\" not found"; exit 2; } 267 | 268 | echo "Creating image file \"$FILE\" ($FILESYSTEM, ${SIZE}M)..." 269 | 270 | { DD_OUTPUT=$(dd if=/dev/zero of="$FILE" bs="1M" count="$SIZE" 2>&1); } || { echo "$DD_OUTPUT"; exit 1; } 271 | { MKFS_OUTPUT=$("mkfs.$FILESYSTEM" "$FILE" 2>&1); } || { echo "$MKFS_OUTPUT"; exit 1; } 272 | 273 | mkdir -p "$FILE-mnt" 274 | mount "$FILE" "$FILE-mnt" 275 | 276 | mkdir "$FILE-mnt/asuswrt-usb-network" 277 | 278 | if [ "$FAKE_ASUS_OPTWARE" = true ]; then 279 | create_fake_asus_optware "$FILE-mnt" 280 | fi 281 | 282 | umount "$FILE-mnt" 283 | rmdir "$FILE-mnt" 284 | } 285 | 286 | create_fake_asus_optware() { 287 | local DESTINATION_PATH="$1" 288 | local ASUSWARE_PATH="$DESTINATION_PATH/asusware.$FAKE_ASUS_OPTWARE_ARCH" 289 | 290 | [ ! -d "$DESTINATION_PATH" ] && { echo "Destination path does not exist"; exit 2; } 291 | 292 | echo "Creating fake Asus Optware installation..." 293 | 294 | mkdir -p "$ASUSWARE_PATH/etc/init.d" "$ASUSWARE_PATH/lib/ipkg/lists" "$ASUSWARE_PATH/lib/ipkg/info" 295 | 296 | echo "dest /opt/ /" > "$ASUSWARE_PATH/etc/ipkg.conf" 297 | touch "$ASUSWARE_PATH/.asusrouter" 298 | 299 | cat < "$ASUSWARE_PATH/etc/init.d/S50asuswrt-usb-network" 300 | #!/bin/sh 301 | 302 | tag="asuswrt-usb-network" 303 | 304 | if [ "\$1" = "start" ]; then 305 | { 306 | mounts="\$(df | grep /dev/sd | awk '{print \$NF}')" 307 | found= 308 | 309 | if [ -n "\$mounts" ]; then 310 | for mount in \$mounts; do 311 | [ ! -d "\$mount/asuswrt-usb-network" ] && continue 312 | found="\$mount" 313 | 314 | logger -st "\$tag" "Waiting for mount \$mount to be idle..." 315 | 316 | timer=0 317 | while [ -n "\$(lsof | grep "\$mount")" ] && [ "\$timer" -lt "60" ] ; do 318 | timer=\$((timer+1)) 319 | sleep 1 320 | done 321 | 322 | logger -st "\$tag" "Writing mark to mount \$mount..." 323 | touch "\$mount/asuswrt-usb-network-mark" && sync 324 | 325 | if umount "\$mount"; then 326 | rm -f "\$mount" 327 | logger -st "\$tag" "Unmounted \$mount..." 328 | else 329 | logger -st "\$tag" "Failed to unmount \$mount" 330 | fi 331 | 332 | break 333 | done 334 | fi 335 | 336 | [ -z "\$found" ] && logger -st "\$tag" "Could not find storage mount point" 337 | } & 338 | EOT 339 | 340 | if [ -n "$FAKE_ASUS_OPTWARE_CMD" ]; then 341 | cat <> "$ASUSWARE_PATH/etc/init.d/S50asuswrt-usb-network" 342 | 343 | logger -st "\$tag" "Executing command: $FAKE_ASUS_OPTWARE_CMD" 344 | eval "$FAKE_ASUS_OPTWARE_CMD" 345 | EOT 346 | else 347 | cat <> "$ASUSWARE_PATH/etc/init.d/S50asuswrt-usb-network" 348 | 349 | nvram_script="\$(nvram get script_usbmount)" 350 | if [ -n "\$nvram_script" ]; then 351 | logger -st "\$tag" "Executing command: \$nvram_script" 352 | eval "\$nvram_script" 353 | fi 354 | EOT 355 | fi 356 | 357 | # list of state vars taken from src/router/rc/services.c 358 | # we reset some apps_ vars to not end up with random bugs (web UI persistently trying to install apps in a loop) 359 | cat <> "$ASUSWARE_PATH/etc/init.d/S50asuswrt-usb-network" 360 | 361 | { 362 | sleep 15 363 | nvram set apps_state_autorun= 364 | nvram set apps_state_install= 365 | nvram set apps_state_remove= 366 | nvram set apps_state_switch= 367 | nvram set apps_state_stop= 368 | nvram set apps_state_enable= 369 | nvram set apps_state_update= 370 | nvram set apps_state_upgrade= 371 | nvram set apps_state_cancel= 372 | nvram set apps_state_error= 373 | nvram set apps_state_action= 374 | nvram set apps_mounted_path= 375 | nvram set apps_dev= 376 | } & 377 | else 378 | logger -st "\$tag" "Unsupported action: \$1" 379 | fi 380 | EOT 381 | 382 | chmod +x "$ASUSWARE_PATH/etc/init.d/S50asuswrt-usb-network" 383 | 384 | cat <> "$ASUSWARE_PATH/lib/ipkg/status" 385 | Package: asuswrt-usb-network 386 | Version: 1.0.0.0 387 | Status: install user installed 388 | Architecture: $FAKE_ASUS_OPTWARE_ARCH 389 | Installed-Time: 0 390 | EOT 391 | 392 | cat <> "$ASUSWARE_PATH/lib/ipkg/lists/optware.asus" 393 | Package: asuswrt-usb-network 394 | Version: 1.0.0.0 395 | Architecture: $FAKE_ASUS_OPTWARE_ARCH 396 | EOT 397 | 398 | cat <> "$ASUSWARE_PATH/lib/ipkg/info/asuswrt-usb-network.control" 399 | Package: asuswrt-usb-network 400 | Architecture: $FAKE_ASUS_OPTWARE_ARCH 401 | Priority: optional 402 | Section: libs 403 | Version: 1.0.0.0 404 | Depends: 405 | Suggests: 406 | Conflicts: 407 | Enabled: yes 408 | Installed-Size: 1 409 | EOT 410 | 411 | # per src/router/rc/init.c and src/router/rom/apps_scripts/ mipsel does not use a postfix 412 | if [ "${FAKE_ASUS_OPTWARE_ARCH,,}" = "mipsel" ]; then 413 | mv "$ASUSWARE_PATH" "$DESTINATION_PATH/asusware" 414 | fi 415 | } 416 | 417 | check_filesystem_in_image() { 418 | local IMAGE="$1" 419 | 420 | [ ! -f "$IMAGE" ] && { echo "Image file does not exist"; exit 2; } 421 | 422 | if ! fdisk -l "$IMAGE" | grep -q "Device" | grep -q "Blocks" | grep -q "Boot"; then 423 | # occasionally e2fsck will exit with a fail code, we need to ignore it to continue 424 | e2fsck -pf "$IMAGE" || true 425 | else 426 | echo "Skipping filesystem check because the image file contains partition table" 427 | fi 428 | } 429 | 430 | interrupt() { 431 | echo -e "\rInterrupt by user, cleaning up..." 432 | 433 | is_started || { gadget_down silent && gadget_cleanup silent; } 434 | 435 | [ "$TEMP_IMAGE_DELETE" = true ] && rm -f "$TEMP_IMAGE_FILE" 436 | } 437 | 438 | ################################################## 439 | 440 | case "$1" in 441 | "start") 442 | require_root 443 | is_started && { echo "Startup already complete"; exit; } 444 | 445 | trap interrupt SIGINT SIGTERM SIGQUIT 446 | set -e 447 | 448 | [ -d "$CONFIGFS_DEVICE_PATH" ] && { gadget_down && gadget_cleanup silent; } 449 | 450 | if [ "$SKIP_MASS_STORAGE" = false ]; then 451 | [ -z "$TEMP_IMAGE_FILE" ] && { echo "Temporary image file is not set"; exit 22; } 452 | 453 | echo "Setting up gadget \"$GADGET_ID\" with function \"mass_storage\"..." 454 | 455 | create_image "$TEMP_IMAGE_FILE" "$TEMP_IMAGE_SIZE" "$TEMP_IMAGE_FS" 456 | 457 | add_function "mass_storage" "$TEMP_IMAGE_FILE" 458 | gadget_up 459 | 460 | MS_INSTANCE=$(find "$CONFIGFS_DEVICE_PATH/functions" -maxdepth 1 -name "mass_storage.*" | grep -o '[^.]*$' || echo "") 461 | LUN_INSTANCE=$(find "$CONFIGFS_DEVICE_PATH/functions/mass_storage.$MS_INSTANCE" -maxdepth 1 -name "lun.*" | grep -o '[^.]*$' || echo "") 462 | 463 | { [ -z "$MS_INSTANCE" ] || [ -z "$LUN_INSTANCE" ]; } && { echo "Could not find function or LUN instance"; exit 2; } 464 | 465 | echo "Waiting for the router to write mark to the image (timeout: ${WAIT_TIMEOUT}s)...." 466 | 467 | [ "$WAIT_RETRY" -ge "$WAIT_TIMEOUT" ] && WAIT_RETRY=0 468 | 469 | _TIMER=0 470 | _RETRY=0 471 | _TIMEOUT=$WAIT_TIMEOUT 472 | while ! debugfs -R "ls -l ." "$TEMP_IMAGE_FILE" 2>/dev/null | grep -q "asuswrt-usb-network-mark" && [ "$_TIMER" -lt "$_TIMEOUT" ]; do 473 | if [ "$WAIT_RETRY" -ge 10 ] && [ "$((_TIMER-_RETRY))" -ge "$WAIT_RETRY" ]; then 474 | _RETRY=$((_RETRY+WAIT_RETRY)) 475 | echo "Recreating gadget \"$GADGET_ID\"..." 476 | gadget_down silent && gadget_up silent 477 | fi 478 | 479 | _TIMER=$((_TIMER+WAIT_SLEEP)) 480 | sleep $WAIT_SLEEP 481 | done 482 | 483 | [ "$_TIMER" -ge "$_TIMEOUT" ] && echo "Timeout reached, continuing anyway..." 484 | 485 | gadget_down && gadget_cleanup silent 486 | [ "$TEMP_IMAGE_DELETE" = true ] && rm -f "$TEMP_IMAGE_FILE" 487 | fi 488 | 489 | if [ -z "$GADGET_STORAGE_FILE" ]; then 490 | echo "Setting up gadget \"$GADGET_ID\" with function \"$NETWORK_FUNCTION\"..." 491 | else 492 | if [ -n "$GADGET_STORAGE_FILE" ] && [ -f "$GADGET_STORAGE_FILE" ] && [ "$GADGET_STORAGE_FILE_CHECK" = true ]; then 493 | echo "Checking filesystem in storage file \"$GADGET_STORAGE_FILE\"..." 494 | check_filesystem_in_image "$GADGET_STORAGE_FILE" 495 | fi 496 | 497 | echo "Setting up gadget \"$GADGET_ID\" with combined functions (mass_storage and $NETWORK_FUNCTION)..." 498 | fi 499 | 500 | add_function "$NETWORK_FUNCTION" 501 | 502 | if [ -n "$GADGET_STORAGE_FILE" ]; then 503 | if [ -f "$GADGET_STORAGE_FILE" ]; then 504 | add_function "mass_storage" "$GADGET_STORAGE_FILE" 505 | else 506 | echo "Image file \"$GADGET_STORAGE_FILE\" does not exist, skipping adding mass storage function..." 507 | fi 508 | fi 509 | 510 | if [ -n "$GADGET_SCRIPT" ] && [ -x "$GADGET_SCRIPT" ]; then 511 | $GADGET_SCRIPT "$CONFIGFS_DEVICE_PATH" 512 | fi 513 | 514 | gadget_up 515 | 516 | NET_INSTANCE=$(find "$CONFIGFS_DEVICE_PATH/functions" -maxdepth 1 -name "$NETWORK_FUNCTION.*" | grep -o '[^.]*$' || echo "") 517 | NET_INTERFACE=$(cat "$CONFIGFS_DEVICE_PATH/functions/$NETWORK_FUNCTION.$NET_INSTANCE/ifname") 518 | 519 | { [ -z "$NET_INSTANCE" ] || [ -z "$NET_INTERFACE" ]; } && { echo "Could not find function instance or read assigned network interface"; exit 2; } 520 | 521 | trap - SIGINT SIGTERM SIGQUIT 522 | 523 | if [ "$VERIFY_CONNECTION" = true ]; then 524 | echo "Checking if router is reachable (timeout: ${VERIFY_TIMEOUT}s)..." 525 | 526 | _TIMER=0 527 | _TIMEOUT=$VERIFY_TIMEOUT 528 | while [ "$_TIMER" -lt "$_TIMEOUT" ]; do 529 | GATEWAY="$(ip route show | grep "$NET_INTERFACE" | grep default | awk '{print $3}')" 530 | 531 | [ -n "$GATEWAY" ] && ping -c1 -W1 "$GATEWAY" >/dev/null 2>&1 && break 532 | 533 | _TIMER=$((_TIMER+VERIFY_SLEEP)) 534 | sleep $VERIFY_SLEEP 535 | done 536 | 537 | [ "$_TIMER" -ge "$_TIMEOUT" ] && { echo "Completed but couldn't determine network status (timeout reached)"; exit; } 538 | fi 539 | 540 | echo "Completed successfully" 541 | ;; 542 | "stop") 543 | require_root 544 | gadget_down && gadget_cleanup 545 | ;; 546 | "status") 547 | if [ -d "$CONFIGFS_DEVICE_PATH" ] && [ -n "$(cat "$CONFIGFS_DEVICE_PATH/UDC")" ]; then 548 | echo "Gadget \"$GADGET_ID\" is running." 549 | else 550 | echo "Gadget \"$GADGET_ID\" is not running." 551 | fi 552 | 553 | NET_INSTANCE="$(get_network_instance)" 554 | if [ -n "$NET_INSTANCE" ]; then 555 | INTERFACE="$(cat "$CONFIGFS_DEVICE_PATH/functions/$NET_INSTANCE/ifname")" 556 | IP_ADDRESS="$(ip -f inet addr show "$INTERFACE" | sed -En -e 's/.*inet ([0-9.]+).*/\1/p')" 557 | MAC_ADDRESS="$(ip -f link addr show "$INTERFACE" | sed -En -e 's/.*link.*([0-9a-fA-F:]{17}) .*/\1/p')" 558 | else 559 | echo "Network interface not found." 560 | fi 561 | 562 | echo "" 563 | generate_mac_addresses 564 | 565 | [ -n "$INTERFACE" ] && echo "Interface: $INTERFACE" 566 | [ -n "$IP_ADDRESS" ] && echo "IP address: $IP_ADDRESS" 567 | [ -n "$GADGET_MAC_DEVICE" ] && echo "Device MAC address: $GADGET_MAC_DEVICE" 568 | 569 | if [ -n "$MAC_ADDRESS" ] && [ "${MAC_ADDRESS^^}" != "${GADGET_MAC_HOST^^}" ]; then 570 | echo "Host MAC address (actual): $MAC_ADDRESS" 571 | echo "Host MAC address (config): $GADGET_MAC_HOST" 572 | else 573 | echo "Host MAC address: $GADGET_MAC_HOST" 574 | fi 575 | ;; 576 | *) 577 | echo "Usage: $0 start|stop|status" 578 | exit 1 579 | ;; 580 | esac 581 | --------------------------------------------------------------------------------