app: Require link carrier to consider Ethernet uplink active
A static IPv4 (e.g. the hotspot gateway address on eth0) was wrongly classified as a DHCP uplink on stacks where ip(8) omits the "dynamic" flag, and a NO-CARRIER interface with any address was reported as up. Gate probeEthernet on /sys/class/net/<iface>/carrier == 1, and accept either the dynamic flag or a finite valid_lft as the DHCP signal so BusyBox's ip output is handled too.
This commit is contained in:
parent
8b1debdddc
commit
92b6113d72
1 changed files with 28 additions and 9 deletions
|
|
@ -3,18 +3,33 @@ package app
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nemunaire/repeater/internal/models"
|
"github.com/nemunaire/repeater/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// probeEthernet reports the wired uplink state of iface by parsing
|
// hasCarrier reports whether the kernel sees an active link on iface.
|
||||||
// `ip -4 -o addr show dev <iface>`. An IPv4 line carrying the "dynamic" flag
|
// `/sys/class/net/<iface>/carrier` is "1" when the link layer is up; reading
|
||||||
// (set by ip(8) on addresses with a finite valid_lft) is treated as a
|
// it on an admin-down interface yields EINVAL, which we treat the same way.
|
||||||
// DHCP-assigned lease — that's the signal we use to decide whether the box
|
func hasCarrier(iface string) bool {
|
||||||
// already has connectivity through Ethernet.
|
data, err := os.ReadFile("/sys/class/net/" + iface + "/carrier")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(data)) == "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// probeEthernet reports the wired uplink state of iface. Active requires both
|
||||||
|
// a live link (`carrier == 1`) and a DHCP-assigned IPv4 — detected either by
|
||||||
|
// the `dynamic` flag emitted by full iproute2, or by a finite `valid_lft`
|
||||||
|
// (which BusyBox's `ip` does emit even when it omits the flag).
|
||||||
func probeEthernet(iface string) (*models.EthernetStatus, error) {
|
func probeEthernet(iface string) (*models.EthernetStatus, error) {
|
||||||
|
if !hasCarrier(iface) {
|
||||||
|
return &models.EthernetStatus{Active: false, Interface: iface}, nil
|
||||||
|
}
|
||||||
|
|
||||||
out, err := exec.Command("ip", "-4", "-o", "addr", "show", "dev", iface).Output()
|
out, err := exec.Command("ip", "-4", "-o", "addr", "show", "dev", iface).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ip addr show %s: %w", iface, err)
|
return nil, fmt.Errorf("ip addr show %s: %w", iface, err)
|
||||||
|
|
@ -23,7 +38,7 @@ func probeEthernet(iface string) (*models.EthernetStatus, error) {
|
||||||
fields := strings.Fields(line)
|
fields := strings.Fields(line)
|
||||||
var addr string
|
var addr string
|
||||||
hasInet := false
|
hasInet := false
|
||||||
hasDynamic := false
|
isDHCP := false
|
||||||
for i, f := range fields {
|
for i, f := range fields {
|
||||||
switch f {
|
switch f {
|
||||||
case "inet":
|
case "inet":
|
||||||
|
|
@ -32,10 +47,14 @@ func probeEthernet(iface string) (*models.EthernetStatus, error) {
|
||||||
addr = fields[i+1]
|
addr = fields[i+1]
|
||||||
}
|
}
|
||||||
case "dynamic":
|
case "dynamic":
|
||||||
hasDynamic = true
|
isDHCP = true
|
||||||
|
case "valid_lft":
|
||||||
|
if i+1 < len(fields) && fields[i+1] != "forever" {
|
||||||
|
isDHCP = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hasInet && hasDynamic {
|
if hasInet && isDHCP {
|
||||||
if slash := strings.IndexByte(addr, '/'); slash >= 0 {
|
if slash := strings.IndexByte(addr, '/'); slash >= 0 {
|
||||||
addr = addr[:slash]
|
addr = addr[:slash]
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +79,7 @@ func ensureUplink(iface string) *models.EthernetStatus {
|
||||||
log.Printf("DHCP address %s on %s, leaving wpa_supplicant alone", eth.IPv4, iface)
|
log.Printf("DHCP address %s on %s, leaving wpa_supplicant alone", eth.IPv4, iface)
|
||||||
return eth
|
return eth
|
||||||
}
|
}
|
||||||
log.Printf("No DHCP address on %s, starting wpa_supplicant", iface)
|
log.Printf("No usable DHCP uplink on %s (carrier or lease missing), starting wpa_supplicant", iface)
|
||||||
if out, err := exec.Command("service", "wpa_supplicant", "start").CombinedOutput(); err != nil {
|
if out, err := exec.Command("service", "wpa_supplicant", "start").CombinedOutput(); err != nil {
|
||||||
log.Printf("Failed to start wpa_supplicant: %v: %s", err, strings.TrimSpace(string(out)))
|
log.Printf("Failed to start wpa_supplicant: %v: %s", err, strings.TrimSpace(string(out)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue