Cellular Failover Via iPhone, Arch Linux and UCG Ultra
While having concrete poured at my home, I needed to protect the fiber optic cable running from the utility pole to my house by routing it through conduit. In the process, the field-terminated Fujikura SC/APC connector on the end of the drop cable was accidentally pulled free from the cable. AT&T couldn’t dispatch a technician until Thursday, so I needed a way to bridge the gap using my iPhone 16 Pro’s cellular connection.
The challenge was making that work well given my constraints. I get almost no usable signal indoors, but in the middle of my backyard I see nearly 300Mbps down, 150Mbps up, and ~35ms ping times. With Thursday only two days away, buying or shipping new equipment wasn’t worth it as the outage would be over before anything arrived.
That meant the phone needed to be outside, ideally over USB for a stable, charging-friendly connection. My home has ethernet runs throughout, including out to the soffits on my patio, so converting cellular to ethernet was the natural path into my UCG Ultra.
I had a few candidates for that conversion, but nothing came together cleanly in the time I had. What ultimately worked was simpler: phone in my son’s bedroom window to charge, my Arch Linux desktop connected to its WiFi hotspot, and that connection shared over ethernet to the UCG Ultra. Not elegant but it worked, and that’s what this post covers.
The Arch machine has two network interfaces relevant to this setup:
wlan0— connects to the iPhone Personal Hotspot over Wi-Fienp8s0— Ethernet, connected directly to the WAN port of the UCG Ultra
iPhone Tethering⌗
The original plan was USB tethering via the ipheth kernel driver, which has
the advantage of simultaneously charging the phone. The prerequisites are
libimobiledevice and usbmuxd:
pacman -S libimobiledevice usbmuxd
usbmuxd ships a udev rule that auto-starts the daemon on device connect.
After connecting the cable and tapping Trust on the iPhone, pair the device:
idevicepair pair
In practice, the USB interface (enp20s0u12c4i2) dropped its default route
when the shared Ethernet connection was brought up later, requiring manual
reconnection each time. Wi-Fi tethering turned out to be more robust for this
workflow. Additionally, I didn’t have a USB cable long enough to go from where
the Arch computer was to my son’s bedroom. I pivoted to Wi-Fi.
To connect via Wi-Fi, enable Personal Hotspot on the iPhone and connect through NetworkManager as you would any access point. Verify internet connectivity before moving on:
ping -c 3 1.1.1.1
Sharing the Connection to enp8s0⌗
NetworkManager’s shared IPv4 method handles everything needed to act as a
router on a downstream interface: it assigns a static IP from the
10.42.0.0/24 subnet, runs dnsmasq for DHCP and DNS, enables IP forwarding,
and configures NAT via nftables.
nmcli connection add \
type ethernet \
ifname enp8s0 \
con-name "shared-ucg" \
ipv4.method shared
nmcli connection up shared-ucg
The interface comes up at 10.42.0.1/24. Once the UCG Ultra sends a DHCP
request it will receive an address in that range. Confirm the lease was issued:
cat /var/lib/NetworkManager/dnsmasq-enp8s0.leases
If that file is empty the UCG Ultra may not have sent a request yet. The ARP table will show an entry once it has sent any traffic:
arp -n -i enp8s0
Verifying NAT⌗
NetworkManager creates a dedicated nftables table for the shared interface. Inspecting the full ruleset confirms masquerading is in place:
nft list ruleset
The relevant table looks like this:
table ip nm-shared-enp8s0 {
chain nat_postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.42.0.0/24 ip daddr != 10.42.0.0/24 masquerade
}
chain filter_forward {
type filter hook forward priority filter; policy accept;
ip daddr 10.42.0.0/24 oifname "enp8s0" ct state { established, related } accept
ip saddr 10.42.0.0/24 iifname "enp8s0" accept
iifname "enp8s0" oifname "enp8s0" accept
iifname "enp8s0" reject
oifname "enp8s0" reject
}
}
The nat_postrouting chain masquerades all traffic from 10.42.0.0/24
destined outside that subnet. The filter_forward chain accepts outbound
traffic originating from enp8s0 and return traffic for established
connections, rejecting everything else. NetworkManager also enables IP
forwarding automatically when the shared connection comes up, but it’s worth
confirming:
sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 1
At this point the UCG Ultra has a WAN IP, the rest of the network behind it is unaware anything changed, and the cellular connection is carrying the load.
Tearing Down⌗
To cleanly remove the shared connection when the primary internet is restored:
nmcli connection down shared-ucg
nmcli connection delete shared-ucg
NetworkManager removes the nftables rules and stops the dnsmasq process automatically. No manual firewall or routing table cleanup is necessary.
Notes⌗
The ipheth kernel module is included in the default Arch kernel, so no custom
kernel configuration is required for USB tethering. If the routing table loses
its default route after bringing up the shared connection — which happened
consistently with USB tethering but not Wi-Fi — reconnecting the upstream
interface restores it:
nmcli device connect enp20s0u12c4i2
Always check ip route for a default via entry before assuming the setup is
complete.