From 5c6ea15a40cc06ac0af833fa3ef883de3f32037b Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Sat, 27 Apr 2019 14:23:29 +0100 Subject: [netboot] use rtnl package After adding the higher-level [rtnl package](https://github.com/jsimonetti/rtnetlink/pull/44) let's switch to it. Pending https://github.com/jsimonetti/rtnetlink/pull/50 Signed-off-by: Andrea Barberio --- netboot/netconf.go | 38 ++++++---- netboot/netconf_integ_test.go | 31 ++++++-- netboot/rtnetlink_linux.go | 154 ++-------------------------------------- netboot/rtnetlink_linux_test.go | 82 --------------------- 4 files changed, 57 insertions(+), 248 deletions(-) delete mode 100644 netboot/rtnetlink_linux_test.go 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. -} -- cgit v1.2.3