Taming the PinePhone's Fickle USB Ethernet
I use my PinePhone with postmarketOS as a mini-modem and SSH target. The goal is simple: plug the phone into my Debian laptop via USB, get a predictable IP address on it, and SSH in. This should be trivial. It was not.
If I rebooted the phone while it was plugged in, the interface would die and not come back correctly.
The first step is kernel log.
# dmesg -w
Plugging in the phone revealed the first layer of the problem. It doesn't just connect once. It connects, disconnects, and then connects again about 15-20 seconds later.
[Sun Jul 13 15:33:28 2025] usb 1-9: new high-speed USB device number 36 using xhci_hcd
[Sun Jul 13 15:33:28 2025] usb 1-9: New USB device found, idVendor=18d1, idProduct=d001
[Sun Jul 13 15:33:28 2025] cdc_ncm 1-9:1.0 enx2e50df0e14da: renamed from eth0
...
[Sun Jul 13 15:33:45 2025] usb 1-9: USB disconnect, device number 36
...
[Sun Jul 13 15:33:46 2025] usb 1-9: new high-speed USB device number 37 using xhci_hcd
[Sun Jul 13 15:33:47 2025] usb 1-9: New USB device found, idVendor=18d1, idProduct=d001
[Sun Jul 13 15:33:47 2025] cdc_ncm 1-9:1.0 enxde9ceb943c18: renamed from eth0
Notice two things:
- The device connects as
number 36
, then disconnects, then reconnects asnumber 37
. This is the phone's OS re-initializing the USB stack after boot. - The kernel first registers a generic
eth0
, which is then immediately renamed to a "predictable" name likeenx2e50df0e14da
.
And to make matters worse, the MAC address is different on each reconnection. Any static configuration in /etc/network/interfaces
based on a MAC address is doomed to fail.
The clear solution is to use a udev
rule to act when the device appears. The stable identifier we have is the USB vendor and product ID, which lsusb
confirms:
$ lsusb | grep Google
Bus 001 Device 037: ID 18d1:d001 Google Inc. Nexus 4 (fastboot)
(It identifies as a Nexus 4 in this mode, which is fine.)
My first attempt was a simple udev rule.
# /etc/udev/rules.d/99-pmos-network.rules (ATTEMPT 1 - WRONG)
ACTION=="add", SUBSYSTEM=="net", ATTRS{idVendor}=="18d1", RUN+="/usr/local/bin/pm-net-configure.sh %k"
This failed because of a race condition. The add
action fires the moment eth0
is created, but before it's renamed to enx...
. My script would be told to configure eth0
, which ceased to exist a millisecond later.
The key to solving udev timing issues is to stop guessing and start observing.
# udevadm monitor --environment --udev
Running this while plugging in the phone produces a firehose of information. After the final reconnection, deep in the output, was the golden ticket:
UDEV [8215599.005027] move /devices/pci.../net/enxde9ceb943c18 (net)
ACTION=move
DEVPATH=/devices/pci.../net/enxde9ceb943c18
SUBSYSTEM=net
INTERFACE=enxde9ceb943c18
IFINDEX=39
ID_VENDOR_ID=18d1
ID_MODEL_ID=d001
...
The system generates a move
event when the interface is renamed. This event is perfect. It only happens after the rename, and it contains both the final interface name (%k
or $env{INTERFACE}
) and the USB device IDs we need for matching.
This leads to the final, correct, and surprisingly simple udev rule.
The Final Solution
1. The Udev Rule
This single rule triggers at the exact moment the interface is renamed to its stable name.
Create /etc/udev/rules.d/99-pmos-network.rules
:
# Trigger on the network interface "move" (rename) event for the PinePhone.
# This avoids all race conditions with initial device naming.
ACTION=="move", SUBSYSTEM=="net", ENV{ID_VENDOR_ID}=="18d1", ENV{ID_MODEL_ID}=="d001", RUN+="/usr/local/bin/pm-net-configure.sh %k"
2. The Configuration Script
This is the script the udev rule calls. The %k
in the rule passes the correct interface name (e.g., enxde9ceb943c18
) as the first argument.
Create /usr/local/bin/pm-net-configure.sh
:
#!/bin/sh
set -e
DEV="$1"
IP_ADDR="172.16.42.2/24"
PEER_IP="172.16.42.1"
LOG_FILE="/tmp/pmos_net_config.log"
# Simple logging to know what's happening
echo "---" >> "$LOG_FILE"
echo "$(date): udev 'move' event on '$DEV'. Configuring." >> "$LOG_FILE"
# Give the interface a second to settle, then bring it up and set the IP.
sleep 1
ip link set dev "$DEV" up
ip addr add "$IP_ADDR" dev "$DEV"
echo "$(date): Successfully configured $DEV" >> "$LOG_FILE"
And make it executable:
# chmod +x /usr/local/bin/pm-net-configure.sh
3. Reload and Test
Tell udev to pick up the new rule.
# udevadm control --reload
Now, reboot the PinePhone. It will do its connect/disconnect dance. After the second connection, the move
event will fire our rule.
# ip a
...
39: enxde9ceb943c18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc ...
link/ether de:9c:eb:94:3c:18 brd ff:ff:ff:ff:ff:ff
inet 172.16.42.2/24 scope global enxde9ceb943c18
valid_lft forever preferred_lft forever
...
# ping -c 2 172.16.42.1
PING 172.16.42.1 (172.16.42.1) 56(84) bytes of data.
64 bytes from 172.16.42.1: icmp_seq=1 ttl=64 time=0.885 ms
64 bytes from 172.16.42.1: icmp_seq=2 ttl=64 time=0.672 ms
It just works. Every time.