summaryrefslogtreecommitdiffhomepage
path: root/netboot
diff options
context:
space:
mode:
Diffstat (limited to 'netboot')
-rw-r--r--netboot/netconf.go38
-rw-r--r--netboot/netconf_integ_test.go31
-rw-r--r--netboot/rtnetlink_linux.go154
-rw-r--r--netboot/rtnetlink_linux_test.go82
4 files changed, 57 insertions, 248 deletions
diff --git a/netboot/netconf.go b/netboot/netconf.go
index 2e11d38..2ce658b 100644
--- a/netboot/netconf.go
+++ b/netboot/netconf.go
@@ -13,6 +13,7 @@ import (
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv6"
"github.com/jsimonetti/rtnetlink"
+ "github.com/jsimonetti/rtnetlink/rtnl"
"github.com/mdlayher/netlink"
)
@@ -138,10 +139,17 @@ func GetNetConfFromPacketv4(d *dhcpv4.DHCPv4) (*NetConf, error) {
}
// IfUp brings up an interface by name, and waits for it to come up until a timeout expires
-func IfUp(ifname string, timeout time.Duration) (*net.Interface, error) {
+func IfUp(ifname string, timeout time.Duration) (_ *net.Interface, err error) {
start := time.Now()
- var rt RTNL
- defer rt.Close()
+ rt, err := rtnl.Dial(nil)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if cerr := rt.Close(); cerr != nil {
+ err = cerr
+ }
+ }()
for time.Since(start) < timeout {
iface, err := net.InterfaceByName(ifname)
if err != nil {
@@ -153,7 +161,7 @@ func IfUp(ifname string, timeout time.Duration) (*net.Interface, error) {
// backward compatibility, routing daemons, dhcp clients can use this
// flag to determine whether they should use the interface.
// Source: https://www.kernel.org/doc/Documentation/networking/operstates.txt
- operState, err := rt.GetLinkState(iface.Index)
+ operState, err := getOperState(iface.Index)
if err != nil {
return nil, err
}
@@ -165,8 +173,7 @@ func IfUp(ifname string, timeout time.Duration) (*net.Interface, error) {
return iface, nil
}
// otherwise try to bring it up
- err = rt.SetLinkState(iface.Index, true)
- if err != nil {
+ if err := rt.LinkUp(iface); err != nil {
return nil, fmt.Errorf("interface %q: %v can't bring it up: %v", ifname, iface, err)
}
time.Sleep(10 * time.Millisecond)
@@ -178,16 +185,23 @@ func IfUp(ifname string, timeout time.Duration) (*net.Interface, error) {
// ConfigureInterface configures a network interface with the configuration held by a
// NetConf structure
-func ConfigureInterface(ifname string, netconf *NetConf) error {
+func ConfigureInterface(ifname string, netconf *NetConf) (err error) {
iface, err := net.InterfaceByName(ifname)
if err != nil {
return err
}
- var rt RTNL
- defer rt.Close()
+ rt, err := rtnl.Dial(nil)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if cerr := rt.Close(); err != nil {
+ err = cerr
+ }
+ }()
// configure interfaces
for _, addr := range netconf.Addresses {
- if err := rt.SetAddr(iface.Index, addr.IPNet); err != nil {
+ if err := rt.AddrAdd(iface, &addr.IPNet); err != nil {
return fmt.Errorf("cannot configure %s on %s: %v", ifname, addr.IPNet, err)
}
}
@@ -218,7 +232,7 @@ func ConfigureInterface(ifname string, netconf *NetConf) error {
// a client would want to add before initiating the DHCP transaction in order not to fail with
// ENETUNREACH. If this default route has a specific metric assigned, it doesn't get removed.
// The code doesn't remove any other default route (i.e. gw != 0.0.0.0).
- if err := rt.RouteDel(net.IPv4zero); err != nil {
+ if err := rt.RouteDel(iface, net.IPNet{IP: net.IPv4zero}); err != nil {
switch err := err.(type) {
case *netlink.OpError:
// ignore the error if it's -EEXIST or -ESRCH
@@ -232,7 +246,7 @@ func ConfigureInterface(ifname string, netconf *NetConf) error {
src := netconf.Addresses[0].IPNet
// TODO handle the remaining Routers if more than one
- if err := rt.RouteAdd(iface.Index, dst, src, netconf.Routers[0]); err != nil {
+ if err := rt.RouteAddSrc(iface, dst, &src, netconf.Routers[0]); err != nil {
return fmt.Errorf("could not add gateway %s for src %s dst %s to interface %s: %v", netconf.Routers[0], src, dst, ifname, err)
}
}
diff --git a/netboot/netconf_integ_test.go b/netboot/netconf_integ_test.go
index e4bad74..5f16782 100644
--- a/netboot/netconf_integ_test.go
+++ b/netboot/netconf_integ_test.go
@@ -3,6 +3,7 @@
package netboot
import (
+ "net"
"testing"
"time"
@@ -10,17 +11,39 @@ import (
"github.com/stretchr/testify/require"
)
+// this assumes that eth0 exists and is configurable
+var ifname = "eth0"
+
func TestIfUp(t *testing.T) {
- // this assumes that eth0 exists and is configurable
- ifname := "eth0"
iface, err := IfUp(ifname, 2*time.Second)
require.NoError(t, err)
assert.Equal(t, ifname, iface.Name)
}
func TestIfUpTimeout(t *testing.T) {
- // this assumes that eth0 exists and is configurable
- ifname := "eth0"
_, err := IfUp(ifname, 0*time.Second)
require.Error(t, err)
}
+
+func TestConfigureInterface(t *testing.T) {
+ nc := NetConf{
+ Addresses: []AddrConf{
+ AddrConf{IPNet: net.IPNet{IP: net.ParseIP("10.20.30.40")}},
+ },
+ }
+ err := ConfigureInterface(ifname, &nc)
+ require.NoError(t, err)
+}
+
+func TestConfigureInterfaceWithRouteAndDNS(t *testing.T) {
+ nc := NetConf{
+ Addresses: []AddrConf{
+ AddrConf{IPNet: net.IPNet{IP: net.ParseIP("10.20.30.40")}},
+ },
+ DNSServers: []net.IP{net.ParseIP("8.8.8.8")},
+ DNSSearchList: []string{"slackware.it"},
+ Routers: []net.IP{net.ParseIP("10.20.30.254")},
+ }
+ err := ConfigureInterface(ifname, &nc)
+ require.NoError(t, err)
+}
diff --git a/netboot/rtnetlink_linux.go b/netboot/rtnetlink_linux.go
index 949f7e5..41f5b3e 100644
--- a/netboot/rtnetlink_linux.go
+++ b/netboot/rtnetlink_linux.go
@@ -1,162 +1,16 @@
package netboot
-import (
- "encoding/binary"
- "net"
+import "github.com/jsimonetti/rtnetlink"
- "github.com/jsimonetti/rtnetlink"
- "golang.org/x/sys/unix"
-)
-
-// RTNL is a rtnetlink object with a high-level interface.
-type RTNL struct {
- conn *rtnetlink.Conn
-}
-
-func (r *RTNL) init() error {
- if r.conn != nil {
- return nil
- }
+// getOperState returns the operational state for the given interface index.
+func getOperState(iface int) (rtnetlink.OperationalState, error) {
conn, err := rtnetlink.Dial(nil)
if err != nil {
- return err
- }
- r.conn = conn
- return nil
-}
-
-// Close closes the netlink connection. Must be called to avoid leaks!
-func (r *RTNL) Close() {
- if r.conn != nil {
- r.conn.Close()
- r.conn = nil
- }
-}
-
-// GetLinkState returns the operational state for the given interface index.
-func (r *RTNL) GetLinkState(iface int) (rtnetlink.OperationalState, error) {
- if err := r.init(); err != nil {
return 0, err
}
- msg, err := r.conn.Link.Get(uint32(iface))
+ msg, err := conn.Link.Get(uint32(iface))
if err != nil {
return 0, err
}
return msg.Attributes.OperationalState, nil
}
-
-// SetLinkState sets the operational state up or down for the given interface
-// index.
-func (r *RTNL) SetLinkState(iface int, up bool) error {
- if err := r.init(); err != nil {
- return err
- }
- var state uint32
- if up {
- state = unix.IFF_UP
- }
- msg := rtnetlink.LinkMessage{
- Family: unix.AF_UNSPEC,
- Type: unix.ARPHRD_NETROM,
- Index: uint32(iface),
- Flags: state,
- Change: unix.IFF_UP,
- }
- if err := r.conn.Link.Set(&msg); err != nil {
- return err
- }
- return nil
-}
-
-func getFamily(ip net.IP) int {
- if ip.To4() != nil {
- return unix.AF_INET
- }
- return unix.AF_INET6
-}
-
-// SetAddr sets the interface address.
-func (r *RTNL) SetAddr(iface int, a net.IPNet) error {
- if err := r.init(); err != nil {
- return err
- }
- ones, _ := a.Mask.Size()
- msg := rtnetlink.AddressMessage{
- Family: uint8(getFamily(a.IP)),
- PrefixLength: uint8(ones),
- // TODO detect the right scope to set, or get it as input argument
- Scope: unix.RT_SCOPE_UNIVERSE,
- Index: uint32(iface),
- Attributes: rtnetlink.AddressAttributes{
- Address: a.IP,
- Local: a.IP,
- },
- }
- if a.IP.To4() != nil {
- // Broadcast is only required for IPv4
- ip := make(net.IP, net.IPv4len)
- binary.BigEndian.PutUint32(
- ip,
- binary.BigEndian.Uint32(a.IP.To4())|
- ^binary.BigEndian.Uint32(net.IP(a.Mask).To4()))
- msg.Attributes.Broadcast = ip
- }
- if err := r.conn.Address.New(&msg); err != nil {
- return err
- }
- return nil
-}
-
-// RouteDel deletes a route to the given destination
-func (r *RTNL) RouteDel(dst net.IP) error {
- if err := r.init(); err != nil {
- return err
- }
- msg := rtnetlink.RouteMessage{
- Family: uint8(getFamily(dst)),
- Table: unix.RT_TABLE_MAIN,
- // TODO make this configurable?
- Protocol: unix.RTPROT_UNSPEC,
- // TODO make this configurable?
- Scope: unix.RT_SCOPE_NOWHERE,
- Type: unix.RTN_UNSPEC,
- Attributes: rtnetlink.RouteAttributes{
- Dst: dst,
- },
- }
- if err := r.conn.Route.Delete(&msg); err != nil {
- return err
- }
- return nil
-}
-
-// RouteAdd adds a route to dst, from src (if set), via gw.
-func (r *RTNL) RouteAdd(iface int, dst, src net.IPNet, gw net.IP) error {
- if err := r.init(); err != nil {
- return err
- }
- dstLen, _ := dst.Mask.Size()
- srcLen, _ := src.Mask.Size()
- msg := rtnetlink.RouteMessage{
- Family: uint8(getFamily(dst.IP)),
- Table: unix.RT_TABLE_MAIN,
- // TODO make this configurable?
- Protocol: unix.RTPROT_BOOT,
- // TODO make this configurable?
- Scope: unix.RT_SCOPE_UNIVERSE,
- Type: unix.RTN_UNICAST,
- DstLength: uint8(dstLen),
- SrcLength: uint8(srcLen),
- Attributes: rtnetlink.RouteAttributes{
- Dst: dst.IP,
- Src: src.IP,
- Gateway: gw,
- OutIface: uint32(iface),
- },
- }
- if err := r.conn.Route.Add(&msg); err != nil {
- return err
- }
- return nil
-
-}
diff --git a/netboot/rtnetlink_linux_test.go b/netboot/rtnetlink_linux_test.go
deleted file mode 100644
index 860ea0f..0000000
--- a/netboot/rtnetlink_linux_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-//+build integration
-
-package netboot
-
-import (
- "net"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "golang.org/x/sys/unix"
-)
-
-// integration tests that require Linux with a properly working rtnetlink
-// interface, and the existence of an "eth0" interface.
-// WARNING: these tests may improperly configure your network interfaces and
-// routing, so be careful before running them. Privileged access and integration
-// build tag required to run them.
-
-var (
- testIfname = "eth0"
-)
-
-func TestInit(t *testing.T) {
- var r RTNL
- err := r.init()
- assert.NoError(t, err)
- require.NotNil(t, r.conn)
- r.Close()
-}
-
-func TestClose(t *testing.T) {
- var r RTNL
- err := r.init()
- assert.NoError(t, err)
- require.NotNil(t, r.conn)
- r.Close()
- require.Nil(t, r.conn)
-}
-
-func TestGetLinkState(t *testing.T) {
- var r RTNL
- defer r.Close()
-
- iface, err := net.InterfaceByName(testIfname)
- require.NoError(t, err)
- _, err = r.GetLinkState(iface.Index)
- require.NoError(t, err)
-}
-
-func TestSetLinkState(t *testing.T) {
- var r RTNL
- defer r.Close()
-
- iface, err := net.InterfaceByName(testIfname)
- require.NoError(t, err)
- err = r.SetLinkState(iface.Index, true)
- require.NoError(t, err)
-}
-
-func Test_getFamily(t *testing.T) {
- require.Equal(t, unix.AF_INET, getFamily(net.IPv4zero))
- require.Equal(t, unix.AF_INET, getFamily(net.IPv4bcast))
- require.Equal(t, unix.AF_INET, getFamily(net.IPv4allrouter))
-
- require.Equal(t, unix.AF_INET6, getFamily(net.IPv6zero))
- require.Equal(t, unix.AF_INET6, getFamily(net.IPv6loopback))
- require.Equal(t, unix.AF_INET6, getFamily(net.IPv6linklocalallrouters))
-}
-
-func TestSetAddr(t *testing.T) {
- var r RTNL
- defer r.Close()
- iface, err := net.InterfaceByName(testIfname)
- require.NoError(t, err)
-
- a := net.IPNet{IP: net.ParseIP("10.0.123.1"), Mask: net.IPv4Mask(255, 255, 255, 0)}
- err = r.SetAddr(iface.Index, a)
- require.NoError(t, err)
- // TODO implement GetAddr to further validate this, and minimize the effect
- // of concurrent tests that may invalidate this check.
-}