inits/pkg/cmd/init/boot/network.go
2024-01-20 14:20:51 +01:00

157 lines
3.2 KiB
Go

package initboot
import (
"bytes"
"fmt"
"log"
"net"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/go-ping/ping"
"novit.tech/direktil/pkg/config"
"novit.tech/direktil/inits/pkg/sys"
"novit.tech/direktil/inits/pkg/vars"
)
var networkStarted = map[string]bool{}
func setupNetworking() {
cfg := sys.Config()
for idx, network := range cfg.Networks {
step(fmt.Sprintf("network:%d", idx), func() { setupNetwork(idx, network) })
}
}
func setupNetwork(idx int, network config.NetworkDef) {
tries := 0
retry:
ifaces, err := net.Interfaces()
if err != nil {
log.Fatalf("FATAL: failed to get network interfaces: %v", err)
}
match := false
for _, iface := range ifaces {
if networkStarted[iface.Name] {
continue
}
if network.Match.Name != "" {
if ok, err := filepath.Match(network.Match.Name, iface.Name); err != nil {
log.Fatalf("FATAL: network[%d] name match error: %v", idx, err)
} else if !ok {
continue
}
}
if network.Match.Ping != nil {
log.Printf("network[%d] ping check on %s", idx, iface.Name)
if ok, err := networkPingCheck(iface.Name, network); err != nil {
log.Printf("ERROR: network[%d] ping check failed: %v", idx, err)
} else if !ok {
continue
}
}
log.Printf("network[%d] matches interface %s", idx, iface.Name)
match = true
startNetwork(iface.Name, idx, network)
if !network.Match.All {
return
}
}
if !match {
log.Printf("WARNING: network[%d] did not match any interface", idx)
tries++
if network.Optional && tries > 3 {
return
}
time.Sleep(1 * time.Second)
log.Printf("WARNING: network[%d] retrying (try: %d)", idx, tries)
goto retry
}
}
func startNetwork(ifaceName string, idx int, network config.NetworkDef) {
cfg := sys.Config()
log.Printf("starting network[%d]", idx)
script := vars.Substitute([]byte(network.Script), cfg)
c := exec.Command("/bin/sh")
c.Stdin = bytes.NewBuffer(script)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
// TODO doc
c.Env = append(append(make([]string, 0), os.Environ()...), "IFNAME="+ifaceName)
if err := c.Run(); err != nil {
links, _ := exec.Command("ip", "link", "ls").CombinedOutput()
log.Fatalf("FATAL: network setup failed (link list below): %v\n%s", err, string(links))
}
networkStarted[ifaceName] = true
}
func networkPingCheck(ifName string, network config.NetworkDef) (b bool, err error) {
check := network.Match.Ping
source := string(vars.Substitute([]byte(check.Source), sys.Config()))
if err = sys.Run("ip", "addr", "add", source, "dev", ifName); err != nil {
return
}
if err = sys.Run("ip", "link", "set", ifName, "up"); err != nil {
return
}
defer func() {
sys.MustRun("ip", "link", "set", ifName, "down")
sys.MustRun("ip", "addr", "del", source, "dev", ifName)
}()
count := 3
if check.Count != 0 {
count = check.Count
}
for n := 0; n < count; n++ {
// TODO probably better to use golang.org/x/net/icmp directly
pinger, e := ping.NewPinger(network.Match.Ping.Target)
if e != nil {
err = e
return
}
pinger.Count = 1
pinger.Timeout = 1 * time.Second
if check.Timeout > 0 {
pinger.Timeout = time.Duration(check.Timeout) * time.Second
}
pinger.SetPrivileged(true)
pinger.Run()
if pinger.Statistics().PacketsRecv > 0 {
b = true
return
}
}
return
}