From c67a39165c00405d58a9af8f13e774fe450dec46 Mon Sep 17 00:00:00 2001 From: jspc Date: Thu, 11 Feb 2021 12:54:10 +0900 Subject: [PATCH 1/3] Allow for wifi profiles Given that sonar borked on this function before, for being too complicated, and given that wifi networks are handled in a completely different way anyway, this commit does some work towards splitting network configuration out into different places. --- netctl/profile.go | 32 +++++++++++++++++++++++++++----- netctl/profile_test.go | 6 ++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/netctl/profile.go b/netctl/profile.go index 219d35c..8f42c7a 100644 --- a/netctl/profile.go +++ b/netctl/profile.go @@ -69,6 +69,7 @@ type Profile struct { Interface string `toml:"interface"` IPv4 Address `toml:",omitempty"` IPv6 Address `toml:",omitempty"` + Wifi bool `toml:wifi,omitempty` // link points to the underlying netlink object link netlink.Link @@ -94,13 +95,34 @@ func (p Profile) Up() (err error) { return } - // Loopback devices are special; we can go ahead and set them - // up the same way each time. In fact, the loopback file only needs - // the value of `Interface` to be set - if loopback.Match([]byte(p.Interface)) { - return p.UpLoopback() + switch { + case loopback.Match([]byte(p.Interface)): + // Loopback devices are special; we can go ahead and set them + // up the same way each time. In fact, the loopback file only needs + // the value of `Interface` to be set + err = p.UpLoopback() + + case p.Wifi: + // Wifi interfaces get handled via wpa_supplicant + err = p.UpWifi() + + default: + // Any interface left-over must be a wired interface + err = p.UpWired() } + return +} + +// UpWifi brings up a wifi network via wpa_supplicant +func (p Profile) UpWifi() (err error) { + err = fmt.Errorf("bringing up wifi networks has not been implemented yet") + + return +} + +// UpWired brings a wired network connection up +func (p Profile) UpWired() (err error) { for idx, addr := range []Address{ p.IPv4, p.IPv6, diff --git a/netctl/profile_test.go b/netctl/profile_test.go index d543b0d..0afca3f 100644 --- a/netctl/profile_test.go +++ b/netctl/profile_test.go @@ -151,6 +151,11 @@ func TestUp(t *testing.T) { Interface: "lo", } + wifi := Profile{ + Interface: "test0", + Wifi: true, + } + for _, test := range []struct { name string profile Profile @@ -163,6 +168,7 @@ func TestUp(t *testing.T) { {"with dhcp errors", ip4DHCPErr, testNetLinkHandle{}, true}, {"dhcp client errors", ip4DHCPClientErr, testNetLinkHandle{}, true}, {"loopback", loopback, testNetLinkHandle{}, false}, + {"wifi fails, not implemented", wifi, testNetLinkHandle{}, true}, } { t.Run(test.name, func(t *testing.T) { handle = test.handle From 549d82682a9272b2ae0664132b0a89c3da93f342 Mon Sep 17 00:00:00 2001 From: jspc Date: Fri, 12 Feb 2021 18:07:36 +0900 Subject: [PATCH 2/3] Provide simple access to wifi via wpa_supplicant --- Makefile | 3 +- bin/cmd/root.go | 2 + bin/cmd/wifi.go | 67 ++++++++++++ bin/cmd/wifiAdd.go | 84 +++++++++++++++ bin/cmd/wifiConnect.go | 60 +++++++++++ go.mod | 3 + go.sum | 4 + wifi/wifi.go | 227 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 bin/cmd/wifi.go create mode 100644 bin/cmd/wifiAdd.go create mode 100644 bin/cmd/wifiConnect.go create mode 100644 wifi/wifi.go diff --git a/Makefile b/Makefile index a25512d..8d8a476 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ CONFDIR ?= "$(PREFIX)/etc/vinyl/network.d" BINARIES := $(BINDIR)/linux-utils SCRIPTS := $(BINDIR)/useradd \ $(BINDIR)/groupadd \ - $(BINDIR)/netctl + $(BINDIR)/netctl \ + $(BINDIR)/wifi CONFIGS := $(CONFDIR)/eth0.toml.sample diff --git a/bin/cmd/root.go b/bin/cmd/root.go index f950a7e..ab41376 100644 --- a/bin/cmd/root.go +++ b/bin/cmd/root.go @@ -59,6 +59,7 @@ var ( groupName string netctlDir string verbose bool + autoConnect bool ) // rootCmd represents the base command when called without any subcommands @@ -102,4 +103,5 @@ func reset() { groupName = "" netctlDir = netctl.DefaultPath verbose = false + autoConnect = false } diff --git a/bin/cmd/wifi.go b/bin/cmd/wifi.go new file mode 100644 index 0000000..86c9cc2 --- /dev/null +++ b/bin/cmd/wifi.go @@ -0,0 +1,67 @@ +// +build linux + +/* +Copyright © 2021 James Condron +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/vinyl-linux/linux-utils/wifi" +) + +// wifiCmd represents the wifi command +var wifiCmd = &cobra.Command{ + Use: "wifi", + Short: "Manage wifi networks", + Long: "Manage wifi networks", + RunE: func(cmd *cobra.Command, args []string) (err error) { + // load profiles, looking for wifi interfaces + w, err := wifi.New() + if err != nil { + return + } + + nets, err := w.List() + if err != nil { + return + } + + fmt.Println(nets) + + return nil + }, +} + +func init() { + rootCmd.AddCommand(wifiCmd) +} diff --git a/bin/cmd/wifiAdd.go b/bin/cmd/wifiAdd.go new file mode 100644 index 0000000..754cca4 --- /dev/null +++ b/bin/cmd/wifiAdd.go @@ -0,0 +1,84 @@ +// +build linux + +/* +Copyright © 2021 James Condron +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +package cmd + +import ( + "fmt" + "strings" + "syscall" + + "github.com/spf13/cobra" + "github.com/vinyl-linux/linux-utils/wifi" + "golang.org/x/crypto/ssh/terminal" +) + +// wifiAddCmd represents the wifiAdd command +var wifiAddCmd = &cobra.Command{ + Use: "add [ssid]", + Short: "Add/ configure a wifi network", + Long: "Add/ configure a wifi network", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + ssid := args[0] + + w, err := wifi.New() + if err != nil { + return + } + + fmt.Printf("Network %q password > ", ssid) + password, err := terminal.ReadPassword(int(syscall.Stdin)) + fmt.Println() + + if err != nil { + return + } + + err = w.Create(ssid, strings.TrimSpace(string(password))) + if err != nil { + return + } + + if autoConnect { + err = w.Connect(ssid) + } + + return + }, +} + +func init() { + wifiCmd.AddCommand(wifiAddCmd) + + wifiAddCmd.Flags().BoolVarP(&autoConnect, "connect", "c", true, "connect to network after creating") +} diff --git a/bin/cmd/wifiConnect.go b/bin/cmd/wifiConnect.go new file mode 100644 index 0000000..47d9572 --- /dev/null +++ b/bin/cmd/wifiConnect.go @@ -0,0 +1,60 @@ +// +build linux + +/* +Copyright © 2021 James Condron +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/vinyl-linux/linux-utils/wifi" +) + +// wifiConnectCmd represents the wifiConnect command +var wifiConnectCmd = &cobra.Command{ + Use: "connect [ssid]", + Short: "Connect to the specified ssid", + Long: "Connect to the specified ssid", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + ssid := args[0] + + w, err := wifi.New() + if err != nil { + return + } + + return w.Connect(ssid) + }, +} + +func init() { + wifiCmd.AddCommand(wifiConnectCmd) +} diff --git a/go.mod b/go.mod index a63d30c..69d2a6e 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ require ( github.com/pelletier/go-toml v1.9.4 github.com/spf13/cobra v1.3.0 github.com/vishvananda/netlink v1.1.0 + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 + pifke.org/wpasupplicant v0.0.0-20200816231324-12bdf536389f ) require ( @@ -19,4 +21,5 @@ require ( github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect ) diff --git a/go.sum b/go.sum index dc860ab..035ea02 100644 --- a/go.sum +++ b/go.sum @@ -386,6 +386,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -571,6 +572,7 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -809,6 +811,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +pifke.org/wpasupplicant v0.0.0-20200816231324-12bdf536389f h1:hI2b46+nRc9gwX1zne3kDsCE669yLLBRzCQ1ORDjrX8= +pifke.org/wpasupplicant v0.0.0-20200816231324-12bdf536389f/go.mod h1:76nFyvc1UBvdBKAle/WKulDZK54rmyX59+mX4zqFQMs= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/wifi/wifi.go b/wifi/wifi.go new file mode 100644 index 0000000..85be42d --- /dev/null +++ b/wifi/wifi.go @@ -0,0 +1,227 @@ +// +build linux + +package wifi + +import ( + "fmt" + "strconv" + "strings" + + "github.com/vinyl-linux/linux-utils/netctl" + "pifke.org/wpasupplicant" +) + +var ( + networkPath = netctl.DefaultPath +) + +type Networks []Network + +func (ns Networks) String() string { + lines := make([]string, 0) + lines = append(lines, fmt.Sprintf("%s\t\t%s\t%s", "SSID", "Active", "Has Configuration")) + + for _, n := range ns { + lines = append(lines, n.String()) + } + + return strings.Join(lines, "\n") +} + +type Network struct { + SSID string + Type string + Active bool + Configured bool +} + +func (n Network) String() string { + var active, configured string + if n.Active { + active = "*" + } + + if n.Configured { + configured = "*" + } + + return fmt.Sprintf("%q\t\t%s\t\t%s", n.SSID, active, configured) +} + +type Wifi struct { + conn wpasupplicant.Conn +} + +func New() (w Wifi, err error) { + n, err := netctl.New(networkPath) + if err != nil { + return + } + + foundCount := 0 + iface := "" + for _, p := range n.Profiles { + if p.Wifi { + iface = p.Interface + foundCount++ + } + } + + switch foundCount { + case 0: + err = fmt.Errorf("no wireless interfaces configured") + + case 1: + // nop - expected case + + default: + err = fmt.Errorf("%d wireless interfaces configured, 1 expected", foundCount) + } + + if err != nil { + return + } + + w.conn, err = wpasupplicant.Unixgram(iface) + + return +} + +// List returns a list of the networks visible to the nic +func (w Wifi) List() (nets Networks, err error) { + err = w.conn.Scan() + if err != nil { + return + } + + conns, err := w.conn.ListNetworks() + if err != nil { + return + } + + status, err := w.conn.Status() + if err != nil { + return + } + + curr := status.SSID() + + scanResults, errs := w.conn.ScanResults() + if len(errs) != 0 { + err = flattenErrs(errs) + + return + } + + nets = make(Networks, len(scanResults)) + + for i, n := range scanResults { + ssid := n.SSID() + + nets[i] = Network{ + SSID: ssid, + Active: ssid == curr, + Configured: contains(ssid, conns), + } + } + + return +} + +// Create takes an SSID, pre-shared key, and creates a connection +func (w Wifi) Create(ssid, psk string) (err error) { + id, err := w.conn.AddNetwork() + if err != nil { + return + } + + err = w.conn.SetNetwork(id, "ssid", ssid) + if err != nil { + return + } + + return w.conn.SetNetwork(id, "psk", psk) +} + +// Connect will connect to an SSID +func (w Wifi) Connect(ssid string) (err error) { + nets, err := w.conn.ListNetworks() + if err != nil { + return + } + + var id int + for _, n := range nets { + if ssid == n.SSID() { + id, err = strconv.Atoi(n.NetworkID()) + if err != nil { + return + } + + return w.conn.SelectNetwork(id) + } + } + + return fmt.Errorf("ssid %s not found", ssid) +} + +// Disconnect will disconnect from all wifi networks +func (w Wifi) Disconnect() (err error) { + status, err := w.conn.Status() + if err != nil { + return + } + + if status.WPAState() != "COMPLETED" { + return + } + + nets, err := w.conn.ListNetworks() + if err != nil { + return + } + + var id int + for _, n := range nets { + if n.SSID() != status.SSID() { + continue + } + + id, err = strconv.Atoi(n.NetworkID()) + if err != nil { + return + } + + return w.conn.DisableNetwork(id) + } + + return fmt.Errorf("not connected to anything, nothing to disconnect") +} + +// Save will save wifi config. +// +// It exists outside of, say, the create function because in some contexts +// we may not want to persist config. For instance: we may store keys and +// details externally (say with vault, or on an external disk). +func (w Wifi) Save() error { + return w.conn.SaveConfig() +} + +func contains(ssid string, cn []wpasupplicant.ConfiguredNetwork) bool { + for _, n := range cn { + if ssid == n.SSID() { + return true + } + } + + return false +} + +func flattenErrs(errs []error) (err error) { + err = fmt.Errorf("error(s): ") + for _, e := range errs { + err = fmt.Errorf("%w %w", err, e) + } + + return +} From 78fe1043907c0bb9522b6b5374a93ca8038dde5b Mon Sep 17 00:00:00 2001 From: jspc Date: Wed, 23 Feb 2022 22:49:34 +0000 Subject: [PATCH 3/3] Wrap errors correctly --- wifi/wifi.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wifi/wifi.go b/wifi/wifi.go index 85be42d..a78f864 100644 --- a/wifi/wifi.go +++ b/wifi/wifi.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package wifi @@ -219,8 +220,9 @@ func contains(ssid string, cn []wpasupplicant.ConfiguredNetwork) bool { func flattenErrs(errs []error) (err error) { err = fmt.Errorf("error(s): ") + for _, e := range errs { - err = fmt.Errorf("%w %w", err, e) + err = fmt.Errorf("%w %s", err, e.Error()) } return