app: Start wpa_supplicant only when Ethernet has no DHCP lease
Probe the configured Ethernet interface (default eth0, overridable via -ethernet-interface) at startup. If no DHCP-assigned IPv4 is present, start the wpa_supplicant service so the WiFi backend has something to talk to; otherwise leave it alone and rely on the wired uplink.
This commit is contained in:
parent
70140bc289
commit
d57c08a6c4
4 changed files with 82 additions and 20 deletions
|
|
@ -57,6 +57,11 @@ func (a *App) Initialize(cfg *config.Config) error {
|
||||||
// Store config reference
|
// Store config reference
|
||||||
a.Config = cfg
|
a.Config = cfg
|
||||||
|
|
||||||
|
// If Ethernet uplink is not already providing connectivity (no DHCP lease
|
||||||
|
// on the configured interface), bring up wpa_supplicant so the WiFi
|
||||||
|
// backend has something to talk to.
|
||||||
|
ensureUplink(cfg.EthernetInterface)
|
||||||
|
|
||||||
// Initialize WiFi backend
|
// Initialize WiFi backend
|
||||||
if err := wifi.Initialize(cfg.WifiInterface, cfg.WifiBackend); err != nil {
|
if err := wifi.Initialize(cfg.WifiInterface, cfg.WifiBackend); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
54
internal/app/network.go
Normal file
54
internal/app/network.go
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hasDHCPAddress reports whether iface holds an IPv4 address that ip(8) marks
|
||||||
|
// as "dynamic" — the flag set on addresses with a finite valid_lft, which is
|
||||||
|
// how DHCP-assigned leases appear (static addresses are "permanent").
|
||||||
|
func hasDHCPAddress(iface string) (bool, error) {
|
||||||
|
out, err := exec.Command("ip", "-4", "-o", "addr", "show", "dev", iface).Output()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("ip addr show %s: %w", iface, err)
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
hasInet := false
|
||||||
|
hasDynamic := false
|
||||||
|
for _, f := range fields {
|
||||||
|
switch f {
|
||||||
|
case "inet":
|
||||||
|
hasInet = true
|
||||||
|
case "dynamic":
|
||||||
|
hasDynamic = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasInet && hasDynamic {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureUplink boots the WiFi uplink only if Ethernet is not already providing
|
||||||
|
// connectivity: when iface has no DHCP-assigned IPv4, start wpa_supplicant so
|
||||||
|
// the WiFi backend can take over.
|
||||||
|
func ensureUplink(iface string) {
|
||||||
|
has, err := hasDHCPAddress(iface)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not probe %s for DHCP address (%v); starting wpa_supplicant as fallback", iface, err)
|
||||||
|
} else if has {
|
||||||
|
log.Printf("DHCP address present on %s, leaving wpa_supplicant alone", iface)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Printf("No DHCP address on %s, starting wpa_supplicant", iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out, err := exec.Command("systemctl", "start", "wpa_supplicant").CombinedOutput(); err != nil {
|
||||||
|
log.Printf("Failed to start wpa_supplicant: %v: %s", err, strings.TrimSpace(string(out)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ func declareFlags(o *Config) {
|
||||||
flag.StringVar(&o.Bind, "bind", "127.0.0.1:8080", "Bind address (host:port). Defaults to localhost; set to ':8080' to expose on the LAN — but note: there is no built-in authentication.")
|
flag.StringVar(&o.Bind, "bind", "127.0.0.1:8080", "Bind address (host:port). Defaults to localhost; set to ':8080' to expose on the LAN — but note: there is no built-in authentication.")
|
||||||
flag.StringVar(&o.WifiInterface, "wifi-interface", "wlan0", "WiFi interface name")
|
flag.StringVar(&o.WifiInterface, "wifi-interface", "wlan0", "WiFi interface name")
|
||||||
flag.StringVar(&o.HotspotInterface, "hotspot-interface", "wlan1", "Hotspot WiFi interface name")
|
flag.StringVar(&o.HotspotInterface, "hotspot-interface", "wlan1", "Hotspot WiFi interface name")
|
||||||
|
flag.StringVar(&o.EthernetInterface, "ethernet-interface", "eth0", "Ethernet interface to probe for a DHCP-assigned address at startup; if no DHCP address is present, wpa_supplicant is started")
|
||||||
flag.StringVar(&o.WifiBackend, "wifi-backend", "", "WiFi backend to use: 'iwd' or 'wpasupplicant' (required)")
|
flag.StringVar(&o.WifiBackend, "wifi-backend", "", "WiFi backend to use: 'iwd' or 'wpasupplicant' (required)")
|
||||||
flag.StringVar(&o.StationBackend, "station-backend", "hostapd", "Station discovery backend: 'arp', 'dhcp', or 'hostapd'")
|
flag.StringVar(&o.StationBackend, "station-backend", "hostapd", "Station discovery backend: 'arp', 'dhcp', or 'hostapd'")
|
||||||
flag.StringVar(&o.DHCPLeasesPath, "dhcp-leases-path", "/var/lib/dhcp/dhcpd.leases", "Path to DHCP leases file")
|
flag.StringVar(&o.DHCPLeasesPath, "dhcp-leases-path", "/var/lib/dhcp/dhcpd.leases", "Path to DHCP leases file")
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Bind string
|
Bind string
|
||||||
WifiInterface string
|
WifiInterface string
|
||||||
HotspotInterface string
|
HotspotInterface string
|
||||||
WifiBackend string
|
EthernetInterface string
|
||||||
StationBackend string // "arp", "dhcp", or "hostapd"
|
WifiBackend string
|
||||||
DHCPLeasesPath string
|
StationBackend string // "arp", "dhcp", or "hostapd"
|
||||||
ARPTablePath string
|
DHCPLeasesPath string
|
||||||
SyslogEnabled bool
|
ARPTablePath string
|
||||||
SyslogPath string
|
SyslogEnabled bool
|
||||||
SyslogFilter []string
|
SyslogPath string
|
||||||
SyslogSource string
|
SyslogFilter []string
|
||||||
|
SyslogSource string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConsolidateConfig fills an Options struct by reading configuration from
|
// ConsolidateConfig fills an Options struct by reading configuration from
|
||||||
|
|
@ -29,15 +30,16 @@ type Config struct {
|
||||||
func ConsolidateConfig() (opts *Config, err error) {
|
func ConsolidateConfig() (opts *Config, err error) {
|
||||||
// Define defaults options
|
// Define defaults options
|
||||||
opts = &Config{
|
opts = &Config{
|
||||||
Bind: "127.0.0.1:8080",
|
Bind: "127.0.0.1:8080",
|
||||||
WifiInterface: "wlan0",
|
WifiInterface: "wlan0",
|
||||||
HotspotInterface: "wlan1",
|
HotspotInterface: "wlan1",
|
||||||
DHCPLeasesPath: "/var/lib/dhcp/dhcpd.leases",
|
EthernetInterface: "eth0",
|
||||||
ARPTablePath: "/proc/net/arp",
|
DHCPLeasesPath: "/var/lib/dhcp/dhcpd.leases",
|
||||||
SyslogEnabled: false,
|
ARPTablePath: "/proc/net/arp",
|
||||||
SyslogPath: "/var/log/messages",
|
SyslogEnabled: false,
|
||||||
SyslogFilter: []string{"daemon.info wpa_supplicant:", "daemon.info iwd:", "daemon.info hostapd:"},
|
SyslogPath: "/var/log/messages",
|
||||||
SyslogSource: "iwd",
|
SyslogFilter: []string{"daemon.info wpa_supplicant:", "daemon.info iwd:", "daemon.info hostapd:"},
|
||||||
|
SyslogSource: "iwd",
|
||||||
}
|
}
|
||||||
|
|
||||||
declareFlags(opts)
|
declareFlags(opts)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue