#!/bin/sh -e # Handle USB tethering with unudhcpd and NetworkManager while also # keeping SSH login over USB working when tethering is disabled. # # Copyright (c) 2023 Dylan Van Assche # SPDX-License-Identifier: GPL-3.0-or-later # Must match with the supplied connection profile, # using UUID allows the user to change the connection name if they want to. con_uuid="83bd1823-feca-4c2b-9205-4b83dc792e1f" interface="usb0" host_ip="172.16.42.1" client_ip="172.16.42.2" # Skip if iface does not match if [ "$DEVICE_IFACE" != "$interface" ]; then exit 0 fi # Trigger a disconnect/connect event on the client side to request a new DHCP lease. reactivate_gadget() { # Mount configFS mkdir -p /config mount -t configfs none /config || true logger -t nm-tethering "configFS mounted" # Reactivate gadget udc=$(cat /config/usb_gadget/g1/UDC) echo "" > /config/usb_gadget/g1/UDC sleep 1 echo "$udc" > /config/usb_gadget/g1/UDC logger -t nm-tethering "gadget reactivated" # Unmount configFS umount /config rm -rf /config logger -t nm-tethering "configFS unmounted" } # Default static IP for SSH acccess # 1. Configure NetworkManager connection to use IP 172.16.42.1, same as initfs. # 2. Disable IPv6 as unudhcpd only supplies IPv4 addresses. # 3. Start unudhcpd to handle DHCP requests. # 4. Reactivate the USB Ethernet gadget to force the clients to reactivate the interface. disable_tethering() { # Configure static IP and bring up connection automatically nmcli connection modify "$con_uuid" ipv4.address "$host_ip/16" nmcli connection modify "$con_uuid" ipv4.method "manual" nmcli connection modify "$con_uuid" ipv6.method "link-local" nmcli connection modify "$con_uuid" connection.autoconnect "true" # If unudhpcd is not running, start it and configure it similar to initfs if [ ! "$(pidof unudhcpd)" ]; then (unudhcpd -i "$interface" -s "$host_ip" -c "$client_ip") & logger -t nm-tethering "unudhcpd started" reactivate_gadget logger -t nm-tethering "USB tethering disabled" fi } # USB tethering # 1. Enforce 172.16.42.1 as host IP even in tethering mode. # 2. Stop unudhcpd as NetworkManager will spawn dnsmasq to handle DNS and DHCP requests. # 3. Reactivate the USB Ethernet gadget to force the clients to reactivate the interface. enable_tethering() { # Enforce the same IP range as when tethering is disabled, this will retrigger # the script as we have to reapply again, also bring up connection automatically. ip=$(nmcli connection show "$con_uuid" --active | grep ipv4.addresses | tr -s " " | cut -d " " -f2) if [ "$ip" != "$host_ip/16" ]; then logger -t nm-tethering "Enforcing $host_ip/16 as DHCP range" nmcli connection modify "$con_uuid" ipv4.address "$host_ip/16" nmcli connection modify "$con_uuid" connection.autoconnect "true" nmcli device reapply "$interface" return fi # Kill unudhcpd if needed if [ "$(pidof unudhcpd)" ]; then killall unudhcpd logger -t nm-tethering "unudhcpd stopped" reactivate_gadget logger -t nm-tethering "USB tethering enabled" fi } # Handle dispatcher events for tethering method=$(nmcli connection show "$con_uuid" --active | grep ipv4.method | tr -s " " | cut -d " " -f2) case $NM_DISPATCHER_ACTION in # Always disable tethering on insert or removal for security "up"|"down") disable_tethering # The connection may have been configured before to tethering which # is not desired when iface comes up or is shut down as tethering must be # disabled. Enforce this by triggering a reapply after modifying the connection # which will cause NetworkManager to trigger the script again. # If the iface is already down since dispatcher scripts are executed async, # the command will fail which is fine to ignore if [ "$method" = "shared" ]; then logger -t nm-tethering "Enforcing tethering disabled on iface up/down" nmcli device reapply "$interface" || true fi ;; # Enable tethering if the user explicitly enabled it "reapply") if [ "$method" = "shared" ]; then enable_tethering elif [ "$method" = "manual" ]; then disable_tethering fi ;; esac