diff --git a/openwrt/dhcp_client.go b/openwrt/dhcp_client.go new file mode 100644 index 0000000..40c81f3 --- /dev/null +++ b/openwrt/dhcp_client.go @@ -0,0 +1,38 @@ +package openwrt + +// DhcpClient represents a dhcp client ... :) +type DhcpClient struct { + exec Executor + iface string +} + +// NewDhcpClient return an UCI instance to interact with UCI +func NewDhcpClient(netIface string) *DhcpClient { + exec := &localExecutor{} + iface := netIface + return &DhcpClient{exec, iface} +} + +// NewDhcpClientWithExecutor return an UCI instance to interact with UCI +func NewDhcpClientWithExecutor(netIface string, exe Executor) *DhcpClient { + exec := exe + iface := netIface + return &DhcpClient{exec, iface} +} + +// NewDhcpClient return an UCI instance to interact with UCI +//func NewDhcpClient(netIface string, exe Executor) *DhcpClient { +// var exec Executor +// if exe == nil { +// exec = &localExecutor{} +// } else { +// exec = exe +// } +// iface := netIface +// return &DhcpClient{exec, iface} +//} + +// AskForIP runs a dhclient ip request with udhcpc +func (dc *DhcpClient) AskForIP() *CommandResult { + return dc.exec.Run("udhcpc", "-i", dc.iface) +} diff --git a/openwrt/dhcp_client_test.go b/openwrt/dhcp_client_test.go new file mode 100644 index 0000000..b21ca7e --- /dev/null +++ b/openwrt/dhcp_client_test.go @@ -0,0 +1,12 @@ +package openwrt + +import "testing" + +func TestDhcpClientAskForIP(t *testing.T) { + uexec := createMockExecutor("", "", 0) + dhc := NewDhcpClientWithExecutor("wlan1", uexec) + res := dhc.AskForIP() + if res.ReturnCode != 0 { + t.Error("Error in DHCP Client !!") + } +} diff --git a/openwrt/executor.go b/openwrt/executor.go new file mode 100644 index 0000000..bdd4275 --- /dev/null +++ b/openwrt/executor.go @@ -0,0 +1,67 @@ +package openwrt + +import ( + "bytes" + "fmt" + "log" + "os/exec" + "syscall" +) + +// Executor interface to describe command runners signature +type Executor interface { + Run(command string, params ...string) *CommandResult +} + +// CommandResult contain all information about a command execution, stdout, stderr +type CommandResult struct { + Stdout string + Stderr string + ReturnCode int +} + +type localExecutor struct{} + +func (e *localExecutor) Run(command string, params ...string) *CommandResult { + + var out bytes.Buffer + var stderr bytes.Buffer + var exitCode int + + defaultFailedCode := 255 + + exe := exec.Command(command, params...) + exe.Stdout = &out + exe.Stderr = &stderr + + err := exe.Run() + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + ws := exitError.Sys().(syscall.WaitStatus) + exitCode = ws.ExitStatus() + } else { + // This will happen (in OSX) if `name` is not available in $PATH, + // in this situation, exit code could not be get, and stderr will be + // empty string very likely, so we use the default fail code, and format err + // to string and set to stderr + log.Printf("Could not get exit code for failed program: %v, %v", command, params) + exitCode = defaultFailedCode + } + fmt.Println(fmt.Sprint(err) + ": " + stderr.String()) + log.Fatal(err) + } + + if err != nil { + // try to get the exit code + } else { + // success, exitCode should be 0 if go is ok + ws := exe.ProcessState.Sys().(syscall.WaitStatus) + exitCode = ws.ExitStatus() + } + + return &CommandResult{ + Stdout: out.String(), + Stderr: stderr.String(), + ReturnCode: exitCode, + } +} diff --git a/openwrt/executor_test.go b/openwrt/executor_test.go new file mode 100644 index 0000000..3909188 --- /dev/null +++ b/openwrt/executor_test.go @@ -0,0 +1,18 @@ +package openwrt + +import ( + "testing" +) + +func TestRun(t *testing.T) { + exec := &localExecutor{} + res := exec.Run("uname", "-a") + if g, e := res.ReturnCode, 0; g != e { + t.Errorf("Run command failed ! Got bad return code [%d], [%d} is expected\n", g, e) + } + + // res = exec.Run("noCommandWithThisNameExists", "-a") + // if g, e := res.ReturnCode, 127; g != e { + // t.Errorf("Run command failed ! Got bad return code [%d], [%d} is expected\n", g, e) + // } +} diff --git a/openwrt/network.go b/openwrt/network.go new file mode 100644 index 0000000..6ba5d47 --- /dev/null +++ b/openwrt/network.go @@ -0,0 +1,77 @@ +package openwrt + +import ( + "fmt" + "io/ioutil" + "net" + "strings" +) + +// Network provides a representation of network +type Network struct { + exec Executor +} + +// NewNetwork return an UCI instance to interact with UCI +func NewNetwork() *Network { + exec := &localExecutor{} + return &Network{exec} +} + +// NewNetworkWithExecutor return an UCI instance to interact with UCI +func NewNetworkWithExecutor(exe Executor) *Network { + exec := exe + return &Network{exec} +} + +// ListInterfaces list all available interfaces on a system using "ip" commmand +func (n *Network) ListInterfaces() []net.Interface { + var result []net.Interface + ifaces, err := net.Interfaces() + if err != nil { + fmt.Print(fmt.Errorf("error listing network interfacess: %+v", err.Error())) + return nil + } + result = append(result, ifaces...) + return result +} + +// ListWirelessInterfaces list all wifi cards +// you need to provide the wireless file or "" to use +// Linux default one "/proc/net/wireless" +func (n *Network) ListWirelessInterfaces(wifiFile string) []net.Interface { + var result []net.Interface + var ifaceNames []string + + if wifiFile == "" { + wifiFile = "/proc/net/wireless" + } + + wifiFileContent, err := ioutil.ReadFile(wifiFile) + check(err) + + index := 0 + for _, line := range strings.Split(string(wifiFileContent), "\n") { + if index < 2 { + index++ + continue + } else { + name := strings.Split(line, ":")[0] + ifaceNames = append(ifaceNames, name) + } + } + + ifaces, err := net.Interfaces() + if err != nil { + fmt.Print(fmt.Errorf("error listing network interfaces : %+v", err.Error())) + return nil + } + for _, i := range ifaces { + for _, name := range ifaceNames { + if name == i.Name { + result = append(result, i) + } + } + } + return result +} diff --git a/openwrt/network_test.go b/openwrt/network_test.go new file mode 100644 index 0000000..bac8574 --- /dev/null +++ b/openwrt/network_test.go @@ -0,0 +1,22 @@ +package openwrt + +import ( + "fmt" + "testing" +) + +func TestNetworkListInterfaces(t *testing.T) { + net := NewNetwork() + iface := net.ListInterfaces() + for _, ife := range iface { + fmt.Printf("%s\n", ife.Name) + } +} + +func TestListWirelessInterfaces(t *testing.T) { + net := NewNetwork() + res := net.ListWirelessInterfaces("./testdata/proc_net_wireless.txt") + for _, el := range res { + fmt.Printf("%s\n", el.Name) + } +} diff --git a/openwrt/test.go b/openwrt/test.go new file mode 100644 index 0000000..5f7ff9a --- /dev/null +++ b/openwrt/test.go @@ -0,0 +1,35 @@ +package openwrt + +import ( + "log" + "strings" +) + +func check(e error) { + if e != nil { + panic(e) + } +} + +func createMockExecutor(stdout string, stderr string, returnCode int) Executor { + return &mockExecutor{ + stdout: stdout, + stderr: stderr, + returnCode: returnCode, + } +} + +type mockExecutor struct { + stdout string + stderr string + returnCode int +} + +func (e *mockExecutor) Run(command string, params ...string) *CommandResult { + log.Printf("executing '%s %s'", command, strings.Join(params, " ")) + return &CommandResult{ + Stderr: e.stderr, + Stdout: e.stdout, + ReturnCode: e.returnCode, + } +} diff --git a/openwrt/testdata/proc_net_wireless.txt b/openwrt/testdata/proc_net_wireless.txt new file mode 100644 index 0000000..5fc603c --- /dev/null +++ b/openwrt/testdata/proc_net_wireless.txt @@ -0,0 +1,4 @@ +Inter-| sta-| Quality | Discarded packets | Missed | WE + face | tus | link level noise | nwid crypt frag retry misc | beacon | 22 + wlan1: 0000 0 0 0 0 0 0 0 0 0 + wlan0: 0000 0 0 0 0 0 0 0 0 0 diff --git a/openwrt/testdata/wifi_cells_output_3.txt b/openwrt/testdata/wifi_cells_output_3.txt new file mode 100644 index 0000000..cb9b12e --- /dev/null +++ b/openwrt/testdata/wifi_cells_output_3.txt @@ -0,0 +1,17 @@ +Cell 40 - Address: 68:A3:78:6E:D9:24 + ESSID: "PyxisWifi" + Mode: Master Channel: 3 + Signal: -90 dBm Quality: 20/70 + Encryption: none + +Cell 41 - Address: B0:39:56:92:59:E2 + ESSID: "NET17" + Mode: Master Channel: 4 + Signal: -88 dBm Quality: 22/70 + Encryption: WPA2 PSK (CCMP) + +Cell 42 - Address: 0C:F4:D5:16:AA:18 + ESSID: "DIJON-METROPOLE-WIFI" + Mode: Master Channel: 13 + Signal: -90 dBm Quality: 20/70 + Encryption: none \ No newline at end of file diff --git a/openwrt/testdata/wifi_cells_output_large.txt b/openwrt/testdata/wifi_cells_output_large.txt new file mode 100644 index 0000000..e4ee5ed --- /dev/null +++ b/openwrt/testdata/wifi_cells_output_large.txt @@ -0,0 +1,293 @@ +Cell 01 - Address: 0C:8D:DB:C4:A0:34 + ESSID: "pfPauvres" + Mode: Master Channel: 11 + Signal: -50 dBm Quality: 60/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 02 - Address: 40:5A:9B:ED:BA:F0 + ESSID: "Cadoles" + Mode: Master Channel: 6 + Signal: -36 dBm Quality: 70/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 03 - Address: A0:04:60:B2:8A:C8 + ESSID: "Cadoles Formations (N)" + Mode: Master Channel: 13 + Signal: -32 dBm Quality: 70/70 + Encryption: WPA2 PSK (CCMP) + +Cell 04 - Address: B0:39:56:D8:38:ED + ESSID: "Frate Dijon EXT" + Mode: Master Channel: 11 + Signal: -57 dBm Quality: 53/70 + Encryption: WPA2 PSK (CCMP) + +Cell 05 - Address: 00:A6:CA:10:DF:00 + ESSID: unknown + Mode: Master Channel: 6 + Signal: -80 dBm Quality: 30/70 + Encryption: WPA2 802.1X (CCMP) + +Cell 06 - Address: AC:84:C9:2F:59:6E + ESSID: "Livebox-596a" + Mode: Master Channel: 1 + Signal: -62 dBm Quality: 48/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 07 - Address: 00:A6:CA:10:DF:01 + ESSID: "EFF-Mobility" + Mode: Master Channel: 6 + Signal: -74 dBm Quality: 36/70 + Encryption: WPA2 PSK (CCMP) + +Cell 08 - Address: A0:1B:29:BE:98:26 + ESSID: "Livebox-9822" + Mode: Master Channel: 6 + Signal: -81 dBm Quality: 29/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 09 - Address: 7C:26:64:66:CC:44 + ESSID: "Livebox-32c8" + Mode: Master Channel: 1 + Signal: -74 dBm Quality: 36/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 10 - Address: 00:A6:CA:10:DF:02 + ESSID: "Keo-HotSpot" + Mode: Master Channel: 6 + Signal: -79 dBm Quality: 31/70 + Encryption: none + +Cell 11 - Address: 7E:26:64:66:CC:44 + ESSID: "orange" + Mode: Master Channel: 1 + Signal: -73 dBm Quality: 37/70 + Encryption: none + +Cell 12 - Address: 3C:52:82:FC:5E:21 + ESSID: "DIRECT-20-HP DeskJet 3630 series" + Mode: Master Channel: 6 + Signal: -78 dBm Quality: 32/70 + Encryption: WPA2 PSK (CCMP) + +Cell 13 - Address: E4:9E:12:8B:EF:73 + ESSID: "Freebox-8BEF72" + Mode: Master Channel: 9 + Signal: -79 dBm Quality: 31/70 + Encryption: WPA2 PSK (CCMP) + +Cell 14 - Address: 40:4A:03:05:D2:68 + ESSID: "ZyXEL" + Mode: Master Channel: 11 + Signal: -71 dBm Quality: 39/70 + Encryption: WPA2 PSK (CCMP) + +Cell 15 - Address: 5C:C3:07:7E:39:D4 + ESSID: "pfP Xa" + Mode: Master Channel: 1 + Signal: -65 dBm Quality: 45/70 + Encryption: WPA2 PSK (CCMP) + +Cell 16 - Address: AC:84:C9:1D:C6:7C + ESSID: "Frate Djon" + Mode: Master Channel: 1 + Signal: -79 dBm Quality: 31/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 17 - Address: 00:17:33:9F:4D:80 + ESSID: "NEUF_4D7C" + Mode: Master Channel: 11 + Signal: -83 dBm Quality: 27/70 + Encryption: WPA PSK (TKIP, CCMP) + +Cell 18 - Address: A2:17:33:9F:4D:81 + ESSID: "SFR WiFi FON" + Mode: Master Channel: 11 + Signal: -85 dBm Quality: 25/70 + Encryption: none + +Cell 19 - Address: BC:F6:85:FE:6D:46 + ESSID: "Dlink" + Mode: Master Channel: 12 + Signal: -70 dBm Quality: 40/70 + Encryption: WPA2 PSK (CCMP) + +Cell 20 - Address: 30:7C:B2:D1:0B:0D + ESSID: "Livebox-0b09" + Mode: Master Channel: 11 + Signal: -81 dBm Quality: 29/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 21 - Address: A2:17:33:9F:4D:83 + ESSID: "SFR WiFi Mobile" + Mode: Master Channel: 11 + Signal: -85 dBm Quality: 25/70 + Encryption: WPA2 802.1X (CCMP) + +Cell 22 - Address: 90:4D:4A:F7:B9:70 + ESSID: "Livebox-B970" + Mode: Master Channel: 11 + Signal: -84 dBm Quality: 26/70 + Encryption: WPA2 PSK (CCMP) + +Cell 23 - Address: 90:4D:4A:F7:B9:71 + ESSID: "orange" + Mode: Master Channel: 11 + Signal: -89 dBm Quality: 21/70 + Encryption: none + +Cell 24 - Address: 00:22:6B:86:5B:71 + ESSID: "linksys" + Mode: Master Channel: 11 + Signal: -86 dBm Quality: 24/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 25 - Address: 68:A3:78:6E:D9:25 + ESSID: "FreeWifi_secure" + Mode: Master Channel: 3 + Signal: -86 dBm Quality: 24/70 + Encryption: WPA2 802.1X (TKIP, CCMP) + +Cell 26 - Address: 6C:38:A1:62:1B:28 + ESSID: "Bbox-1B7889A9" + Mode: Master Channel: 1 + Signal: -90 dBm Quality: 20/70 + Encryption: mixed WPA/WPA2 PSK (CCMP) + +Cell 27 - Address: 78:81:02:5E:B7:14 + ESSID: "Livebox-B714" + Mode: Master Channel: 6 + Signal: -86 dBm Quality: 24/70 + Encryption: WPA2 PSK (CCMP) + +Cell 28 - Address: F4:CA:E5:98:3B:DC + ESSID: "Freebox-5D2400" + Mode: Master Channel: 11 + Signal: -84 dBm Quality: 26/70 + Encryption: WPA PSK (CCMP) + +Cell 29 - Address: 8C:DC:D4:93:69:17 + ESSID: "HP-Print-17-Photosmart 5520" + Mode: Master Channel: 11 + Signal: -87 dBm Quality: 23/70 + Encryption: none + +Cell 30 - Address: 44:CE:7D:20:5C:A4 + ESSID: "SFR_5CA0" + Mode: Master Channel: 6 + Signal: -86 dBm Quality: 24/70 + Encryption: WPA PSK (TKIP, CCMP) + +Cell 31 - Address: F4:CA:E5:98:3B:DE + ESSID: "FreeWifi_secure" + Mode: Master Channel: 11 + Signal: -72 dBm Quality: 38/70 + Encryption: WPA2 802.1X (TKIP, CCMP) + +Cell 32 - Address: 70:0B:01:C0:B3:E0 + ESSID: "Livebox-B3E0" + Mode: Master Channel: 11 + Signal: -80 dBm Quality: 30/70 + Encryption: WPA2 PSK (CCMP) + +Cell 33 - Address: D2:CE:7D:20:5C:A7 + ESSID: "SFR WiFi Mobile" + Mode: Master Channel: 6 + Signal: -85 dBm Quality: 25/70 + Encryption: WPA2 802.1X (CCMP) + +Cell 34 - Address: 68:A3:78:0D:B6:51 + ESSID: "Freebox-0DB650" + Mode: Master Channel: 1 + Signal: -92 dBm Quality: 18/70 + Encryption: WPA2 PSK (CCMP) + +Cell 35 - Address: F8:AB:05:1D:6A:E0 + ESSID: "Bbox-8CE43C68" + Mode: Master Channel: 6 + Signal: -88 dBm Quality: 22/70 + Encryption: mixed WPA/WPA2 PSK (CCMP) + +Cell 36 - Address: F4:CA:E5:98:3B:DD + ESSID: "FreeWifi" + Mode: Master Channel: 11 + Signal: -87 dBm Quality: 23/70 + Encryption: none + +Cell 37 - Address: 14:0C:76:79:C0:D9 + ESSID: "freebox_ZFSFUA" + Mode: Master Channel: 4 + Signal: -88 dBm Quality: 22/70 + Encryption: WPA PSK (TKIP, CCMP) + +Cell 38 - Address: 68:15:90:36:63:60 + ESSID: "Livebox-6360" + Mode: Master Channel: 1 + Signal: -81 dBm Quality: 29/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 39 - Address: 64:7C:34:29:2B:7C + ESSID: "Bbox-D646CB51" + Mode: Master Channel: 1 + Signal: -90 dBm Quality: 20/70 + Encryption: mixed WPA/WPA2 PSK (CCMP) + +Cell 40 - Address: 68:A3:78:6E:D9:24 + ESSID: "FreeWifi" + Mode: Master Channel: 3 + Signal: -90 dBm Quality: 20/70 + Encryption: none + +Cell 41 - Address: B0:39:56:92:59:E2 + ESSID: "NETGEAR17" + Mode: Master Channel: 4 + Signal: -88 dBm Quality: 22/70 + Encryption: WPA2 PSK (CCMP) + +Cell 42 - Address: 0C:F4:D5:16:AA:18 + ESSID: "DIJON-METROPOLE-WIFI" + Mode: Master Channel: 13 + Signal: -90 dBm Quality: 20/70 + Encryption: none + +Cell 43 - Address: D2:CE:7D:20:5C:A5 + ESSID: "SFR WiFi FON" + Mode: Master Channel: 6 + Signal: -81 dBm Quality: 29/70 + Encryption: none + +Cell 44 - Address: 34:27:92:42:CD:72 + ESSID: "Freebox-42CD71" + Mode: Master Channel: 8 + Signal: -88 dBm Quality: 22/70 + Encryption: WPA2 PSK (CCMP) + +Cell 45 - Address: 72:5D:51:78:4C:87 + ESSID: "SFR WiFi FON" + Mode: Master Channel: 11 + Signal: -87 dBm Quality: 23/70 + Encryption: none + +Cell 46 - Address: 68:A3:78:6E:D9:23 + ESSID: "Freebox-6ED922" + Mode: Master Channel: 3 + Signal: -76 dBm Quality: 34/70 + Encryption: WPA2 PSK (CCMP) + +Cell 47 - Address: 00:19:70:4F:DE:F2 + ESSID: "Livebox-45cc" + Mode: Master Channel: 6 + Signal: -78 dBm Quality: 32/70 + Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + +Cell 48 - Address: AC:84:C9:CC:AE:90 + ESSID: "Livebox-AE90" + Mode: Master Channel: 11 + Signal: -81 dBm Quality: 29/70 + Encryption: WPA2 PSK (CCMP) + +Cell 49 - Address: 00:07:7D:89:81:B0 + ESSID: "orange" + Mode: Master Channel: 6 + Signal: -85 dBm Quality: 25/70 + Encryption: none \ No newline at end of file diff --git a/openwrt/uci.go b/openwrt/uci.go new file mode 100644 index 0000000..0613643 --- /dev/null +++ b/openwrt/uci.go @@ -0,0 +1,72 @@ +package openwrt + +import ( + "fmt" + "time" +) + +// Action is the result of an UCI action output and return code +type Action struct { + *CommandResult +} + +// UCI "Object" +type UCI struct { + exec Executor +} + +// NewUCI return an UCI instance to interact with UCI +func NewUCI() *UCI { + exec := &localExecutor{} + return &UCI{exec} +} + +// NewUCIWithExecutor returns a UCI Instance an gives you the ability to provide +// a different command executor than the default one. +func NewUCIWithExecutor(exec Executor) *UCI { + return &UCI{exec} +} + +// uciRun, private method to run the UCI command +func (u *UCI) uciRun(uciAction string, param string) *Action { + cmd := "uci" + + res := u.exec.Run(cmd, uciAction, param) + return &Action{res} +} + +// Add add an entry to UCI configuration, specify the Module and the value +func (u *UCI) Add(module string, name string) *Action { + commandRes := u.exec.Run("uci add", module, name) + return &Action{commandRes} +} + +// Delete delete an entry from UCI configuration specify the entry name +func (u *UCI) Delete(entry string) *Action { + return u.uciRun("delete", entry) +} + +// Set set a value ton an UCI configuration entry +func (u *UCI) Set(entry string, value string) *Action { + return u.uciRun("set", fmt.Sprintf("%s=%s", entry, value)) +} + +// Commit the recent actions to UCI +func (u *UCI) Commit() *Action { + return u.uciRun("commit", "") +} + +// Reload reload uci configuration +func (u *UCI) Reload() *Action { + cmdResult := u.exec.Run("reload_config") + + time.Sleep(5 * time.Second) + + return &Action{cmdResult} +} + +// AddWireless Create a new Wireless entry in UCI configuration +func (u *UCI) AddWireless(name string) *Action { + res := u.Add("wireless", name) + return res +} diff --git a/openwrt/uci_test.go b/openwrt/uci_test.go new file mode 100644 index 0000000..36418af --- /dev/null +++ b/openwrt/uci_test.go @@ -0,0 +1,91 @@ +package openwrt + +import ( + "fmt" + "testing" +) + +func TestUCIAdd(t *testing.T) { + exec := createMockExecutor("", "", 0) + uci := NewUCIWithExecutor(exec) + res := uci.Add("wireless", "test") + if res.ReturnCode != 0 { + t.Error("Bad Return Code !") + } + if res.Stdout != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stdout is not empty ...") + } + if res.Stderr != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stderr is not empty ...") + } +} + +func TestUCIDelete(t *testing.T) { + exec := createMockExecutor("", "", 0) + uci := NewUCIWithExecutor(exec) + res := uci.Delete("wireless.@wifi-iface[1]") + if res.ReturnCode != 0 { + t.Error("Bad Return Code !") + } + if res.Stdout != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stdout is not empty ...") + } + if res.Stderr != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stderr is not empty ...") + } +} + +func TestUCISet(t *testing.T) { + exec := createMockExecutor("", "", 0) + uci := NewUCIWithExecutor(exec) + res := uci.Set("wireless.@wifi-iface[1].network", "OrionNetwork") + if res.ReturnCode != 0 { + t.Error("Bad Return Code !") + } + if res.Stdout != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stdout is not empty ...") + } + if res.Stderr != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stderr is not empty ...") + } +} + +func TestUCICommit(t *testing.T) { + exec := createMockExecutor("", "", 0) + uci := NewUCIWithExecutor(exec) + res := uci.Commit() + if res.ReturnCode != 0 { + t.Error("Bad Return Code !") + } + if res.Stdout != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stdout is not empty ...") + } + if res.Stderr != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stderr is not empty ...") + } +} + +func TestUCIReload(t *testing.T) { + exec := createMockExecutor("", "", 0) + uci := NewUCIWithExecutor(exec) + res := uci.Reload() + if res.ReturnCode != 0 { + t.Error("Bad Return Code !") + } + if res.Stdout != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stdout is not empty ...") + } + if res.Stderr != "" { + fmt.Printf("[%s] - ", res.Stdout) + t.Error("Stderr is not empty ...") + } +} diff --git a/openwrt/wifi.go b/openwrt/wifi.go new file mode 100644 index 0000000..e930bc5 --- /dev/null +++ b/openwrt/wifi.go @@ -0,0 +1,91 @@ +package openwrt + +import ( + "log" + "strings" + "time" +) + +// Wifi gives access to al OpenWRT Wifi operations +type Wifi struct { + exec Executor + iface string + Cells []*WifiCell +} + +// NewWifi return an UCI instance to interact with UCI +func NewWifi(wIface string) *Wifi { + exec := &localExecutor{} + iface := wIface + return &Wifi{exec, iface, nil} +} + +// NewWifiWithExecutor returns a Wifi Instance an gives you the ability to provide +// a different command executor than the default one. +func NewWifiWithExecutor(exec Executor, wIface string) *Wifi { + return &Wifi{exec, wIface, nil} +} + +func (w *Wifi) getEncryption(line string) string { + enc := "unkn" + if strings.Contains(line, "WPA2 PSK") { + enc = "psk" + } else if strings.Contains(line, "none") { + enc = "none" + } + return enc +} + +func (w *Wifi) parseWifiCells(stdout string) int { + new := false + mac, ssid, enc := "", "", "" + for _, line := range strings.Split(strings.TrimSuffix(stdout, "\n"), "\n") { + if strings.HasPrefix(line, "Cell") && new == false { + new = true + mac = strings.Split(line, " ")[4] + } + if strings.Contains(line, "ESSID:") { + ssid = strings.Split(line, " ")[1] + ssid = strings.Trim(ssid, "\"") + } + if strings.Contains(line, "Encryption:") { + enc = w.getEncryption(line) + } + if len(mac) > 0 && len(ssid) > 0 && len(enc) > 0 { + cell := NewWifiCell(ssid, mac, enc) + w.Cells = append(w.Cells, cell) + ssid, mac, enc = "", "", "" + new = false + } + } + return 0 +} + +// GetWifiCells retrieves all availabe wifi cells for a card ! +func (w *Wifi) GetWifiCells() int { + res := w.exec.Run("iwinfo", w.iface, "scan") + if res.ReturnCode != 0 { + log.Fatal(res.Stderr) + return res.ReturnCode + } + + for res.Stdout == "Scanning not possible" { + time.Sleep(time.Second) + res = w.exec.Run("iwinfo", w.iface, "scan") + if res.ReturnCode != 0 { + log.Fatal(res.Stderr) + return res.ReturnCode + } + } + return w.parseWifiCells(res.Stdout) +} + +// GetCell retreives an WifiCell by SSID provided in parameter +func (w *Wifi) GetCell(ssid string) *WifiCell { + for _, v := range w.Cells { + if v.Ssid == ssid { + return v + } + } + return nil +} diff --git a/openwrt/wifi_cell.go b/openwrt/wifi_cell.go new file mode 100644 index 0000000..3ce823e --- /dev/null +++ b/openwrt/wifi_cell.go @@ -0,0 +1,93 @@ +package openwrt + +import "time" + +// WifiCell reprensents wifi network Cell +type WifiCell struct { + Ssid string + MacAdress string + Encryption string +} + +// NewWifiCell returns a new WifiCell object +func NewWifiCell(ssid string, mac string, encrypt string) *WifiCell { + return &WifiCell{ + Ssid: ssid, + MacAdress: mac, + Encryption: encrypt, + } +} + +func (cell *WifiCell) uciWifiConfigure(uci *UCI, secret string) *Action { + setRes := uci.Set("wireless.@wifi-iface[1].network", "PyxisNetwork") + if setRes.ReturnCode != 0 { + return setRes + } + + setRes = uci.Set("wireless.@wifi-iface[1].ssid", cell.Ssid) + if setRes.ReturnCode != 0 { + return setRes + } + setRes = uci.Set("wireless.@wifi-iface[1].encryption", cell.Encryption) + if setRes.ReturnCode != 0 { + return setRes + } + setRes = uci.Set("wireless.@wifi-iface[1].device", "radio1") + if setRes.ReturnCode != 0 { + return setRes + } + setRes = uci.Set("wireless.@wifi-iface[1].mode", "sta") + if setRes.ReturnCode != 0 { + return setRes + } + setRes = uci.Set("wireless.@wifi-iface[1].bssid", cell.MacAdress) + if setRes.ReturnCode != 0 { + return setRes + } + setRes = uci.Set("wireless.@wifi-iface[1].key", secret) + if setRes.ReturnCode != 0 { + return setRes + } + + return &Action{ + &CommandResult{ + Stdout: "", + Stderr: "", + ReturnCode: 0, + }, + } +} + +// Connect to wifi Cell +func (cell *WifiCell) Connect(uci *UCI, secret string) *Action { + delRes := uci.Delete("wireless.@wifi-iface[1]") + if delRes.ReturnCode != 0 { + return delRes + } + addRes := uci.AddWireless("wifi-iface") + if addRes.ReturnCode != 0 { + return addRes + } + + setRes := cell.uciWifiConfigure(uci, secret) + if setRes.ReturnCode != 0 { + return setRes + } + + setRes = uci.Commit() + if setRes.ReturnCode != 0 { + return setRes + } + setRes = uci.Reload() + if setRes.ReturnCode != 0 { + return setRes + } + time.Sleep(20 * time.Second) + return &Action{ + &CommandResult{ + Stdout: "", + Stderr: "", + ReturnCode: 0, + }, + } +} diff --git a/openwrt/wifi_cell_test.go b/openwrt/wifi_cell_test.go new file mode 100644 index 0000000..c54da57 --- /dev/null +++ b/openwrt/wifi_cell_test.go @@ -0,0 +1,21 @@ +package openwrt + +import "testing" + +func TestWifiCellConnection(t *testing.T) { + uexec := createMockExecutor("", "", 0) + uci := NewUCIWithExecutor(uexec) + + cellList := `Cell 40 - Address: 68:A3:78:6E:D9:24 + ESSID: "PyxisWifi" + Mode: Master Channel: 3 + Signal: -90 dBm Quality: 20/70 + Encryption: WPA2 PSK (CCMP)` + + exec := createMockExecutor(cellList, "", 0) + wifi := NewWifiWithExecutor(exec, "wlan1") + _ = wifi.GetWifiCells() + + cell := wifi.GetCell("PyxisWifi") + cell.Connect(uci, "secret") +} diff --git a/openwrt/wifi_test.go b/openwrt/wifi_test.go new file mode 100644 index 0000000..a168c7f --- /dev/null +++ b/openwrt/wifi_test.go @@ -0,0 +1,71 @@ +package openwrt + +import ( + "fmt" + "io/ioutil" + "testing" +) + +// Test GestWifiCells method with 3 Cells +func TestGetWifiCells(t *testing.T) { + + cellList, err := ioutil.ReadFile("testdata/wifi_cells_output_3.txt") + if err != nil { + t.Fatal(err) + } + + exec := createMockExecutor(string(cellList), "", 0) + wifi := NewWifiWithExecutor(exec, "wlan1") + _ = wifi.GetWifiCells() + if len(wifi.Cells) != 3 { + fmt.Printf("Size of wifi.Cells is %d and not 3 !!!\n", len(wifi.Cells)) + t.Error("Cell list is empty ... This can not append !! Fix your code Dummy !") + } + if g, e := wifi.Cells[0].Ssid, "PyxisWifi"; g != e { + t.Errorf("The first Cell have a bad SSID !\n %s is expected and we have %s", e, g) + } + + if g, e := wifi.Cells[0].MacAdress, "68:A3:78:6E:D9:24"; g != e { + t.Errorf("The first Cell have a bad MAC !\n %s is expected and we have %s", e, g) + } + + if g, e := wifi.Cells[0].Encryption, "none"; g != e { + t.Errorf("The first Cell have a bad Encryption!\n %s is expected and we have %s", e, g) + } + + if g, e := wifi.Cells[1].Encryption, "psk"; g != e { + t.Errorf("The second Cell have a bad Encryption!\n %s is expected and we have %s", e, g) + } + + if g, e := wifi.Cells[2].MacAdress, "0C:F4:D5:16:AA:18"; g != e { + t.Errorf("The last Cell have a bad MAC !\n %s is expected and we have %s", e, g) + } +} + +// Test GestWifiCells method with empty list +func TestGetWifiCellsEmpty(t *testing.T) { + exec := createMockExecutor("", "", 0) + wifi := NewWifiWithExecutor(exec, "wlan1") + _ = wifi.GetWifiCells() + if len(wifi.Cells) != 0 { + fmt.Printf("Size of wifi.Cells is %d and not 0 !!!\n", len(wifi.Cells)) + t.Error("Cell list is empty ... This can not append !! Fix your code Dummy !") + } +} + +// Test GestWifiCells method with 3 Cells +func TestGetWifiCellsLarge(t *testing.T) { + + cellList, err := ioutil.ReadFile("testdata/wifi_cells_output_large.txt") + if err != nil { + t.Fatal(err) + } + + exec := createMockExecutor(string(cellList), "", 0) + wifi := NewWifiWithExecutor(exec, "wlan1") + _ = wifi.GetWifiCells() + if len(wifi.Cells) != 49 { + fmt.Printf("Size of wifi.Cells is %d and not 49 !!!\n", len(wifi.Cells)) + t.Error("Cell list is empty ... This can not append !! Fix your code Dummy !") + } +}