123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- // +build linux
- package wifi
- import (
- "errors"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "time"
- "github.com/valyala/fasttemplate"
- )
- //Toggle WiFi On Off. Only allow on sudo mode
- func (w *WiFiManager) SetInterfacePower(wlanInterface string, on bool) error {
- status := "up"
- if on == false {
- status = "down"
- }
- cmd := exec.Command("ifconfig", wlanInterface, status)
- out, err := cmd.CombinedOutput()
- if err != nil {
- log.Println("*WiFi* WiFi toggle failed: ", string(out))
- return err
- }
- //OK
- return nil
- }
- func (w *WiFiManager) GetInterfacePowerStatuts(wlanInterface string) (bool, error) {
- //Check if interface is in list
- interfaceList, err := w.GetWirelessInterfaces()
- if err != nil {
- return false, err
- }
- interfaceExists := false
- for _, localInterface := range interfaceList {
- if localInterface == wlanInterface {
- interfaceExists = true
- }
- }
- if !interfaceExists {
- return false, errors.New("wlan Interface not exists")
- }
- //Check if the interface appears in ifconfig. If yes, this interface is online
- cmd := exec.Command("bash", "-c", "ifconfig | grep "+wlanInterface)
- out, err := cmd.CombinedOutput()
- if err != nil {
- return false, errors.New(string(out))
- }
- if strings.TrimSpace(string(out)) != "" {
- //Interface exists in ifconfig. Report it as powered on
- return true, nil
- } else {
- return false, nil
- }
- }
- //Scan Nearby WiFi
- func (w *WiFiManager) ScanNearbyWiFi(interfaceName string) ([]WiFiInfo, error) {
- rcmd := `iwlist ` + interfaceName + ` scan`
- if w.sudo_mode {
- rcmd = "sudo " + rcmd
- }
- cmd := exec.Command("bash", "-c", rcmd)
- out, err := cmd.CombinedOutput()
- if err != nil {
- //Scan failed, the following code is used to handle edge case on some SBC that only nmcli works but not iwlist
- /*
- If the interface is not supported, something like this will show up:
- wlan0 Interface doesn't support scanning.
- */
- if strings.Contains(string(out), "Interface doesn't support scanning") {
- //Try nmcli instead if exists
- if pkg_exists("nmcli") {
- log.Println("*WiFi* Running WiFi scan in nmcli compatibility mode")
- cmd := exec.Command("bash", "-c", "nmcli d wifi list")
- out, err := cmd.CombinedOutput()
- if err != nil {
- return []WiFiInfo{}, err
- }
- //Parse the nmcli output
- lines := strings.Split(string(out), "\n")
- if len(lines) > 1 {
- //Remove the header
- lines = lines[1:]
- }
- results := []WiFiInfo{}
- //Prase the wifi information
- for _, line := range lines {
- //Replace all double space as split sign
- line = strings.TrimSpace(line)
- for strings.Contains(line, " ") {
- line = strings.ReplaceAll(line, " ", "$")
- }
- for strings.Contains(line, "$$") {
- line = strings.ReplaceAll(line, "$$", "$")
- }
- //Process the wifi info chunk
- wifiInfoSlice := strings.Split(line, "$")
- thisWifiInfo := new(WiFiInfo)
- if len(wifiInfoSlice) == 7 {
- //This is a valid entry
- thisWifiInfo.ESSID = strings.TrimSpace(wifiInfoSlice[0])
- thisWifiInfo.Quality = wifiInfoSlice[4] + "/100"
- thisWifiInfo.Frequency = wifiInfoSlice[3]
- channel, _ := strconv.Atoi(wifiInfoSlice[2])
- thisWifiInfo.Channel = channel
- thisWifiInfo.SignalLevel = w.getSignalLevelEstimation(wifiInfoSlice[5])
- //Check connect before
- if w.database.KeyExists("wifi", thisWifiInfo.ESSID) {
- thisWifiInfo.ConnectedBefore = true
- } else {
- thisWifiInfo.ConnectedBefore = false
- }
- } else if len(wifiInfoSlice) == 8 {
- //Entry with inuse * at front
- thisWifiInfo.ESSID = strings.TrimSpace(wifiInfoSlice[1])
- thisWifiInfo.Quality = wifiInfoSlice[5] + "/100"
- thisWifiInfo.Frequency = wifiInfoSlice[4]
- channel, _ := strconv.Atoi(wifiInfoSlice[2])
- thisWifiInfo.Channel = channel
- thisWifiInfo.SignalLevel = w.getSignalLevelEstimation(wifiInfoSlice[6])
- thisWifiInfo.ConnectedBefore = true //It is connected
- } else {
- //Line not valid. Skip this line
- continue
- }
- results = append(results, *thisWifiInfo)
- }
- return results, nil
- } else {
- log.Println("*WiFi* Scan Failed: ", err.Error())
- return []WiFiInfo{}, errors.New("Interface doesn't support scanning")
- }
- }
- return []WiFiInfo{}, err
- }
- //parse the output of the WiFi Scan
- lines := strings.Split(strings.TrimSpace(string(out)), "\n")
- for i, thisline := range lines {
- lines[i] = strings.TrimSpace(thisline)
- }
- //Ignore first line if it contains "Scan completed"
- if strings.Contains(lines[0], "Scan completed") {
- lines = lines[1:]
- }
- var results = []WiFiInfo{}
- //Loop through each line and construct the WiFi Info slice
- processingWiFiNode := new(WiFiInfo)
- for _, line := range lines {
- if strings.Contains(line, "Address: ") {
- //Push the previous results into results and create a new Node
- if processingWiFiNode.Address != "" {
- //Check if the ESSID already exists
- if pkg_exists("nmcli") {
- //Make use of nmcli storage
- if w.database.KeyExists("wifi", processingWiFiNode.ESSID) {
- processingWiFiNode.ConnectedBefore = true
- } else {
- processingWiFiNode.ConnectedBefore = false
- }
- } else {
- //Direct access to wpa_supplicant
- if fileExists("./system/network/wifi/ap/" + processingWiFiNode.ESSID + ".config") {
- processingWiFiNode.ConnectedBefore = true
- } else {
- processingWiFiNode.ConnectedBefore = false
- }
- }
- results = append(results, *processingWiFiNode)
- processingWiFiNode = new(WiFiInfo)
- }
- //Analysis this node
- datachunk := strings.Split(line, " ")
- if len(datachunk) > 0 {
- processingWiFiNode.Address = datachunk[len(datachunk)-1]
- }
- } else if strings.Contains(line, "Channel") && strings.Contains(line, "Frequency") == false {
- datachunk := strings.Split(line, ":")
- if len(datachunk) > 0 {
- channel, err := strconv.Atoi(datachunk[len(datachunk)-1])
- if err != nil {
- channel = -1
- }
- processingWiFiNode.Channel = channel
- }
- } else if strings.Contains(line, "Frequency") {
- tmp := strings.Split(line, ":")
- if len(tmp) > 0 {
- frequencyData := tmp[len(tmp)-1]
- frequencyDataChunk := strings.Split(frequencyData, " ")
- if len(frequencyDataChunk) > 1 {
- frequencyString := frequencyDataChunk[:2]
- processingWiFiNode.Frequency = strings.Join(frequencyString, " ")
- }
- }
- } else if strings.Contains(line, "Quality=") {
- //Need to seperate quality data from signal level. Example source: Quality=70/70 Signal level=-40 dBm
- analysisItem := strings.Split(line, " ")
- if len(analysisItem) == 2 {
- //Get the quality of connections
- processingWiFiNode.Quality = analysisItem[0][8:]
- //Get the signal level of the connections
- processingWiFiNode.SignalLevel = analysisItem[1][13:]
- }
- } else if strings.Contains(line, "Encryption key") {
- ek := strings.Split(line, ":")
- if len(ek) > 0 {
- status := ek[1]
- if status == "on" {
- processingWiFiNode.EncryptionKey = true
- } else {
- processingWiFiNode.EncryptionKey = false
- }
- }
- } else if strings.Contains(line, "ESSID") {
- iddata := strings.Split(line, ":")
- if len(iddata) > 0 {
- ESSID := iddata[1]
- ESSID = strings.ReplaceAll(ESSID, "\"", "")
- if ESSID == "" {
- ESSID = "Hidden Network"
- }
- processingWiFiNode.ESSID = ESSID
- }
- }
- }
- return results, nil
- }
- //Hack the signal level out of the nmcli bars
- func (w *WiFiManager) getSignalLevelEstimation(bar string) string {
- bar = strings.TrimSpace(bar)
- if bar == "▂▄▆█" {
- return "-45 dBm[Estimated]"
- } else if bar == "▂▄▆_" {
- return "-55 dBm[Estimated]"
- } else if bar == "▂▄__" {
- return "-75 dBm[Estimated]"
- } else if bar == "▂___" {
- return "-85 dBm[Estimated]"
- } else {
- return "-95 dBm[Estimated]"
- }
- }
- //Get all the network interfaces
- func (w *WiFiManager) GetWirelessInterfaces() ([]string, error) {
- rcmd := `iw dev | awk '$1=="Interface"{print $2}'`
- cmd := exec.Command("bash", "-c", rcmd)
- out, err := cmd.CombinedOutput()
- if err != nil {
- return []string{}, errors.New(string(out))
- }
- interfaces := strings.Split(strings.TrimSpace(string(out)), "\n")
- //sort.Strings(interfaces)
- sort.Sort(sort.StringSlice(interfaces))
- return interfaces, nil
- }
- func (w *WiFiManager) ConnectWiFi(ssid string, password string, connType string, identity string) (*WiFiConnectionResult, error) {
- //Build the network config file
- //Updates 21-10-2020, use nmcli if exists
- if pkg_exists("nmcli") {
- oldSSID, _, _ := w.GetConnectedWiFi()
- if ssid != "" {
- //There is an existing connection to another wifi AP. Disconnect it
- cmd := exec.Command("nmcli", "con", "down", oldSSID)
- out, err := cmd.CombinedOutput()
- if err != nil {
- log.Println("*WiFi* Connecting previous SSID failed: " + string(out))
- log.Println("*WiFi* Trying to connect new AP anyway")
- }
- }
- if connType == "switch" {
- //Load ssid and password from database
- w.database.Write("wifi", ssid, &password)
- }
- //Try to connect the new AP
- cmd := exec.Command("nmcli", "device", "wifi", "connect", ssid, "password", password)
- out, err := cmd.CombinedOutput()
- if err != nil {
- log.Println("*WiFi* Connecting to SSID " + ssid + " failed: " + string(out))
- return &WiFiConnectionResult{Success: false}, errors.New(string(out))
- }
- if connType != "switch" {
- //Save the ssid and password to database
- w.database.Write("wifi", ssid, password)
- }
- log.Println(string(out))
- //Check and return the current connection ssid
- //Wait until the WiFi is conencted
- rescanCount := 0
- connectedSSID, _, _ := w.GetConnectedWiFi()
- //Wait for 30 seconds
- for rescanCount < 10 && connectedSSID == "" {
- connectedSSID, _, _ = w.GetConnectedWiFi()
- log.Println(connectedSSID)
- rescanCount = rescanCount + 1
- log.Println("*WiFi* Waiting WiFi Connection (Retry " + strconv.Itoa(rescanCount) + "/10)")
- time.Sleep(3 * time.Second)
- }
- return &WiFiConnectionResult{
- ConnectedSSID: connectedSSID,
- Success: true,
- }, nil
- }
- //DO NOT TOUCH THE INDENTATION!! THEY MUST BE KEEP LIKE THIS
- writeToConfig := true
- networkConfigFile := ""
- if connType == "" {
- //Home use network / WPA2
- if password == "" {
- //No need password
- networkConfigFile = `network={
- ssid="` + ssid + `"
- key_mgmt=NONE
- priority={{priority}}
- }`
- } else {
- networkConfigFile = `network={
- ssid="` + ssid + `"
- psk="` + password + `"
- priority={{priority}}
- }`
- }
- } else if connType == "WPA-EAP" {
- if identity == "" {
- return &WiFiConnectionResult{Success: false}, errors.New("Identify not defined")
- }
- networkConfigFile = `network={
- ssid="` + ssid + `"
- key_mgmt=WPA-EAP
- identity="` + identity + `"
- password="` + password + `"
- }`
- } else if connType == "switch" {
- //Special case, for handling WiFi Switching without retyping the password
- writeToConfig = false
- } else {
- log.Println("*WiFi* Unsupported connection type")
- return &WiFiConnectionResult{Success: false}, errors.New("Unsupported Connection Type")
- }
- //Generate new wpa_supplicant_conf from file
- if !fileExists("./system/network/wifi/ap") {
- os.MkdirAll("./system/network/wifi/ap", 0755)
- }
- if writeToConfig == true {
- log.Println("*WiFi* WiFi Config Generated. Writing to file...")
- //Write config file to disk
- err := ioutil.WriteFile("./system/network/wifi/ap/"+ssid+".config", []byte(networkConfigFile), 0755)
- if err != nil {
- log.Println(err.Error())
- return &WiFiConnectionResult{Success: false}, err
- }
- } else {
- log.Println("*WiFi* Switching WiFi AP...")
- }
- //Start creating the new wpa_supplicant file
- //Get header
- configHeader, err := ioutil.ReadFile("./system/network/wifi/wpa_supplicant.conf_template.config")
- if err != nil {
- //Template header not found. Use default one from Raspberry Pi
- log.Println("*WiFi* Warning! wpa_supplicant template file not found. Using default template.")
- configHeader = []byte(`ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
- update_config=1
- {{networks}}
- `)
- }
- //Build network informations
- networksConfigs, err := filepath.Glob("./system/network/wifi/ap/*.config")
- if err != nil {
- log.Println(err.Error())
- return &WiFiConnectionResult{Success: false}, err
- }
- //Read each of the network and append it into a string slice
- networks := []string{}
- for _, configFile := range networksConfigs {
- thisNetworkConfig, err := ioutil.ReadFile(configFile)
- if err != nil {
- log.Println("*WiFi* Failed to read Network Config File: " + configFile)
- continue
- }
- if strings.TrimSuffix(filepath.Base(configFile), filepath.Ext(configFile)) == ssid {
- //The new SSID. Set this to higher priority
- networks = append(networks, template_apply(string(thisNetworkConfig), map[string]interface{}{
- "priority": strconv.Itoa(1),
- }))
- } else {
- //Old SSID. Use default priority
- networks = append(networks, template_apply(string(thisNetworkConfig), map[string]interface{}{
- "priority": strconv.Itoa(0),
- }))
- }
- }
- //Subsitute the results into the template
- networksConfigString := strings.Join(networks, "\n")
- newconfig := template_apply(string(configHeader), map[string]interface{}{
- "networks": networksConfigString,
- })
- //Try to write the new config to wpa_supplicant
- err = ioutil.WriteFile(w.wpa_supplicant_path, []byte(newconfig), 0777)
- if err != nil {
- log.Println("*WiFi* Failed to update wpa_supplicant config, are you sure you have access permission to that file?")
- return &WiFiConnectionResult{Success: false}, err
- }
- log.Println("*WiFi* WiFi Config Updated. Restarting Wireless Interfaces...")
- //Restart network services
- cmd := exec.Command("wpa_cli", "-i", w.wan_interface_name, "reconfigure")
- out, err := cmd.CombinedOutput()
- if err != nil {
- //Maybe the user forgot to set the flag. Try auto detect.
- autoDetectedWIface, err := w.GetWirelessInterfaces();
- if err != nil || len(autoDetectedWIface) == 0{
- log.Println("failed to restart network: " + string(out))
- return &WiFiConnectionResult{Success: false}, err
- }
- firstWlanInterface := autoDetectedWIface[0]
- cmd := exec.Command("wpa_cli", "-i", firstWlanInterface, "reconfigure")
- out, err := cmd.CombinedOutput()
- if err != nil {
- //Really failed.
- log.Println("failed to restart network: " + string(out))
- return &WiFiConnectionResult{Success: false}, err
- }
-
- }
- log.Println("*WiFi* Trying to connect new AP")
- //Wait until the WiFi is conencted
- rescanCount := 0
- connectedSSID, _, _ := w.GetConnectedWiFi()
- //Wait for 30 seconds
- for rescanCount < 10 && connectedSSID == "" {
- connectedSSID, _, _ = w.GetConnectedWiFi()
- log.Println(connectedSSID)
- rescanCount = rescanCount + 1
- log.Println("*WiFi* Waiting WiFi Connection (Retry " + strconv.Itoa(rescanCount) + "/10)")
- time.Sleep(3 * time.Second)
- }
- result := new(WiFiConnectionResult)
- if (rescanCount) >= 10 {
- result.Success = false
- } else {
- result.ConnectedSSID = connectedSSID
- result.Success = true
- }
- return result, nil
- }
- //Get the current connected wifi, return ESSID, wifi interface name and error if any
- //Return ESSID, interface and error
- func (w *WiFiManager) GetConnectedWiFi() (string, string, error) {
- cmd := exec.Command("iwgetid")
- out, err := cmd.CombinedOutput()
- if err != nil {
- //Check nmcli working or not
- if pkg_exists("nmcli") {
- //Try nmcli method instead
- cmd := exec.Command("nmcli", "-t", "-f", "NAME,DEVICE", "connection", "show", "--active")
- out, err := cmd.CombinedOutput()
- if err != nil {
- return "", "", errors.New(string(out))
- }
- //nmcli return something. Use the first one
- outString := strings.TrimSpace(string(out))
- currentSSIDInfo := ""
- if strings.Contains(outString, "\n") {
- connectedIds := strings.Split(outString, "\n")
- for _, conn := range connectedIds {
- if !strings.Contains(conn, "Wired") {
- //The first connection that is not wired
- currentSSIDInfo = conn
- break
- }
- }
- } else {
- currentSSIDInfo = outString
- }
- if strings.Contains(currentSSIDInfo, "Wired") {
- //This is an ethernet port?
- //Nothing connected
- return "OFFLINE", "N/A", nil
- }
- if currentSSIDInfo == "" {
- //No connection to any interface, which is strange?
- return "", "", errors.New("No established connection")
- }
- //Split the information
- SSIDinfoChunk := strings.Split(currentSSIDInfo, ":")
- currentSSID := SSIDinfoChunk[0]
- interfaceName := SSIDinfoChunk[1]
- return currentSSID, interfaceName, nil
- } else {
- return "", "", errors.New(string(out))
- }
- return "", "", errors.New(string(out))
- }
- if len(string(out)) == 0 {
- return "OFFLINE", "N/A", nil
- }
- //Try to parse the data
- trimmedData := string(out)
- for strings.Contains(trimmedData, " ") {
- trimmedData = strings.ReplaceAll(trimmedData, " ", " ")
- }
- dc := strings.Split(trimmedData, " ")
- if len(dc) == 0 {
- return "", "", errors.New("No valid wlan Interface Found")
- }
- wlanInterface := dc[0]
- ESSID := strings.Join(dc[1:], " ")[7:]
- ESSID = strings.TrimSpace(ESSID)
- ESSID = ESSID[:len(ESSID)-1]
- if strings.TrimSpace(ESSID) == "\"" {
- ESSID = ""
- }
- return ESSID, wlanInterface, nil
- }
- func (w *WiFiManager) CheckInterfaceIsAP(wlanInterfaceName string) (bool, error) {
- cmd := exec.Command("bash", "-c", "iwconfig wlan1 | grep Mode")
- out, err := cmd.CombinedOutput()
- if err != nil {
- return false, err
- }
- if len(string(out)) == 0 {
- return false, errors.New("Missing iwconfig package")
- }
- //Check if the output contains Mode:Master
- if strings.Contains(string(out), "Mode:Master") {
- return true, nil
- } else {
- return false, nil
- }
- }
- func (w *WiFiManager) RemoveWifi(ssid string) error {
- if pkg_exists("nmcli") {
- //Make use of nmcli storage
- if w.database.KeyExists("wifi", ssid) {
- w.database.Delete("wifi", ssid)
- }
- } else {
- //Fall back to systemctl
- if !fileInDir("./system/network/wifi/ap/"+ssid+".config", "./system/network/wifi/ap/") {
- return errors.New("Invalid SSID")
- }
- if fileExists("./system/network/wifi/ap/" + ssid + ".config") {
- os.Remove("./system/network/wifi/ap/" + ssid + ".config")
- } else {
- return errors.New("Record not found")
- }
- }
- return nil
- }
- //Helper functions
- func fileInDir(filesourcepath string, directory string) bool {
- filepathAbs, err := filepath.Abs(filesourcepath)
- if err != nil {
- return false
- }
- directoryAbs, err := filepath.Abs(directory)
- if err != nil {
- return false
- }
- //Check if the filepathabs contain directoryAbs
- if strings.Contains(filepathAbs, directoryAbs) {
- return true
- } else {
- return false
- }
- }
- func fileExists(filename string) bool {
- _, err := os.Stat(filename)
- if os.IsNotExist(err) {
- return false
- }
- return true
- }
- func template_apply(templateString string, replacement map[string]interface{}) string {
- t := fasttemplate.New(templateString, "{{", "}}")
- s := t.ExecuteString(replacement)
- return string(s)
- }
- func pkg_exists(pkgname string) bool {
- cmd := exec.Command("which", pkgname)
- out, _ := cmd.CombinedOutput()
- if len(string(out)) > 1 {
- return true
- } else {
- return false
- }
- }
|