From 11aeee2d7649f25ef91c5632cdf733e36e4e238d Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Wed, 15 May 2019 13:21:13 -0400 Subject: [PATCH] internal/wglinux: gracefully degrade if kernel implementation not available --- internal/wglinux/client_linux.go | 19 +++++++++++++------ internal/wglinux/client_linux_test.go | 26 ++++++++++++++++++++++++-- os_linux.go | 17 ++++++++++++----- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/internal/wglinux/client_linux.go b/internal/wglinux/client_linux.go index ebbc534..9e7cf57 100644 --- a/internal/wglinux/client_linux.go +++ b/internal/wglinux/client_linux.go @@ -26,22 +26,29 @@ type Client struct { interfaces func() ([]string, error) } -// New creates a new Client. -func New() (*Client, error) { +// New creates a new Client and returns whether or not the generic netlink +// interface is available. +func New() (*Client, bool, error) { c, err := genetlink.Dial(nil) if err != nil { - return nil, err + return nil, false, err } return initClient(c) } // initClient is the internal Client constructor used in some tests. -func initClient(c *genetlink.Conn) (*Client, error) { +func initClient(c *genetlink.Conn) (*Client, bool, error) { f, err := c.GetFamily(wgh.GenlName) if err != nil { _ = c.Close() - return nil, err + + if netlink.IsNotExist(err) { + // The generic netlink interface is not available. + return nil, false, nil + } + + return nil, false, err } return &Client{ @@ -50,7 +57,7 @@ func initClient(c *genetlink.Conn) (*Client, error) { // By default, gather only WireGuard interfaces using rtnetlink. interfaces: rtnlInterfaces, - }, nil + }, true, nil } // Close implements wginternal.Client. diff --git a/internal/wglinux/client_linux_test.go b/internal/wglinux/client_linux_test.go index d8cd223..533f12b 100644 --- a/internal/wglinux/client_linux_test.go +++ b/internal/wglinux/client_linux_test.go @@ -131,10 +131,14 @@ func TestLinuxClientIsPermission(t *testing.T) { t.Skip("skipping, test must be run without elevated privileges") } - c, err := New() + c, ok, err := New() if err != nil { t.Fatalf("failed to create Client: %v", err) } + if !ok { + t.Skip("skipping, the WireGuard generic netlink API is not available") + } + defer c.Close() // Check for permission denied as unprivileged user. @@ -143,6 +147,21 @@ func TestLinuxClientIsPermission(t *testing.T) { } } +func Test_initClientNotExist(t *testing.T) { + conn := genltest.Dial(func(_ genetlink.Message, _ netlink.Message) ([]genetlink.Message, error) { + // Simulate genetlink family not found. + return nil, genltest.Error(int(unix.ENOENT)) + }) + + _, ok, err := initClient(conn) + if err != nil { + t.Fatalf("failed to open Client: %v", err) + } + if ok { + t.Fatal("the generic netlink API should not be available from genltest") + } +} + func Test_parseRTNLInterfaces(t *testing.T) { // marshalAttrs creates packed netlink attributes with a prepended ifinfomsg // structure, as returned by rtnetlink. @@ -266,10 +285,13 @@ func testClient(t *testing.T, fn genltest.Func) *Client { conn := genltest.Dial(genltest.ServeFamily(family, fn)) - c, err := initClient(conn) + c, ok, err := initClient(conn) if err != nil { t.Fatalf("failed to open Client: %v", err) } + if !ok { + t.Fatal("the generic netlink API was not available from genltest") + } c.interfaces = func() ([]string, error) { return []string{okName}, nil diff --git a/os_linux.go b/os_linux.go index 1c36c51..a6ab3d9 100644 --- a/os_linux.go +++ b/os_linux.go @@ -10,19 +10,26 @@ import ( // newClients configures wginternal.Clients for Linux systems. func newClients() ([]wginternal.Client, error) { - // Linux has an in-kernel WireGuard implementation. - nlc, err := wglinux.New() + var clients []wginternal.Client + + // Linux has an in-kernel WireGuard implementation. Determine if it is + // available and make use of it if so. + kc, ok, err := wglinux.New() if err != nil { return nil, err } + if ok { + clients = append(clients, kc) + } // Although it isn't recommended to use userspace implementations on Linux, // it can be used. We make use of it in integration tests as well. - cfgc, err := wguser.New() + uc, err := wguser.New() if err != nil { return nil, err } - // Netlink devices seem to appear first in wg(8). - return []wginternal.Client{nlc, cfgc}, nil + // Kernel devices seem to appear first in wg(8). + clients = append(clients, uc) + return clients, nil }