├── .gitignore ├── asset ├── flexi.system └── build.sh ├── flex └── parser.go ├── README.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | bin/ -------------------------------------------------------------------------------- /asset/flexi.system: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=flex radio discovery 3 | Wants=network-online.target 4 | After=network-online.target 5 | 6 | [Service] 7 | ExecStart=/usr/bin/flexi --SERVERIP=192.168.92.7 --SERVERPORT=7777 --RABBITCONN=amqp://guest:guest@192.168.92.7:5672/ 8 | Restart=always 9 | RestartSec=10 10 | StandardOutput=null 11 | 12 | [Install] 13 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /asset/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | 4 | # Linux 5 | env GOOS=linux GOARCH=amd64 go build -o ../../../../bin/flex6k-discovery-util-go-build/linux64/flexi 6 | env GOOS=linux GOARCH=386 go build -o ../../../../bin/flex6k-discovery-util-go-build/linux32/flexi 7 | 8 | # Raspi 9 | env GOOS=linux GOARCH=arm GOARM=5 go build -o ../../../../bin/flex6k-discovery-util-go-build/raspberryPi/flexi 10 | 11 | # Windows 12 | env GOOS=windows GOARCH=amd64 go build -o ../../../../bin/flex6k-discovery-util-go-build/Win64/flexi.exe 13 | env GOOS=windows GOARCH=386 go build -o ../../../../bin/flex6k-discovery-util-go-build/Win32/flexi.exe 14 | 15 | 16 | # pfsense 17 | env GOOS=freebsd GOARCH=amd64 go build -o ../../../../bin/flex6k-discovery-util-go-build/pfSense64/flexi 18 | env GOOS=freebsd GOARCH=386 go build -o ../../../../bin/flex6k-discovery-util-go-build/pfSense32/flexi 19 | -------------------------------------------------------------------------------- /flex/parser.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type DiscoveryPackage struct { 8 | Discovery_protocol_version string 9 | Model string 10 | Serial string 11 | Version string 12 | Nickname string 13 | Callsign string 14 | Ip string 15 | Port string 16 | Status string 17 | Inuse_ip string 18 | Inuse_host string 19 | Max_licensed_version string 20 | Radio_license_id string 21 | } 22 | 23 | func Parse(msg []byte) (DiscoveryPackage) { 24 | 25 | s := string(msg[:]) 26 | s = s[strings.Index(s, "discovery_protocol_version"):] 27 | tokens := strings.Split(s, " ") 28 | var discoveryPackage DiscoveryPackage 29 | 30 | for i := 0; i < len(tokens); i++ { 31 | 32 | 33 | values := strings.Split(tokens[i], "=") 34 | 35 | switch values[0] { 36 | case "discovery_protocol_version": 37 | discoveryPackage.Discovery_protocol_version = values[1] 38 | continue; 39 | case "model": 40 | discoveryPackage.Model = values[1] 41 | continue; 42 | case "serial": 43 | discoveryPackage.Serial = values[1] 44 | continue; 45 | case "version": 46 | discoveryPackage.Version = values[1] 47 | continue; 48 | case "nickname": 49 | discoveryPackage.Nickname = values[1] 50 | continue; 51 | case "callsign": 52 | discoveryPackage.Callsign = values[1] 53 | continue; 54 | case "ip": 55 | discoveryPackage.Ip = values[1] 56 | continue; 57 | case "port": 58 | discoveryPackage.Port = values[1] 59 | continue; 60 | case "status": 61 | discoveryPackage.Status = values[1] 62 | continue; 63 | case "inuse_ip": 64 | discoveryPackage.Inuse_ip = values[1] 65 | continue; 66 | case "inuse_host": 67 | discoveryPackage.Inuse_host = values[1] 68 | continue; 69 | case "max_licensed_version": 70 | discoveryPackage.Max_licensed_version = values[1] 71 | continue; 72 | case "radio_license_id": 73 | discoveryPackage.Radio_license_id = values[1] 74 | continue; 75 | } 76 | } 77 | 78 | return discoveryPackage 79 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flex6k-discovery-util-go 2 | 3 | Lightweight replacement for: https://github.com/krippendorf/flex6k-discovery-util 4 | util to use FRS 6XXX(R) signature series radios across subnets / routed VPNs 5 | 6 | 7 | Currently tested on RasberryPi, X64 Linux, and Windows 8 | 9 | 10 | ## Download precompiled for your system 11 | 12 | Most recent binary release is here (precompiled) 13 | https://github.com/krippendorf/flex6k-discovery-util-go/releases/download/v0.1-br/flex6k-discovery-util-go-0.1-BR-REL_ret.zip 14 | 15 | ...currently for 386/amd64 Linux (ubuntu, etc and Windows) FreeBSD (PfSense) and ARM 5 linux (RaspberryPi) 16 | If your platform isn't listed, send me a pull request for the pretty simple build.sh file. 17 | 18 | ## Example Usage 19 | 20 | How it works: You need a client and a server which establish a link. One of them has to be installed in the subnet where your radio is connected and the other where SmartSDR runs. 21 | It does not matter where you install the client and where the server. 22 | 23 | ### Simple setup (Client/Server): 24 | 25 | * Server: 192.168.1.4 is on a VPN site with [n] radios in the subnet 26 | * Client: 10.147.20.144 is a VPN router, connected via a TUN device and routes ????? 192.168.92.0/40 27 | * 192.168.1.7 Radio in this example 28 | 29 | #### Server VPN/Network site (server is installed close to the radio) 30 | ``` 31 | Linux command: ./flexi --SERVERIP=192.168.1.4 --SERVERPORT=7777 32 | Windows command: flexi -SERVERIP=192.168.1.4 -SERVERPORT=7777 33 | ``` 34 | 35 | #### Client VPN/Network site (Client in the subnet of SmartSDR) 36 | 37 | ``` 38 | Linux command ./flexi --REMOTES=10.147.20.144:7777 --LOCALIFIP=192.168.1.4 --LOCALPORT=7788 39 | Windows command: flexi -REMOTES=10.147.20.144:7777 -LOCALIFIP=192.168.1.4 -LOCALPORT=7788 40 | ``` 41 | The ports can be changed. SERVERPORT and REMOTES port has to be the same (here 7777) 42 | 43 | If you execute these two commands the following output should result: 44 | Client side: 45 | CLT RECEIVED PKG FROM SRV @ 192.168.1.4 46 | broadcasting in local subnet 47 | CLT RECEIVED PKG FROM SRV @ 192.168.1.4 48 | broadcasting in local subnet 49 | CLT RECEIVED PKG FROM SRV @ 192.168.1.4 50 | broadcasting in local subnet 51 | 52 | Server side: 53 | REGISTRATION R;10.147.20.144;7788 from 10.147.20.144:55973 54 | SRV: Number of regs: 1 55 | SRV BROADCAST RECEIVED [192.168.1.7:4992] 56 | ==> Notifying remote [R;10.147.20.144;7788] 57 | SRV BROADCAST RECEIVED [192.168.1.7:4992] 58 | ==> Notifying remote [R;10.147.20.144;7788] 59 | 60 | Hint: ZeroTier works also with LTE routers and non-public IP adress 61 | 62 | 63 | -------------- END OF standard installation ----------- 64 | 65 | 66 | If you need to redirect the traffic on clientside to anything other than 255.255.255.255 (default) you can apply the ``` LOCALBR``` argument e.g. ``` --LOCALBR=192.168.40.255``` I've discoverd that especially PfSense drops UDP packages directly to 255.255.255.255 - probably due to the fact it does not decide on which interface to send out the traffic 67 | 68 | ### Multi server (Client/Server/Server) 69 | 70 | #### Server 1 VPN/Network site 71 | ``` 72 | ./flex6k-discovery-util-go --SERVERIP=192.168.92.1 --SERVERPORT=7777 73 | ``` 74 | 75 | #### Server 2 VPN/Network site 76 | ``` 77 | ./flex6k-discovery-util-go --SERVERIP=192.168.87.1 --SERVERPORT=7777 78 | ``` 79 | 80 | #### Client VPN/Network site 81 | Simple add all server to the REMOTES argument. 82 | 83 | ``` 84 | ./flex6k-discovery-util-go --REMOTES=192.168.92.1:7777;192.168.87.1:7777 --LOCALIFIP=192.168.40.1 --LOCALPORT=7788 85 | ``` 86 | 87 | 88 | ### Multi site 89 | If you host one or more radios on both sides of the tunnel or in different subnets and want to share accross networks you can run CLIENT & SERVER mode at the same time with the same process. 90 | 91 | #### Multi site node / relay loop 92 | 93 | ``` 94 | ./flex6k-discovery-util-go --REMOTES=192.168.92.1:7777;REMOTES=192.168.87.1:7777 --LOCALIFIP=192.168.40.1 --LOCALPORT=7788 --SERVERIP=192.168.40.1 --SERVERPORT=7777 95 | ``` 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | .... work in progress... use on own risk! 3 | 4 | 2016, 2017 by Frank Werner-Krippendorf / HB9FXQ, mail@hb9fxq.ch 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package main 23 | 24 | import ( 25 | "encoding/json" 26 | "flag" 27 | "fmt" 28 | "github.com/eclipse/paho.mqtt.golang" 29 | "github.com/krippendorf/flex6k-discovery-util-go/flex" 30 | "net" 31 | "os" 32 | "reflect" 33 | "strconv" 34 | "strings" 35 | "sync" 36 | "time" 37 | ) 38 | 39 | type AppContext struct { 40 | serverIp string // registraton server IP & PORT 41 | serverPort int 42 | 43 | localIp string // client listener IP & PORT 44 | localPort int 45 | localBroadcast string 46 | 47 | allLocalIp string // client listener IP & PORT 48 | 49 | mqttBrokerAddress string 50 | mqttClientId string 51 | mqttTopic string 52 | 53 | remotes []string // remotes to be notified 54 | 55 | registrations map[string]ListenerRegistration 56 | 57 | lastState string 58 | sync.Mutex 59 | lastPackage *flex.DiscoveryPackage 60 | mqttClient mqtt.Client 61 | } 62 | 63 | type ListenerRegistration struct { 64 | listenerPort int 65 | listenerIp string 66 | raw string 67 | since int64 68 | } 69 | 70 | const NDEF_STRING string = "NDEF" 71 | const FRS_DISCOVEY_ADDR string = "255.255.255.255:4992" 72 | const UDP_NETWORK string = "udp4" 73 | 74 | func setupMqttClient(appctx *AppContext) { 75 | opts := mqtt.NewClientOptions().AddBroker(appctx.mqttBrokerAddress).SetClientID(appctx.mqttClientId) 76 | opts.SetKeepAlive(2 * time.Second) 77 | opts.SetPingTimeout(1 * time.Second) 78 | opts.CleanSession = false 79 | 80 | appctx.mqttClient = mqtt.NewClient(opts) 81 | if token := appctx.mqttClient.Connect(); token.Wait() && token.Error() != nil { 82 | panic(token.Error()) 83 | } 84 | } 85 | 86 | func main() { 87 | appctx := new(AppContext) 88 | 89 | var remotes string 90 | flag.StringVar(&remotes, "REMOTES", NDEF_STRING, "List remote server to subscribe to. One or more, format is [SERVER_IP:SERVER_PORT], if more than one, delimit subscriptions by ';' e.g. --REMOTES=192.168.62.1:7224;192.168.63.1:7228") 91 | flag.StringVar(&appctx.localIp, "LOCALIFIP", NDEF_STRING, "Client local interface IPinterface, where servers will forward pkgs to") 92 | flag.IntVar(&appctx.localPort, "LOCALPORT", 0, "Local port") 93 | flag.StringVar(&appctx.localBroadcast, "LOCALBR", NDEF_STRING, "Local broadcast address address, default 255.255.255.255 - e.g. 192.168.2.255. Required on PfSense!") 94 | flag.StringVar(&appctx.serverIp, "SERVERIP", NDEF_STRING, "Broadcast server IP address") 95 | flag.StringVar(&appctx.mqttBrokerAddress, "MQTTBROKER", NDEF_STRING, "MQTT Broker Address") 96 | flag.StringVar(&appctx.mqttClientId, "MQTTCLIENTID", NDEF_STRING, "MQTT Client ID") 97 | flag.StringVar(&appctx.mqttTopic, "MQTTTOPIC", NDEF_STRING, "MQTT Topic") 98 | flag.IntVar(&appctx.serverPort, "SERVERPORT", 0, "Broadcast server port") 99 | flag.Parse() 100 | 101 | appctx.lastState = "empty" 102 | appctx.allLocalIp = FetchAllLocalIPs() 103 | fmt.Println("APP Identified local IPs: " + appctx.allLocalIp) 104 | 105 | flag.Usage = func() { 106 | fmt.Printf("Usage of %s:\n", os.Args[0]) 107 | fmt.Printf(" ..:: see https://github.com/krippendorf/flex6k-discovery-util-go for instructions ::..\n") 108 | 109 | flag.PrintDefaults() 110 | } 111 | 112 | if appctx.mqttBrokerAddress != NDEF_STRING { 113 | setupMqttClient(appctx) 114 | } 115 | 116 | if remotes != NDEF_STRING && appctx.localIp != NDEF_STRING { 117 | appctx.remotes = strings.Split(remotes, ";") 118 | go NotifyRemotes(appctx) 119 | go ListenForRelayedPkgs(appctx) 120 | } 121 | 122 | if appctx.serverIp != NDEF_STRING && 0 < appctx.serverPort { 123 | appctx.registrations = make(map[string]ListenerRegistration) 124 | fmt.Printf("SRV listening for registrations on: %s:%d \n", appctx.serverIp, appctx.serverPort) 125 | go BroadcastListener(appctx) 126 | go ServerListener(appctx) 127 | } 128 | 129 | for { 130 | time.Sleep(1 * time.Second) 131 | } 132 | } 133 | 134 | func FetchAllLocalIPs() (allips string) { 135 | 136 | allips = "0.0.0.0 127.0.0.1 " 137 | ifaces, err := net.Interfaces() 138 | CheckError("FetchAllLocalIPs", err) 139 | 140 | for _, i := range ifaces { 141 | addrs, err := i.Addrs() 142 | CheckError("Fetch if IP", err) 143 | for _, addr := range addrs { 144 | switch v := addr.(type) { 145 | case *net.IPNet: 146 | allips += v.IP.String() + " " 147 | case *net.IPAddr: 148 | allips += v.IP.String() + " " 149 | } 150 | } 151 | } 152 | 153 | return allips 154 | } 155 | 156 | func ListenForRelayedPkgs(appctx *AppContext) { 157 | ListenerLocalAddress, err := net.ResolveUDPAddr(UDP_NETWORK, appctx.localIp+":"+strconv.Itoa(appctx.localPort)) 158 | CheckError("Listener reslolve local", err) 159 | if err != nil { 160 | ListenForRelayedPkgs(appctx) 161 | } 162 | 163 | ServerConn, err := net.ListenUDP(UDP_NETWORK, ListenerLocalAddress) 164 | CheckError("Listener listen", err) 165 | 166 | if err != nil { 167 | ListenForRelayedPkgs(appctx) 168 | } 169 | 170 | buf := make([]byte, 1024) 171 | 172 | for { 173 | n, addr, err := ServerConn.ReadFromUDP(buf) 174 | 175 | if err != nil { 176 | continue 177 | } 178 | 179 | if strings.Contains(appctx.allLocalIp, addr.IP.String()) { 180 | continue // skip, if comes from local server instance, if registered in local loop 181 | } 182 | 183 | fmt.Println("CLT RECEIVED PKG FROM SRV @", addr.IP.String()) 184 | 185 | RelayLocal(appctx, buf[0:n]) 186 | 187 | if err != nil { 188 | fmt.Println("CLT Error: ", err) 189 | } 190 | } 191 | } 192 | 193 | func RelayLocal(appctx *AppContext, bytes []byte) { 194 | fmt.Printf(" broadcasting in local subnet\n") 195 | 196 | defAddr := FRS_DISCOVEY_ADDR 197 | 198 | if NDEF_STRING != appctx.localBroadcast { 199 | defAddr = appctx.localBroadcast + ":4992" 200 | } 201 | 202 | ServerAddr, err := net.ResolveUDPAddr(UDP_NETWORK, defAddr) 203 | CheckError("broadcasting net.ResolveUDPAddr I", err) 204 | if err != nil { 205 | return 206 | } 207 | 208 | LocalAddr, err := net.ResolveUDPAddr(UDP_NETWORK, appctx.localIp+":"+strconv.Itoa(appctx.localPort+1)) 209 | CheckError("broadcasting net.ResolveUDPAddr II", err) 210 | if err != nil { 211 | return 212 | } 213 | 214 | Conn, err := net.DialUDP(UDP_NETWORK, LocalAddr, ServerAddr) 215 | CheckError("broadcasting DialUDP", err) 216 | if err != nil { 217 | return 218 | } 219 | 220 | defer Conn.Close() 221 | 222 | _, ewrite := Conn.Write(bytes) 223 | 224 | if ewrite != nil { 225 | fmt.Println("CLT Failed to broadcast", err) 226 | } 227 | } 228 | 229 | func NotifyRemotes(appctx *AppContext) { 230 | 231 | for { 232 | for _, remote := range appctx.remotes { 233 | fmt.Printf(" ==> Notifying remote [%s]\n", remote) 234 | 235 | ServerAddr, err := net.ResolveUDPAddr(UDP_NETWORK, remote) 236 | CheckError("net.ResolveUDPAddr I", err) 237 | if err != nil { 238 | continue 239 | } 240 | 241 | LocalAddr, err := net.ResolveUDPAddr(UDP_NETWORK, appctx.localIp+":0") 242 | CheckError("net.ResolveUDPAddr II", err) 243 | 244 | if err != nil { 245 | continue 246 | } 247 | 248 | Conn, err := net.DialUDP(UDP_NETWORK, LocalAddr, ServerAddr) 249 | CheckError("DialUDP", err) 250 | 251 | if err != nil { 252 | continue 253 | } 254 | 255 | msg := "R;" + appctx.localIp + ";" + strconv.Itoa(appctx.localPort) 256 | 257 | buf := []byte(msg) 258 | _, ewrite := Conn.Write(buf) 259 | 260 | if ewrite != nil { 261 | fmt.Println(msg, err) 262 | } 263 | 264 | Conn.Close() 265 | } 266 | 267 | time.Sleep(time.Second * 10) 268 | } 269 | } 270 | 271 | func ServerListener(appctx *AppContext) { 272 | 273 | FLexBroadcastAddr, err := net.ResolveUDPAddr(UDP_NETWORK, appctx.serverIp+":"+strconv.Itoa(appctx.serverPort)) 274 | CheckError("SRV FIND IP", err) 275 | if err != nil { 276 | ServerListener(appctx) 277 | } 278 | 279 | ServerConn, err := net.ListenUDP(UDP_NETWORK, FLexBroadcastAddr) 280 | CheckError("SRV LISTEN", err) 281 | if err != nil { 282 | ServerListener(appctx) 283 | } 284 | 285 | defer ServerConn.Close() 286 | 287 | buf := make([]byte, 1024) 288 | 289 | for { 290 | n, addr, err := ServerConn.ReadFromUDP(buf) 291 | content := string(buf[0:n]) 292 | 293 | fmt.Println("REGISTRATION ", content, " from ", addr) 294 | 295 | tokens := strings.Split(content, ";") 296 | 297 | tokenPort, err := strconv.Atoi(tokens[2]) 298 | CheckError("PARSE REG CONTENT", err) 299 | if err != nil { 300 | continue 301 | } 302 | 303 | appctx.Lock() 304 | appctx.registrations[content] = ListenerRegistration{listenerIp: tokens[1], listenerPort: tokenPort, since: getCurrentUtcLinux(), raw: content} 305 | appctx.Unlock() 306 | 307 | fmt.Printf("SRV: Number of regs: %d\n", len(appctx.registrations)) 308 | 309 | if err != nil { 310 | fmt.Println("Error: ", err) 311 | } 312 | } 313 | } 314 | 315 | func getCurrentUtcLinux() int64 { 316 | return time.Now().UTC().UnixNano() / int64(time.Millisecond) 317 | } 318 | 319 | func NotifyListener(appctx *AppContext, listener ListenerRegistration, msg []byte) { 320 | fmt.Printf(" ==> Notifying remote [%s]\n", listener.raw) 321 | 322 | ListenerAddr, err := net.ResolveUDPAddr(UDP_NETWORK, listener.listenerIp+":"+strconv.Itoa(listener.listenerPort)) 323 | 324 | if err != nil { 325 | fmt.Println("SRV ERR, Could not notify listener", err) 326 | return 327 | } 328 | 329 | LocalAddr, err := net.ResolveUDPAddr(UDP_NETWORK, appctx.serverIp+":0") 330 | if err != nil { 331 | fmt.Println("SRV ERR, Could not notify listener", err) 332 | return 333 | } 334 | 335 | Conn, err := net.DialUDP(UDP_NETWORK, LocalAddr, ListenerAddr) 336 | if err != nil { 337 | fmt.Println("SRV ERR, Could not notify listener", err) 338 | return 339 | } 340 | 341 | defer Conn.Close() 342 | 343 | _, ewrite := Conn.Write(msg) 344 | 345 | if ewrite != nil { 346 | fmt.Println(msg, err) 347 | } 348 | } 349 | 350 | func BroadcastListener(appctx *AppContext) { 351 | 352 | LocalAddr := net.UDPAddr{IP: net.IPv4zero, Port: 4992} 353 | 354 | ServerConn, err := net.ListenUDP(UDP_NETWORK, &LocalAddr) 355 | CheckError("BR listen", err) 356 | defer ServerConn.Close() 357 | 358 | buf := make([]byte, 1024) 359 | prev := make([]byte, 1024) 360 | 361 | var ackCnt int 362 | 363 | for { 364 | n, addr, err := ServerConn.ReadFromUDP(buf) 365 | 366 | if !IsFrsFlexDiscoveryPkgInBuffer(buf, n) { 367 | continue // thats not ourts 368 | } 369 | 370 | if reflect.DeepEqual(buf, prev) { 371 | continue // skip own pkgs, that where captured on other local network interface 372 | } 373 | 374 | copy(prev, buf) 375 | 376 | if strings.Contains(appctx.allLocalIp, addr.IP.String()+" ") { 377 | ackCnt++ 378 | fmt.Println("SRV ACK [" + strconv.Itoa(ackCnt) + "]") 379 | continue 380 | } 381 | 382 | ackCnt = 0 383 | fmt.Printf("SRV BROADCAST RECEIVED [%s]\n", addr) 384 | appctx.Lock() 385 | 386 | parsed := flex.Parse(buf[0:n]) 387 | if parsed.Status != appctx.lastState { 388 | appctx.lastState = parsed.Status 389 | appctx.lastPackage = &parsed 390 | } 391 | 392 | if appctx.mqttBrokerAddress != NDEF_STRING { 393 | go pushMqtt(appctx, parsed) 394 | } 395 | 396 | if 0 < len(appctx.registrations) { 397 | for _, registration := range appctx.registrations { 398 | if registration.since+30000 < getCurrentUtcLinux() { 399 | delete(appctx.registrations, registration.raw) 400 | fmt.Printf("TTL for registration %s:%d\n", registration.listenerIp, registration.listenerPort) 401 | continue 402 | } 403 | 404 | go NotifyListener(appctx, registration, buf[0:n]) 405 | } 406 | } 407 | 408 | appctx.Unlock() 409 | 410 | if err != nil { 411 | fmt.Println("Error: ", err) 412 | } 413 | } 414 | 415 | } 416 | 417 | func pushMqtt(context *AppContext, discoveryPackage flex.DiscoveryPackage) { 418 | 419 | pkgJson, err := json.Marshal(discoveryPackage) 420 | if err != nil { 421 | fmt.Println(err) 422 | return 423 | } 424 | 425 | token := context.mqttClient.Publish(context.mqttTopic, 0, false, pkgJson) 426 | token.Wait() 427 | } 428 | 429 | func CheckError(where string, err error) { 430 | if err != nil { 431 | fmt.Println("FATAL: ("+where+") sleeping 5 seconds", err) 432 | time.Sleep(5 * time.Second) 433 | } 434 | } 435 | 436 | func IsFrsFlexDiscoveryPkgInBuffer(buf []byte, length int) (res bool) { 437 | 438 | if 900 < length { 439 | res = false 440 | fmt.Printf("ERROR: INVALID DATA, size: %d", length) 441 | return 442 | } 443 | 444 | content := string(buf[0:length]) 445 | res = strings.Contains(content, "serial=") && strings.Contains(content, "version=") && strings.Contains(content, "ip=") 446 | 447 | if !res { 448 | fmt.Println("ERROR: INVALID DATA") 449 | } 450 | 451 | return 452 | } 453 | --------------------------------------------------------------------------------