#!/bin/sh # Alpine 5G Router – connection script (run by 5g-router service or manually) # Uses /etc/5g-router.conf if present. Supports watchdog and failover. # Rev: 2 (see REVISION in repo root) set -e CONFIG="/etc/5g-router.conf" LOG_FILE="/var/log/5g-router.log" # Defaults AT_PORT="/dev/ttyUSB1" APN="internet" WAN_IF="eth1" LAN_IF="eth0.100" FAILOVER_ENABLED="no" FAILOVER_IF="eth0" FAILOVER_METRIC="202" WATCHDOG_INTERVAL="0" LOG_SIGNAL="yes" WEAK_SIGNAL_CSQ="10" [ -f "$CONFIG" ] && . "$CONFIG" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" 2>/dev/null || echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; } # Fix broken /dev/ttyUSB* node (if it is a regular file instead of char device - e.g. after modem reconnect) fix_ttyusb_if_needed() { case "$AT_PORT" in /dev/ttyUSB[0-9]*) ;; *) return 0 ;; esac [ ! -e "$AT_PORT" ] && return 0 if [ -f "$AT_PORT" ] && [ ! -c "$AT_PORT" ]; then _num="${AT_PORT#/dev/ttyUSB}" log "Fixing $AT_PORT (was regular file, recreating as char device)" rm -f "$AT_PORT" mknod "$AT_PORT" c 188 "$_num" chmod 660 "$AT_PORT" chown root:dialout "$AT_PORT" 2>/dev/null || true fi } get_at_response() { _cmd="$1" _wait="${2:-2}" timeout $((_wait + 4)) sh -c " cat $AT_PORT 2>/dev/null & _pid=\$! sleep 0.5 echo \"${_cmd}\r\" > $AT_PORT sleep $_wait kill \$_pid 2>/dev/null " 2>&1 } wait_for_modem() { log "Waiting for modem AT port..." for _i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do fix_ttyusb_if_needed if [ -c "$AT_PORT" ]; then log "Modem AT port available" return 0 fi sleep 2 done log "Modem AT port not available" return 1 } check_usb_mode() { if lsusb 2>/dev/null | grep -q "0e8d:7127"; then log "WARN: Modem is in Mode 41 (7127). AT commands may not work. Reboot or power-cycle modem." return 1 fi return 0 } do_connect() { # eth1 (RNDIS) does NOT provide DHCP. We get IP (and DNS) only via AT commands # and configure the interface manually. Do not run dhclient on eth1. check_usb_mode || return 1 # Modem can take 3–5s to respond after boot; use 5s so service (started at boot) gets OK get_at_response "AT" 5 | grep -q "OK" || { log "AT not OK (try longer wait or different AT port)"; return 1; } # Signal (optional log) _resp=$(get_at_response "AT+CSQ" 1) _csq=$(echo "$_resp" | grep "+CSQ:" | grep -oE '[0-9]+' | head -1) [ -n "$_csq" ] && [ "$LOG_SIGNAL" = "yes" ] && echo "$(date -Iseconds) CSQ=$_csq" >> "$LOG_FILE" 2>/dev/null || true [ -n "$_csq" ] && [ -n "$WEAK_SIGNAL_CSQ" ] && [ "$WEAK_SIGNAL_CSQ" -gt 0 ] && [ "$_csq" -lt "$WEAK_SIGNAL_CSQ" ] && log "WARN weak signal CSQ=$_csq" log "Configuring APN: $APN" get_at_response "AT+CGDCONT=1,\"IP\",\"$APN\"" 2 | grep -q "OK" || true log "Activating PDP context..." get_at_response "AT+CGACT=1,1" 3 | grep -qE "(OK|CGEV)" || true _ip="" for _retry in 1 2 3 4 5; do _resp=$(get_at_response "AT+CGPADDR=1" 2) _ip=$(echo "$_resp" | grep "+CGPADDR:" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1) [ -n "$_ip" ] && break [ $_retry -lt 5 ] && log "No IP yet, retry $_retry/5 in 3s..." && sleep 3 done [ -z "$_ip" ] && log "Could not get modem IP (check signal/registration: AT+CSQ, AT+CEREG?)" && return 1 # Get DNS from modem (AT+CGCONTRDP=1); modem does not provide DHCP, only IP/DNS via AT _dns1=""; _dns2="" _contrdp=$(get_at_response "AT+CGCONTRDP=1" 2) _allips="" for _a in $(echo "$_contrdp" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'); do [ "$_a" = "$_ip" ] && continue _allips="${_allips} ${_a}" done for _a in $_allips; do [ -z "$_a" ] && continue; _dns2="$_dns1"; _dns1="$_a"; done [ -z "$_dns1" ] && [ -n "$DNS_SERVERS" ] && _dns1=$(echo "$DNS_SERVERS" | cut -d',' -f1 | tr -d ' ') && _dns2=$(echo "$DNS_SERVERS" | cut -d',' -f2 | tr -d ' ') if [ -n "$_dns1" ]; then : > /etc/resolv.conf echo "nameserver $_dns1" >> /etc/resolv.conf [ -n "$_dns2" ] && echo "nameserver $_dns2" >> /etc/resolv.conf log "DNS set (from modem or config)" fi log "Configuring $WAN_IF with IP $_ip (no DHCP - from AT+CGPADDR=1)" ip link set "$WAN_IF" up 2>/dev/null || true ip addr flush dev "$WAN_IF" 2>/dev/null || true ip addr add "$_ip/32" dev "$WAN_IF" ip route add default dev "$WAN_IF" metric 50 2>/dev/null || true log "Interface configured" log "Setting up NAT..." echo 1 > /proc/sys/net/ipv4/ip_forward iptables -t nat -C POSTROUTING -o "$WAN_IF" -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o "$WAN_IF" -j MASQUERADE iptables -C FORWARD -i "$LAN_IF" -o "$WAN_IF" -j ACCEPT 2>/dev/null || iptables -A FORWARD -i "$LAN_IF" -o "$WAN_IF" -j ACCEPT iptables -C FORWARD -i "$WAN_IF" -o "$LAN_IF" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || iptables -A FORWARD -i "$WAN_IF" -o "$LAN_IF" -m state --state RELATED,ESTABLISHED -j ACCEPT log "NAT configured" return 0 } do_failover() { [ "$FAILOVER_ENABLED" != "yes" ] && return 0 # Ensure there is a default route via failover interface (higher metric so 5G wins when up) ip route add default dev "$FAILOVER_IF" metric "$FAILOVER_METRIC" 2>/dev/null || true } # Main run_once() { wait_for_modem || { do_failover; return 1; } if do_connect; then log "5G connection successful!" return 0 else do_failover return 1 fi } if [ -n "$WATCHDOG_INTERVAL" ] && [ "$WATCHDOG_INTERVAL" -gt 0 ]; then log "Starting 5G connection (watchdog every ${WATCHDOG_INTERVAL}s)..." while true; do run_once || true sleep "$WATCHDOG_INTERVAL" done else log "Starting 5G connection..." run_once || true log "Holding process for service (stop with: service 5g-router stop)." exec sleep infinity fi