// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. package environment import ( "bytes" "crypto/sha1" "io" "net" "sync" "sync/atomic" "time" "github.com/Safing/safing-core/log" ) // TODO: find a good way to identify a network // best options until now: // MAC of gateway // domain parameter of dhcp // TODO: get dhcp servers on windows: // windows: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365917 // this info might already be included in the interfaces api provided by golang! const ( UNKNOWN uint8 = iota OFFLINE LIMITED // local network only PORTAL // there seems to be an internet connection, but we are being intercepted ONLINE ) const ( connectivityRecheck = 2 * time.Second interfacesRecheck = 2 * time.Second gatewaysRecheck = 2 * time.Second nameserversRecheck = 2 * time.Second ) var ( connectivity uint8 connectivityLock sync.Mutex connectivityExpires = time.Now() // interfaces = make(map[*net.IP]net.Flags) // interfacesLock sync.Mutex // interfacesExpires = time.Now() gateways = make([]*net.IP, 0) gatewaysLock sync.Mutex gatewaysExpires = time.Now() nameservers = make([]Nameserver, 0) nameserversLock sync.Mutex nameserversExpires = time.Now() lastNetworkChange *int64 lastNetworkChecksum []byte ) type Nameserver struct { IP net.IP Search []string } func init() { lnc := int64(0) lastNetworkChange = &lnc go func() { time.Sleep(1 * time.Second) Connectivity() }() go monitorNetworkChanges() } // Connectivity returns the current state of connectivity to the network/Internet func Connectivity() uint8 { // locking connectivityLock.Lock() defer connectivityLock.Unlock() // cache if connectivityExpires.After(time.Now()) { return connectivity } // logic // TODO: implement more methods status, err := getConnectivityStateFromDbus() if err != nil { log.Warningf("environment: could not get connectivity: %s", err) setConnectivity(UNKNOWN) return UNKNOWN } setConnectivity(status) return status } func setConnectivity(status uint8) { if connectivity != status { connectivity = status connectivityExpires = time.Now().Add(connectivityRecheck) var connectivityName string switch connectivity { case UNKNOWN: connectivityName = "unknown" case OFFLINE: connectivityName = "offline" case LIMITED: connectivityName = "limited" case PORTAL: connectivityName = "portal" case ONLINE: connectivityName = "online" default: connectivityName = "invalid" } log.Infof("environment: connectivity changed to %s", connectivityName) } } // ConnectionSucceeded should be called when a module was able to successfully connect to the internet (do not call too often) func ConnectionSucceeded() { connectivityLock.Lock() defer connectivityLock.Unlock() setConnectivity(ONLINE) } func monitorNetworkChanges() { // TODO: make more elegant solution for { time.Sleep(2 * time.Second) hasher := sha1.New() interfaces, err := net.Interfaces() if err != nil { log.Warningf("environment: failed to get interfaces: %s", err) continue } for _, iface := range interfaces { io.WriteString(hasher, iface.Name) // log.Tracef("adding: %s", iface.Name) io.WriteString(hasher, iface.Flags.String()) // log.Tracef("adding: %s", iface.Flags.String()) addrs, err := iface.Addrs() if err != nil { log.Warningf("environment: failed to get addrs from interface %s: %s", iface.Name, err) continue } for _, addr := range addrs { io.WriteString(hasher, addr.String()) // log.Tracef("adding: %s", addr.String()) } } newChecksum := hasher.Sum(nil) if !bytes.Equal(lastNetworkChecksum, newChecksum) { if len(lastNetworkChecksum) == 0 { lastNetworkChecksum = newChecksum continue } lastNetworkChecksum = newChecksum atomic.StoreInt64(lastNetworkChange, time.Now().Unix()) log.Info("environment: network changed") triggerNetworkChanged() } } }