diff --git a/cmd/freifunkmanager/main.go b/cmd/freifunkmanager/main.go index 91b7e74..9508d6f 100644 --- a/cmd/freifunkmanager/main.go +++ b/cmd/freifunkmanager/main.go @@ -16,6 +16,7 @@ import ( "github.com/genofire/golang-lib/worker" configPackage "github.com/FreifunkBremen/freifunkmanager/config" + wifiController "github.com/FreifunkBremen/freifunkmanager/controller" "github.com/FreifunkBremen/freifunkmanager/runtime" "github.com/FreifunkBremen/freifunkmanager/ssh" "github.com/FreifunkBremen/freifunkmanager/websocket" @@ -43,14 +44,16 @@ func main() { commands = runtime.NewCommands(sshmanager) // nodesUpdateWorker := worker.NewWorker(time.Duration(3)*time.Minute, nodes.Updater) nodesSaveWorker := worker.NewWorker(time.Duration(3)*time.Second, nodes.Saver) + controller := wifiController.NewController(sshmanager) // go nodesUpdateWorker.Start() go nodesSaveWorker.Start() + go controller.Start() websocket.Start(nodes, commands) if config.Yanic.Enable { - yanicDialer := yanic.Dial(config.Yanic.Type, config.Yanic.Address) + yanicDialer = yanic.Dial(config.Yanic.Type, config.Yanic.Address) yanicDialer.NodeHandler = nodes.LearnNode yanicDialer.GlobalsHandler = func(data *runtimeYanic.GlobalStats) { stats = data @@ -90,6 +93,7 @@ func main() { // Stop services websocket.Close() srv.Close() + controller.Close() if config.Yanic.Enable { yanicDialer.Close() } diff --git a/controller/main.go b/controller/main.go new file mode 100644 index 0000000..ef946ad --- /dev/null +++ b/controller/main.go @@ -0,0 +1,139 @@ +package controller + +import ( + "fmt" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/genofire/golang-lib/log" + + "github.com/FreifunkBremen/freifunkmanager/ssh" +) + +const ( + maxDB = 80 +) + +type Controller struct { + sshMgmt *ssh.Manager + no24Ghz map[string]bool + node map[string]map[string]time.Time + quit chan struct{} + sync.Mutex +} + +func NewController(ssh *ssh.Manager) *Controller { + return &Controller{ + sshMgmt: ssh, + quit: make(chan struct{}), + } +} +func (c *Controller) Start() { + ticker := time.NewTicker(time.Second) + for { + select { + case <-ticker.C: + c.tick() + case <-c.quit: + ticker.Stop() + return + } + } +} + +func (c *Controller) Close() { + close(c.quit) +} + +func (c *Controller) tick() { + c.sshMgmt.RunEverywhere("if [ \"$(uci get wireless.radio0.hwmode | grep -c a)\" -ne 0 ]; then echo \"radio0\"; elif [ \"$(uci get wireless.radio1.hwmode | grep -c a)\" -ne 0 ]; then echo \"radio1\"; fi", ssh.SSHResultToStringHandler(func(ip string, iface string, err error) { + if err != nil { + return + } + addr := net.TCPAddr{IP: net.ParseIP(ip), Port: 22} + result, err := c.sshMgmt.RunOn(addr, fmt.Sprintf("iwinfo %s assoclist | grep SNR", iface)) + if err != nil { + return + } + for _, line := range strings.Split(result, "\n") { + parts := strings.Fields(line) + mac := parts[0] + db, err := strconv.Atoi(parts[1]) + if err != nil { + return + } + + c.no24Ghz[mac] = true + + if db > maxDB { + + node, ok := c.node[ip] + if !ok { + node = make(map[string]time.Time) + c.node[ip] = node + } + + now := time.Now() + + if last, ok := node[mac]; ok && last.After(now.Add(-3*time.Second)) { + continue + } + log.Log.Info("force roamed %s from %s", mac, addr) + cmd := fmt.Sprintf("ubus call hostapd.%s del_client '{\"addr\":\"%s\", \"reason\":1, \"deauth\":true, \"ban_time\":1000}'", iface, mac) + c.sshMgmt.ExecuteOn(addr, cmd) + node[mac] = now + + } + } + + })) + + c.sshMgmt.RunEverywhere("if [ \"$(uci get wireless.radio0.hwmode | grep -c g)\" -ne 0 ]; then echo \"radio0\"; elif [ \"$(uci get wireless.radio1.hwmode | grep -c g)\" -ne 0 ]; then echo \"radio1\"; fi", ssh.SSHResultToStringHandler(func(ip string, iface string, err error) { + if err != nil { + return + } + addr := net.TCPAddr{IP: net.ParseIP(ip), Port: 22} + result, err := c.sshMgmt.RunOn(addr, fmt.Sprintf("iwinfo %s assoclist | grep SNR", iface)) + if err != nil { + return + } + for _, line := range strings.Split(result, "\n") { + parts := strings.Fields(line) + mac := parts[0] + db, err := strconv.Atoi(parts[1]) + if err != nil { + return + } + + cmd := fmt.Sprintf("ubus call hostapd.%s del_client '{\"addr\":\"%s\", \"reason\":1, \"deauth\":true, \"ban_time\":1000}'", iface, mac) + + if c.no24Ghz[mac] { + log.Log.Info("kicked becouse it use 2.4 Ghz %s from %s", mac, addr) + c.sshMgmt.ExecuteOn(addr, cmd) + } + + if db > maxDB { + + node, ok := c.node[ip] + if !ok { + node = make(map[string]time.Time) + c.node[ip] = node + } + + now := time.Now() + + if last, ok := node[mac]; ok && last.After(now.Add(-3*time.Second)) { + continue + } + log.Log.Info("force roamed %s from %s", mac, addr) + c.sshMgmt.ExecuteOn(addr, cmd) + node[mac] = now + + } + } + + })) +} diff --git a/ssh/run.go b/ssh/run.go index 0cfca34..33ffda4 100644 --- a/ssh/run.go +++ b/ssh/run.go @@ -8,7 +8,7 @@ import ( "golang.org/x/crypto/ssh" ) -type SSHResultHandler func(string, error) +type SSHResultHandler func(string, string, error) func SSHResultToString(result string) string { if len(result) > 0 { @@ -18,15 +18,15 @@ func SSHResultToString(result string) string { } func SSHResultToStringHandler(handler SSHResultHandler) SSHResultHandler { - return func(result string, err error) { - handler(SSHResultToString(result), err) + return func(addr string, result string, err error) { + handler(addr, SSHResultToString(result), err) } } func (m *Manager) RunEverywhere(cmd string, handler SSHResultHandler) { for host, client := range m.clients { result, err := m.run(host, client, cmd) - handler(result, err) + handler(host, result, err) } }