Initial commit after restructure
This commit is contained in:
44
network/environment/addresses.go
Normal file
44
network/environment/addresses.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package environment
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/safing-core/network/netutils"
|
||||
)
|
||||
|
||||
func GetAssignedAddresses() (ipv4 []net.IP, ipv6 []net.IP, err error) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ip := net.ParseIP(strings.Split(addr.String(), "/")[0])
|
||||
if ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
ipv4 = append(ipv4, ip4)
|
||||
} else {
|
||||
ipv6 = append(ipv6, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetAssignedGlobalAddresses() (ipv4 []net.IP, ipv6 []net.IP, err error) {
|
||||
allv4, allv6, err := GetAssignedAddresses()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, ip4 := range allv4 {
|
||||
if netutils.IPIsGlobal(ip4) {
|
||||
ipv4 = append(ipv4, ip4)
|
||||
}
|
||||
}
|
||||
for _, ip6 := range allv6 {
|
||||
if netutils.IPIsGlobal(ip6) {
|
||||
ipv6 = append(ipv6, ip6)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
27
network/environment/addresses_test.go
Normal file
27
network/environment/addresses_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package environment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetAssignedAddresses(t *testing.T) {
|
||||
ipv4, ipv6, err := GetAssignedAddresses()
|
||||
fmt.Printf("all v4: %v", ipv4)
|
||||
fmt.Printf("all v6: %v", ipv6)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get addresses: %s", err)
|
||||
}
|
||||
if len(ipv4) == 0 && len(ipv6) == 0 {
|
||||
t.Fatal("GetAssignedAddresses did not return any addresses")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAssignedGlobalAddresses(t *testing.T) {
|
||||
ipv4, ipv6, err := GetAssignedGlobalAddresses()
|
||||
fmt.Printf("all global v4: %v", ipv4)
|
||||
fmt.Printf("all global v6: %v", ipv6)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get addresses: %s", err)
|
||||
}
|
||||
}
|
||||
212
network/environment/dbus_linux.go
Normal file
212
network/environment/dbus_linux.go
Normal file
@@ -0,0 +1,212 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
// +build !server
|
||||
|
||||
package environment
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/godbus/dbus"
|
||||
)
|
||||
|
||||
var (
|
||||
dbusConn *dbus.Conn
|
||||
dbusConnLock sync.Mutex
|
||||
)
|
||||
|
||||
func getNameserversFromDbus() ([]Nameserver, error) {
|
||||
// cmdline tool for exploring: gdbus introspect --system --dest org.freedesktop.NetworkManager --object-path /org/freedesktop/NetworkManager
|
||||
|
||||
var nameservers []Nameserver
|
||||
var err error
|
||||
|
||||
if dbusConn == nil {
|
||||
dbusConn, err = dbus.SystemBus()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
primaryConnectionVariant, err := getNetworkManagerProperty(dbusConn, dbus.ObjectPath("/org/freedesktop/NetworkManager"), "org.freedesktop.NetworkManager.PrimaryConnection")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
primaryConnection, ok := primaryConnectionVariant.Value().(dbus.ObjectPath)
|
||||
if !ok {
|
||||
return nil, errors.New("dbus: could not assert type of /org/freedesktop/NetworkManager:org.freedesktop.NetworkManager.PrimaryConnection")
|
||||
}
|
||||
|
||||
activeConnectionsVariant, err := getNetworkManagerProperty(dbusConn, dbus.ObjectPath("/org/freedesktop/NetworkManager"), "org.freedesktop.NetworkManager.ActiveConnections")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
activeConnections, ok := activeConnectionsVariant.Value().([]dbus.ObjectPath)
|
||||
if !ok {
|
||||
return nil, errors.New("dbus: could not assert type of /org/freedesktop/NetworkManager:org.freedesktop.NetworkManager.ActiveConnections")
|
||||
}
|
||||
|
||||
sortedConnections := []dbus.ObjectPath{primaryConnection}
|
||||
for _, activeConnection := range activeConnections {
|
||||
if !objectPathInSlice(activeConnection, sortedConnections) {
|
||||
sortedConnections = append(sortedConnections, activeConnection)
|
||||
}
|
||||
}
|
||||
|
||||
for _, activeConnection := range sortedConnections {
|
||||
|
||||
ip4ConfigVariant, err := getNetworkManagerProperty(dbusConn, activeConnection, "org.freedesktop.NetworkManager.Connection.Active.Ip4Config")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip4Config, ok := ip4ConfigVariant.Value().(dbus.ObjectPath)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dbus: could not assert type of %s:org.freedesktop.NetworkManager.Connection.Active.Ip4Config", activeConnection)
|
||||
}
|
||||
|
||||
nameserverIP4sVariant, err := getNetworkManagerProperty(dbusConn, ip4Config, "org.freedesktop.NetworkManager.IP4Config.Nameservers")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameserverIP4s, ok := nameserverIP4sVariant.Value().([]uint32)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dbus: could not assert type of %s:org.freedesktop.NetworkManager.IP4Config.Nameservers", ip4Config)
|
||||
}
|
||||
|
||||
nameserverDomainsVariant, err := getNetworkManagerProperty(dbusConn, ip4Config, "org.freedesktop.NetworkManager.IP4Config.Domains")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameserverDomains, ok := nameserverDomainsVariant.Value().([]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dbus: could not assert type of %s:org.freedesktop.NetworkManager.IP4Config.Domains", ip4Config)
|
||||
}
|
||||
|
||||
nameserverSearchesVariant, err := getNetworkManagerProperty(dbusConn, ip4Config, "org.freedesktop.NetworkManager.IP4Config.Searches")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameserverSearches, ok := nameserverSearchesVariant.Value().([]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dbus: could not assert type of %s:org.freedesktop.NetworkManager.IP4Config.Searches", ip4Config)
|
||||
}
|
||||
|
||||
for _, ip := range nameserverIP4s {
|
||||
a := uint8(ip / 16777216)
|
||||
b := uint8((ip % 16777216) / 65536)
|
||||
c := uint8((ip % 65536) / 256)
|
||||
d := uint8(ip % 256)
|
||||
nameservers = append(nameservers, Nameserver{
|
||||
IP: net.IPv4(d, c, b, a),
|
||||
Search: append(nameserverDomains, nameserverSearches...),
|
||||
})
|
||||
}
|
||||
|
||||
ip6ConfigVariant, err := getNetworkManagerProperty(dbusConn, activeConnection, "org.freedesktop.NetworkManager.Connection.Active.Ip6Config")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip6Config, ok := ip6ConfigVariant.Value().(dbus.ObjectPath)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dbus: could not assert type of %s:org.freedesktop.NetworkManager.Connection.Active.Ip6Config", activeConnection)
|
||||
}
|
||||
|
||||
nameserverIP6sVariant, err := getNetworkManagerProperty(dbusConn, ip6Config, "org.freedesktop.NetworkManager.IP6Config.Nameservers")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameserverIP6s, ok := nameserverIP6sVariant.Value().([][]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dbus: could not assert type of %s:org.freedesktop.NetworkManager.IP6Config.Nameservers", ip6Config)
|
||||
}
|
||||
|
||||
nameserverDomainsVariant, err = getNetworkManagerProperty(dbusConn, ip6Config, "org.freedesktop.NetworkManager.IP6Config.Domains")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameserverDomains, ok = nameserverDomainsVariant.Value().([]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dbus: could not assert type of %s:org.freedesktop.NetworkManager.IP6Config.Domains", ip6Config)
|
||||
}
|
||||
|
||||
nameserverSearchesVariant, err = getNetworkManagerProperty(dbusConn, ip6Config, "org.freedesktop.NetworkManager.IP6Config.Searches")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameserverSearches, ok = nameserverSearchesVariant.Value().([]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dbus: could not assert type of %s:org.freedesktop.NetworkManager.IP6Config.Searches", ip6Config)
|
||||
}
|
||||
|
||||
for _, ip := range nameserverIP6s {
|
||||
if len(ip) != 16 {
|
||||
return nil, fmt.Errorf("dbus: query returned IPv6 address (%s) with invalid length", ip)
|
||||
}
|
||||
nameservers = append(nameservers, Nameserver{
|
||||
IP: net.IP(ip),
|
||||
Search: append(nameserverDomains, nameserverSearches...),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nameservers, nil
|
||||
}
|
||||
|
||||
func getConnectivityStateFromDbus() (uint8, error) {
|
||||
var err error
|
||||
|
||||
if dbusConn == nil {
|
||||
dbusConn, err = dbus.SystemBus()
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
connectivityStateVariant, err := getNetworkManagerProperty(dbusConn, dbus.ObjectPath("/org/freedesktop/NetworkManager"), "org.freedesktop.NetworkManager.Connectivity")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
connectivityState, ok := connectivityStateVariant.Value().(uint32)
|
||||
if !ok {
|
||||
return 0, errors.New("dbus: could not assert type of /org/freedesktop/NetworkManager:org.freedesktop.NetworkManager.Connectivity")
|
||||
}
|
||||
|
||||
// NMConnectivityState
|
||||
// NM_CONNECTIVITY_UNKNOWN = 0 Network connectivity is unknown.
|
||||
// NM_CONNECTIVITY_NONE = 1 The host is not connected to any network.
|
||||
// NM_CONNECTIVITY_PORTAL = 2 The host is behind a captive portal and cannot reach the full Internet.
|
||||
// NM_CONNECTIVITY_LIMITED = 3 The host is connected to a network, but does not appear to be able to reach the full Internet.
|
||||
// NM_CONNECTIVITY_FULL = 4 The host is connected to a network, and appears to be able to reach the full Internet.
|
||||
|
||||
switch connectivityState {
|
||||
case 0:
|
||||
return UNKNOWN, nil
|
||||
case 1:
|
||||
return OFFLINE, nil
|
||||
case 2:
|
||||
return PORTAL, nil
|
||||
case 3:
|
||||
return LIMITED, nil
|
||||
case 4:
|
||||
return ONLINE, nil
|
||||
}
|
||||
|
||||
return UNKNOWN, nil
|
||||
}
|
||||
|
||||
func getNetworkManagerProperty(conn *dbus.Conn, objectPath dbus.ObjectPath, property string) (dbus.Variant, error) {
|
||||
object := conn.Object("org.freedesktop.NetworkManager", objectPath)
|
||||
return object.GetProperty(property)
|
||||
}
|
||||
|
||||
func objectPathInSlice(a dbus.ObjectPath, list []dbus.ObjectPath) bool {
|
||||
for _, b := range list {
|
||||
if string(b) == string(a) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
14
network/environment/dbus_linux_mock.go
Normal file
14
network/environment/dbus_linux_mock.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package environment
|
||||
|
||||
func getNameserversFromDbus() ([]Nameserver, error) {
|
||||
var nameservers []Nameserver
|
||||
return nameservers, nil
|
||||
}
|
||||
|
||||
func getConnectivityStateFromDbus() (uint8, error) {
|
||||
return UNKNOWN, nil
|
||||
}
|
||||
19
network/environment/dbus_linux_test.go
Normal file
19
network/environment/dbus_linux_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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 "testing"
|
||||
|
||||
func TestDbus(t *testing.T) {
|
||||
nameservers, err := getNameserversFromDbus()
|
||||
if err != nil {
|
||||
t.Errorf("getNameserversFromDbus failed: %s", err)
|
||||
}
|
||||
t.Logf("getNameserversFromDbus: %v", nameservers)
|
||||
|
||||
connectivityState, err := getConnectivityStateFromDbus()
|
||||
if err != nil {
|
||||
t.Errorf("getConnectivityStateFromDbus failed: %s", err)
|
||||
}
|
||||
t.Logf("getConnectivityStateFromDbus: %v", connectivityState)
|
||||
}
|
||||
167
network/environment/environment.go
Normal file
167
network/environment/environment.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
208
network/environment/environment_linux.go
Normal file
208
network/environment/environment_linux.go
Normal file
@@ -0,0 +1,208 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/Safing/safing-core/log"
|
||||
"github.com/Safing/safing-core/network/netutils"
|
||||
)
|
||||
|
||||
// Gateways returns the currently active gateways
|
||||
func Gateways() []*net.IP {
|
||||
// locking
|
||||
gatewaysLock.Lock()
|
||||
defer gatewaysLock.Unlock()
|
||||
// cache
|
||||
if gatewaysExpires.After(time.Now()) {
|
||||
return gateways
|
||||
}
|
||||
// update cache expiry when finished
|
||||
defer func() {
|
||||
gatewaysExpires = time.Now().Add(gatewaysRecheck)
|
||||
}()
|
||||
// logic
|
||||
|
||||
newGateways := make([]*net.IP, 0)
|
||||
var decoded []byte
|
||||
|
||||
// open file
|
||||
route, err := os.Open("/proc/net/route")
|
||||
if err != nil {
|
||||
log.Warningf("environment: could not read /proc/net/route: %s", err)
|
||||
return newGateways
|
||||
}
|
||||
defer route.Close()
|
||||
|
||||
// file scanner
|
||||
scanner := bufio.NewScanner(route)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
// parse
|
||||
for scanner.Scan() {
|
||||
line := strings.SplitN(scanner.Text(), "\t", 4)
|
||||
if len(line) < 4 {
|
||||
continue
|
||||
}
|
||||
if line[1] == "00000000" {
|
||||
decoded, err = hex.DecodeString(line[2])
|
||||
if err != nil {
|
||||
log.Warningf("environment: could not parse gateway %s from /proc/net/route: %s", line[2], err)
|
||||
continue
|
||||
}
|
||||
if len(decoded) != 4 {
|
||||
log.Warningf("environment: decoded gateway %s from /proc/net/route has wrong length")
|
||||
continue
|
||||
}
|
||||
gate := net.IPv4(decoded[3], decoded[2], decoded[1], decoded[0])
|
||||
newGateways = append(newGateways, &gate)
|
||||
}
|
||||
}
|
||||
|
||||
// open file
|
||||
v6route, err := os.Open("/proc/net/ipv6_route")
|
||||
if err != nil {
|
||||
log.Warningf("environment: could not read /proc/net/ipv6_route: %s", err)
|
||||
return newGateways
|
||||
}
|
||||
defer v6route.Close()
|
||||
|
||||
// file scanner
|
||||
scanner = bufio.NewScanner(v6route)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
// parse
|
||||
for scanner.Scan() {
|
||||
line := strings.SplitN(scanner.Text(), " ", 6)
|
||||
if len(line) < 6 {
|
||||
continue
|
||||
}
|
||||
if line[0] == "00000000000000000000000000000000" && line[4] != "00000000000000000000000000000000" {
|
||||
decoded, err := hex.DecodeString(line[4])
|
||||
if err != nil {
|
||||
log.Warningf("environment: could not parse gateway %s from /proc/net/ipv6_route: %s", line[2], err)
|
||||
continue
|
||||
}
|
||||
if len(decoded) != 16 {
|
||||
log.Warningf("environment: decoded gateway %s from /proc/net/ipv6_route has wrong length")
|
||||
continue
|
||||
}
|
||||
gate := net.IP(decoded)
|
||||
newGateways = append(newGateways, &gate)
|
||||
}
|
||||
}
|
||||
|
||||
return newGateways
|
||||
}
|
||||
|
||||
// Nameservers returns the currently active nameservers
|
||||
func Nameservers() []Nameserver {
|
||||
// locking
|
||||
nameserversLock.Lock()
|
||||
defer nameserversLock.Unlock()
|
||||
// cache
|
||||
if nameserversExpires.After(time.Now()) {
|
||||
return nameservers
|
||||
}
|
||||
// update cache expiry when finished
|
||||
defer func() {
|
||||
nameserversExpires = time.Now().Add(nameserversRecheck)
|
||||
}()
|
||||
|
||||
// logic
|
||||
// TODO: try:
|
||||
// 1. NetworkManager DBUS
|
||||
// 2. /etc/resolv.conf
|
||||
// 2.1. if /etc/resolv.conf has localhost nameserver, check for dnsmasq config (are there others?)
|
||||
nameservers = make([]Nameserver, 0)
|
||||
|
||||
// get nameservers from DBUS
|
||||
dbusNameservers, err := getNameserversFromDbus()
|
||||
if err != nil {
|
||||
log.Warningf("environment: could not get nameservers from dbus: %s", err)
|
||||
} else {
|
||||
nameservers = addNameservers(nameservers, dbusNameservers)
|
||||
}
|
||||
|
||||
// get nameservers from /etc/resolv.conf
|
||||
resolvconfNameservers, err := getNameserversFromResolvconf()
|
||||
if err != nil {
|
||||
log.Warningf("environment: could not get nameservers from resolvconf: %s", err)
|
||||
resolvconfNameservers = make([]Nameserver, 0)
|
||||
} else {
|
||||
nameservers = addNameservers(nameservers, resolvconfNameservers)
|
||||
}
|
||||
|
||||
return nameservers
|
||||
}
|
||||
|
||||
func getNameserversFromResolvconf() ([]Nameserver, error) {
|
||||
// open file
|
||||
resolvconf, err := os.Open("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
log.Warningf("environment: could not read /etc/resolv.conf: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
defer resolvconf.Close()
|
||||
|
||||
// file scanner
|
||||
scanner := bufio.NewScanner(resolvconf)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
var searchDomains []string
|
||||
var servers []net.IP
|
||||
|
||||
// parse
|
||||
for scanner.Scan() {
|
||||
line := strings.SplitN(scanner.Text(), " ", 3)
|
||||
if len(line) < 2 {
|
||||
continue
|
||||
}
|
||||
switch line[0] {
|
||||
case "search":
|
||||
if netutils.IsValidFqdn(dns.Fqdn(line[1])) {
|
||||
searchDomains = append(searchDomains, line[1])
|
||||
}
|
||||
case "nameserver":
|
||||
ip := net.ParseIP(line[1])
|
||||
if ip != nil {
|
||||
servers = append(servers, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build array
|
||||
var nameservers []Nameserver
|
||||
for _, server := range servers {
|
||||
nameservers = append(nameservers, Nameserver{
|
||||
IP: server,
|
||||
Search: searchDomains,
|
||||
})
|
||||
}
|
||||
return nameservers, nil
|
||||
|
||||
}
|
||||
|
||||
func addNameservers(nameservers, newNameservers []Nameserver) []Nameserver {
|
||||
for _, newNameserver := range newNameservers {
|
||||
found := false
|
||||
for _, nameserver := range nameservers {
|
||||
if nameserver.IP.Equal(newNameserver.IP) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
nameservers = append(nameservers, newNameserver)
|
||||
}
|
||||
}
|
||||
return nameservers
|
||||
}
|
||||
24
network/environment/environment_test.go
Normal file
24
network/environment/environment_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// 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 "testing"
|
||||
|
||||
func TestEnvironment(t *testing.T) {
|
||||
|
||||
connectivityTest := Connectivity()
|
||||
t.Logf("connectivity: %v", connectivityTest)
|
||||
|
||||
nameserversTest, err := getNameserversFromResolvconf()
|
||||
if err != nil {
|
||||
t.Errorf("failed to get namerservers from resolvconf: %s", err)
|
||||
}
|
||||
t.Logf("nameservers from resolvconf: %v", nameserversTest)
|
||||
|
||||
nameserversTest = Nameservers()
|
||||
t.Logf("nameservers: %v", nameserversTest)
|
||||
|
||||
gatewaysTest := Gateways()
|
||||
t.Logf("gateways: %v", gatewaysTest)
|
||||
|
||||
}
|
||||
11
network/environment/environment_windows.go
Normal file
11
network/environment/environment_windows.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package environment
|
||||
|
||||
import "net"
|
||||
|
||||
func Nameservers() []Nameserver {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Gateways() []*net.IP {
|
||||
return nil
|
||||
}
|
||||
23
network/environment/events.go
Normal file
23
network/environment/events.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package environment
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
networkChangedEventCh = make(chan struct{}, 0)
|
||||
networkChangedEventLock sync.Mutex
|
||||
)
|
||||
|
||||
func triggerNetworkChanged() {
|
||||
networkChangedEventLock.Lock()
|
||||
defer networkChangedEventLock.Unlock()
|
||||
close(networkChangedEventCh)
|
||||
networkChangedEventCh = make(chan struct{}, 0)
|
||||
}
|
||||
|
||||
func NetworkChanged() <-chan struct{} {
|
||||
networkChangedEventLock.Lock()
|
||||
defer networkChangedEventLock.Unlock()
|
||||
return networkChangedEventCh
|
||||
}
|
||||
29
network/environment/interface.go
Normal file
29
network/environment/interface.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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 (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type EnvironmentInterface struct {
|
||||
lastNetworkChange int64
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewInterface() *EnvironmentInterface {
|
||||
return &EnvironmentInterface{
|
||||
lastNetworkChange: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (env *EnvironmentInterface) NetworkChanged() bool {
|
||||
env.lock.Lock()
|
||||
defer env.lock.Unlock()
|
||||
lnc := atomic.LoadInt64(lastNetworkChange)
|
||||
if lnc > env.lastNetworkChange {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
115
network/environment/location.go
Normal file
115
network/environment/location.go
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"github.com/Safing/safing-core/network/netutils"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
// TODO: reference forking
|
||||
// TODO: Create IPv6 version of GetApproximateInternetLocation
|
||||
|
||||
// GetApproximateInternetLocation returns the IP-address of the nearest ping-answering internet node
|
||||
func GetApproximateInternetLocation() (net.IP, error) {
|
||||
// TODO: first check if we have a public IP
|
||||
// net.InterfaceAddrs()
|
||||
|
||||
// Traceroute example
|
||||
|
||||
var dst net.IPAddr
|
||||
dst.IP = net.IPv4(8, 8, 8, 8)
|
||||
|
||||
c, err := net.ListenPacket("ip4:1", "0.0.0.0") // ICMP for IPv4
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
p := ipv4.NewPacketConn(c)
|
||||
err = p.SetControlMessage(ipv4.FlagTTL|ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wm := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
// TODO: think of something better and not suspicious
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
},
|
||||
}
|
||||
rb := make([]byte, 1500)
|
||||
|
||||
next:
|
||||
for i := 1; i <= 64; i++ { // up to 64 hops
|
||||
repeat:
|
||||
for j := 1; j <= 5; j++ {
|
||||
wm.Body.(*icmp.Echo).Seq = i
|
||||
|
||||
wb, err := wm.Marshal(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = p.SetTTL(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = p.WriteTo(wb, nil, &dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = p.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// n, cm, peer, err := p.ReadFrom(rb)
|
||||
// readping:
|
||||
for {
|
||||
|
||||
n, _, peer, err := p.ReadFrom(rb)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
continue repeat
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rm, err := icmp.ParseMessage(1, rb[:n])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
ip := net.ParseIP(peer.String())
|
||||
if ip == nil {
|
||||
return nil, errors.New(fmt.Sprintf("failed to parse IP: %s", peer.String()))
|
||||
}
|
||||
if !netutils.IPIsLocal(ip) {
|
||||
return ip, nil
|
||||
}
|
||||
continue next
|
||||
case ipv4.ICMPTypeEchoReply:
|
||||
continue next
|
||||
default:
|
||||
// log.Tracef("unknown ICMP message: %+v\n", rm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
15
network/environment/location_test.go
Normal file
15
network/environment/location_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
// +build root
|
||||
|
||||
package environment
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetApproximateInternetLocation(t *testing.T) {
|
||||
ip, err := GetApproximateInternetLocation()
|
||||
if err != nil {
|
||||
t.Errorf("GetApproximateInternetLocation failed: %s", err)
|
||||
}
|
||||
t.Logf("GetApproximateInternetLocation: %s", ip.String())
|
||||
}
|
||||
Reference in New Issue
Block a user