This guide explains how to configure your Linux system for proper multi-network bonding with SRTLA.
- Why This Matters
- The Core Problem
- Managing the IP List
- Step-by-Step Setup
- Verification
- Quick Reference
SRTLA bonds multiple network connections by sending packets through different source IP addresses. However, Linux doesn't automatically route packets through the correct interface just because you bound to a specific IP.
Without proper configuration:
- All your packets go through ONE interface (usually the first one that connected)
- Your other modems sit idle
- You get zero benefit from bonding
┌──────────────────────────────────────────────────────────────┐
│ │
│ You have 3 modems: │
│ • usb0: 10.0.0.10 (T-Mobile) │
│ • usb1: 10.0.1.10 (Verizon) │
│ • usb2: 10.0.2.10 (AT&T) │
│ │
│ SRTLA binds sockets to each IP and sends packets... │
│ │
│ BUT the kernel routing table says: │
│ "default via 10.0.0.1 dev usb0" │
│ │
│ Result: ALL packets exit through usb0! │
│ Your Verizon and AT&T modems do nothing. │
│ │
└──────────────────────────────────────────────────────────────┘
Policy routing adds rules that say: "If a packet comes FROM this IP, use THIS routing table."
┌──────────────────────────────────────────────────────────────┐
│ │
│ Policy Rules: │
│ • Packets from 10.0.0.10 → use routing table "usb0" │
│ • Packets from 10.0.1.10 → use routing table "usb1" │
│ • Packets from 10.0.2.10 → use routing table "usb2" │
│ │
│ Each table has its own default gateway: │
│ • Table usb0: default via 10.0.0.1 dev usb0 │
│ • Table usb1: default via 10.0.1.1 dev usb1 │
│ • Table usb2: default via 10.0.2.1 dev usb2 │
│ │
│ Result: Each source IP routes through its own modem! ✓ │
│ │
└──────────────────────────────────────────────────────────────┘
The srtla_send process reads source IPs from a file (default: /tmp/srtla_ips). Something needs to:
- Detect which network interfaces are available
- Write their IPs to the file
- Signal
srtla_sendto reload when interfaces change
If you're using CeraUI, this is handled automatically. CeraUI:
- Scans network interfaces on startup
- Writes enabled interface IPs to
/tmp/srtla_ips - Listens for network changes (modem connect/disconnect)
- Sends
SIGHUPtosrtla_sendwhen the list changes
┌─────────────────────────────────────────────────────────────┐
│ CeraUI Backend │
│ │
│ Modem connects ──▶ Detect new IP ──▶ Update file ──▶ SIGHUP│
│ │
│ Modem disconnects ──▶ Detect removal ──▶ Update ──▶ SIGHUP │
│ │
└─────────────────────────────────────────────────────────────┘
The implementation in CeraUI (srtla.ts):
// Scan all network interfaces and collect enabled IPs
export function genSrtlaIpList() {
const list: Array<string> = [];
const networkInterfaces = getNetworkInterfaces();
for (const i in networkInterfaces) {
const networkInterface = networkInterfaces[i];
if (networkInterface?.enabled && networkInterface.ip) {
list.push(networkInterface.ip);
}
}
return list;
}
// Write IPs to file
export function setSrtlaIpList(addresses: string[]) {
const list = addresses.join("\n");
fs.writeFileSync(setup.ips_file, list);
}
// Signal srtla_send to reload
export function restartSrtla() {
killall(["-HUP", "srtla_send"]);
}And in the streaming loop, it registers for network change events:
// Initial write
handleSrtlaIpAddresses();
// Listen for changes
removeNetworkInterfacesChangeListener = onNetworkInterfacesChange(
handleSrtlaIpAddresses, // Re-scan and update on any change
);For standalone use without CeraUI, create a script that monitors interfaces:
#!/bin/bash
# /usr/local/bin/srtla-ip-monitor.sh
# Monitors network interfaces and updates srtla_ips file
IPS_FILE="/tmp/srtla_ips"
LAST_HASH=""
update_ips() {
# Get all non-loopback IPv4 addresses
NEW_IPS=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '^127\.')
NEW_HASH=$(echo "$NEW_IPS" | md5sum)
# Only update if changed
if [ "$NEW_HASH" != "$LAST_HASH" ]; then
echo "$NEW_IPS" > "$IPS_FILE"
LAST_HASH="$NEW_HASH"
# Signal srtla_send to reload
pkill -HUP srtla_send 2>/dev/null
logger "SRTLA IPs updated: $(echo $NEW_IPS | tr '\n' ' ')"
fi
}
# Initial update
update_ips
# Monitor for changes (using ip monitor)
ip monitor address | while read -r line; do
update_ips
doneRun as a service:
# /etc/systemd/system/srtla-ip-monitor.service
[Unit]
Description=SRTLA IP Monitor
After=network.target
[Service]
ExecStart=/usr/local/bin/srtla-ip-monitor.sh
Restart=always
[Install]
WantedBy=multi-user.targetYou can extend the DHCP hook to also update the IP list:
# Add to /etc/dhcp/dhclient-exit-hooks.d/srtla-source-routing
# After setting up routing, also update the IP list
update_srtla_ips() {
ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '^127\.' > /tmp/srtla_ips
pkill -HUP srtla_send 2>/dev/null
}
case "$reason" in
BOUND|RENEW|REBIND|REBOOT|EXPIRE|FAIL|RELEASE|STOP)
update_srtla_ips
;;
esacLinux needs named routing tables. Add these to /etc/iproute2/rt_tables:
# USB modems (usb0 - usb4)
printf "100 usb0\n101 usb1\n102 usb2\n103 usb3\n104 usb4\n" | sudo tee -a /etc/iproute2/rt_tables
# Ethernet interfaces (eth0 - eth4)
printf "110 eth0\n111 eth1\n112 eth2\n113 eth3\n114 eth4\n" | sudo tee -a /etc/iproute2/rt_tables
# WiFi interfaces (wlan0 - wlan4)
printf "120 wlan0\n121 wlan1\n122 wlan2\n123 wlan3\n124 wlan4\n" | sudo tee -a /etc/iproute2/rt_tablesModems get new IPs via DHCP. We need routes to update automatically.
Create /etc/dhcp/dhclient-exit-hooks.d/srtla-source-routing:
#!/bin/bash
# Automatic source-based routing for SRTLA
# Triggers on DHCP events for interfaces in /etc/network/interfaces
if [ -n "$new_ip_address" ] && [ -n "$new_routers" ]; then
# Map interface name to routing table
case "$interface" in
usb[0-4]) table_id=$((100 + ${interface#usb})) ;;
eth[0-4]) table_id=$((110 + ${interface#eth})) ;;
*) exit 0 ;; # Ignore other interfaces
esac
table_name="$interface"
case "$reason" in
BOUND|RENEW|REBIND|REBOOT)
# Clear old routes
ip route flush table $table_name 2>/dev/null
# Add default route for this interface's table
ip route add default via $new_routers dev $interface table $table_name
# Add policy rule: packets FROM this IP use this table
ip rule del from $new_ip_address 2>/dev/null
ip rule add from $new_ip_address table $table_name priority $table_id
logger "SRTLA: $interface ($new_ip_address) → table $table_name via $new_routers"
;;
EXPIRE|FAIL|RELEASE|STOP)
ip route flush table $table_name 2>/dev/null
ip rule del from $new_ip_address 2>/dev/null
logger "SRTLA: removed $interface ($new_ip_address)"
;;
esac
fiMake executable:
sudo chmod +x /etc/dhcp/dhclient-exit-hooks.d/srtla-source-routingWiFi is managed by NetworkManager. Create /etc/NetworkManager/dispatcher.d/srtla-wifi-routing:
#!/bin/bash
# Source routing for WiFi interfaces
IFACE="$1"
ACTION="$2"
case "$IFACE" in
wlan[0-4]) ;;
*) exit 0 ;;
esac
table_id=$((120 + ${IFACE#wlan}))
table_name="$IFACE"
case "$ACTION" in
up)
IP=$(ip -4 addr show dev $IFACE | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
GW=$(ip route show dev $IFACE | grep -oP '(?<=default via )\d+(\.\d+){3}' | head -1)
if [ -n "$IP" ] && [ -n "$GW" ]; then
ip route flush table $table_name 2>/dev/null
ip route add default via $GW dev $IFACE table $table_name
ip rule del from $IP 2>/dev/null
ip rule add from $IP table $table_name priority $table_id
logger "SRTLA WiFi: $IFACE ($IP) → table $table_name via $GW"
fi
;;
down)
ip route flush table $table_name 2>/dev/null
logger "SRTLA WiFi: removed $IFACE"
;;
esacMake executable:
sudo chmod +x /etc/NetworkManager/dispatcher.d/srtla-wifi-routingFor reliable multi-modem setup (especially when modems share MAC addresses), use /etc/network/interfaces:
# /etc/network/interfaces
auto lo
iface lo inet loopback
# USB modems
auto usb0
iface usb0 inet dhcp
auto usb1
iface usb1 inet dhcp
auto usb2
iface usb2 inet dhcp
auto usb3
iface usb3 inet dhcp
Note: Using
/etc/network/interfacesinstead of NetworkManager is more reliable for USB modems, especially Huawei models that share MAC addresses.
Mobile carriers provide DNS that only works through their network. Use public DNS:
# Add to /etc/resolvconf/resolv.conf.d/head
printf "\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nnameserver 1.1.1.1\n" | sudo tee -a /etc/resolvconf/resolv.conf.d/head
# Apply changes
sudo resolvconf -uSRTLA uses 32MB buffers. Ensure the system allows this:
# Add to /etc/sysctl.conf for persistence
echo "net.core.rmem_max=67108864" | sudo tee -a /etc/sysctl.conf
echo "net.core.wmem_max=67108864" | sudo tee -a /etc/sysctl.conf
# Apply now
sudo sysctl -pcat /etc/iproute2/rt_tables | grep -E "(usb|eth|wlan)"Expected output:
100 usb0
101 usb1
...
120 wlan0
...
ip rule showExpected output (example with 2 modems):
0: from all lookup local
100: from 10.0.0.10 lookup usb0
101: from 10.0.1.10 lookup usb1
32766: from all lookup main
32767: from all lookup default
# Check which interface each source IP uses
ip route get 8.8.8.8 from 10.0.0.10
ip route get 8.8.8.8 from 10.0.1.10Expected output:
8.8.8.8 from 10.0.0.10 via 10.0.0.1 dev usb0 table usb0
8.8.8.8 from 10.0.1.10 via 10.0.1.1 dev usb1 table usb1
On sender:
ping -I 10.0.0.10 <receiver_ip>
ping -I 10.0.1.10 <receiver_ip>On receiver:
tcpdump -i any icmpYou should see pings arriving from different source IPs.
| What | Why | File/Command |
|---|---|---|
| Routing tables | Named tables for each interface | /etc/iproute2/rt_tables |
| DHCP hook | Auto-routing for USB/Ethernet | /etc/dhcp/dhclient-exit-hooks.d/ |
| NM dispatcher | Auto-routing for WiFi | /etc/NetworkManager/dispatcher.d/ |
| Interface config | Reliable modem enumeration | /etc/network/interfaces |
| DNS | Avoid carrier-specific DNS | /etc/resolvconf/resolv.conf.d/head |
| UDP buffers | Handle high-bitrate bursts | /etc/sysctl.conf |
Check ip rule show - you should see rules for each IP. If not, the DHCP hook isn't running:
- Verify the hook is executable
- Check
/var/log/syslogfor "SRTLA" messages - Try manually running
sudo dhclient usb0
The interface might be managed by NetworkManager instead of dhclient:
- Move it to
/etc/network/interfaces - Or create a NetworkManager dispatcher script
Check that /tmp/srtla_ips contains all your interface IPs:
cat /tmp/srtla_ipsSend SIGHUP to reload:
kill -HUP $(pidof srtla_send)