kopia lustrzana https://github.com/cyoung/stratux
				
				
				
			
		
			
				
	
	
		
			627 wiersze
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			627 wiersze
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
/*
 | 
						|
	Copyright (c) 2015-2016 Christopher Young
 | 
						|
	Distributable under the terms of The "BSD New"" License
 | 
						|
	that can be found in the LICENSE file, herein included
 | 
						|
	as part of this header.
 | 
						|
 | 
						|
	network.go: Client networking routines, DHCP lease monitoring, queue management, ICMP monitoring.
 | 
						|
*/
 | 
						|
 | 
						|
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"github.com/tarm/serial"
 | 
						|
	"golang.org/x/net/icmp"
 | 
						|
	"golang.org/x/net/ipv4"
 | 
						|
	"io/ioutil"
 | 
						|
	"log"
 | 
						|
	"math"
 | 
						|
	"math/rand"
 | 
						|
	"net"
 | 
						|
	"os"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
type networkMessage struct {
 | 
						|
	msg       []byte
 | 
						|
	msgType   uint8
 | 
						|
	queueable bool
 | 
						|
	ts        time.Time
 | 
						|
}
 | 
						|
 | 
						|
type networkConnection struct {
 | 
						|
	Conn            *net.UDPConn
 | 
						|
	Ip              string
 | 
						|
	Port            uint32
 | 
						|
	Capability      uint8
 | 
						|
	messageQueue    [][]byte // Device message queue.
 | 
						|
	MessageQueueLen int      // Length of the message queue. For debugging.
 | 
						|
	/*
 | 
						|
		Sleep mode/throttle variables. "sleep mode" is actually now just a very reduced packet rate, since we don't know positively
 | 
						|
		 when a client is ready to accept packets - we just assume so if we don't receive ICMP Unreachable packets in 5 secs.
 | 
						|
	*/
 | 
						|
	LastUnreachable time.Time // Last time the device sent an ICMP Unreachable packet.
 | 
						|
	nextMessageTime time.Time // The next time that the device is "able" to receive a message.
 | 
						|
	numOverflows    uint32    // Number of times the queue has overflowed - for calculating the amount to chop off from the queue.
 | 
						|
	SleepFlag       bool      // Whether or not this client has been marked as sleeping - only used for debugging (relies on messages being sent to update this flag in sendToAllConnectedClients()).
 | 
						|
	FFCrippled      bool
 | 
						|
}
 | 
						|
 | 
						|
type serialConnection struct {
 | 
						|
	DeviceString string
 | 
						|
	Baud         int
 | 
						|
	serialPort   *serial.Port
 | 
						|
}
 | 
						|
 | 
						|
var messageQueue chan networkMessage
 | 
						|
var outSockets map[string]networkConnection
 | 
						|
var dhcpLeases map[string]string
 | 
						|
var netMutex *sync.Mutex
 | 
						|
 | 
						|
var totalNetworkMessagesSent uint32
 | 
						|
 | 
						|
var pingResponse map[string]time.Time // Last time an IP responded to an "echo" response.
 | 
						|
 | 
						|
const (
 | 
						|
	NETWORK_GDL90_STANDARD = 1
 | 
						|
	NETWORK_AHRS_FFSIM     = 2
 | 
						|
	NETWORK_AHRS_GDL90     = 4
 | 
						|
	dhcp_lease_file        = "/var/lib/dhcp/dhcpd.leases"
 | 
						|
	extra_hosts_file       = "/etc/stratux-static-hosts.conf"
 | 
						|
)
 | 
						|
 | 
						|
// Read the "dhcpd.leases" file and parse out IP/hostname.
 | 
						|
func getDHCPLeases() (map[string]string, error) {
 | 
						|
	dat, err := ioutil.ReadFile(dhcp_lease_file)
 | 
						|
	ret := make(map[string]string)
 | 
						|
	if err != nil {
 | 
						|
		return ret, err
 | 
						|
	}
 | 
						|
	lines := strings.Split(string(dat), "\n")
 | 
						|
	open_block := false
 | 
						|
	block_ip := ""
 | 
						|
	for _, line := range lines {
 | 
						|
		spaced := strings.Split(line, " ")
 | 
						|
		if len(spaced) > 2 && spaced[0] == "lease" {
 | 
						|
			open_block = true
 | 
						|
			block_ip = spaced[1]
 | 
						|
		} else if open_block && len(spaced) >= 4 && spaced[2] == "client-hostname" {
 | 
						|
			hostname := strings.TrimRight(strings.TrimLeft(strings.Join(spaced[3:], " "), "\""), "\";")
 | 
						|
			ret[block_ip] = hostname
 | 
						|
			open_block = false
 | 
						|
		} else if open_block && strings.HasPrefix(spaced[0], "}") { // No hostname.
 | 
						|
			open_block = false
 | 
						|
			ret[block_ip] = ""
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Added the ability to have static IP hosts stored in /etc/stratux-static-hosts.conf
 | 
						|
 | 
						|
	dat2, err := ioutil.ReadFile(extra_hosts_file)
 | 
						|
	if err != nil {
 | 
						|
		return ret, nil
 | 
						|
	}
 | 
						|
 | 
						|
	iplines := strings.Split(string(dat2), "\n")
 | 
						|
	block_ip2 := ""
 | 
						|
	for _, ipline := range iplines {
 | 
						|
		spacedip := strings.Split(ipline, " ")
 | 
						|
		if len(spacedip) == 2 {
 | 
						|
			// The ip is in block_ip2
 | 
						|
			block_ip2 = spacedip[0]
 | 
						|
			// the hostname is here
 | 
						|
			ret[block_ip2] = spacedip[1]
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return ret, nil
 | 
						|
}
 | 
						|
 | 
						|
func isSleeping(k string) bool {
 | 
						|
	ipAndPort := strings.Split(k, ":")
 | 
						|
	// No ping response. Assume disconnected/sleeping device.
 | 
						|
	if lastPing, ok := pingResponse[ipAndPort[0]]; !ok || stratuxClock.Since(lastPing) > (10*time.Second) {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	if stratuxClock.Since(outSockets[k].LastUnreachable) < (5 * time.Second) {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// Throttle mode for testing port open and giving some start-up time to the app.
 | 
						|
// Throttling is 0.1% data rate for first 15 seconds.
 | 
						|
func isThrottled(k string) bool {
 | 
						|
	return (rand.Int()%1000 != 0) && stratuxClock.Since(outSockets[k].LastUnreachable) < (15*time.Second)
 | 
						|
}
 | 
						|
 | 
						|
func sendToAllConnectedClients(msg networkMessage) {
 | 
						|
	if (msg.msgType & NETWORK_GDL90_STANDARD) != 0 {
 | 
						|
		// It's a GDL90 message. Send to serial output channel (which may or may not cause something to happen).
 | 
						|
		serialOutputChan <- msg.msg
 | 
						|
		networkGDL90Chan <- msg.msg
 | 
						|
	}
 | 
						|
 | 
						|
	netMutex.Lock()
 | 
						|
	defer netMutex.Unlock()
 | 
						|
	for k, netconn := range outSockets {
 | 
						|
		sleepFlag := isSleeping(k)
 | 
						|
 | 
						|
		netconn.SleepFlag = sleepFlag
 | 
						|
		outSockets[k] = netconn
 | 
						|
 | 
						|
		// Check if this port is able to accept the type of message we're sending.
 | 
						|
		if (netconn.Capability & msg.msgType) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		// Send non-queueable messages immediately, or discard if the client is in sleep mode.
 | 
						|
 | 
						|
		if !sleepFlag {
 | 
						|
			netconn.numOverflows = 0 // Reset the overflow counter whenever the client is not sleeping so that we're not penalizing future sleepmodes.
 | 
						|
		}
 | 
						|
 | 
						|
		if !msg.queueable {
 | 
						|
			if sleepFlag {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			netconn.Conn.Write(msg.msg) // Write immediately.
 | 
						|
			totalNetworkMessagesSent++
 | 
						|
			globalStatus.NetworkDataMessagesSent++
 | 
						|
			globalStatus.NetworkDataMessagesSentNonqueueable++
 | 
						|
			globalStatus.NetworkDataBytesSent += uint64(len(msg.msg))
 | 
						|
			globalStatus.NetworkDataBytesSentNonqueueable += uint64(len(msg.msg))
 | 
						|
		} else {
 | 
						|
			// Queue the message if the message is "queueable".
 | 
						|
			if len(netconn.messageQueue) >= maxUserMsgQueueSize { // Too many messages queued? Drop the oldest.
 | 
						|
				log.Printf("%s:%d - message queue overflow.\n", netconn.Ip, netconn.Port)
 | 
						|
				netconn.numOverflows++
 | 
						|
				s := 2 * netconn.numOverflows // Double the amount we chop off on each overflow.
 | 
						|
				if int(s) >= len(netconn.messageQueue) {
 | 
						|
					netconn.messageQueue = make([][]byte, 0)
 | 
						|
				} else {
 | 
						|
					netconn.messageQueue = netconn.messageQueue[s:]
 | 
						|
				}
 | 
						|
			}
 | 
						|
			netconn.messageQueue = append(netconn.messageQueue, msg.msg) // each netconn.messageQueue is therefore an array (well, a slice) of formatted GDL90 messages
 | 
						|
			outSockets[k] = netconn
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var serialOutputChan chan []byte
 | 
						|
var networkGDL90Chan chan []byte
 | 
						|
 | 
						|
func networkOutWatcher() {
 | 
						|
	for {
 | 
						|
		ch := <-networkGDL90Chan
 | 
						|
		gdl90Update.SendJSON(ch)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Monitor serial output channel, send to serial port.
 | 
						|
func serialOutWatcher() {
 | 
						|
	// Check every 30 seconds for a serial output device.
 | 
						|
	serialTicker := time.NewTicker(30 * time.Second)
 | 
						|
 | 
						|
	serialDev := "/dev/serialout0" //FIXME: This is temporary. Only one serial output device for now.
 | 
						|
 | 
						|
	for {
 | 
						|
		select {
 | 
						|
		case <-serialTicker.C:
 | 
						|
			if _, err := os.Stat(serialDev); !os.IsNotExist(err) { // Check if the device file exists.
 | 
						|
				var thisSerialConn serialConnection
 | 
						|
				// Check if we need to start handling a new device.
 | 
						|
				if val, ok := globalSettings.SerialOutputs[serialDev]; !ok {
 | 
						|
					newSerialOut := serialConnection{DeviceString: serialDev, Baud: 38400}
 | 
						|
					log.Printf("detected new serial output, setting up now: %s. Default baudrate 38400.\n", serialDev)
 | 
						|
					if globalSettings.SerialOutputs == nil {
 | 
						|
						globalSettings.SerialOutputs = make(map[string]serialConnection)
 | 
						|
					}
 | 
						|
					globalSettings.SerialOutputs[serialDev] = newSerialOut
 | 
						|
					saveSettings()
 | 
						|
					thisSerialConn = newSerialOut
 | 
						|
				} else {
 | 
						|
					thisSerialConn = val
 | 
						|
				}
 | 
						|
				// Check if we need to open the connection now.
 | 
						|
				if thisSerialConn.serialPort == nil {
 | 
						|
					cfg := &serial.Config{Name: thisSerialConn.DeviceString, Baud: thisSerialConn.Baud}
 | 
						|
					p, err := serial.OpenPort(cfg)
 | 
						|
					if err != nil {
 | 
						|
						log.Printf("serialout port (%s) err: %s\n", thisSerialConn.DeviceString, err.Error())
 | 
						|
						break // We'll attempt again in 30 seconds.
 | 
						|
					} else {
 | 
						|
						log.Printf("opened serialout: Name: %s, Baud: %d\n", thisSerialConn.DeviceString, thisSerialConn.Baud)
 | 
						|
					}
 | 
						|
					// Save the serial port connection.
 | 
						|
					thisSerialConn.serialPort = p
 | 
						|
					globalSettings.SerialOutputs[serialDev] = thisSerialConn
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
		case b := <-serialOutputChan:
 | 
						|
			if val, ok := globalSettings.SerialOutputs[serialDev]; ok {
 | 
						|
				if val.serialPort != nil {
 | 
						|
					_, err := val.serialPort.Write(b)
 | 
						|
					if err != nil { // Encountered an error in writing to the serial port. Close it and set Serial_out_enabled.
 | 
						|
						log.Printf("serialout (%s) port err: %s. Closing port.\n", val.DeviceString, err.Error())
 | 
						|
						val.serialPort.Close()
 | 
						|
						val.serialPort = nil
 | 
						|
						globalSettings.SerialOutputs[serialDev] = val
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Returns the number of DHCP leases and prints queue lengths.
 | 
						|
func getNetworkStats() {
 | 
						|
 | 
						|
	var numNonSleepingClients uint
 | 
						|
 | 
						|
	for k, netconn := range outSockets {
 | 
						|
		queueBytes := 0
 | 
						|
		for _, msg := range netconn.messageQueue {
 | 
						|
			queueBytes += len(msg)
 | 
						|
		}
 | 
						|
		if globalSettings.DEBUG {
 | 
						|
			log.Printf("On  %s:%d,  Queue length = %d messages / %d bytes\n", netconn.Ip, netconn.Port, len(netconn.messageQueue), queueBytes)
 | 
						|
		}
 | 
						|
		ipAndPort := strings.Split(k, ":")
 | 
						|
		if len(ipAndPort) != 2 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		ip := ipAndPort[0]
 | 
						|
		if pingRespTime, ok := pingResponse[ip]; ok {
 | 
						|
			// Don't count the ping time if it is the same as stratuxClock epoch.
 | 
						|
			// If the client has responded to a ping in the last 15 minutes, count it as "connected" or "recent".
 | 
						|
			if !pingRespTime.Equal(time.Time{}) && stratuxClock.Since(pingRespTime) < 15*time.Minute {
 | 
						|
				numNonSleepingClients++
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	globalStatus.Connected_Users = numNonSleepingClients
 | 
						|
}
 | 
						|
 | 
						|
// See who has a DHCP lease and make a UDP connection to each of them.
 | 
						|
func refreshConnectedClients() {
 | 
						|
	netMutex.Lock()
 | 
						|
	defer netMutex.Unlock()
 | 
						|
	validConnections := make(map[string]bool)
 | 
						|
	t, err := getDHCPLeases()
 | 
						|
	if err != nil {
 | 
						|
		log.Printf("getDHCPLeases(): %s\n", err.Error())
 | 
						|
		return
 | 
						|
	}
 | 
						|
	dhcpLeases = t
 | 
						|
	// Client connected that wasn't before.
 | 
						|
	for ip, hostname := range dhcpLeases {
 | 
						|
		for _, networkOutput := range globalSettings.NetworkOutputs {
 | 
						|
			ipAndPort := ip + ":" + strconv.Itoa(int(networkOutput.Port))
 | 
						|
			if _, ok := outSockets[ipAndPort]; !ok {
 | 
						|
				log.Printf("client connected: %s:%d (%s).\n", ip, networkOutput.Port, hostname)
 | 
						|
				addr, err := net.ResolveUDPAddr("udp", ipAndPort)
 | 
						|
				if err != nil {
 | 
						|
					log.Printf("ResolveUDPAddr(%s): %s\n", ipAndPort, err.Error())
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				outConn, err := net.DialUDP("udp", nil, addr)
 | 
						|
				if err != nil {
 | 
						|
					log.Printf("DialUDP(%s): %s\n", ipAndPort, err.Error())
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				newq := make([][]byte, 0)
 | 
						|
				outSockets[ipAndPort] = networkConnection{Conn: outConn, Ip: ip, Port: networkOutput.Port, Capability: networkOutput.Capability, messageQueue: newq}
 | 
						|
			}
 | 
						|
			validConnections[ipAndPort] = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// Client that was connected before that isn't.
 | 
						|
	for ipAndPort, conn := range outSockets {
 | 
						|
		if _, ok := validConnections[ipAndPort]; !ok {
 | 
						|
			log.Printf("removed connection %s.\n", ipAndPort)
 | 
						|
			conn.Conn.Close()
 | 
						|
			delete(outSockets, ipAndPort)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func messageQueueSender() {
 | 
						|
	secondTimer := time.NewTicker(15 * time.Second)
 | 
						|
	queueTimer := time.NewTicker(100 * time.Millisecond)
 | 
						|
 | 
						|
	var lastQueueTimeChange time.Time // Reevaluate	send frequency every 5 seconds.
 | 
						|
	for {
 | 
						|
		select {
 | 
						|
		case msg := <-messageQueue:
 | 
						|
			sendToAllConnectedClients(msg)
 | 
						|
		case <-queueTimer.C:
 | 
						|
			netMutex.Lock()
 | 
						|
 | 
						|
			averageSendableQueueSize := float64(0.0)
 | 
						|
			for k, netconn := range outSockets {
 | 
						|
				if len(netconn.messageQueue) > 0 && !isSleeping(k) && !isThrottled(k) {
 | 
						|
					averageSendableQueueSize += float64(len(netconn.messageQueue)) // Add num sendable messages.
 | 
						|
 | 
						|
					var queuedMsg []byte
 | 
						|
 | 
						|
					// Combine the first 256 entries in netconn.messageQueue to avoid flooding wlan0 with too many IOPS.
 | 
						|
					// Need to play nice with non-queued messages, so this limits the number of entries to combine.
 | 
						|
					// UAT uplink block is 432 bytes, so transmit block size shouldn't be larger than 108 KiB. 10 Mbps per device would therefore be needed to send within a 100 ms window.
 | 
						|
 | 
						|
					mqDepth := len(netconn.messageQueue)
 | 
						|
					if mqDepth > 256 {
 | 
						|
						mqDepth = 256
 | 
						|
					}
 | 
						|
 | 
						|
					for j := 0; j < mqDepth; j++ {
 | 
						|
						queuedMsg = append(queuedMsg, netconn.messageQueue[j]...)
 | 
						|
					}
 | 
						|
 | 
						|
					/*
 | 
						|
						for j, _ := range netconn.messageQueue {
 | 
						|
							queuedMsg = append(queuedMsg, netconn.messageQueue[j]...)
 | 
						|
						}
 | 
						|
					*/
 | 
						|
 | 
						|
					netconn.Conn.Write(queuedMsg)
 | 
						|
					totalNetworkMessagesSent++
 | 
						|
					globalStatus.NetworkDataMessagesSent++
 | 
						|
					globalStatus.NetworkDataBytesSent += uint64(len(queuedMsg))
 | 
						|
 | 
						|
					//netconn.messageQueue = [][]byte{}
 | 
						|
					if mqDepth < len(netconn.messageQueue) {
 | 
						|
						netconn.messageQueue = netconn.messageQueue[mqDepth:]
 | 
						|
					} else {
 | 
						|
						netconn.messageQueue = [][]byte{}
 | 
						|
					}
 | 
						|
					outSockets[k] = netconn
 | 
						|
 | 
						|
					/*
 | 
						|
						tmpConn := netconn
 | 
						|
						tmpConn.Conn.Write(tmpConn.messageQueue[0])
 | 
						|
						totalNetworkMessagesSent++
 | 
						|
						globalStatus.NetworkDataMessagesSent++
 | 
						|
						globalStatus.NetworkDataBytesSent += uint64(len(tmpConn.messageQueue[0]))
 | 
						|
						tmpConn.messageQueue = tmpConn.messageQueue[1:]
 | 
						|
						outSockets[k] = tmpConn
 | 
						|
					*/
 | 
						|
				}
 | 
						|
				netconn.MessageQueueLen = len(netconn.messageQueue)
 | 
						|
				outSockets[k] = netconn
 | 
						|
			}
 | 
						|
 | 
						|
			if stratuxClock.Since(lastQueueTimeChange) >= 5*time.Second {
 | 
						|
				var pd float64
 | 
						|
				if averageSendableQueueSize > 0.0 && len(outSockets) > 0 {
 | 
						|
					averageSendableQueueSize = averageSendableQueueSize / float64(len(outSockets)) // It's a total, not an average, up until this point.
 | 
						|
					pd = math.Max(float64(1.0/750.0), float64(1.0/(4.0*averageSendableQueueSize))) // Say 250ms is enough to get through the whole queue.
 | 
						|
				} else {
 | 
						|
					pd = float64(0.1) // 100ms.
 | 
						|
				}
 | 
						|
 | 
						|
				if globalSettings.DEBUG {
 | 
						|
					log.Printf("Average sendable queue is %v messages. Changing queue timer to %f seconds\n", averageSendableQueueSize, pd)
 | 
						|
				}
 | 
						|
 | 
						|
				queueTimer.Stop()
 | 
						|
				queueTimer = time.NewTicker(time.Duration(pd*1000000000.0) * time.Nanosecond)
 | 
						|
				lastQueueTimeChange = stratuxClock.Time
 | 
						|
			}
 | 
						|
			netMutex.Unlock()
 | 
						|
		case <-secondTimer.C:
 | 
						|
			getNetworkStats()
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func sendMsg(msg []byte, msgType uint8, queueable bool) {
 | 
						|
	messageQueue <- networkMessage{msg: msg, msgType: msgType, queueable: queueable, ts: stratuxClock.Time}
 | 
						|
}
 | 
						|
 | 
						|
func sendGDL90(msg []byte, queueable bool) {
 | 
						|
	sendMsg(msg, NETWORK_GDL90_STANDARD, queueable)
 | 
						|
}
 | 
						|
 | 
						|
func monitorDHCPLeases() {
 | 
						|
	timer := time.NewTicker(30 * time.Second)
 | 
						|
	for {
 | 
						|
		select {
 | 
						|
		case <-timer.C:
 | 
						|
			refreshConnectedClients()
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func icmpEchoSender(c *icmp.PacketConn) {
 | 
						|
	timer := time.NewTicker(5 * time.Second)
 | 
						|
	for {
 | 
						|
		<-timer.C
 | 
						|
		// Collect IPs.
 | 
						|
		ips := make(map[string]bool)
 | 
						|
		for k, _ := range outSockets {
 | 
						|
			ipAndPort := strings.Split(k, ":")
 | 
						|
			ips[ipAndPort[0]] = true
 | 
						|
		}
 | 
						|
		// Send to all IPs.
 | 
						|
		for ip, _ := range ips {
 | 
						|
			wm := icmp.Message{
 | 
						|
				Type: ipv4.ICMPTypeEcho, Code: 0,
 | 
						|
				Body: &icmp.Echo{
 | 
						|
					ID: os.Getpid() & 0xffff, Seq: 1,
 | 
						|
					Data: []byte("STRATUX"),
 | 
						|
				},
 | 
						|
			}
 | 
						|
			wb, err := wm.Marshal(nil)
 | 
						|
			if err != nil {
 | 
						|
				log.Printf("couldn't send ICMP Echo: %s\n", err.Error())
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			if _, err := c.WriteTo(wb, &net.IPAddr{IP: net.ParseIP(ip)}); err != nil {
 | 
						|
				log.Printf("couldn't send ICMP Echo: %s\n", err.Error())
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			totalNetworkMessagesSent++
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Monitor clients going in/out of sleep mode via ICMP unreachable packets.
 | 
						|
func sleepMonitor() {
 | 
						|
	c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
 | 
						|
	if err != nil {
 | 
						|
		log.Printf("error listening for udp - sending data to all ports for all connected clients. err: %s", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	go icmpEchoSender(c)
 | 
						|
	defer c.Close()
 | 
						|
	for {
 | 
						|
		buf := make([]byte, 1500)
 | 
						|
		n, peer, err := c.ReadFrom(buf)
 | 
						|
		if err != nil {
 | 
						|
			log.Printf("%s\n", err.Error())
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		msg, err := icmp.ParseMessage(1, buf[:n])
 | 
						|
		if err != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		ip := peer.String()
 | 
						|
 | 
						|
		// Look for echo replies, mark it as received.
 | 
						|
		if msg.Type == ipv4.ICMPTypeEchoReply {
 | 
						|
			pingResponse[ip] = stratuxClock.Time
 | 
						|
			continue // No further processing needed.
 | 
						|
		}
 | 
						|
 | 
						|
		// Only deal with ICMP Unreachable packets (since that's what iOS and Android seem to be sending whenever the apps are not available).
 | 
						|
		if msg.Type != ipv4.ICMPTypeDestinationUnreachable {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		// Packet parsing.
 | 
						|
		mb, err := msg.Body.Marshal(1)
 | 
						|
		if err != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if len(mb) < 28 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// The unreachable port.
 | 
						|
		port := (uint16(mb[26]) << 8) | uint16(mb[27])
 | 
						|
		ipAndPort := ip + ":" + strconv.Itoa(int(port))
 | 
						|
 | 
						|
		netMutex.Lock()
 | 
						|
		p, ok := outSockets[ipAndPort]
 | 
						|
		if !ok {
 | 
						|
			// Can't do anything, the client isn't even technically connected.
 | 
						|
			netMutex.Unlock()
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		p.LastUnreachable = stratuxClock.Time
 | 
						|
		outSockets[ipAndPort] = p
 | 
						|
		netMutex.Unlock()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func networkStatsCounter() {
 | 
						|
	timer := time.NewTicker(1 * time.Second)
 | 
						|
	var previousNetworkMessagesSent, previousNetworkBytesSent, previousNetworkMessagesSentNonqueueable, previousNetworkBytesSentNonqueueable uint64
 | 
						|
 | 
						|
	for {
 | 
						|
		<-timer.C
 | 
						|
		globalStatus.NetworkDataMessagesSentLastSec = globalStatus.NetworkDataMessagesSent - previousNetworkMessagesSent
 | 
						|
		globalStatus.NetworkDataBytesSentLastSec = globalStatus.NetworkDataBytesSent - previousNetworkBytesSent
 | 
						|
		globalStatus.NetworkDataMessagesSentNonqueueableLastSec = globalStatus.NetworkDataMessagesSentNonqueueable - previousNetworkMessagesSentNonqueueable
 | 
						|
		globalStatus.NetworkDataBytesSentNonqueueableLastSec = globalStatus.NetworkDataBytesSentNonqueueable - previousNetworkBytesSentNonqueueable
 | 
						|
 | 
						|
		// debug option. Uncomment to log per-second network statistics. Useful for debugging WiFi instability.
 | 
						|
		//log.Printf("Network data messages sent: %d total, %d last second.  Network data bytes sent: %d total, %d last second.\n", globalStatus.NetworkDataMessagesSent, globalStatus.NetworkDataMessagesSentLastSec, globalStatus.NetworkDataBytesSent, globalStatus.NetworkDataBytesSentLastSec)
 | 
						|
 | 
						|
		previousNetworkMessagesSent = globalStatus.NetworkDataMessagesSent
 | 
						|
		previousNetworkBytesSent = globalStatus.NetworkDataBytesSent
 | 
						|
		previousNetworkMessagesSentNonqueueable = globalStatus.NetworkDataMessagesSentNonqueueable
 | 
						|
		previousNetworkBytesSentNonqueueable = globalStatus.NetworkDataBytesSentNonqueueable
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
	ffMonitor().
 | 
						|
		Watches for "i-want-to-play-ffm-udp", "i-can-play-ffm-udp", and "i-cannot-play-ffm-udp" UDP messages broadcasted on
 | 
						|
		 port 50113. Tags the client, issues a warning, and disables AHRS.
 | 
						|
 | 
						|
*/
 | 
						|
 | 
						|
func ffMonitor() {
 | 
						|
	ff_warned := false // Has a warning been issued via globalStatus.Errors?
 | 
						|
 | 
						|
	addr := net.UDPAddr{Port: 50113, IP: net.ParseIP("0.0.0.0")}
 | 
						|
	conn, err := net.ListenUDP("udp", &addr)
 | 
						|
	if err != nil {
 | 
						|
		log.Printf("ffMonitor(): error listening on port 50113: %s\n", err.Error())
 | 
						|
		return
 | 
						|
	}
 | 
						|
	defer conn.Close()
 | 
						|
	for {
 | 
						|
		buf := make([]byte, 1024)
 | 
						|
		n, addr, err := conn.ReadFrom(buf)
 | 
						|
		ipAndPort := strings.Split(addr.String(), ":")
 | 
						|
		ip := ipAndPort[0]
 | 
						|
		if err != nil {
 | 
						|
			log.Printf("err: %s\n", err.Error())
 | 
						|
			return
 | 
						|
		}
 | 
						|
		// Got message, check if it's in the correct format.
 | 
						|
		if n < 3 || buf[0] != 0xFF || buf[1] != 0xFE {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		s := string(buf[2:n])
 | 
						|
		s = strings.Replace(s, "\x00", "", -1)
 | 
						|
		ffIpAndPort := ip + ":4000"
 | 
						|
		netMutex.Lock()
 | 
						|
		p, ok := outSockets[ffIpAndPort]
 | 
						|
		if !ok {
 | 
						|
			// Can't do anything, the client isn't even technically connected.
 | 
						|
			netMutex.Unlock()
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if strings.HasPrefix(s, "i-want-to-play-ffm-udp") || strings.HasPrefix(s, "i-can-play-ffm-udp") || strings.HasPrefix(s, "i-cannot-play-ffm-udp") {
 | 
						|
			p.FFCrippled = true
 | 
						|
			//FIXME: AHRS doesn't need to be disabled globally, just messages need to be filtered.
 | 
						|
			globalSettings.AHRS_Enabled = false
 | 
						|
			if !ff_warned {
 | 
						|
				e := errors.New("Stratux is not supported by your EFB app. Your EFB app is known to regularly make changes that cause compatibility issues with Stratux. See the README for a list of apps that officially support Stratux.")
 | 
						|
				addSystemError(e)
 | 
						|
				ff_warned = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
		outSockets[ffIpAndPort] = p
 | 
						|
		netMutex.Unlock()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func initNetwork() {
 | 
						|
	messageQueue = make(chan networkMessage, 1024) // Buffered channel, 1024 messages.
 | 
						|
	serialOutputChan = make(chan []byte, 1024)     // Buffered channel, 1024 GDL90 messages.
 | 
						|
	networkGDL90Chan = make(chan []byte, 1024)
 | 
						|
	outSockets = make(map[string]networkConnection)
 | 
						|
	pingResponse = make(map[string]time.Time)
 | 
						|
	netMutex = &sync.Mutex{}
 | 
						|
	refreshConnectedClients()
 | 
						|
	go monitorDHCPLeases()
 | 
						|
	go messageQueueSender()
 | 
						|
	go sleepMonitor()
 | 
						|
	go networkStatsCounter()
 | 
						|
	go serialOutWatcher()
 | 
						|
	go networkOutWatcher()
 | 
						|
}
 |