Bug fixes and misc changes

- Fix DHCP longrun using up instead of run
- Fix wireguard circular dependency
- Use net/netip for anything but net.HardwareAddr
- Add thetford-mines.canada functional example host
- Add a navigator for the structure
- Add helper functions
- Fix types of some elements
This commit is contained in:
Alex D. 2024-09-10 13:20:23 +00:00
parent 0bcad8a5c6
commit 0d38256c5d
Signed by: caskd
GPG Key ID: F92BA85F61F4C173
8 changed files with 452 additions and 140 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
hosts/

View File

@ -3,6 +3,7 @@ package s6netdev
import (
"fmt"
"net"
"net/netip"
"strings"
)
@ -128,7 +129,7 @@ func (t *S6SvcTree) NetdevIfaceProp(iface Iface, p Property) *S6Svc {
return l
}
func (t *S6SvcTree) ConfigureIfaceAddr(iface Iface, addr string) *S6Svc {
func (t *S6SvcTree) NetdevConfigureIfaceMAC(iface Iface, addr net.HardwareAddr) *S6Svc {
svc_name := S6SvcName(fmt.Sprintf("interface.%s.ether.addr", iface.Name)).Sanitize()
l := t.S6New(svc_name, &S6SvcTypes.Oneshot)
@ -138,7 +139,7 @@ func (t *S6SvcTree) ConfigureIfaceAddr(iface Iface, addr string) *S6Svc {
ExeclineXDGConfig(),
ExeclineXDGEnvdirConfig(l.Name),
ExeclineDefine("INTERFACE", iface.Name),
ExeclineImportas("ADDRESS", false, addr),
ExeclineImportas("ADDRESS", false, addr.String()),
"ip link set $INTERFACE address $ADDRESS",
}, "\n")
@ -177,7 +178,7 @@ func (t *S6SvcTree) NetdevIfaceDHCP(iface Iface, ipv int) *S6Svc {
if ipv == 6 {
daemon = fmt.Sprintf("%s%d", daemon, ipv)
}
l.Up = strings.Join([]string{
l.Run = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", iface.Name),
"fdmove -c 2 1",
@ -191,7 +192,7 @@ func (t *S6SvcTree) NetdevIfaceDHCP(iface Iface, ipv int) *S6Svc {
return l
}
func (t *S6SvcTree) netdevIfaceAddrTemplate(action, iface string, ipv int, addr net.IP) string {
func (t *S6SvcTree) netdevIfaceAddrTemplate(action, iface string, ipv int, addr netip.Prefix) string {
return strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", iface),
@ -201,8 +202,8 @@ func (t *S6SvcTree) netdevIfaceAddrTemplate(action, iface string, ipv int, addr
}, "\n")
}
func (t *S6SvcTree) NetdevIfaceAddr(iface Iface, addr net.IP) *S6Svc {
ipv := NetdevIPAddrVer(addr)
func (t *S6SvcTree) NetdevIfaceAddr(iface Iface, addr netip.Prefix) *S6Svc {
ipv := NetdevIPAddrVer(addr.Addr())
svc_name := S6SvcName(fmt.Sprintf("interface.%s.ip.addr.%d.%s", iface.Name, ipv, addr)).Sanitize()
l := t.S6New(svc_name, &S6SvcTypes.Oneshot)
@ -223,7 +224,7 @@ func (t *S6SvcTree) netdevRouteTemplate(iface Iface, route Route) (up, down stri
cmd = []string{
"ip",
fmt.Sprintf("-%d", NetdevIPAddrVer(route.Net.IP)), // IP version
fmt.Sprintf("-%d", NetdevIPAddrVer(route.Net.Addr())), // IP version
"route",
"%s",
}
@ -247,7 +248,7 @@ func (t *S6SvcTree) netdevRouteTemplate(iface Iface, route Route) (up, down stri
cmd = append(cmd, "$ROUTE")
svcname = append(svcname, r)
if route.Via != nil {
if route.Via.IsValid() {
lines = append(lines, ExeclineImportas("VIA", false, route.Via.String()))
cmd = append(cmd, "via", "$VIA")
}

55
hosts/generic/main.go Normal file
View File

@ -0,0 +1,55 @@
package main
import (
"log"
"net/netip"
"os"
"git.redxen.eu/caskd/s6-netdev"
)
func main() {
var (
err error
l = log.New(os.Stderr, "s6-netdev: ", log.Ltime|log.Lshortfile|log.Lmsgprefix)
)
t := s6netdev.S6NewTree()
// Main VRF
main_vrf := s6netdev.Iface{
Name: "vrf-main",
Type: &s6netdev.NetdevIfTypes.Vrf,
Slaves: []*s6netdev.Iface{
{
Name: "br-main",
Type: &s6netdev.NetdevIfTypes.Bridge,
Routes: []s6netdev.Route{
{
Net: netip.MustParsePrefix("fd00::/64"),
},
},
Properties: []s6netdev.Property{
{Key: "stp_state", Value: "1", Default: "0"},
{Key: "mcast_snooping", Value: "0", Default: "1"},
},
Slaves: []*s6netdev.Iface{
{
Name: "enp1s0",
Type: &s6netdev.NetdevIfTypes.Phys,
},
},
},
},
}
t.NavServices(main_vrf)
for _, v := range t.S6Services() {
if s6netdev.NetdevIsDummy(v.Name) {
continue
}
l.Printf("Commiting %s\n", v.Name)
if err = t.S6CommitService(v); err != nil {
l.Fatalf("Failed to commit %s, %s\n", v.Name, err)
}
}
}

View File

@ -0,0 +1,184 @@
package main
import (
"log"
"net"
"net/netip"
"os"
"git.redxen.eu/caskd/s6-netdev"
)
func main() {
var (
err error
l = log.New(os.Stderr, "s6-netdev: ", log.Ltime|log.Lshortfile|log.Lmsgprefix)
)
t := s6netdev.S6NewTree()
// No VRF
phys := s6netdev.Iface{
Name: "phys",
Type: &s6netdev.NetdevIfTypes.Bridge,
MACAddr: net.HardwareAddr{0x52, 0x54, 0x00, 0x81, 0xcb, 0x62},
DHCP: s6netdev.DHCP_IP{V4: true},
Properties: []s6netdev.Property{
{Key: "stp_state", Value: "1", Default: "0"},
{Key: "mcast_snooping", Value: "0", Default: "1"},
},
Slaves: []*s6netdev.Iface{
{
Name: "enp12s0",
Type: &s6netdev.NetdevIfTypes.Phys,
},
},
}
t.NavServices(phys)
// DN42 VRF
vrf_dn42 := s6netdev.Iface{
Name: "vrf-dn42",
Type: &s6netdev.NetdevIfTypes.Vrf,
Table: 20,
Slaves: []*s6netdev.Iface{
{
Name: "br-dn42",
Type: &s6netdev.NetdevIfTypes.Bridge,
Properties: []s6netdev.Property{
{Key: "stp_state", Value: "0", Default: "0"},
{Key: "mcast_snooping", Value: "0", Default: "1"},
},
Slaves: []*s6netdev.Iface{
{
Name: "enp15s0",
Type: &s6netdev.NetdevIfTypes.Phys,
},
{
Name: "phys.42",
Type: &s6netdev.NetdevIfTypes.Vlan,
VlanId: 42,
Parent: &phys,
},
},
},
},
}
t.NavServices(vrf_dn42)
// V6 VRF
vrf_v6 := s6netdev.Iface{
Name: "vrf-v6",
Type: &s6netdev.NetdevIfTypes.Vrf,
Table: 10,
Slaves: []*s6netdev.Iface{
{
Name: "vultrbgp",
Type: &s6netdev.NetdevIfTypes.Wireguard,
Addresses: []netip.Prefix{
netip.MustParsePrefix("fe80::2/64"),
},
},
{
Name: "b00b",
Type: &s6netdev.NetdevIfTypes.Bridge,
MACAddr: net.HardwareAddr{0x02, 0x00, 0x00, 0x01, 0xb0, 0x0b},
Addresses: []netip.Prefix{
netip.MustParsePrefix("2a04:5b81:2060:b00b::2/64"),
},
Slaves: []*s6netdev.Iface{
{
Name: "phys.66",
Type: &s6netdev.NetdevIfTypes.Vlan,
VlanId: 66,
Parent: &phys,
},
{
Name: "enp9s0",
Type: &s6netdev.NetdevIfTypes.Phys,
},
},
Sysctls: s6netdev.Sysctl_IP{
V6: []s6netdev.Property{
{Key: "forwarding", Value: "0", Default: "0"},
{Key: "autoconf", Value: "0", Default: "0"},
},
},
Properties: []s6netdev.Property{
{Key: "stp_state", Value: "0", Default: "0"},
{Key: "mcast_snooping", Value: "0", Default: "1"},
},
},
{
Name: "f33d",
Type: &s6netdev.NetdevIfTypes.Bridge,
MACAddr: net.HardwareAddr{0x02, 0x00, 0x00, 0x01, 0xf3, 0x3d},
Addresses: []netip.Prefix{
netip.MustParsePrefix("2a04:5b81:2060:f33d::2/64"),
},
Slaves: []*s6netdev.Iface{
{
Name: "phys.100",
Type: &s6netdev.NetdevIfTypes.Vlan,
VlanId: 100,
Parent: &phys,
},
{
Name: "enp14s0",
Type: &s6netdev.NetdevIfTypes.Phys,
},
},
Sysctls: s6netdev.Sysctl_IP{
V6: []s6netdev.Property{
{Key: "forwarding", Value: "0", Default: "0"},
{Key: "autoconf", Value: "0", Default: "0"},
},
},
Properties: []s6netdev.Property{
{Key: "stp_state", Value: "0", Default: "0"},
{Key: "mcast_snooping", Value: "0", Default: "1"},
},
},
{
Name: "d00d",
Type: &s6netdev.NetdevIfTypes.Bridge,
MACAddr: net.HardwareAddr{0x02, 0x00, 0x00, 0x01, 0xd0, 0x0d},
Addresses: []netip.Prefix{
netip.MustParsePrefix("2a04:5b81:2060:d00d::2/64"),
},
Slaves: []*s6netdev.Iface{
{
Name: "phys.101",
Type: &s6netdev.NetdevIfTypes.Vlan,
VlanId: 101,
Parent: &phys,
},
{
Name: "enp13s0",
Type: &s6netdev.NetdevIfTypes.Phys,
},
},
Sysctls: s6netdev.Sysctl_IP{
V6: []s6netdev.Property{
{Key: "forwarding", Value: "0", Default: "0"},
{Key: "autoconf", Value: "0", Default: "0"},
},
},
Properties: []s6netdev.Property{
{Key: "stp_state", Value: "0", Default: "0"},
{Key: "mcast_snooping", Value: "0", Default: "1"},
},
},
},
}
t.NavServices(vrf_v6)
for _, v := range t.S6Services() {
if s6netdev.NetdevIsDummy(v.Name) {
continue
}
l.Printf("Commiting %s\n", v.Name)
if err = t.S6CommitService(v); err != nil {
l.Fatalf("Failed to commit %s, %s\n", v.Name, err)
}
}
}

View File

@ -2,136 +2,58 @@ package s6netdev
import (
"fmt"
"strings"
"net"
"net/netip"
)
func (t *S6SvcTree) Services(i Iface) (r []*S6Svc) {
r = append(r, t.ConfigureServices(i)...)
r = append(r, t.ReadyServices(i)...)
func (i *Iface) NetdevIfaceAddAddr(addr string) (err error) {
var (
a netip.Prefix
)
if a, err = netip.ParsePrefix(addr); err != nil {
err = fmt.Errorf("Failed to parse address %s, %w", addr, err)
}
i.Addresses = append(i.Addresses, a)
return
}
func (t *S6SvcTree) ConfigureServices(i Iface) (r []*S6Svc) {
svc_create := t.CreateIfaceService(i.Name)
r = append(r, svc_create)
// Defaults to be overridden
svc_create.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
fmt.Sprintf("ip link add $INTERFACE type %s", i.Type.str),
}, "\n")
svc_create.Down = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
"ip link del dev $INTERFACE",
}, "\n")
for _, v := range i.Type.deps {
// External dummy services, should be ignored at commit
cs := t.S6New(S6SvcName(v), &S6SvcTypes.Oneshot)
svc_create.S6Children(cs)
}
// Overrides
switch i.Type {
case &NetdevIfTypes.Phys:
fallthrough
case &NetdevIfTypes.Loopback:
{
svc_create.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
"bcnm-waitif 1 $INTERFACE",
}, "\n")
svc_create.Down = ""
func (i *Iface) NetdevIfaceAddRoute(route, r_type, via string, vrf *Iface, table *RouteTable, metric Metric) (err error) {
var (
r = Route{
Type: r_type,
Vrf: vrf,
Table: table,
Metric: metric,
}
case &NetdevIfTypes.Vlan:
{
svc_create.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
ExeclineDefine("PARENT", i.Parent.Name),
ExeclineDefine("VLAN", fmt.Sprintf("%d", i.VlanId)),
fmt.Sprintf("ip link add link $PARENT name $INTERFACE type %s id $VLAN", i.Type.str),
}, "\n")
svc_create.S6Children(t.CreateIfaceService(i.Parent.Name))
}
case &NetdevIfTypes.Vrf:
{
// Default if no table defined
if i.Table == 0 {
i.Table = RouteTable(500)
}
svc_create.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
ExeclineDefine("TABLE", fmt.Sprintf("%d", i.Table)),
fmt.Sprintf("ip link add $INTERFACE type %s table $TABLE", i.Type.str),
}, "\n")
}
case &NetdevIfTypes.Wireguard:
{
wg_cfg := t.NetdevWireguardConfig(i)
svc_create.S6Children(wg_cfg)
r = append(r, wg_cfg)
)
if via != "" {
if r.Via, err = netip.ParseAddr(via); err != nil {
err = fmt.Errorf("Failed to parse via %s, %w", via, err)
}
}
for _, v := range i.Slaves {
r = append(r, t.NetdevEnslaveInterface(v.Name, i.Name))
}
for _, v := range i.Properties {
r = append(r, t.NetdevIfaceProp(i, v))
}
if i.DHCP.V4 {
r = append(r, t.NetdevIfaceDHCP(i, 4))
}
if i.DHCP.V6 {
r = append(r, t.NetdevIfaceDHCP(i, 6))
}
for _, v := range i.Sysctls.V4 {
r = append(r, t.NetdevIfaceIpSysctl(i, 4, v))
}
for _, v := range i.Sysctls.V6 {
r = append(r, t.NetdevIfaceIpSysctl(i, 6, v))
}
for _, v := range i.Addresses {
r = append(r, t.NetdevIfaceAddr(i, v))
}
for _, v := range i.Routes {
r = append(r, t.NetdevRoute(i, v))
}
bundle_configure := t.NetdevDependBundleStage(i.Name, "configure", svc_create)
r = append(r, bundle_configure)
r = append(r, svc_create.Children...)
i.Routes = append(i.Routes, r)
return
}
func (t *S6SvcTree) ReadyServices(i Iface) (r []*S6Svc) {
svc_link := t.S6New(S6SvcName(fmt.Sprintf("interface.%s.link", i.Name)), &S6SvcTypes.Oneshot)
r = append(r, svc_link)
svc_link.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
"ip link set dev $INTERFACE up",
}, "\n")
svc_link.Down = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
"ip link set dev $INTERFACE down",
}, "\n")
svc_link.S6Children(t.CreateIfaceService(i.Name))
bundle_ready := t.NetdevDependBundleStage(i.Name, "ready", svc_link)
r = append(r, bundle_ready)
func (i *Iface) NetdevIfaceMAC(mac string) (err error) {
i.MACAddr, err = net.ParseMAC(mac)
return
}
func (i *Iface) NetdevIfaceEnslave(s ...*Iface) {
i.Slaves = append(i.Slaves, s...)
}
func (i *Iface) NetdevIfaceSysctl(ipv int, key, value, def string) {
l := &i.Sysctls.V4
if ipv == 6 {
l = &i.Sysctls.V6
}
*l = append(*l, Property{Key: key, Value: value, Default: def})
}
func (i *Iface) NetdevIfaceProperty(key, value, def string) {
i.Properties = append(i.Properties, Property{Key: key, Value: value, Default: def})
}

View File

@ -3,6 +3,7 @@ package s6netdev
import (
"fmt"
"net"
"net/netip"
"strings"
)
@ -42,8 +43,8 @@ func (t *S6SvcTree) NetdevEnslaveInterface(iface, master string) *S6Svc {
return l
}
func NetdevIPAddrVer(IP net.IP) int {
if IP.To4() == nil {
func NetdevIPAddrVer(IP netip.Addr) int {
if IP.Is6() {
return 6
}
return 4
@ -53,3 +54,7 @@ func NetdevIsDummy(s S6SvcName) (r bool) {
_, r = dummy_svc_blacklist[s]
return
}
func NetdevOnlyMAC(x net.HardwareAddr, _ error) net.HardwareAddr {
return x
}

View File

@ -2,6 +2,7 @@ package s6netdev
import (
"fmt"
"strings"
)
func (t *S6SvcTree) CreateIfaceService(iface string) *S6Svc {
@ -11,3 +12,143 @@ func (t *S6SvcTree) CreateIfaceService(iface string) *S6Svc {
func (t *S6SvcTree) LinkIfaceService(iface string) *S6Svc {
return t.S6New(S6SvcName(fmt.Sprintf("interface.%s.link", iface)), &S6SvcTypes.Oneshot)
}
func (t *S6SvcTree) NavServices(i Iface) (r []*S6Svc) {
r = t.Services(i)
if i.Parent != nil {
r = append(r, t.NavServices(*i.Parent)...)
}
for _, v := range i.Slaves {
r = append(r, t.NavServices(*v)...)
}
return
}
func (t *S6SvcTree) Services(i Iface) (r []*S6Svc) {
r = append(r, t.ConfigureServices(i)...)
r = append(r, t.ReadyServices(i)...)
return
}
func (t *S6SvcTree) ConfigureServices(i Iface) (r []*S6Svc) {
svc_create := t.CreateIfaceService(i.Name)
r = append(r, svc_create)
// Defaults to be overridden
svc_create.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
fmt.Sprintf("ip link add $INTERFACE type %s", i.Type.str),
}, "\n")
svc_create.Down = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
"ip link del dev $INTERFACE",
}, "\n")
for _, v := range i.Type.deps {
// External dummy services, should be ignored at commit
cs := t.S6New(S6SvcName(v), &S6SvcTypes.Oneshot)
svc_create.S6Children(cs)
}
// Overrides
switch i.Type {
case &NetdevIfTypes.Phys:
fallthrough
case &NetdevIfTypes.Loopback:
{
svc_create.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
"bcnm-waitif 1 $INTERFACE",
}, "\n")
svc_create.Down = ""
}
case &NetdevIfTypes.Vlan:
{
svc_create.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
ExeclineDefine("PARENT", i.Parent.Name),
ExeclineDefine("VLAN", fmt.Sprintf("%d", i.VlanId)),
fmt.Sprintf("ip link add link $PARENT name $INTERFACE type %s id $VLAN", i.Type.str),
}, "\n")
svc_create.S6Children(t.CreateIfaceService(i.Parent.Name))
}
case &NetdevIfTypes.Vrf:
{
// Default if no table defined
if i.Table == 0 {
i.Table = RouteTable(1924)
}
svc_create.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
ExeclineDefine("TABLE", fmt.Sprintf("%d", i.Table)),
fmt.Sprintf("ip link add $INTERFACE type %s table $TABLE", i.Type.str),
}, "\n")
}
}
for _, v := range i.Slaves {
r = append(r, t.NetdevEnslaveInterface(v.Name, i.Name))
}
for _, v := range i.Properties {
r = append(r, t.NetdevIfaceProp(i, v))
}
if i.DHCP.V4 {
r = append(r, t.NetdevIfaceDHCP(i, 4))
}
if i.DHCP.V6 {
r = append(r, t.NetdevIfaceDHCP(i, 6))
}
for _, v := range i.Sysctls.V4 {
r = append(r, t.NetdevIfaceIpSysctl(i, 4, v))
}
for _, v := range i.Sysctls.V6 {
r = append(r, t.NetdevIfaceIpSysctl(i, 6, v))
}
for _, v := range i.Addresses {
r = append(r, t.NetdevIfaceAddr(i, v))
}
for _, v := range i.Routes {
r = append(r, t.NetdevRoute(i, v))
}
if i.MACAddr != nil {
r = append(r, t.NetdevConfigureIfaceMAC(i, i.MACAddr))
}
bundle_configure := t.NetdevDependBundleStage(i.Name, "configure", svc_create)
r = append(r, bundle_configure)
r = append(r, svc_create.Children...)
return
}
func (t *S6SvcTree) ReadyServices(i Iface) (r []*S6Svc) {
svc_link := t.S6New(S6SvcName(fmt.Sprintf("interface.%s.link", i.Name)), &S6SvcTypes.Oneshot)
r = append(r, svc_link)
svc_link.Up = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
"ip link set dev $INTERFACE up",
}, "\n")
svc_link.Down = strings.Join([]string{
NETDEV_EXECLINE_HEADER,
ExeclineDefine("INTERFACE", i.Name),
"ip link set dev $INTERFACE down",
}, "\n")
svc_link.S6Children(t.netdevCreateBundleStage(i.Name, "configure"))
bundle_ready := t.NetdevDependBundleStage(i.Name, "ready", svc_link)
r = append(r, bundle_ready)
return
}

View File

@ -1,6 +1,9 @@
package s6netdev
import "net"
import (
"net"
"net/netip"
)
type (
RouteTable uint16
@ -13,7 +16,11 @@ type Property struct {
}
type Sysctl_IP struct {
V4, V6 []Property // Should we start dhcp on this interface?
V4, V6 []Property
}
type DHCP_IP struct {
V4, V6 bool // Should we start dhcp on this interface?
}
type Iface struct {
@ -26,14 +33,12 @@ type Iface struct {
Table RouteTable // Routing table, for VRF
MACAddr []byte // MAC address of interface (only valid for physical, bridges and VLAN)
MACAddr net.HardwareAddr // MAC address of interface (only valid for physical, bridges and VLAN)
Addresses []net.IP // Addresses to be assigned to interface
Routes []Route // Routes to be assigned to interface
Addresses []netip.Prefix // Addresses to be assigned to interface
Routes []Route // Routes to be assigned to interface
DHCP struct {
V4, V6 bool // Should we start dhcp on this interface?
}
DHCP DHCP_IP // Should we start dhcp on this interface?
Properties []Property // List of properties of the interface, valid for many
Sysctls Sysctl_IP // Sysctls associated with this interface
@ -43,8 +48,8 @@ type Route struct {
Default bool
// VRF would be a field but it can be derived from parent
Type string // unicast default, can be others
Net net.IPNet
Via *net.IP
Net netip.Prefix
Via netip.Addr
Vrf *Iface
Table *RouteTable
Metric Metric // Should be explicitly initialised to 1024