#!/usr/bin/env bash
# $Id: dudhya-host-connect 121 2026-09-05 17:26:26Z peter $
#
# Copyright (C) 2026 Péter Felecan
# Assisted by Claude Sonnet 5 (Anthropic)
#
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# dudhya-host-connect --- set up this host (e.g. a laptop) to reach a
# dudhya device and relay internet access to it for package
# installation/upgrade, over either connection type dudhya devices
# support. Symmetric counterpart: Provisioning/dudhya-device-update,
# which runs on the device itself.
#
# Usage: dudhya-host-connect cable|radio
#
#   cable --- the device is connected over USB. Auto-detects the USB
#     gadget interface from dmesg (its name is not fixed --- it is
#     derived from the device's own USB MAC address, which is not
#     stable across the device's reboots; see Dudhya.org and
#     ResearchJournal.org, 2026-07-31), assigns it the host side of
#     the point-to-point address already established elsewhere in
#     this project (172.16.42.2/24, device is 172.16.42.1), and NATs
#     it out through this host's own internet-facing interface.
#
#   radio --- the device is broadcasting its own WiFi access point
#     (dudhya-wifi-mode ap on the device side) and this host has
#     already joined it manually (via the host's own normal WiFi
#     settings --- this script does not attempt to join a WiFi
#     network itself, since how to do that varies too much across
#     host setups to script reliably). Detects which local interface
#     already holds an address in the AP's own subnet
#     (192.168.4.0/24, see WiFi/dnsmasq.conf.example) and NATs that
#     out through this host's own internet-facing interface, the same
#     way as cable mode.
#
# In both modes, "this host's own internet-facing interface" is
# whichever interface currently owns the default route --- detected
# automatically, not assumed to have any particular name.
#
# NOT YET VERIFIED against real hardware in either mode --- see
# ResearchJournal.org. Written and syntax/lint-checked (shellcheck)
# without access to a real USB-attached device, real WiFi hardware,
# or even the ip/iptables tools themselves in the environment this
# was developed in; the interface-detection and NAT logic below is
# reasoned through carefully, not exercised against real command
# output.

set -euo pipefail

device_address="172.16.42.2/24"
ap_subnet_pattern="192\\.168\\.4\\."

usage()
{
	echo "usage: $0 cable|radio" >&2
	exit 1
}

if [ "$#" -ne 1 ]
then
{
	usage
}
fi

mode="$1"

detect_usb_interface()
{
	# The kernel logs the USB gadget's final interface name directly
	# when udev renames it from the generic "usb0" to its MAC-derived
	# "enx..." name --- see ResearchJournal.org, 2026-07-31, for why
	# this project relies on dmesg rather than a fixed interface name.
	# -oP (PCRE) used specifically to avoid depending on dmesg's
	# variable-width timestamp padding breaking a naive field count.
	dmesg | grep -oP 'cdc_ncm \S+ \K\w+(?=: renamed from usb0)' | tail -1
}

detect_ap_interface()
{
	# -o (oneline) keeps each address on its own line, avoiding the
	# multi-line-per-interface format's own parsing fragility.
	ip -4 -o addr show | grep "inet ${ap_subnet_pattern}" | awk '{print $2}' | tail -1
}

detect_internet_interface()
{
	ip route show default | awk '{print $5}' | head -1
}

ensure_nat()
{
	local out_iface="$1"

	sysctl -w net.ipv4.ip_forward=1

	# Check-then-add, not a bare append: running this script twice
	# should not accumulate duplicate MASQUERADE rules.
	if ! iptables -t nat -C POSTROUTING -o "${out_iface}" -j MASQUERADE 2>/dev/null
	then
	{
		iptables -t nat -A POSTROUTING -o "${out_iface}" -j MASQUERADE
	}
	fi
}

case "${mode}" in
	cable)
	{
		device_iface="$(detect_usb_interface || true)"
		if [ -z "${device_iface}" ]
		then
		{
			echo "dudhya-host-connect: no USB gadget interface found in dmesg --- is the device actually connected over USB?" >&2
			exit 1
		}
		fi

		# ip addr replace, not add: safe to re-run, and this project
		# has found dhclient unreliable on this point-to-point link in
		# past sessions (nothing on the device side offers DHCP over
		# it), where static assignment always works instantly.
		ip addr replace "${device_address}" dev "${device_iface}"
		ip link set "${device_iface}" up

		internet_iface="$(detect_internet_interface || true)"
		if [ -z "${internet_iface}" ]
		then
		{
			echo "dudhya-host-connect: no default route found on this host --- no internet connection to relay?" >&2
			exit 1
		}
		fi
		if [ "${internet_iface}" = "${device_iface}" ]
		then
		{
			# Confirmed as a real, not just hypothetical, risk
			# 2026-08-01: a host reboot was observed assigning the
			# wired ethernet and the USB gadget the exact same
			# interface name (see ResearchJournal.org) --- likely
			# udev's predictable-naming scheme falling back to a
			# MAC-derived name for one or both interfaces under some
			# boot-timing condition, though the exact mechanism isn't
			# confirmed. Whatever the cause, NATing an interface
			# through itself is nonsensical; refuse rather than do
			# something silently wrong.
			echo "dudhya-host-connect: the USB gadget interface and this host's own internet-facing interface both resolved to the same name (${device_iface}) --- a real naming collision has been observed on this host before (see ResearchJournal.org, 2026-08-01), not a bug in this check. Try rebooting the host, or check 'ip a' manually to see whether the two interfaces are actually distinct right now." >&2
			exit 1
		}
		fi

		ensure_nat "${internet_iface}"
		echo "dudhya-host-connect: relaying internet from ${internet_iface} to the device on ${device_iface} (${device_address})"
	}
	;;
	radio)
	{
		device_iface="$(detect_ap_interface || true)"
		if [ -z "${device_iface}" ]
		then
		{
			echo "dudhya-host-connect: no interface found with an address in ${ap_subnet_pattern}0/24 --- has this host already joined the device's own WiFi AP? (see WiFi/hostapd.conf.example for the SSID)" >&2
			exit 1
		}
		fi

		internet_iface="$(detect_internet_interface || true)"
		if [ -z "${internet_iface}" ]
		then
		{
			echo "dudhya-host-connect: no default route found on this host --- no internet connection to relay?" >&2
			exit 1
		}
		fi
		if [ "${internet_iface}" = "${device_iface}" ]
		then
		{
			echo "dudhya-host-connect: this host's default route is over the same interface (${internet_iface}) it's using to reach the device --- nothing to relay from; connect the device over a different interface than this host's own internet connection (e.g. wired ethernet for internet, WiFi joined to the device's AP)." >&2
			exit 1
		}
		fi

		# Mitigation for a real, matched issue: mobile-class Broadcom
		# WiFi chips acting as an AP (this device's own brcmfmac/BCM4339
		# included) are documented to interact badly with a connected
		# client's own WiFi power-saving mode --- periodic packet loss
		# and eventual re-authentication failures, not a signal/distance
		# problem. See ResearchJournal.org, 2026-08-01, for the matched
		# historical report (same Broadcom chip family, same AP role)
		# this is based on. Not fatal if it fails (older iw/drivers
		# may not support the setting), and not yet independently
		# confirmed to fix this project's own specific case.
		iw dev "${device_iface}" set power_save off 2>/dev/null || true

		ensure_nat "${internet_iface}"
		echo "dudhya-host-connect: relaying internet from ${internet_iface} to the device on ${device_iface}"
	}
	;;
	*)
	{
		usage
	}
	;;
esac

# Local Variables:
# mode: shell-script
# End:
