From 3a9f9f3b61fcb3cb505d1ee2a624b432da1f5075 Mon Sep 17 00:00:00 2001 From: leberKleber Date: Thu, 20 Oct 2022 23:27:01 +0200 Subject: [PATCH] feat: handle device wifi list --- README.md | 37 +++++++++----- device/manager.go | 15 ++++++ device/wifi.go | 106 +++++++++++++++++++++++++++++++++++++++++ device/wifi_test.go | 50 +++++++++++++++++++ general/permissions.go | 2 +- utils/output.go | 2 +- 6 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 device/manager.go create mode 100644 device/wifi.go create mode 100644 device/wifi_test.go diff --git a/README.md b/README.md index 0d6e173..2917194 100644 --- a/README.md +++ b/README.md @@ -7,36 +7,51 @@ go wrapper for command line tool `nmcli`. The library can be used in a `nmcli` like scheme. ### General -| original command | library path | implemented | -|---------------------------------------------------|-------------------------------------|-------------------------------| -| `nmcli general status` | `NMCli.General.Status(...)` | :negative_squared_cross_mark: | -| `nmcli general hostname` | `NMCli.General.Hostname(...)` | :heavy_check_mark: | -| `nmcli general hostname ` | `NMCli.General.ChangeHostname(...)` | :heavy_check_mark: | -| `nmcli general permissions` | `NMCli.General.Permissions(...)` | :heavy_check_mark: | -| `nmcli general logging` | `NMCli.General.Logging(...)` | :negative_squared_cross_mark: | -| `nmcli general logging ` | `NMCli.General.ChangeLogging(...)` | :negative_squared_cross_mark: | + +| original command | library path | implemented | +|-----------------------------|----------------------------------|-------------------------------| +| `nmcli general status` | `NMCli.General.Status(...)` | :negative_squared_cross_mark: | +| `nmcli general hostname` | `NMCli.General.Hostname(...)` | :heavy_check_mark: | +| `nmcli general permissions` | `NMCli.General.Permissions(...)` | :heavy_check_mark: | +| `nmcli general logging` | `NMCli.General.Logging(...)` | :negative_squared_cross_mark: | ### Networking + | original command | library path | implemented | |-------------------------------------|----------------------------------|-------------------------------| | `nmcli networking ...` | not implemented yet | :negative_squared_cross_mark: | ### Radio + | original command | library path | implemented | |-------------------------------------|----------------------------------|-------------------------------| | `nmcli radio ...` | not implemented yet | :negative_squared_cross_mark: | ### Device -| original command | library path | implemented | -|-------------------------------------|----------------------------------|-------------------------------| -| `nmcli device ...` | not implemented yet | :negative_squared_cross_mark: | + +| original command | library path | implemented | +|-----------------------------------|------------------------------|-------------------------------| +| `nmcli device status` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device show` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device set` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device reapply` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device modify` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device disconnect` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device wifi list` | `NMCli.Device.WiFiList(...)` | :heavy_check_mark: | +| `nmcli device wifi connect` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device wifi hotspot` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device wifi rescan` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device wifi show-password` | not implemented yet | :negative_squared_cross_mark: | +| `nmcli device wifi lldp` | not implemented yet | :negative_squared_cross_mark: | ### Agent + | original command | library path | implemented | |-------------------------------------|----------------------------------|-------------------------------| | `nmcli agent ...` | not implemented yet | :negative_squared_cross_mark: | ### Monitor + | original command | library path | implemented | |-------------------------------------|----------------------------------|-------------------------------| | `nmcli monitor ...` | not implemented yet | :negative_squared_cross_mark: | diff --git a/device/manager.go b/device/manager.go new file mode 100644 index 0000000..3a21c0e --- /dev/null +++ b/device/manager.go @@ -0,0 +1,15 @@ +//go:generate go run github.com/golang/mock/mockgen -destination=utils_cmd_mock_test.go -package=device_test github.com/leberKleber/go-nmcli/utils Cmd + +package device + +import ( + "context" + + "github.com/leberKleber/go-nmcli/utils" +) + +const nmcliCmd = "nmcli" + +type Manager struct { + CommandContext func(ctx context.Context, name string, args ...string) utils.Cmd +} diff --git a/device/wifi.go b/device/wifi.go new file mode 100644 index 0000000..e2b8d87 --- /dev/null +++ b/device/wifi.go @@ -0,0 +1,106 @@ +package device + +import ( + "context" + "fmt" + "github.com/leberKleber/go-nmcli/utils" + "strings" +) + +type WiFiListOptions struct { + IfName string + BSSID string + Rescan WiFiListOptionsRescan +} + +type WiFiListOptionsRescan string + +var ( + WiFiListOptionsRescanAuto WiFiListOptionsRescan = "auto" + WiFiListOptionsRescanYes WiFiListOptionsRescan = "yes" + WiFiListOptionsRescanNo WiFiListOptionsRescan = "no" +) + +func (a WiFiListOptions) rawArgs() []string { + var args []string + + args = appendWhenNotEmpty(args, a.IfName, "ifname") + args = appendWhenNotEmpty(args, a.BSSID, "bssid") + args = appendWhenNotEmpty(args, string(a.Rescan), "--rescan") + + return args +} + +type WiFi struct { + Name string + SSID string + SSIDHEX string + BSSID string + Mode string + Chan string + Frequency string + Rate string + Signal string + Bars string + Security string + WPAFlags string + RSNFlags string + Device string + Active string + InUse string + DBusPath string +} + +// WiFiList List available Wi-Fi access points. +// The IfName and BSSID options can be used to list APs for a particular interface, or with a specific BSSID. +// The Rescan flag tells whether a new Wi-Fi scan should be triggered. +func (m Manager) WiFiList(ctx context.Context, args WiFiListOptions) ([]WiFi, error) { + fields := []string{"NAME", "SSID", "SSID-HEX", "BSSID", "MODE", "CHAN", "FREQ", "RATE", "SIGNAL", "BARS", "SECURITY", "WPA-FLAGS", "RSN-FLAGS", "DEVICE", "ACTIVE", "IN-USE", "DBUS-PATH"} + + cmdArgs := []string{"-g", strings.Join(fields, ",")} + cmdArgs = append(cmdArgs, "device", "wifi", "list") + cmdArgs = append(cmdArgs, args.rawArgs()...) + + output, err := m.CommandContext(ctx, nmcliCmd, cmdArgs...).Output() + if err != nil { + return nil, fmt.Errorf("failed to execute nmcli with args %+q: %w", cmdArgs, err) + } + + parsedOutput, err := utils.ParseCmdOutput(output, len(fields)) + if err != nil { + return nil, fmt.Errorf("failed to parse nmcli output: %w", err) + } + + var wifis []WiFi + for _, fields := range parsedOutput { + wifis = append(wifis, WiFi{ + Name: fields[0], + SSID: fields[1], + SSIDHEX: fields[2], + BSSID: fields[3], + Mode: fields[4], + Chan: fields[5], + Frequency: fields[6], + Rate: fields[7], + Signal: fields[8], + Bars: fields[9], + Security: fields[10], + WPAFlags: fields[11], + RSNFlags: fields[12], + Device: fields[13], + Active: fields[14], + InUse: fields[15], + DBusPath: fields[16], + }) + } + + return wifis, nil +} + +func appendWhenNotEmpty(slice []string, toCheck string, preAppend string) []string { + if toCheck != "" { + slice = append(slice, preAppend, toCheck) + } + + return slice +} diff --git a/device/wifi_test.go b/device/wifi_test.go new file mode 100644 index 0000000..2369116 --- /dev/null +++ b/device/wifi_test.go @@ -0,0 +1,50 @@ +package device_test + +import ( + "context" + "github.com/golang/mock/gomock" + "github.com/leberKleber/go-nmcli/device" + "github.com/leberKleber/go-nmcli/utils" + "github.com/stretchr/testify/require" + "testing" +) + +func TestManager_WiFiList(t *testing.T) { + ctrl := gomock.NewController(t) + + mockedCmd := NewMockCmd(ctrl) + mockedCmd.EXPECT().Output().Return([]byte(`AP[1]:FRITZ!Box 7530 NT:465249545A21426F782037353330204E54:3C\:37\:12\:79\:85\:1D:Infra:6:2437 MHz:130 Mbit/s:100:▂▄▆█:WPA2:(none):pair_ccmp group_ccmp psk:wlp58s0:no: :/org/freedesktop/NetworkManager/AccessPoint/68 +AP[2]:FRITZ!Box 7530 NT:465249545A21426F782037353330204E54:3C\:37\:12\:79\:85\:1E:Infra:52:5260 MHz:270 Mbit/s:87:▂▄▆█:WPA2:(none):pair_ccmp group_ccmp psk:wlp58s0:yes:*:/org/freedesktop/NetworkManager/AccessPoint/1`), nil).Times(1) + + m := device.Manager{ + CommandContext: func(ctx context.Context, name string, args ...string) utils.Cmd { + require.Equal(t, "nmcli", name) + require.EqualValues(t, + []string{"-g", "NAME,SSID,SSID-HEX,BSSID,MODE,CHAN,FREQ,RATE,SIGNAL,BARS,SECURITY,WPA-FLAGS,RSN-FLAGS,DEVICE,ACTIVE,IN-USE,DBUS-PATH", "device", "wifi", "list", "ifname", "wlp58s0", "bssid", "3C:37:12:79:85:1E", "--rescan", "auto"}, + args, + ) + + return mockedCmd + }, + } + + wifis, err := m.WiFiList(context.Background(), device.WiFiListOptions{ + Rescan: device.WiFiListOptionsRescanAuto, + IfName: "wlp58s0", + BSSID: "3C:37:12:79:85:1E", + }) + + require.NoError(t, err) + require.EqualValues(t, []device.WiFi{ + {"AP[1]", "FRITZ!Box 7530 NT", "465249545A21426F782037353330204E54", "3C:37:12:79:85:1D", + "Infra", "6", "2437 MHz", "130 Mbit/s", "100", "▂▄▆█", "WPA2", + "(none)", "pair_ccmp group_ccmp psk", "wlp58s0", "no", " ", + "/org/freedesktop/NetworkManager/AccessPoint/68", + }, + {"AP[2]", "FRITZ!Box 7530 NT", "465249545A21426F782037353330204E54", "3C:37:12:79:85:1E", + "Infra", "52", "5260 MHz", "270 Mbit/s", "87", "▂▄▆█", "WPA2", + "(none)", "pair_ccmp group_ccmp psk", "wlp58s0", "yes", "*", + "/org/freedesktop/NetworkManager/AccessPoint/1", + }, + }, wifis) +} diff --git a/general/permissions.go b/general/permissions.go index 759eb72..018504a 100644 --- a/general/permissions.go +++ b/general/permissions.go @@ -21,7 +21,7 @@ const ( PermissionValueNo PermissionValue = "no" ) -// Permissions shows caller permissions for authenticated operations. +// Permissions Show caller permissions for authenticated operations. func (m Manager) Permissions(ctx context.Context) ([]Permission, error) { fields := []string{"PERMISSION", "VALUE"} args := []string{"-g", strings.Join(fields, ","), "general", "permissions"} diff --git a/utils/output.go b/utils/output.go index 7608c65..a825a69 100644 --- a/utils/output.go +++ b/utils/output.go @@ -26,7 +26,7 @@ func ParseCmdOutput(output []byte, expectedCountOfFields int) ([][]string, error } func splitBySeparator(separator, line string) []string { - escape := `/` + escape := `\` tempEscapedSeparator := "\x00" replacedEscape := strings.ReplaceAll(line, escape+separator, tempEscapedSeparator)