WiFi: error fixes for process wrapping of hostapd,wpa_supplicant; new util lib

This commit is contained in:
MaMe82 2018-09-07 15:50:18 +02:00
parent df003e5eb5
commit 4764dd4d57
4 changed files with 535 additions and 388 deletions

View File

@ -11,14 +11,13 @@ import (
"github.com/mame82/P4wnP1_go/common_web"
)
type EventManager struct {
eventQueue chan *pb.Event
ctx context.Context
cancel context.CancelFunc
ctx context.Context
cancel context.CancelFunc
registeredReceivers map[*EventReceiver]bool
receiverDeleteList map[*EventReceiver]bool
registeredReceivers map[*EventReceiver]bool
receiverDeleteList map[*EventReceiver]bool
receiverRegisterList map[*EventReceiver]bool
receiverDelListMutex *sync.Mutex
receiverRegListMutex *sync.Mutex
@ -26,12 +25,12 @@ type EventManager struct {
func NewEventManager(queueSize int) *EventManager {
EvMgr := &EventManager{
eventQueue: make(chan *pb.Event, queueSize),
eventQueue: make(chan *pb.Event, queueSize),
receiverDelListMutex: &sync.Mutex{},
receiverRegListMutex: &sync.Mutex{},
receiverRegisterList: make(map[*EventReceiver]bool),
registeredReceivers: make(map[*EventReceiver]bool),
receiverDeleteList: make(map[*EventReceiver]bool),
registeredReceivers: make(map[*EventReceiver]bool),
receiverDeleteList: make(map[*EventReceiver]bool),
}
EvMgr.ctx, EvMgr.cancel = context.WithCancel(context.Background())
return EvMgr
@ -50,24 +49,23 @@ func (evm *EventManager) Stop() {
func (em *EventManager) Emit(event *pb.Event) {
//fmt.Printf("Emitting event: %+v\n", event)
em.eventQueue <-event
em.eventQueue <- event
}
func (em *EventManager) Write(p []byte) (n int, err error) {
ev := ConstructEventLog("logWriter", 1, string(p))
em.Emit(ev)
return len(p),nil
return len(p), nil
}
func (em *EventManager) RegisterReceiver(filterEventType int64) *EventReceiver {
// fmt.Println("!!!Event listener registered for " + strconv.Itoa(int(filterEventType)))
// fmt.Println("!!!Event listener registered for " + strconv.Itoa(int(filterEventType)))
ctx,cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(context.Background())
er := &EventReceiver{
EventQueue: make(chan *pb.Event, 10), //allow buffering 10 events per receiver
Ctx: ctx,
Cancel: cancel,
EventQueue: make(chan *pb.Event, 10), //allow buffering 10 events per receiver
Ctx: ctx,
Cancel: cancel,
FilterEventType: filterEventType,
}
fmt.Printf("Registered receiver for %d\n", er.FilterEventType)
@ -86,10 +84,10 @@ func (em *EventManager) UnregisterReceiver(receiver *EventReceiver) {
func (em *EventManager) dispatch() {
fmt.Println("Started event dispatcher")
loop:
loop:
for {
select {
case evToDispatch := <- em.eventQueue:
case evToDispatch := <-em.eventQueue:
// distribute to registered receiver
// Note: no mutex on em.registeredReceivers needed, only accessed in this method
for receiver := range em.registeredReceivers {
@ -137,29 +135,24 @@ func (em *EventManager) dispatch() {
fmt.Println("Stopped event dispatcher")
}
type EventReceiver struct {
Ctx context.Context
Cancel context.CancelFunc
EventQueue chan *pb.Event
Ctx context.Context
Cancel context.CancelFunc
EventQueue chan *pb.Event
FilterEventType int64
}
func ConstructEventLog(source string, level int, message string) *pb.Event {
tJson,_ := time.Now().MarshalJSON()
tJson, _ := time.Now().MarshalJSON()
return &pb.Event{
Type: common_web.EVT_LOG,
Values: []*pb.EventValue{
{Val: &pb.EventValue_Tstring{Tstring:source} },
{Val: &pb.EventValue_Tint64{Tint64:int64(level)} },
{Val: &pb.EventValue_Tstring{Tstring:message} },
{Val: &pb.EventValue_Tstring{Tstring:string(tJson)} },
{Val: &pb.EventValue_Tstring{Tstring: source}},
{Val: &pb.EventValue_Tint64{Tint64: int64(level)}},
{Val: &pb.EventValue_Tstring{Tstring: message}},
{Val: &pb.EventValue_Tstring{Tstring: string(tJson)}},
},
}
}
@ -178,24 +171,25 @@ func ConstructEventHID(hidEvent hid.Event) *pb.Event {
hasError = true
errString = job.ResultErr.Error()
}
resString,_ = job.ResultJsonString()
resString, _ = job.ResultJsonString()
}
if eVM := hidEvent.Vm; eVM != nil {
vmID = eVM.Id
}
if eVM := hidEvent.Vm; eVM != nil { vmID = eVM.Id }
tJson,_ := time.Now().MarshalJSON()
tJson, _ := time.Now().MarshalJSON()
return &pb.Event{
Type: common_web.EVT_HID, //Type
Values: []*pb.EventValue{
{Val: &pb.EventValue_Tint64{Tint64:int64(hidEvent.Type)} }, //SubType = Type of hid.Event
{Val: &pb.EventValue_Tint64{Tint64:int64(vmID)} }, //ID of VM
{Val: &pb.EventValue_Tint64{Tint64:int64(jobID)} }, //ID of job
{Val: &pb.EventValue_Tbool{Tbool:hasError} }, //isError (f.e. if a job was interrupted)
{Val: &pb.EventValue_Tstring{Tstring:resString} }, //result String
{Val: &pb.EventValue_Tstring{Tstring:errString} }, //error String (message in case of error)
{Val: &pb.EventValue_Tstring{Tstring:message} }, //Mesage text of event
{Val: &pb.EventValue_Tstring{Tstring:string(tJson)} },//Timestamp of event genration
{Val: &pb.EventValue_Tint64{Tint64: int64(hidEvent.Type)}}, //SubType = Type of hid.Event
{Val: &pb.EventValue_Tint64{Tint64: int64(vmID)}}, //ID of VM
{Val: &pb.EventValue_Tint64{Tint64: int64(jobID)}}, //ID of job
{Val: &pb.EventValue_Tbool{Tbool: hasError}}, //isError (f.e. if a job was interrupted)
{Val: &pb.EventValue_Tstring{Tstring: resString}}, //result String
{Val: &pb.EventValue_Tstring{Tstring: errString}}, //error String (message in case of error)
{Val: &pb.EventValue_Tstring{Tstring: message}}, //Mesage text of event
{Val: &pb.EventValue_Tstring{Tstring: string(tJson)}}, //Timestamp of event genration
},
}
}

90
service/util/signal.go Normal file
View File

@ -0,0 +1,90 @@
package util
import (
"sync"
"time"
"errors"
)
type Signal struct {
isSet bool
autoReset bool
*sync.Mutex
chNotSet chan interface{} // channel is open, when signal isn't set
}
func NewSignal(isSet, autoReset bool) (s *Signal) {
s = &Signal{
Mutex: &sync.Mutex{},
isSet: isSet,
autoReset: autoReset,
chNotSet: make(chan interface{}, 0),
}
if s.isSet {
s.Lock()
defer s.Unlock()
s.isSet = true
close(s.chNotSet)
}
return
}
func (s *Signal) Set() {
// if signaled, the channel has to be closed
// we can't test if the channel is already closed (without waiting with select), so we keep track of the state
// in isSet, to avoid closing multiple times
s.Lock()
defer s.Unlock()
if s.isSet {
return
}
s.isSet = true
close(s.chNotSet)
return
}
func (s *Signal) Reset() {
// in reset state, the channel has to exist, but mustn't be recreated if already existing (already in unset state)
s.Lock()
defer s.Unlock()
if s.isSet {
// channel shouldn't exist
s.chNotSet = make(chan interface{}, 0)
s.isSet = false
}
return
}
func (s Signal) IsSet() bool {
return s.isSet
}
func (s *Signal) Wait() {
select {
case <-s.chNotSet: // when channel isn't closed, this blocks
// if autoReset, recreate channel (setting signal to off)
if s.autoReset {
s.Lock()
s.chNotSet = make(chan interface{}, 0)
s.isSet = false
s.Unlock()
}
}
return
}
func (s *Signal) WaitTimeout(timeout time.Duration) error {
select {
case <-s.chNotSet: // when channel isn't closed, this blocks
// if autoReset, recreate channel (setting signal to off)
if s.autoReset {
s.Lock()
s.chNotSet = make(chan interface{}, 0)
s.isSet = false
s.Unlock()
}
return nil
case <-time.After(timeout):
return errors.New("Timeout reached")
}
}

66
service/util/teelogger.go Normal file
View File

@ -0,0 +1,66 @@
package util
import (
"io"
"log"
"sync"
"os"
)
type TeeLogger struct {
*sync.Mutex
*log.Logger
outs []io.Writer
LogWriter io.Writer
}
type sublogger struct {
*TeeLogger
}
// struct to present an additional io.Writer, wrapping TeeLogger to ise its Print() method
func (sl sublogger) Write(p []byte) (n int, err error) {
sl.TeeLogger.Print(string(p))
return len(p), nil
}
func NewTeeLogger(addStdout bool) (res *TeeLogger) {
res = &TeeLogger{
Mutex: &sync.Mutex{},
Logger: &log.Logger{},
outs: make([]io.Writer,0),
}
if addStdout {
res.AddOutput(os.Stdout)
}
// Create a sub-struct which presents a new writer used to prepend the PREFIX in front of written data (the TeeLogger's own io.Writer is used for tee'ing)
res.LogWriter = sublogger{ TeeLogger: res }
res.SetFlags(log.Ltime)
res.SetOutput(res)
return res
}
func (tl *TeeLogger) AddOutput(out io.Writer) {
tl.Lock()
defer tl.Unlock()
tl.outs = append(tl.outs, out)
}
func (tl *TeeLogger) Write(p []byte) (n int, err error) {
outmsg := []byte(string(p))
tl.Lock()
defer tl.Unlock()
for _,out := range tl.outs {
for written:=0; written < len(outmsg); {
len,err := out.Write(outmsg)
if err != nil { return written, err }
written += len
}
}
return len(p), nil
}

View File

@ -3,7 +3,8 @@ package service
import (
pb "github.com/mame82/P4wnP1_go/proto"
"log"
"github.com/docker/libcontainer/netlink"
"github.com/mame82/P4wnP1_go/netlink"
"github.com/mame82/P4wnP1_go/service/util"
"net"
"errors"
"fmt"
@ -11,13 +12,11 @@ import (
"strings"
"os"
"io/ioutil"
"strconv"
"syscall"
"time"
"bufio"
"io"
"regexp"
"sync"
"regexp"
"strconv"
)
const (
@ -28,6 +27,7 @@ const (
//VERY LOW PRIORITY, as this basically means reimplementing the whole toolset for a way too small benefit
type WiFiAuthMode int
const (
WiFiAuthMode_OPEN WiFiAuthMode = iota
//WiFiAuthMode_WEP
@ -41,28 +41,180 @@ const (
)
type WifiState struct {
Settings *pb.WiFiSettings
CmdWpaSupplicant *exec.Cmd
mutexWpaSupplicant *sync.Mutex
IfaceName string
PathWpaSupplicantConf string
mutexSettings *sync.Mutex
Settings *pb.WiFiSettings
CmdWpaSupplicant *exec.Cmd
mutexWpaSupplicant *sync.Mutex
CmdHostapd *exec.Cmd
mutexHostapd *sync.Mutex
IfaceName string
PathWpaSupplicantConf string
PathHostapdConf string
LoggerHostapd *util.TeeLogger
LoggerWpaSupplicant *util.TeeLogger
OutMonitorWpaSupplicant *wpaSupplicantOutMonitor
}
func NewWifiState(startupSettings *pb.WiFiSettings, ifName string) (res *WifiState) {
if !wifiWpaSupplicantAvailable() {
panic("wpa_supplicant seems to be missing, please install it")
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() {
return n, nil
}
res = &WifiState{
IfaceName: ifName,
Settings: startupSettings,
CmdWpaSupplicant: nil,
mutexWpaSupplicant: &sync.Mutex{},
PathWpaSupplicantConf: confFileWpaSupplicant(ifName),
// 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"):
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{},
}
}
func NewWifiState(startupSettings *pb.WiFiSettings, ifName string) (res *WifiState) {
if !binaryAvailable("wpa_supplicant") {
panic("wpa_supplicant seems to be missing, please install it")
}
// to create wpa_supplicant.conf
if !binaryAvailable("wpa_passphrase") {
panic("wpa_passphrase seems to be missing, please install it")
}
if !binaryAvailable("hostapd") {
panic("hostapd seems to be missing, please install it")
}
// for wifiScan
if !binaryAvailable("iw") {
panic("The tool 'iw' seems to be missing, please install it")
}
res = &WifiState{
mutexSettings: &sync.Mutex{},
IfaceName: ifName,
Settings: startupSettings,
CmdWpaSupplicant: nil,
mutexWpaSupplicant: &sync.Mutex{},
CmdHostapd: nil,
mutexHostapd: &sync.Mutex{},
PathWpaSupplicantConf: fmt.Sprintf("/tmp/wpa_supplicant_%s.conf", ifName),
PathHostapdConf: fmt.Sprintf("/tmp/hostapd_%s.conf", ifName),
}
res.OutMonitorWpaSupplicant = NewWpaSupplicantOutMonitor()
res.LoggerHostapd = util.NewTeeLogger(true)
res.LoggerHostapd.SetPrefix("hostapd: ")
res.LoggerWpaSupplicant = util.NewTeeLogger(true)
res.LoggerWpaSupplicant.SetPrefix("wpa_supplicant: ")
res.LoggerWpaSupplicant.AddOutput(res.OutMonitorWpaSupplicant) // add watcher too tee'ed output writers
return
}
func (wifiState *WifiState) StartHostapd() (err error) {
log.Printf("Starting hostapd for interface '%s'...\n", wifiState.IfaceName)
wifiState.mutexHostapd.Lock()
defer wifiState.mutexHostapd.Unlock()
//check if interface is valid
if_exists, _ := CheckInterfaceExistence(wifiState.IfaceName)
if !if_exists {
return errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", wifiState.IfaceName))
}
//stop hostapd if already running
if wifiState.CmdHostapd != nil {
// avoid deadlock
wifiState.mutexHostapd.Unlock()
wifiState.StopHostapd()
wifiState.mutexHostapd.Lock()
}
//We use the run command and allow hostapd to daemonize
//wifiState.CmdHostapd = exec.Command("/usr/sbin/hostapd", "-f", logFileHostapd(wifiState.IfaceName), wifiState.PathHostapdConf)
wifiState.CmdHostapd = exec.Command("/usr/sbin/hostapd", wifiState.PathHostapdConf)
wifiState.CmdHostapd.Stdout = wifiState.LoggerHostapd.LogWriter
wifiState.CmdHostapd.Stderr = wifiState.LoggerHostapd.LogWriter
err = wifiState.CmdHostapd.Start()
if err != nil {
//bytes, _ := wifiState.CmdHostapd.CombinedOutput()
//println(string(bytes))
wifiState.CmdHostapd.Wait()
return errors.New(fmt.Sprintf("Error starting hostapd '%v'", err))
}
log.Printf("... hostapd for interface '%s' started\n", wifiState.IfaceName)
return nil
}
func (wifiState *WifiState) StopHostapd() (err error) {
eSuccess := fmt.Sprintf("... hostapd for interface '%s' stopped", wifiState.IfaceName)
eCantStop := fmt.Sprintf("... couldn't terminate hostapd for interface '%s'", wifiState.IfaceName)
wifiState.mutexHostapd.Lock()
defer wifiState.mutexHostapd.Unlock()
if wifiState.CmdHostapd == nil {
log.Printf("... hostapd for interface '%s' isn't running, no need to stop it\n", wifiState.IfaceName)
return nil
}
wifiState.CmdHostapd.Process.Signal(syscall.SIGTERM)
wifiState.CmdHostapd.Wait()
if !wifiState.CmdHostapd.ProcessState.Exited() {
log.Printf("... hostapd didn't react on SIGTERM for interface '%s', trying SIGKILL\n", wifiState.IfaceName)
wifiState.CmdHostapd.Process.Kill()
time.Sleep(500 * time.Millisecond)
if wifiState.CmdHostapd.ProcessState.Exited() {
wifiState.CmdHostapd = nil
log.Println(eSuccess)
return nil
} else {
log.Println(eCantStop)
return errors.New(eCantStop)
}
}
wifiState.CmdHostapd = nil
log.Println(eSuccess)
return nil
}
func (wifiState *WifiState) StopWpaSupplicant() (err error) {
eSuccess := fmt.Sprintf("... wpa_supplicant for interface '%s' stopped", wifiState.IfaceName)
@ -78,13 +230,14 @@ func (wifiState *WifiState) StopWpaSupplicant() (err error) {
return nil
}
log.Printf("... sending SIGTERM for wpa_supplicant on interface '%s' with PID\n", wifiState.IfaceName, wifiState.CmdWpaSupplicant.Process.Pid)
wifiState.CmdWpaSupplicant.Process.Signal(syscall.SIGTERM)
wifiState.CmdWpaSupplicant.Wait()
if !wifiState.CmdWpaSupplicant.ProcessState.Exited() {
log.Printf("... wpa_supplicant didn't react on SIGTERM for interface '%s', trying SIGKILL\n", wifiState.IfaceName)
wifiState.CmdWpaSupplicant.Process.Kill()
time.Sleep(500*time.Millisecond)
time.Sleep(500 * time.Millisecond)
if wifiState.CmdWpaSupplicant.ProcessState.Exited() {
wifiState.CmdWpaSupplicant = nil
log.Println(eSuccess)
@ -100,7 +253,6 @@ func (wifiState *WifiState) StopWpaSupplicant() (err error) {
return nil
}
func (wifiState *WifiState) StartWpaSupplicant(timeout time.Duration) (err error) {
log.Printf("Starting wpa_supplicant for interface '%s'...\n", wifiState.IfaceName)
@ -108,139 +260,160 @@ func (wifiState *WifiState) StartWpaSupplicant(timeout time.Duration) (err error
defer wifiState.mutexWpaSupplicant.Unlock()
//check if interface is valid
if_exists,_ := CheckInterfaceExistence(wifiState.IfaceName)
if_exists, _ := CheckInterfaceExistence(wifiState.IfaceName)
if !if_exists {
return errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", wifiState.IfaceName))
}
//stop wpa_supplicant if already running
if wifiState.CmdWpaSupplicant != nil {
// avoid dead lock
wifiState.mutexWpaSupplicant.Unlock()
wifiState.StopWpaSupplicant()
wifiState.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
wifiState.CmdWpaSupplicant = exec.Command("/sbin/wpa_supplicant", "-c", wifiState.PathWpaSupplicantConf, "-i", wifiState.IfaceName)
wpa_stdout, err := wifiState.CmdWpaSupplicant.StdoutPipe()
if err != nil { return err}
wifiState.CmdWpaSupplicant.Stdout = wifiState.LoggerWpaSupplicant.LogWriter
err = wifiState.CmdWpaSupplicant.Start()
if err != nil { return err}
//result channel
wpa_res := make(chan string, 1)
defer close(wpa_res)
//start output parser
wpa_stdout_reader := bufio.NewReader(wpa_stdout)
go wifiWpaSupplicantOutParser(wpa_res, wpa_stdout_reader)
//analyse output
select {
case res := <-wpa_res:
if strings.Contains(res, "CONNECTED") {
//We could return success and keep wpa_supplicant running
log.Println("... connected to given WiFi network, wpa_supplicant running")
return nil
}
if strings.Contains(res, "WRONG_KEY") {
//we stop wpa_supplicant and return err
log.Println("... seems the wrong PSK wwas provided for the given WiFi network, stopping wpa_supplicant ...")
//wifiStopWpaSupplicant(nameIface)
log.Println("... killing wpa_supplicant")
wifiState.StopWpaSupplicant()
return errors.New("Wrong PSK")
}
case <- time.After(timeout):
//we stop wpa_supplicant and return err
log.Printf("... wpa_supplicant reached timeout of '%v' without beeing able to connect to given network\n", timeout)
log.Println("... killing wpa_supplicant")
wifiState.StopWpaSupplicant()
return errors.New("TIMEOUT REACHED")
if err != nil {
return err
}
//wait for result in output
connected, errcon := wifiState.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
wifiState.mutexWpaSupplicant.Unlock()
wifiState.StopWpaSupplicant()
wifiState.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
wifiState.mutexWpaSupplicant.Unlock()
wifiState.StopWpaSupplicant()
wifiState.mutexWpaSupplicant.Lock()
return errors.New("Wrong PSK")
}
return nil
}
type BSS struct {
SSID string
BSSID net.HardwareAddr
Frequency int
SSID string
BSSID net.HardwareAddr
Frequency int
BeaconInterval time.Duration //carefull, on IE level beacon interval isn't meassured in milliseconds
AuthMode WiFiAuthMode
Signal float32 //Signal strength in dBm
AuthMode WiFiAuthMode
Signal float32 //Signal strength in dBm
}
func (state WifiState) GetDeployWifiSettings() (ws *pb.WiFiSettings,err error) {
func (state WifiState) GetDeployWifiSettings() (ws *pb.WiFiSettings, err error) {
return state.Settings, nil
}
func (state *WifiState) DeployWifiSettings(ws *pb.WiFiSettings) (err error) {
// ToDo: Lock state while setting up
log.Printf("Trying to deploy WiFi settings:\n%v\n", ws)
func (state *WifiState) DeployWifiSettings(newWifiSettings *pb.WiFiSettings) (err error) {
log.Printf("Trying to deploy WiFi settings:\n%v\n", newWifiSettings)
ifName := wifi_if_name
state.mutexSettings.Lock()
defer state.mutexSettings.Unlock()
//Get Interface
iface, err := net.InterfaceByName(ifName)
if err != nil {
return errors.New(fmt.Sprintf("No WiFi interface present: %v\n", err))
}
if ws.DisableNexmon {
firmwareChange := false
if newWifiSettings.DisableNexmon {
//load legacy driver + firmware
if wifiIsNexmonLoaded() {
err = wifiLoadLegacy()
if err != nil {return}
if err != nil {
return
}
firmwareChange = true
}
} else {
//load nexmon driver + firmware
if !wifiIsNexmonLoaded() {
err = wifiLoadNexmon()
if err != nil {return}
if err != nil {
return
}
firmwareChange = true
}
}
if ws.Disabled {
log.Printf("Setting WiFi interface %s to DOWN\n", iface.Name)
err = netlink.NetworkLinkDown(iface)
} else {
log.Printf("Setting WiFi interface %s to UP\n", iface.Name)
err = netlink.NetworkLinkUp(iface)
if firmwareChange {
ReInitNetworkInterface(ifName)
}
linkStateChange := false
currentlyEnabled, errstate := netlink.NetworkLinkGetStateUp(iface)
if errstate != nil {
linkStateChange = true
} // current link state couldn't be retireved, regard as changed
if currentlyEnabled == newWifiSettings.Disabled {
linkStateChange = true
} //Is disabled and should be enabled, or the other way around
if linkStateChange || firmwareChange { // Enable/Disable if only if needed
// ToDo: the new interface state isn't reflected to respective ethernet settings
if newWifiSettings.Disabled {
log.Printf("Setting WiFi interface %s to DOWN\n", iface.Name)
err = netlink.NetworkLinkDown(iface)
} else {
log.Printf("Setting WiFi interface %s to UP\n", iface.Name)
err = netlink.NetworkLinkUp(iface)
}
}
//set proper regulatory dom
err = wifiSetReg(ws.Reg)
err = wifiSetReg(newWifiSettings.Reg)
if err != nil {
log.Printf("Error setting WiFi regulatory domain '%s': %v\n", ws.Reg, err) //we don't abort on error here
log.Printf("Error setting WiFi regulatory domain '%s': %v\n", newWifiSettings.Reg, err) //we don't abort on error here
}
//stop wpa_supplicant if needed
state.StopWpaSupplicant()
//kill hostapd in case it is still running
err = wifiStopHostapd(ifName)
if err != nil { return err } // ToDo: returning at this point is a bit harsh
err = state.StopHostapd()
if err != nil {
return err // ToDo: returning at this point is a bit harsh
}
switch ws.Mode {
switch newWifiSettings.Mode {
case pb.WiFiSettings_AP:
//generate hostapd.conf (overwrite old one)
hostapdCreateConfigFile(ws, confFileHostapd(ifName))
hostapdCreateConfigFile(newWifiSettings, state.PathHostapdConf)
//start hostapd
err = wifiStartHostapd(ifName)
if err != nil { return err }
err = state.StartHostapd()
if err != nil {
return err
}
case pb.WiFiSettings_STA:
if ws.BssCfgClient == nil { return errors.New("Error: WiFi mode set to station (STA) but no BSS configuration for target WiFi provided")}
if len(ws.BssCfgClient.SSID) == 0 { return errors.New("Error: WiFi mode set to station (STA) but no SSID provided to identify BSS to join")}
if newWifiSettings.BssCfgClient == nil {
return errors.New("Error: WiFi mode set to station (STA) but no BSS configuration for target WiFi provided")
}
if len(newWifiSettings.BssCfgClient.SSID) == 0 {
return errors.New("Error: WiFi mode set to station (STA) but no SSID provided to identify BSS to join")
}
//scan for provided wifi
scanres, err := WifiScan(ifName)
@ -248,38 +421,38 @@ func (state *WifiState) DeployWifiSettings(ws *pb.WiFiSettings) (err error) {
return errors.New(fmt.Sprintf("Scanning for existing WiFi networks failed: %v", err))
}
var matchingBss *BSS = nil
for _,bss := range scanres {
if bss.SSID == ws.BssCfgClient.SSID {
for _, bss := range scanres {
if bss.SSID == newWifiSettings.BssCfgClient.SSID {
matchingBss = &bss
break
}
}
if matchingBss == nil {
return errors.New(fmt.Sprintf("SSID not found during scan: '%s'", ws.BssCfgClient.SSID))
return errors.New(fmt.Sprintf("SSID not found during scan: '%s'", newWifiSettings.BssCfgClient.SSID))
}
if len(ws.BssCfgClient.PSK) == 0 && matchingBss.AuthMode != WiFiAuthMode_OPEN {
if len(newWifiSettings.BssCfgClient.PSK) == 0 && matchingBss.AuthMode != WiFiAuthMode_OPEN {
//seems we try to connect an OPEN AUTHENTICATION network, but the existing BSS isn't OPEN AUTH
return errors.New(fmt.Sprintf("WiFi SSID '%s' found during scan, but authentication mode isn't OPEN and no PSK was provided", ws.BssCfgClient.SSID))
return errors.New(fmt.Sprintf("WiFi SSID '%s' found during scan, but authentication mode isn't OPEN and no PSK was provided", newWifiSettings.BssCfgClient.SSID))
} else {
err = WifiCreateWpaSupplicantConfigFile(ws.BssCfgClient.SSID, ws.BssCfgClient.PSK, confFileWpaSupplicant(wifi_if_name))
if err != nil { return err }
err = WifiCreateWpaSupplicantConfigFile(newWifiSettings.BssCfgClient.SSID, newWifiSettings.BssCfgClient.PSK, state.PathWpaSupplicantConf)
if err != nil {
return err
}
//ToDo: proper error handling, in case connection not possible
err = state.StartWpaSupplicant(WPA_SUPPLICANT_CONNECT_TIMEOUT)
if err != nil { return err }
if err != nil {
return err
}
}
}
log.Printf("... WiFi settings deployed successfully, checking for stored interface configuration...\n")
// store new state
state.Settings = ws
state.Settings = newWifiSettings
ReInitNetworkInterface(ifName)
return nil
}
@ -300,7 +473,7 @@ func wifiLoadLegacy() error {
func wifiSetReg(reg string) (err error) {
if len(reg) == 0 {
reg = "US" //default
reg = "US" //default
log.Printf("No ISO/IEC 3166-1 alpha2 regulatory domain provided, defaulting to '%s'\n", reg)
}
@ -308,19 +481,19 @@ func wifiSetReg(reg string) (err error) {
proc := exec.Command("/sbin/iw", "reg", "set", reg)
err = proc.Run()
if err != nil { return err}
if err != nil {
return err
}
log.Printf("Notified kernel to use ISO/IEC 3166-1 alpha2 regulatory domain '%s'\n", reg)
return nil
}
func WifiScan(ifName string) (result []BSS, err error) {
if !wifiIwAvailable() { return nil,errors.New("The tool 'iw' is missing, please install it to make this work")}
proc := exec.Command("/sbin/iw", ifName, "scan")
res, err := proc.CombinedOutput()
if err != nil {
return nil,errors.New(fmt.Sprintf("Error running scan: '%s'\niw outpur: %s", err, res))
return nil, errors.New(fmt.Sprintf("Error running scan: '%s'\niw outpur: %s", err, res))
}
result, err = ParseIwScan(string(res))
@ -331,29 +504,28 @@ func WifiScan(ifName string) (result []BSS, err error) {
func WifiCreateWpaSupplicantConfigFile(ssid string, psk string, filename string) (err error) {
log.Printf("Creating wpa_suuplicant configuration file at '%s'\n", filename)
fileContent, err := wifiCreateWpaSupplicantConfString(ssid, psk)
if err != nil {return}
if err != nil {
return
}
err = ioutil.WriteFile(filename, []byte(fileContent), os.ModePerm)
return
}
func wifiCreateWpaSupplicantConfString(ssid string, psk string) (config string, err error) {
if !wifiWpaPassphraseAvailable() { return "",errors.New("The tool 'wpa_passphrase' is missing, please install it to make this work")}
// if a PSK is provided, we assume it is needed, otherwise we assum OPEN AUTHENTICATION
if len(psk) > 0 {
fmt.Println("Connecting WiFi with PSK")
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)))
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")
fmt.Println("Connecting WiFi with OPEN AUTH")
config = fmt.Sprintf(
`network={
`network={
ssid="%s"
key_mgmt=NONE
}`, ssid)
@ -362,7 +534,6 @@ fmt.Println("Connecting WiFi with OPEN AUTH")
config = "ctrl_interface=/run/wpa_supplicant\n" + config
return
}
@ -377,24 +548,24 @@ func wifiCreateHostapdConfString(ws *pb.WiFiSettings) (config string, err error)
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("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("macaddr_acl=0\n") //Accept all MAC addresses
config += fmt.Sprintf("ssid=%s\n", ws.BssCfgAP.SSID)
config += fmt.Sprintf("channel=%d\n", ws.ApChannel)
if ws.AuthMode == pb.WiFiSettings_WPA2_PSK {
config += fmt.Sprintf("auth_algs=1\n") //Use WPA authentication
config += fmt.Sprintf("wpa=2\n") //Use WPA2
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.BssCfgAP.PSK) //Set PSK
config += fmt.Sprintf("rsn_pairwise=CCMP\n") //Use Use AES, instead of TKIP
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
}
@ -408,214 +579,16 @@ func wifiCreateHostapdConfString(ws *pb.WiFiSettings) (config string, err error)
return
}
func hostapdCreateConfigFile(s *pb.WiFiSettings, filename string) (err error) {
log.Printf("Creating hostapd configuration file at '%s'\n", filename)
fileContent, err := wifiCreateHostapdConfString(s)
if err != nil {return}
if err != nil {
return
}
err = ioutil.WriteFile(filename, []byte(fileContent), os.ModePerm)
return
}
func wifiWpaSupplicantAvailable() bool {
return binaryAvailable("wpa_supplicant")
}
func wifiWpaPassphraseAvailable() bool {
return binaryAvailable("wpa_passphrase")
}
func wifiHostapdAvailable() bool {
return binaryAvailable("hostapd")
}
func wifiIwAvailable() bool {
return binaryAvailable("iw")
}
func wifiStartHostapd(nameIface string) (err error) {
log.Printf("Starting hostapd for interface '%s'...\n", nameIface)
//check if interface is valid
if_exists,_ := CheckInterfaceExistence(nameIface)
if !if_exists {
return errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", nameIface))
}
if !wifiHostapdAvailable() {
return errors.New("hostapd seems to be missing, please install it")
}
confpath := confFileHostapd(nameIface)
//stop hostapd if already running
wifiStopHostapd(nameIface)
//We use the run command and allow hostapd to daemonize
proc := exec.Command("/usr/sbin/hostapd", "-B", "-P", pidFileHostapd(nameIface), "-f", logFileHostapd(nameIface), confpath)
err = proc.Run()
if err != nil {
return errors.New(fmt.Sprintf("Error starting hostapd '%v'", err))
}
log.Printf("... hostapd for interface '%s' started\n", nameIface)
return nil
}
func wifiStopHostapd(nameIface string) (err error) {
log.Printf("... stop running hostapd processes for interface '%s'\n", nameIface)
running,pid,err := wifiIsHostapdRunning(wifi_if_name)
if err != nil { return err }
if !running {
log.Printf("... hostapd for interface '%s' isn't running, no need to stop it\n", nameIface)
return nil
}
//kill the pid
err = syscall.Kill(pid, syscall.SIGTERM)
if err != nil { return }
time.Sleep(500*time.Millisecond)
//check if stopped
running,pid,err = wifiIsHostapdRunning(nameIface)
if err != nil { return }
if (running) {
log.Printf("... couldn't terminate hostapd for interface '%s'\n", nameIface)
} else {
log.Printf("... hostapd for interface '%s' stopped\n", nameIface)
}
//Delete PID file
os.Remove(pidFileHostapd(nameIface))
return nil
}
func wifiWpaSupplicantOutParser(chanResult chan string, reader *bufio.Reader) {
log.Println("... Start monitoring wpa_supplicant output")
for {
line, _, err := reader.ReadLine()
if err != nil {
//in case wpa_supplicant is killed, we should land here, which ends the goroutine
if err != io.EOF {
log.Printf("Can't read wpa_supplicant output: %s\n", err)
}
break
}
strLine := string(line)
//fmt.Printf("Read:\n%s\n", strLine)
switch {
case strings.Contains(strLine, "WRONG_KEY"):
log.Printf("Seems the provided PSK doesn't match\n")
chanResult <- "WRONG_KEY"
break
case strings.Contains(strLine, "CTRL-EVENT-CONNECTED"):
log.Printf("Connected to target network\n")
chanResult <- "CONNECTED"
break // stop loop
}
}
log.Println("... stopped monitoring wpa_supplicant output")
}
func pidFileHostapd(nameIface string) string {
return fmt.Sprintf("/var/run/hostapd_%s.pid", nameIface)
}
func logFileHostapd(nameIface string) string {
return fmt.Sprintf("/tmp/hostapd_%s.log", nameIface)
}
func confFileHostapd(nameIface string) string {
return fmt.Sprintf("/tmp/hostapd_%s.conf", nameIface)
}
func confFileWpaSupplicant(nameIface string) string {
return fmt.Sprintf("/tmp/wpa_supplicant_%s.conf", nameIface)
}
func logFileWpaSupplicant(nameIface string) string {
return fmt.Sprintf("/tmp/wpa_supplicant_%s.log", nameIface)
}
func pidFileWpaSupplicant(nameIface string) string {
return fmt.Sprintf("/var/run/wpa_supplicant_%s.pid", nameIface)
}
func wifiIsHostapdRunning(nameIface string) (running bool, pid int, err error) {
pid_file := pidFileHostapd(nameIface)
//Check if the pidFile exists
if _, err := os.Stat(pid_file); os.IsNotExist(err) {
return false, 0,nil //file doesn't exist, so we assume hostapd isn't running
}
//File exists, read the PID
content, err := ioutil.ReadFile(pid_file)
if err != nil { return false, 0, err}
pid, err = strconv.Atoi(strings.TrimSuffix(string(content), "\n"))
if err != nil { return false, 0, errors.New(fmt.Sprintf("Error parsing PID file %s: %v", pid_file, err))}
//With PID given, check if the process is indeed running (pid_file could stay, even if the hostapd process has died already)
err_kill := syscall.Kill(pid, 0) //sig 0: doesn't send a signal, but error checking is still performed
switch err_kill{
case nil:
//ToDo: Check if the running process image is indeed hostapd
return true, pid, nil //Process is running
case syscall.ESRCH:
//Process doesn't exist
return false, pid, nil
case syscall.EPERM:
//process exists, but we have no access permission
return true, pid, err_kill
default:
return false, pid, err_kill
}
}
func wifiIsWpaSupplicantRunning(nameIface string) (running bool, pid int, err error) {
pid_file := pidFileWpaSupplicant(nameIface)
//Check if the pidFile exists
if _, err := os.Stat(pid_file); os.IsNotExist(err) {
return false, 0,nil //file doesn't exist, so we assume wpa_supplicant isn't running
}
//File exists, read the PID
content, err := ioutil.ReadFile(pid_file)
if err != nil { return false, 0, err}
pid, err = strconv.Atoi(strings.TrimSuffix(string(content), "\n"))
if err != nil { return false, 0, errors.New(fmt.Sprintf("Error parsing PID file %s: %v", pid_file, err))}
//With PID given, check if the process is indeed running (pid_file could stay, even if the wpa_supplicant process has died already)
err_kill := syscall.Kill(pid, 0) //sig 0: doesn't send a signal, but error checking is still performed
switch err_kill{
case nil:
//ToDo: Check if the running process image is indeed wpa_supplicant
return true, pid, nil //Process is running
case syscall.ESRCH:
//Process doesn't exist
return false, pid, nil
case syscall.EPERM:
//process exists, but we have no access permission
return true, pid, err_kill
default:
return false, pid, err_kill
}
}
//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)
@ -650,32 +623,44 @@ func ParseIwScan(scanresult string) (bsslist []BSS, err error) {
strBSSID := rp_bssid.FindString(strBSS)
fmt.Printf("BSSID: %s\n", strBSSID)
currentBSS.BSSID, err = net.ParseMAC(strBSSID)
if err != nil { return nil,err}
if err != nil {
return nil, err
}
//freq
strFreq_sub := rp_freq.FindStringSubmatch(strBSS)
strFreq := "0"
if len(strFreq_sub) > 1 { strFreq = strFreq_sub[1]}
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 }
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]}
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]}
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)
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
@ -684,11 +669,19 @@ func ParseIwScan(scanresult string) (bsslist []BSS, err error) {
//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 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}
if !rp_PSK.MatchString(strBSS) {
currentBSS.AuthMode = WiFiAuthMode_UNSUPPORTED
}
}
switch currentBSS.AuthMode {
case WiFiAuthMode_UNSUPPORTED:
@ -704,14 +697,18 @@ func ParseIwScan(scanresult string) (bsslist []BSS, err error) {
//signal
strSignal_sub := rp_signal.FindStringSubmatch(strBSS)
strSignal := "0.0"
if len(strSignal_sub) > 1 { strSignal = strSignal_sub[1]}
if len(strSignal_sub) > 1 {
strSignal = strSignal_sub[1]
}
tmpFloat, err := strconv.ParseFloat(strSignal, 32)
if err != nil { return nil, err }
if err != nil {
return nil, err
}
currentBSS.Signal = float32(tmpFloat)
fmt.Printf("Signal: %s dBm\n", strSignal)
bsslist = append(bsslist, currentBSS)
}
return bsslist,nil
}
return bsslist, nil
}