991 lines
30 KiB
Go
Raw Permalink Normal View History

2018-09-13 01:07:57 +02:00
// +build linux
package service
import (
2018-12-07 00:59:54 +01:00
"github.com/mame82/P4wnP1_aloa/common_web"
"github.com/mame82/P4wnP1_aloa/netlink"
pb "github.com/mame82/P4wnP1_aloa/proto"
"sync"
"os/exec"
2018-12-07 00:59:54 +01:00
"github.com/mame82/P4wnP1_aloa/service/util"
"errors"
"fmt"
"strings"
"log"
"time"
"syscall"
"net"
"io/ioutil"
"os"
"regexp"
"strconv"
)
const (
2018-09-13 01:07:57 +02:00
wifi_if_name string = "wlan0"
WPA_SUPPLICANT_CONNECT_TIMEOUT = time.Second * 20
HOSTAPD_WAIT_AP_UP_TIMEOUT = time.Second * 8
)
func wifiCheckExternalBinaries() error {
if !binaryAvailable("wpa_supplicant") {
return errors.New("wpa_supplicant seems to be missing, please install it")
}
// to create wpa_supplicant.conf
if !binaryAvailable("wpa_passphrase") {
return errors.New("wpa_passphrase seems to be missing, please install it")
}
if !binaryAvailable("hostapd") {
return errors.New("hostapd seems to be missing, please install it")
}
// for wifiScan
if !binaryAvailable("iw") {
return errors.New("The tool 'iw' seems to be missing, please install it")
}
return nil
}
type WiFiService struct {
RootSvc *Service
2018-09-13 01:07:57 +02:00
State *pb.WiFiState
//Settings *pb.WiFi2Settings
mutexSettings *sync.Mutex // Lock settings on change
CmdWpaSupplicant *exec.Cmd //Manages wpa-supplicant process
mutexWpaSupplicant *sync.Mutex //mutex for wpa-supplicant proc
CmdHostapd *exec.Cmd //Manages hostapd process
mutexHostapd *sync.Mutex //hostapd proc lock
IfaceName string //Name of WiFi interface
PathWpaSupplicantConf string // path to config file for wpa-supplicant
PathHostapdConf string // path to config file for hostapd
LoggerHostapd *util.TeeLogger //logger for hostapd
LoggerWpaSupplicant *util.TeeLogger //logger for WPA supplicant
OutMonitorWpaSupplicant *wpaSupplicantOutMonitor //Monitors wpa_supplicant output and sets signals where needed
2018-09-21 12:43:08 +02:00
OutMonitorHostapd *hostapdOutMonitor //Monitors hostapd output and sets signals where needed
}
2018-09-21 12:43:08 +02:00
func (wSvc *WiFiService) StartHostapd(timeout time.Duration) (err error) {
log.Printf("Starting hostapd for interface '%s'...\n", wSvc.IfaceName)
wSvc.mutexHostapd.Lock()
defer wSvc.mutexHostapd.Unlock()
//stop hostapd if already running
if wSvc.CmdHostapd != nil {
// avoid deadlock
wSvc.mutexHostapd.Unlock()
wSvc.StopHostapd()
wSvc.mutexHostapd.Lock()
}
wSvc.CmdHostapd = exec.Command("/usr/sbin/hostapd", wSvc.PathHostapdConf)
// the logger is the signal generator for OutMonitorHostapd, so we reset the signal before applying the logger
wSvc.OutMonitorHostapd.resultReceived.Reset()
wSvc.CmdHostapd.Stdout = wSvc.LoggerHostapd.LogWriter
wSvc.CmdHostapd.Stderr = wSvc.LoggerHostapd.LogWriter
err = wSvc.CmdHostapd.Start()
if err != nil {
wSvc.CmdHostapd.Wait()
return errors.New(fmt.Sprintf("Error starting hostapd '%v'", err))
}
2018-09-21 12:43:08 +02:00
//wait for result in output
apUp, errcon := wSvc.OutMonitorHostapd.WaitConnectResultOnce(timeout)
if errcon != nil {
log.Printf("... hostapd reached timeout of '%v' without beeing able to bring up an Access Point\n", timeout)
// avoid dead lock
wSvc.mutexHostapd.Unlock()
wSvc.StopHostapd()
wSvc.mutexHostapd.Lock()
return errors.New("TIMEOUT REACHED")
}
if apUp {
//We could return success and keep wpa_supplicant running
log.Printf("... hostapd AP for interface '%s' started\n", wSvc.IfaceName)
return nil
} else {
log.Println("... hostapd failed to bring up an Access Point, stopping ...")
//wifiStopWpaSupplicant(nameIface)
// avoid dead lock
wSvc.mutexHostapd.Unlock()
wSvc.StopHostapd()
wSvc.mutexHostapd.Lock()
log.Println("... hostapd terminated")
2018-09-21 12:43:08 +02:00
return errors.New("Hostapd failed to bring up Access Point")
}
return nil
}
func (wSvc *WiFiService) StopHostapd() (err error) {
eSuccess := fmt.Sprintf("... hostapd for interface '%s' stopped", wSvc.IfaceName)
eCantStop := fmt.Sprintf("... couldn't terminate hostapd for interface '%s'", wSvc.IfaceName)
log.Println("... killing hostapd")
wSvc.mutexHostapd.Lock()
defer wSvc.mutexHostapd.Unlock()
if wSvc.CmdHostapd == nil {
log.Printf("... hostapd for interface '%s' isn't running, no need to stop it\n", wSvc.IfaceName)
return nil
}
/*
wSvc.CmdHostapd.Process.Signal(syscall.SIGTERM)
time.Sleep(time.Millisecond * 500)
if wSvc.CmdHostapd.ProcessState == nil || !wSvc.CmdHostapd.ProcessState.Exited() {
wSvc.CmdHostapd.Process.Kill()
wSvc.CmdHostapd.Wait()
//wSvc.CmdHostapd.Process.Kill()
if wSvc.CmdHostapd.ProcessState.Exited() {
wSvc.CmdHostapd = nil
log.Println(eSuccess)
return nil
} else {
log.Println(eCantStop)
return errors.New(eCantStop)
}
}
*/
err = ProcSoftKill(wSvc.CmdHostapd, time.Second)
if err != nil { return errors.New(eCantStop) }
wSvc.CmdHostapd = nil
log.Println(eSuccess)
return nil
}
func (wSvc *WiFiService) StopWpaSupplicant() (err error) {
eSuccess := fmt.Sprintf("... wpa_supplicant for interface '%s' stopped", wSvc.IfaceName)
eCantStop := fmt.Sprintf("... couldn't terminate wpa_supplicant for interface '%s'", wSvc.IfaceName)
log.Printf("... stop running wpa_supplicant processes for interface '%s'\n", wSvc.IfaceName)
wSvc.mutexWpaSupplicant.Lock()
defer wSvc.mutexWpaSupplicant.Unlock()
if wSvc.CmdWpaSupplicant == nil {
log.Printf("... wpa_supplicant for interface '%s' wasn't running, no need to stop it\n", wSvc.IfaceName)
return nil
}
/*
log.Printf("... sending SIGTERM for wpa_supplicant on interface '%s' with PID\n", wSvc.IfaceName, wSvc.CmdWpaSupplicant.Process.Pid)
wSvc.CmdWpaSupplicant.Process.Signal(syscall.SIGTERM)
wSvc.CmdWpaSupplicant.Wait()
if !wSvc.CmdWpaSupplicant.ProcessState.Exited() {
log.Printf("... wpa_supplicant didn't react on SIGTERM for interface '%s', trying SIGKILL\n", wSvc.IfaceName)
wSvc.CmdWpaSupplicant.Process.Kill()
time.Sleep(500 * time.Millisecond)
if wSvc.CmdWpaSupplicant.ProcessState.Exited() {
wSvc.CmdWpaSupplicant = nil
log.Println(eSuccess)
return nil
} else {
log.Println(eCantStop)
return errors.New(eCantStop)
}
}
*/
log.Printf("... stopping wpa_supplicant\n", wSvc.IfaceName, wSvc.CmdWpaSupplicant.Process.Pid)
err = ProcSoftKill(wSvc.CmdWpaSupplicant, time.Second*2)
if err != nil { return errors.New(eCantStop) }
wSvc.CmdWpaSupplicant = nil
log.Println(eSuccess)
return nil
}
func (wSvc *WiFiService) StartWpaSupplicant(timeout time.Duration) (err error) {
log.Printf("Starting wpa_supplicant for interface '%s'...\n", wSvc.IfaceName)
wSvc.mutexWpaSupplicant.Lock()
defer wSvc.mutexWpaSupplicant.Unlock()
//stop wpa_supplicant if already running
if wSvc.CmdWpaSupplicant != nil {
// avoid dead lock
wSvc.mutexWpaSupplicant.Unlock()
wSvc.StopWpaSupplicant()
wSvc.mutexWpaSupplicant.Lock()
}
//we monitor output of wpa_supplicant till we are connected, fail due to wrong PSK or timeout is reached
//Note: PID file creation doesn't work when not started as daemon, so we do it manually, later on
wSvc.CmdWpaSupplicant = exec.Command("/sbin/wpa_supplicant", "-c", wSvc.PathWpaSupplicantConf, "-i", wSvc.IfaceName)
// the logger is the signal generator for OutMonitorWpaSupplicant, so we reset the signal before apllying the logger
wSvc.OutMonitorWpaSupplicant.resultReceived.Reset()
wSvc.CmdWpaSupplicant.Stdout = wSvc.LoggerWpaSupplicant.LogWriter
err = wSvc.CmdWpaSupplicant.Start()
if err != nil {
return err
}
//wait for result in output
connected, errcon := wSvc.OutMonitorWpaSupplicant.WaitConnectResultOnce(timeout)
if errcon != nil {
log.Printf("... wpa_supplicant reached timeout of '%v' without beeing able to connect to given network\n", timeout)
log.Println("... killing wpa_supplicant")
// avoid dead lock
wSvc.mutexWpaSupplicant.Unlock()
wSvc.StopWpaSupplicant()
wSvc.mutexWpaSupplicant.Lock()
return errors.New("TIMEOUT REACHED")
}
if connected {
//We could return success and keep wpa_supplicant running
log.Println("... connected to given WiFi network, wpa_supplicant running")
return nil
} else {
//we stop wpa_supplicant and return err
log.Println("... seems the wrong PSK was provided for the given WiFi network, stopping wpa_supplicant ...")
//wifiStopWpaSupplicant(nameIface)
log.Println("... killing wpa_supplicant")
// avoid dead lock
wSvc.mutexWpaSupplicant.Unlock()
wSvc.StopWpaSupplicant()
wSvc.mutexWpaSupplicant.Lock()
return errors.New("Wrong PSK")
}
return nil
}
2018-09-13 01:07:57 +02:00
func MatchGivenBBSToScanResult(scanRes []BSS, targets []*pb.WiFiBSSCfg) (matches []*pb.WiFiBSSCfg) {
for _, bssCfgTarget := range targets {
for _, bssCfgScan := range scanRes {
if bssCfgScan.SSID == bssCfgTarget.SSID {
2018-09-13 01:07:57 +02:00
log.Printf("Found SSID '%s'\n", bssCfgScan.SSID)
if len(bssCfgTarget.PSK) == 0 && bssCfgScan.AuthMode != WiFiAuthMode_OPEN {
log.Printf("No PSK provided for '%s', but authentication mode isn't OPEN. Ignoring this network ...\n", bssCfgScan.SSID)
} else {
// SSID match, possible candidate
matches = append(matches, bssCfgTarget)
}
}
}
}
return
}
2018-09-13 01:07:57 +02:00
func (wSvc *WiFiService) runStaMode(newWifiSettings *pb.WiFiSettings) (err error) {
if len(newWifiSettings.Client_BSSList) == 0 {
return errors.New("Error: WiFi mode set to station (STA) but no BSS configurations provided")
}
//scan for provided wifi
scanres, err := WifiScan(wSvc.IfaceName)
if err != nil {
return errors.New(fmt.Sprintf("Scanning for existing WiFi networks failed: %v", err))
}
matchingBssList := MatchGivenBBSToScanResult(scanres, newWifiSettings.Client_BSSList)
if len(matchingBssList) == 0 {
return errors.New(fmt.Sprintf("Non of the given SSIDs found during scan\n"))
}
// Create config for the remaining networks
confstr, err := wifiCreateWpaSupplicantConfStringList(matchingBssList)
2018-09-13 01:07:57 +02:00
if err != nil {
return err
}
// store config to file
log.Printf("Creating wpa_supplicant configuration file at '%s'\n", wSvc.PathWpaSupplicantConf)
err = ioutil.WriteFile(wSvc.PathWpaSupplicantConf, []byte(confstr), os.ModePerm)
2018-09-13 01:07:57 +02:00
if err != nil {
return err
}
//ToDo: proper error handling, in case connection not possible
err = wSvc.StartWpaSupplicant(WPA_SUPPLICANT_CONNECT_TIMEOUT)
2018-09-13 01:07:57 +02:00
if err != nil {
return err
}
return nil
}
// ToDo: Output monitor for AP-ENABLED (same approach as for wpa_supplicant)
2018-09-13 01:07:57 +02:00
func (wSvc *WiFiService) runAPMode(newWifiSettings *pb.WiFiSettings) (err error) {
//generate hostapd.conf (overwrite old one)
hostapdCreateConfigFile2(newWifiSettings, wSvc.PathHostapdConf)
//start hostapd
2018-09-21 12:43:08 +02:00
err = wSvc.StartHostapd(HOSTAPD_WAIT_AP_UP_TIMEOUT)
if err != nil {
2018-09-21 12:43:08 +02:00
fmt.Println("Wait 2 seconds and retry to to start hostapd once...")
time.Sleep(2 * time.Second)
2018-09-21 12:43:08 +02:00
err = wSvc.StartHostapd(HOSTAPD_WAIT_AP_UP_TIMEOUT)
if err != nil {
return err
}
}
return nil
}
2018-09-13 01:07:57 +02:00
func (wSvc *WiFiService) DeploySettings(newWifiSettings *pb.WiFiSettings) (wstate *pb.WiFiState, err error) {
log.Println("Deploying new WiFi settings...")
log.Printf("Settings: %+v\n", newWifiSettings)
wSvc.mutexSettings.Lock()
defer wSvc.mutexSettings.Unlock()
//ToDo: Dis/Enable nexmon if needed
//stop wpa_supplicant if needed
err = wSvc.StopWpaSupplicant()
2018-09-13 01:07:57 +02:00
if err != nil {
return wSvc.State, err
}
//kill hostapd in case it is still running
err = wSvc.StopHostapd()
2018-09-13 01:07:57 +02:00
if err != nil {
return wSvc.State, err
}
// Note: When hostapd is killed, the WiFi interface is shut down. If the firmware is reloade (toggeling
// nexmon) the whole interface gets destroyed. Both have influence on processes depending on the network
// configuration of the WiFi interface (e.g. DHCP server / client). In order to avoid errors, we reconfigure the
// WiFi network interface after changing the WiFi state.
// We do this after setting the respective WiFi mode (STA / AP / FAILOVER), but in order to make this modes
// work, we enable the interface first - regardless of its 'enabled' state.
iface, tmperr := net.InterfaceByName(wSvc.IfaceName)
if tmperr == nil {
netlink.NetworkLinkUp(iface)
}
// Set proper regulatory domain
errReg := wifiSetReg(newWifiSettings.Regulatory)
if errReg != nil {
log.Printf("Error setting WiFi regulatory domain '%s': %v\n", newWifiSettings.Regulatory , err) //we don't abort on error here
}
var triggerEvent *pb.Event = nil
if !newWifiSettings.Disabled {
switch newWifiSettings.WorkingMode {
2018-09-13 01:07:57 +02:00
case pb.WiFiWorkingMode_AP:
err = wSvc.runAPMode(newWifiSettings)
// emit Trigger event if AP is Up
if err == nil {
triggerEvent = ConstructEventTrigger(common_web.TRIGGER_EVT_TYPE_WIFI_AP_STARTED)
}
2018-09-13 01:07:57 +02:00
case pb.WiFiWorkingMode_STA, pb.WiFiWorkingMode_STA_FAILOVER_AP:
errSta := wSvc.runStaMode(newWifiSettings)
if errSta == nil {
triggerEvent = ConstructEventTrigger(common_web.TRIGGER_EVT_TYPE_WIFI_CONNECTED_AS_STA)
} else {
//in failover mode, we try to enable AP first
2018-09-13 01:07:57 +02:00
if newWifiSettings.WorkingMode == pb.WiFiWorkingMode_STA_FAILOVER_AP {
log.Println(errSta)
log.Printf("Trying to fail over to Access Point Mode...")
err = wSvc.runAPMode(newWifiSettings)
if err == nil {
triggerEvent = ConstructEventTrigger(common_web.TRIGGER_EVT_TYPE_WIFI_AP_STARTED)
}
} else {
err = errSta
}
}
default:
// None allowed working mode, so we leave wSvc as UNKNOWN
err = errors.New("Unknown working mode")
}
}
//fmt.Println("HOSTAPD ERR CHECK\n==============\n-->", err)
if err == nil {
log.Printf("... WiFi settings deployed successfully\n")
} else {
log.Printf("... deploying WiFi settings failed: %s\n", err.Error())
return wSvc.State, err
}
2018-09-13 01:07:57 +02:00
// At this point, we reestablish the interface settings
//ReInitNetworkInterface(wSvc.IfaceName)
if nim,err := wSvc.RootSvc.SubSysNetwork.GetManagedInterface(wSvc.IfaceName); err == nil {
nim.ReDeploy()
}
2018-09-13 01:07:57 +02:00
// update settings (wSvc is updated by runAPMode/runStaMode)
2018-09-13 01:07:57 +02:00
wSvc.State.CurrentSettings = newWifiSettings
// update rest of state
if serr := wSvc.UpdateStateFromIw(); serr != nil {
log.Println("Couldn't update internal WiFi state:", serr)
}
// Fire the event after everything is done, especially after redeployment of the network interface settings
// to allow an ActionTrigger which deploys another ethernet settings template (without changing the settings
// in parallel)
// ToDo: check if it makes sense to lock the methods responsible for deploying ethernet interface settings and WifI settings (both long-running)
if triggerEvent != nil {
wSvc.RootSvc.SubSysEvent.Emit(triggerEvent)
}
return wSvc.State, nil
}
func NewWifiService(rootSvc *Service) (res *WiFiService) {
ifName := wifi_if_name
err := wifiCheckExternalBinaries()
2018-09-13 01:07:57 +02:00
if err != nil {
panic(err)
}
//Check interface existence
if exists := CheckInterfaceExistence(ifName); !exists {
panic(errors.New(fmt.Sprintf("WiFi interface '%s' not present")))
}
res = &WiFiService{
RootSvc: rootSvc,
mutexSettings: &sync.Mutex{},
CmdWpaSupplicant: nil,
mutexWpaSupplicant: &sync.Mutex{},
CmdHostapd: nil,
mutexHostapd: &sync.Mutex{},
IfaceName: ifName,
PathWpaSupplicantConf: fmt.Sprintf("/tmp/wpa_supplicant_%s.conf", ifName),
PathHostapdConf: fmt.Sprintf("/tmp/hostapd_%s.conf", ifName),
}
res.OutMonitorWpaSupplicant = NewWpaSupplicantOutMonitor()
2018-09-21 12:43:08 +02:00
res.OutMonitorHostapd = NewHostapdOutMonitor()
res.LoggerHostapd = util.NewTeeLogger(true)
res.LoggerHostapd.SetPrefix("hostapd: ")
2018-09-21 12:43:08 +02:00
res.LoggerHostapd.AddOutput(res.OutMonitorHostapd)
res.LoggerWpaSupplicant = util.NewTeeLogger(true)
res.LoggerWpaSupplicant.SetPrefix("wpa_supplicant: ")
res.LoggerWpaSupplicant.AddOutput(res.OutMonitorWpaSupplicant) // add watcher too tee'ed output writers
// Initial settings and state on service start
2018-09-13 01:07:57 +02:00
res.State = &pb.WiFiState{
Mode: pb.WiFiStateMode_STA_NOT_CONNECTED,
Channel: 0,
Ssid: "",
}
2018-09-13 01:07:57 +02:00
res.State.CurrentSettings = &pb.WiFiSettings{
Disabled: false,
WorkingMode: pb.WiFiWorkingMode_AP,
Client_BSSList: []*pb.WiFiBSSCfg{&pb.WiFiBSSCfg{SSID:"", PSK:""}},
Ap_BSS: &pb.WiFiBSSCfg{},
}
return res
}
2018-09-21 12:43:08 +02:00
// io.Writer firing a signal if predefined output arrives
type hostapdOutMonitor struct {
resultReceived *util.Signal
result bool
*sync.Mutex
}
func (m *hostapdOutMonitor) Write(p []byte) (n int, err error) {
// if result already received, the write could exit (early out)
if m.resultReceived.IsSet() {
2020-02-06 00:20:54 +01:00
// fix endless loop of writing 'CTRL-EVENT-TERMINATING'
return len(p), nil
2018-09-21 12:43:08 +02:00
}
// check if buffer contains relevant strings (assume write is called line wise by the hosted process
// otherwise we'd need to utilize an io.Reader
line := string(p)
switch {
case strings.Contains(line, "AP-DISABLED"):
2018-09-21 12:43:08 +02:00
log.Printf("Starting Access Point failed\n")
m.Lock()
defer m.Unlock()
m.result = false
m.resultReceived.Set()
case strings.Contains(line, "AP-ENABLED"):
log.Printf("Access point is up\n")
m.Lock()
defer m.Unlock()
m.result = true
m.resultReceived.Set()
}
return len(p), nil
}
func (m *hostapdOutMonitor) WaitConnectResultOnce(timeout time.Duration) (connected bool, err error) {
err = m.resultReceived.WaitTimeout(timeout)
if err != nil {
return false, errors.New("Couldn't retrieve hostapd connection state before timeout")
}
m.Lock()
defer m.Unlock()
connected = m.result
m.resultReceived.Reset() //Disable result received, for next use
return
}
func NewHostapdOutMonitor() *hostapdOutMonitor {
return &hostapdOutMonitor{
resultReceived: util.NewSignal(false, false),
Mutex: &sync.Mutex{},
result: false,
2018-09-21 12:43:08 +02:00
}
}
type wpaSupplicantOutMonitor struct {
resultReceived *util.Signal
result bool
*sync.Mutex
}
func (m *wpaSupplicantOutMonitor) Write(p []byte) (n int, err error) {
// if result already received, the write could exit (early out)
if m.resultReceived.IsSet() {
2020-02-06 00:20:54 +01:00
return len(p), nil
}
// check if buffer contains relevant strings (assume write is called line wise by the hosted process
// otherwise we'd need to utilize an io.Reader
line := string(p)
switch {
case strings.Contains(line, "WRONG_KEY"):
log.Printf("Seems the provided PSK doesn't match\n")
m.Lock()
defer m.Unlock()
m.result = false
m.resultReceived.Set()
case strings.Contains(line, "CTRL-EVENT-CONNECTED"):
// CTRL-EVENT-CONNECTED - Connection to 4e:66:41:a0:5b:35 completed
log.Printf("Connected to target network\n")
m.Lock()
defer m.Unlock()
m.result = true
m.resultReceived.Set()
}
return len(p), nil
}
func (m *wpaSupplicantOutMonitor) WaitConnectResultOnce(timeout time.Duration) (connected bool, err error) {
err = m.resultReceived.WaitTimeout(timeout)
if err != nil {
return false, errors.New("Couldn't retrieve wpa_supplicant connection state before timeout")
}
m.Lock()
defer m.Unlock()
connected = m.result
m.resultReceived.Reset() //Disable result received, for next use
return
}
func NewWpaSupplicantOutMonitor() *wpaSupplicantOutMonitor {
return &wpaSupplicantOutMonitor{
resultReceived: util.NewSignal(false, false),
Mutex: &sync.Mutex{},
}
}
type WiFiAuthMode int
const (
WiFiAuthMode_OPEN WiFiAuthMode = iota
//WiFiAuthMode_WEP
WiFiAuthMode_WPA_PSK
WiFiAuthMode_WPA2_PSK
WiFiAuthMode_UNSUPPORTED
)
type BSS struct {
SSID string
BSSID net.HardwareAddr
Frequency int
BeaconInterval time.Duration //carefull, on IE level beacon interval isn't measured in milliseconds
AuthMode WiFiAuthMode
Signal float32 //Signal strength in dBm
}
func WifiScan(ifName string) (result []BSS, err error) {
proc := exec.Command("/sbin/iw", ifName, "scan")
res, err := proc.CombinedOutput()
if err != nil {
2018-09-13 01:07:57 +02:00
return nil, errors.New(fmt.Sprintf("Error running scan: '%s'\niw output: %s", err, res))
}
result, err = ParseIwScan(string(res))
return
}
2018-09-13 01:07:57 +02:00
func wifiCreateWpaSupplicantConfStringList(bsslist []*pb.WiFiBSSCfg) (config string, err error) {
// if a PSK is provided, we assume it is needed, otherwise we assume OPEN AUTHENTICATION
2018-09-13 01:07:57 +02:00
for _, bss := range bsslist {
ssid := bss.SSID
psk := bss.PSK
if len(psk) > 0 {
fmt.Println("Connecting WiFi with PSK")
proc := exec.Command("/usr/bin/wpa_passphrase", ssid, psk)
cres, err := proc.CombinedOutput()
if err != nil {
return "", errors.New(fmt.Sprintf("Error craeting wpa_supplicant.conf for SSID '%s' with PSK '%s': %s", ssid, psk, string(cres)))
}
config += string(cres)
} else {
fmt.Println("Connecting WiFi with OPEN AUTH")
config += fmt.Sprintf(
`network={
ssid="%s"
key_mgmt=NONE
}
`, ssid)
}
}
config = "ctrl_interface=/run/wpa_supplicant\n" + config
return
}
2018-09-13 01:07:57 +02:00
func wifiCreateHostapdConfString(ws *pb.WiFiSettings) (config string, err error) {
if ws.WorkingMode != pb.WiFiWorkingMode_AP && ws.WorkingMode != pb.WiFiWorkingMode_STA_FAILOVER_AP {
return "", errors.New("Couldn't create hostapd configuration, the settings don't include an AP")
}
if ws.Ap_BSS == nil {
return "", errors.New("WiFiSettings don't contain a BSS configuration for an AP")
}
config = fmt.Sprintf("interface=%s\n", wifi_if_name)
config += fmt.Sprintf("driver=nl80211\n") //netlink capable driver
config += fmt.Sprintf("hw_mode=g\n") //Use 2.4GHz band
config += fmt.Sprintf("ieee80211n=1\n") //Enable 802.111n
config += fmt.Sprintf("wmm_enabled=1\n") //Enable WMM
config += fmt.Sprintf("ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]\n") // 40MHz channels with 20ns guard interval
config += fmt.Sprintf("macaddr_acl=0\n") //Accept all MAC addresses
config += fmt.Sprintf("ssid=%s\n", ws.Ap_BSS.SSID)
config += fmt.Sprintf("channel=%d\n", ws.Channel)
2018-09-13 01:07:57 +02:00
if ws.AuthMode == pb.WiFiAuthMode_WPA2_PSK {
config += fmt.Sprintf("auth_algs=1\n") //Use WPA authentication
config += fmt.Sprintf("wpa=2\n") //Use WPA2
//ToDo: check if PSK could be provided encrypted
config += fmt.Sprintf("wpa_key_mgmt=WPA-PSK\n") //Use a pre-shared key
config += fmt.Sprintf("wpa_passphrase=%s\n", ws.Ap_BSS.PSK) //Set PSK
2018-09-13 01:07:57 +02:00
config += fmt.Sprintf("rsn_pairwise=CCMP\n") //Use Use AES, instead of TKIP
} else {
config += fmt.Sprintf("auth_algs=3\n") //Both, open and shared auth
}
if ws.HideSsid {
config += fmt.Sprintf("ignore_broadcast_ssid=2\n") //Require clients to know the SSID
} else {
config += fmt.Sprintf("ignore_broadcast_ssid=0\n") //Send beacons + probes
}
return
}
2018-09-13 01:07:57 +02:00
func hostapdCreateConfigFile2(s *pb.WiFiSettings, filename string) (err error) {
log.Printf("Creating hostapd configuration file at '%s'\n", filename)
2018-09-13 01:07:57 +02:00
fileContent, err := wifiCreateHostapdConfString(s)
if err != nil {
return
}
err = ioutil.WriteFile(filename, []byte(fileContent), os.ModePerm)
return
}
//ToDo: Create netlink based implementation (not relying on 'iw'): low priority
func ParseIwScan(scanresult string) (bsslist []BSS, err error) {
//fmt.Printf("Parsing:\n%s\n", scanresult)
//split into BSS sections
rp := regexp.MustCompile("(?msU)^BSS.*")
strBSSList := rp.Split(scanresult, -1)
if len(strBSSList) < 1 {
return nil, errors.New("Error parsing iw scan result") //splitting should always result in one element at least
}
bsslist = []BSS{}
rp_bssid := regexp.MustCompile("[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}")
rp_freq := regexp.MustCompile("(?m)freq:\\s*([0-9]{4})")
rp_ssid := regexp.MustCompile("(?m)SSID:\\s*(.*)\n")
rp_beacon_intv := regexp.MustCompile("(?m)beacon interval:\\s*([0-9]*)TU")
rp_WEP := regexp.MustCompile("(?m)WEP:")
rp_WPA := regexp.MustCompile("(?m)WPA:")
rp_WPA2 := regexp.MustCompile("(?m)RSN:")
rp_PSK := regexp.MustCompile("(?m)Authentication suites: PSK") //ToDo: check if PSK occurs under respective IE (RSN or WPA, when either is chosen)
//signal: -75.00 dBm
rp_signal := regexp.MustCompile("(?m)signal:\\s*(-?[0-9]*\\.[0-9]*)")
for idx, strBSS := range strBSSList[1:] {
currentBSS := BSS{}
//fmt.Printf("BSS %d\n================\n%s\n", idx, strBSS)
fmt.Printf("BSS %d\n================\n", idx)
//BSSID (should be in first line)
strBSSID := rp_bssid.FindString(strBSS)
fmt.Printf("BSSID: %s\n", strBSSID)
currentBSS.BSSID, err = net.ParseMAC(strBSSID)
if err != nil {
return nil, err
}
//freq
strFreq_sub := rp_freq.FindStringSubmatch(strBSS)
strFreq := "0"
if len(strFreq_sub) > 1 {
strFreq = strFreq_sub[1]
}
fmt.Printf("Freq: %s\n", strFreq)
tmpI64, err := strconv.ParseInt(strFreq, 10, 32)
if err != nil {
return nil, err
}
currentBSS.Frequency = int(tmpI64)
//ssid
strSsid_sub := rp_ssid.FindStringSubmatch(strBSS)
strSSID := ""
if len(strSsid_sub) > 1 {
strSSID = strSsid_sub[1]
}
fmt.Printf("SSID: '%s'\n", strSSID)
currentBSS.SSID = strSSID
//beacon interval
strBI_sub := rp_beacon_intv.FindStringSubmatch(strBSS)
strBI := "100"
if len(strBI_sub) > 1 {
strBI = strBI_sub[1]
}
fmt.Printf("Beacon Interval: %s\n", strBI)
tmpI64, err = strconv.ParseInt(strBI, 10, 32)
if err != nil {
return nil, err
}
currentBSS.BeaconInterval = time.Microsecond * time.Duration(tmpI64*1024) //1TU = 1024 microseconds (not 1000)
//auth type
//assume OPEN
//if "WEP: is present assume UNSUPPORTED
//if "WPA:" is present assume WPA (overwrite WEP/UNSUPPORTED)
//if "RSN:" is present assume WPA2 (overwrite WPA/UNSUPPORTED)
//in case of WPA/WPA2 check for presence of "Authentication suites: PSK" to assure PSK support, otherwise assume unsupported (no EAP/CHAP support for now)
currentBSS.AuthMode = WiFiAuthMode_OPEN
if rp_WEP.MatchString(strBSS) {
currentBSS.AuthMode = WiFiAuthMode_UNSUPPORTED
}
if rp_WPA.MatchString(strBSS) {
currentBSS.AuthMode = WiFiAuthMode_WPA_PSK
}
if rp_WPA2.MatchString(strBSS) {
currentBSS.AuthMode = WiFiAuthMode_WPA2_PSK
}
if currentBSS.AuthMode == WiFiAuthMode_WPA_PSK || currentBSS.AuthMode == WiFiAuthMode_WPA2_PSK {
if !rp_PSK.MatchString(strBSS) {
currentBSS.AuthMode = WiFiAuthMode_UNSUPPORTED
}
}
switch currentBSS.AuthMode {
case WiFiAuthMode_UNSUPPORTED:
fmt.Println("AuthMode: UNSUPPORTED")
case WiFiAuthMode_OPEN:
fmt.Println("AuthMode: OPEN")
case WiFiAuthMode_WPA_PSK:
fmt.Println("AuthMode: WPA PSK")
case WiFiAuthMode_WPA2_PSK:
fmt.Println("AuthMode: WPA2 PSK")
}
//signal
strSignal_sub := rp_signal.FindStringSubmatch(strBSS)
strSignal := "0.0"
if len(strSignal_sub) > 1 {
strSignal = strSignal_sub[1]
}
tmpFloat, err := strconv.ParseFloat(strSignal, 32)
if err != nil {
return nil, err
}
currentBSS.Signal = float32(tmpFloat)
fmt.Printf("Signal: %s dBm\n", strSignal)
bsslist = append(bsslist, currentBSS)
}
return bsslist, nil
}//ToDo: Create netlink based implementation (not relying on 'iw'): low priority
func (wsvc WiFiService) UpdateStateFromIw() (err error) {
proc := exec.Command("/sbin/iw", "dev", wsvc.IfaceName, "info")
res, err := proc.CombinedOutput()
if err != nil {
return errors.New(fmt.Sprintf("Error fetching wifi info: '%s'\niw output: %s", err, res))
}
/*
AP
--
Interface wlan0
ifindex 2
wdev 0x1
addr b8:27:eb:71:bb:bc
ssid \xf0\x9f\x92\xa5\xf0\x9f\x96\xa5\xf0\x9f\x92\xa5 \xe2\x93\x85\xe2\x9e\x83\xe2\x93\x8c\xe2\x93\x83\xf0\x9f\x85\x9f\xe2\x9d\xb6
type AP
wiphy 0
channel 2 (2417 MHz), width: 20 MHz, center1: 2417 MHz
txpower 31.00 dBm
NOT CONNECTED
-------------
Interface wlan0
ifindex 2
wdev 0x1
addr b8:27:eb:71:bb:bc
type managed
wiphy 0
channel 2 (2417 MHz), width: 20 MHz, center1: 2417 MHz
txpower 31.00 dBm
CONNECTED
-----------
Interface wlan0
ifindex 2
wdev 0x1
addr b8:27:eb:71:bb:bc
ssid WLAN-579086
type managed
wiphy 0
channel 6 (2437 MHz), width: 20 MHz, center1: 2437 MHz
txpower 31.00 dBm
*/
output := string(res)
//split into BSS sections
reSsid := regexp.MustCompile("(?m)ssid (.*)\n")
reMode := regexp.MustCompile("(?m)type (.*)\n")
reChannel := regexp.MustCompile("(?m)channel ([0-9]+) .*\n")
strSsid_sub := reSsid.FindStringSubmatch(output)
strSsid := ""
if len(strSsid_sub) > 1 {
unSsid,uerr := strconv.Unquote(fmt.Sprintf("\"%s\"", strSsid_sub[1]))
if uerr == nil {
strSsid = unSsid
} else {
fmt.Println("Unquote error", uerr)
}
}
// fmt.Printf("SSID: %s\n", strSsid)
strChannel_sub := reChannel.FindStringSubmatch(output)
strChannel := "0"
if len(strChannel_sub) > 1 {
strChannel = strChannel_sub[1]
}
// fmt.Printf("Channel: %s\n", strChannel)
strMode_sub := reMode.FindStringSubmatch(output)
strMode := "0"
if len(strMode_sub) > 1 {
strMode = strMode_sub[1]
}
switch strings.ToLower(strMode) {
case "ap":
wsvc.State.Mode = pb.WiFiStateMode_AP_UP
default:
wsvc.State.Mode = pb.WiFiStateMode_STA_NOT_CONNECTED
}
if len(strSsid) > 0 {
wsvc.State.Ssid = strSsid
if wsvc.State.Mode == pb.WiFiStateMode_STA_NOT_CONNECTED {
wsvc.State.Mode = pb.WiFiStateMode_STA_CONNECTED // when a SSID is present, the wifi interface is connected to an AP
}
}
intCh := 0
intCh,_ = strconv.Atoi(strChannel)
wsvc.State.Channel = uint32(intCh)
return nil
}
2018-09-13 01:07:57 +02:00
func wifiSetReg(reg string) (err error) {
if len(reg) == 0 {
reg = "US" //default
log.Printf("No ISO/IEC 3166-1 alpha2 regulatory domain provided, defaulting to '%s'\n", reg)
}
reg = strings.ToUpper(reg)
proc := exec.Command("/sbin/iw", "reg", "set", reg)
err = proc.Run()
if err != nil {
return err
}
log.Printf("Notified kernel to use ISO/IEC 3166-1 alpha2 regulatory domain '%s'\n", reg)
return nil
}
func ProcSoftKill(cmd *exec.Cmd, timeToKill time.Duration) (err error) {
if cmd.Process == nil {
// process already dead
return nil
}
//send SIGTERM for softkill
cmd.Process.Signal(syscall.SIGTERM)
//we wait for process to exit or issue SIGKILL after timeout
hasExitted := make(chan interface{},0)
go func() {
cmd.Process.Wait() // even if waite ends with error, the process should have died
//fmt.Println("WAIT RES ENDED")
close(hasExitted)
}()
select {
case <- hasExitted:
//fmt.Println("HAS EXITED")
return nil
case <-time.After(timeToKill):
//timeout exceeded, send SIGKILL
//fmt.Println("TIMEOUT, SENDING SIGKILL")
cmd.Process.Kill()
return nil
}
}