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-Fi
  • enp8s0 — 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.

Backup