mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
Merge pull request #278 from safing/fix/netenv
Fix and improve netenv for Windows and Linux
This commit is contained in:
commit
4390df8cb5
21 changed files with 861 additions and 304 deletions
|
@ -139,15 +139,37 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
|||
|
||||
switch meta.Protocol {
|
||||
case packet.ICMP:
|
||||
// Submit to ICMP listener.
|
||||
submitted := netenv.SubmitPacketToICMPListener(pkt)
|
||||
|
||||
// Always permit ICMP.
|
||||
log.Debugf("filter: fast-track accepting ICMP: %s", pkt)
|
||||
_ = pkt.PermanentAccept()
|
||||
|
||||
// If the packet was submitted to the listener, we must not do a
|
||||
// permanent accept, because then we won't see any future packets of that
|
||||
// connection and thus cannot continue to submit them.
|
||||
if submitted {
|
||||
_ = pkt.Accept()
|
||||
} else {
|
||||
_ = pkt.PermanentAccept()
|
||||
}
|
||||
return true
|
||||
|
||||
case packet.ICMPv6:
|
||||
// Submit to ICMP listener.
|
||||
submitted := netenv.SubmitPacketToICMPListener(pkt)
|
||||
|
||||
// Always permit ICMPv6.
|
||||
log.Debugf("filter: fast-track accepting ICMPv6: %s", pkt)
|
||||
_ = pkt.PermanentAccept()
|
||||
|
||||
// If the packet was submitted to the listener, we must not do a
|
||||
// permanent accept, because then we won't see any future packets of that
|
||||
// connection and thus cannot continue to submit them.
|
||||
if submitted {
|
||||
_ = pkt.Accept()
|
||||
} else {
|
||||
_ = pkt.PermanentAccept()
|
||||
}
|
||||
return true
|
||||
|
||||
case packet.UDP, packet.TCP:
|
||||
|
|
|
@ -179,11 +179,13 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
|
|||
verdictPending: abool.New(),
|
||||
}
|
||||
|
||||
if attrs.Payload != nil {
|
||||
pkt.Payload = *attrs.Payload
|
||||
if attrs.Payload == nil {
|
||||
// There is not payload.
|
||||
log.Warningf("nfqueue: packet #%s has no payload", pkt.pktID)
|
||||
return 0
|
||||
}
|
||||
|
||||
if err := pmpacket.Parse(pkt.Payload, pkt.Info()); err != nil {
|
||||
if err := pmpacket.Parse(*attrs.Payload, &pkt.Base); err != nil {
|
||||
log.Warningf("nfqueue: failed to parse payload: %s", err)
|
||||
_ = pkt.Drop()
|
||||
return 0
|
||||
|
|
|
@ -65,6 +65,11 @@ func (pkt *packet) ID() string {
|
|||
return fmt.Sprintf("pkt:%d qid:%d", pkt.pktID, pkt.queue.id)
|
||||
}
|
||||
|
||||
// LoadPacketData does nothing on Linux, as data is always fully parsed.
|
||||
func (pkt *packet) LoadPacketData() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(ppacher): revisit the following behavior:
|
||||
// The legacy implementation of nfqueue (and the interception) module
|
||||
// always accept a packet but may mark it so that a subsequent rule in
|
||||
|
|
|
@ -230,6 +230,7 @@ func GetPayload(packetID uint32, packetSize uint32) ([]byte, error) {
|
|||
if packetSize < uint32(len(buf)) {
|
||||
return buf[:packetSize], nil
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ type Packet struct {
|
|||
}
|
||||
|
||||
// GetPayload returns the full raw packet.
|
||||
func (pkt *Packet) GetPayload() ([]byte, error) {
|
||||
func (pkt *Packet) LoadPacketData() error {
|
||||
pkt.lock.Lock()
|
||||
defer pkt.lock.Unlock()
|
||||
|
||||
|
@ -33,17 +33,21 @@ func (pkt *Packet) GetPayload() ([]byte, error) {
|
|||
|
||||
payload, err := GetPayload(pkt.verdictRequest.id, pkt.verdictRequest.packetSize)
|
||||
if err != nil {
|
||||
log.Tracer(pkt.Ctx()).Warningf("windowskext: failed to load payload %s", err)
|
||||
log.Errorf("windowskext: failed to load payload %s", err)
|
||||
return nil, packet.ErrFailedToLoadPayload
|
||||
log.Tracer(pkt.Ctx()).Warningf("windowskext: failed to load payload: %s", err)
|
||||
return packet.ErrFailedToLoadPayload
|
||||
}
|
||||
|
||||
err = packet.Parse(payload, &pkt.Base)
|
||||
if err != nil {
|
||||
log.Tracer(pkt.Ctx()).Warningf("windowskext: failed to parse payload: %s", err)
|
||||
return packet.ErrFailedToLoadPayload
|
||||
}
|
||||
pkt.Payload = payload
|
||||
}
|
||||
|
||||
if len(pkt.Payload) == 0 {
|
||||
return nil, packet.ErrFailedToLoadPayload
|
||||
if len(pkt.Raw()) == 0 {
|
||||
return packet.ErrFailedToLoadPayload
|
||||
}
|
||||
return pkt.Payload, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Accept accepts the packet.
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
package netenv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetAssignedAddresses(t *testing.T) {
|
||||
ipv4, ipv6, err := GetAssignedAddresses()
|
||||
fmt.Printf("all v4: %v", ipv4)
|
||||
fmt.Printf("all v6: %v", ipv6)
|
||||
t.Logf("all v4: %v", ipv4)
|
||||
t.Logf("all v6: %v", ipv6)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get addresses: %s", err)
|
||||
}
|
||||
|
@ -19,8 +18,8 @@ func TestGetAssignedAddresses(t *testing.T) {
|
|||
|
||||
func TestGetAssignedGlobalAddresses(t *testing.T) {
|
||||
ipv4, ipv6, err := GetAssignedGlobalAddresses()
|
||||
fmt.Printf("all global v4: %v", ipv4)
|
||||
fmt.Printf("all global v6: %v", ipv6)
|
||||
t.Logf("all global v4: %v", ipv4)
|
||||
t.Logf("all global v6: %v", ipv6)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get addresses: %s", err)
|
||||
}
|
||||
|
|
51
netenv/api.go
Normal file
51
netenv/api.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package netenv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
)
|
||||
|
||||
func registerAPIEndpoints() error {
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "network/gateways",
|
||||
Read: api.PermitUser,
|
||||
StructFunc: func(ar *api.Request) (i interface{}, err error) {
|
||||
return Gateways(), nil
|
||||
},
|
||||
Name: "Get Default Gateways",
|
||||
Description: "Returns the current active default gateways of the network.",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "network/nameservers",
|
||||
Read: api.PermitUser,
|
||||
StructFunc: func(ar *api.Request) (i interface{}, err error) {
|
||||
return Nameservers(), nil
|
||||
},
|
||||
Name: "Get System Nameservers",
|
||||
Description: "Returns the currently configured nameservers on the OS.",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "network/location",
|
||||
Read: api.PermitUser,
|
||||
StructFunc: func(ar *api.Request) (i interface{}, err error) {
|
||||
locs, ok := GetInternetLocation()
|
||||
if ok {
|
||||
return locs, nil
|
||||
}
|
||||
return nil, errors.New("no location data available")
|
||||
},
|
||||
Name: "Get Approximate Internet Location",
|
||||
Description: "Returns an approximation of where the device is on the Internet.",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -8,6 +8,8 @@ import (
|
|||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
|
@ -19,7 +21,7 @@ var (
|
|||
func getNameserversFromDbus() ([]Nameserver, error) { //nolint:gocognit // TODO
|
||||
// cmdline tool for exploring: gdbus introspect --system --dest org.freedesktop.NetworkManager --object-path /org/freedesktop/NetworkManager
|
||||
|
||||
var nameservers []Nameserver
|
||||
var ns []Nameserver
|
||||
var err error
|
||||
|
||||
dbusConnLock.Lock()
|
||||
|
@ -34,7 +36,7 @@ func getNameserversFromDbus() ([]Nameserver, error) { //nolint:gocognit // TODO
|
|||
|
||||
primaryConnectionVariant, err := getNetworkManagerProperty(dbusConn, dbus.ObjectPath("/org/freedesktop/NetworkManager"), "org.freedesktop.NetworkManager.PrimaryConnection")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("dbus: failed to access NetworkManager.PrimaryConnection: %s", err)
|
||||
}
|
||||
primaryConnection, ok := primaryConnectionVariant.Value().(dbus.ObjectPath)
|
||||
if !ok {
|
||||
|
@ -43,7 +45,7 @@ func getNameserversFromDbus() ([]Nameserver, error) { //nolint:gocognit // TODO
|
|||
|
||||
activeConnectionsVariant, err := getNetworkManagerProperty(dbusConn, dbus.ObjectPath("/org/freedesktop/NetworkManager"), "org.freedesktop.NetworkManager.ActiveConnections")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("dbus: failed to access NetworkManager.ActiveConnections: %s", err)
|
||||
}
|
||||
activeConnections, ok := activeConnectionsVariant.Value().([]dbus.ObjectPath)
|
||||
if !ok {
|
||||
|
@ -58,102 +60,107 @@ func getNameserversFromDbus() ([]Nameserver, error) { //nolint:gocognit // TODO
|
|||
}
|
||||
|
||||
for _, activeConnection := range sortedConnections {
|
||||
|
||||
ip4ConfigVariant, err := getNetworkManagerProperty(dbusConn, activeConnection, "org.freedesktop.NetworkManager.Connection.Active.Ip4Config")
|
||||
new, err := dbusGetInterfaceNameservers(dbusConn, activeConnection, 4)
|
||||
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)
|
||||
log.Warningf("failed to get nameserver: %s", err)
|
||||
} else {
|
||||
ns = append(ns, new...)
|
||||
}
|
||||
|
||||
nameserverIP4sVariant, err := getNetworkManagerProperty(dbusConn, ip4Config, "org.freedesktop.NetworkManager.IP4Config.Nameservers")
|
||||
new, err = dbusGetInterfaceNameservers(dbusConn, activeConnection, 6)
|
||||
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)
|
||||
log.Warningf("failed to get nameserver: %s", err)
|
||||
} else {
|
||||
ns = append(ns, new...)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
func dbusGetInterfaceNameservers(dbusConn *dbus.Conn, interfaceObject dbus.ObjectPath, ipVersion uint8) ([]Nameserver, error) {
|
||||
ipConfigPropertyKey := fmt.Sprintf("org.freedesktop.NetworkManager.Connection.Active.Ip%dConfig", ipVersion)
|
||||
nameserversIPsPropertyKey := fmt.Sprintf("org.freedesktop.NetworkManager.IP%dConfig.Nameservers", ipVersion)
|
||||
nameserversDomainsPropertyKey := fmt.Sprintf("org.freedesktop.NetworkManager.IP%dConfig.Domains", ipVersion)
|
||||
nameserversSearchesPropertyKey := fmt.Sprintf("org.freedesktop.NetworkManager.IP%dConfig.Searches", ipVersion)
|
||||
|
||||
// Get Interface Configuration.
|
||||
ipConfigVariant, err := getNetworkManagerProperty(dbusConn, interfaceObject, ipConfigPropertyKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to access %s:%s: %s", interfaceObject, ipConfigPropertyKey, err)
|
||||
}
|
||||
ipConfig, ok := ipConfigVariant.Value().(dbus.ObjectPath)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not assert type of %s:%s", interfaceObject, ipConfigPropertyKey)
|
||||
}
|
||||
|
||||
// Check if interface is active in the selected IP version
|
||||
if !ipConfig.IsValid() || ipConfig == "/" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get Nameserver IPs
|
||||
nameserverIPsVariant, err := getNetworkManagerProperty(dbusConn, ipConfig, nameserversIPsPropertyKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to access %s:%s: %s", ipConfig, nameserversIPsPropertyKey, err)
|
||||
}
|
||||
var nameserverIPs []net.IP
|
||||
switch ipVersion {
|
||||
case 4:
|
||||
nameserverIP4s, ok := nameserverIPsVariant.Value().([]uint32)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not assert type of %s:%s", ipConfig, nameserversIPsPropertyKey)
|
||||
}
|
||||
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...),
|
||||
})
|
||||
nameserverIPs = append(nameserverIPs, net.IPv4(d, c, b, a))
|
||||
}
|
||||
|
||||
ip6ConfigVariant, err := getNetworkManagerProperty(dbusConn, activeConnection, "org.freedesktop.NetworkManager.Connection.Active.Ip6Config")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip6Config, ok := ip6ConfigVariant.Value().(dbus.ObjectPath)
|
||||
case 6:
|
||||
nameserverIP6s, ok := nameserverIPsVariant.Value().([][]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dbus: could not assert type of %s:org.freedesktop.NetworkManager.Connection.Active.Ip6Config", activeConnection)
|
||||
return nil, fmt.Errorf("could not assert type of %s:%s", ipConfig, nameserversIPsPropertyKey)
|
||||
}
|
||||
|
||||
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)
|
||||
return nil, fmt.Errorf("query returned IPv6 address with invalid length: %q", ip)
|
||||
}
|
||||
nameservers = append(nameservers, Nameserver{
|
||||
IP: net.IP(ip),
|
||||
Search: append(nameserverDomains, nameserverSearches...),
|
||||
})
|
||||
nameserverIPs = append(nameserverIPs, net.IP(ip))
|
||||
}
|
||||
}
|
||||
|
||||
return nameservers, nil
|
||||
// Get Nameserver Domains
|
||||
nameserverDomainsVariant, err := getNetworkManagerProperty(dbusConn, ipConfig, nameserversDomainsPropertyKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to access %s:%s: %s", ipConfig, nameserversDomainsPropertyKey, err)
|
||||
}
|
||||
nameserverDomains, ok := nameserverDomainsVariant.Value().([]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not assert type of %s:%s", ipConfig, nameserversDomainsPropertyKey)
|
||||
}
|
||||
|
||||
// Get Nameserver Searches
|
||||
nameserverSearchesVariant, err := getNetworkManagerProperty(dbusConn, ipConfig, nameserversSearchesPropertyKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to access %s:%s: %s", ipConfig, nameserversSearchesPropertyKey, err)
|
||||
}
|
||||
nameserverSearches, ok := nameserverSearchesVariant.Value().([]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not assert type of %s:%s", ipConfig, nameserversSearchesPropertyKey)
|
||||
}
|
||||
|
||||
ns := make([]Nameserver, 0, len(nameserverIPs))
|
||||
searchDomains := append(nameserverDomains, nameserverSearches...)
|
||||
for _, nameserverIP := range nameserverIPs {
|
||||
ns = append(ns, Nameserver{
|
||||
IP: nameserverIP,
|
||||
Search: searchDomains,
|
||||
})
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func getConnectivityStateFromDbus() (OnlineStatus, error) {
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
|
@ -15,35 +14,25 @@ import (
|
|||
"github.com/safing/portmaster/network/netutils"
|
||||
)
|
||||
|
||||
const (
|
||||
gatewaysRecheck = 2 * time.Second
|
||||
nameserversRecheck = 2 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
gateways = make([]net.IP, 0)
|
||||
gatewaysLock sync.Mutex
|
||||
gatewaysExpires = time.Now()
|
||||
gateways = make([]net.IP, 0)
|
||||
gatewaysLock sync.Mutex
|
||||
gatewaysNetworkChangedFlag = GetNetworkChangedFlag()
|
||||
|
||||
nameservers = make([]Nameserver, 0)
|
||||
nameserversLock sync.Mutex
|
||||
nameserversExpires = time.Now()
|
||||
nameservers = make([]Nameserver, 0)
|
||||
nameserversLock sync.Mutex
|
||||
nameserversNetworkChangedFlag = GetNetworkChangedFlag()
|
||||
)
|
||||
|
||||
// Gateways returns the currently active gateways.
|
||||
func Gateways() []net.IP {
|
||||
// locking
|
||||
gatewaysLock.Lock()
|
||||
defer gatewaysLock.Unlock()
|
||||
// cache
|
||||
if gatewaysExpires.After(time.Now()) {
|
||||
// Check if the network changed, if not, return cache.
|
||||
if !gatewaysNetworkChangedFlag.IsSet() {
|
||||
return gateways
|
||||
}
|
||||
// update cache expiry when finished
|
||||
defer func() {
|
||||
gatewaysExpires = time.Now().Add(gatewaysRecheck)
|
||||
}()
|
||||
// logic
|
||||
gatewaysNetworkChangedFlag.Refresh()
|
||||
|
||||
gateways = make([]net.IP, 0)
|
||||
var decoded []byte
|
||||
|
@ -119,17 +108,13 @@ func Gateways() []net.IP {
|
|||
|
||||
// Nameservers returns the currently active nameservers.
|
||||
func Nameservers() []Nameserver {
|
||||
// locking
|
||||
nameserversLock.Lock()
|
||||
defer nameserversLock.Unlock()
|
||||
// cache
|
||||
if nameserversExpires.After(time.Now()) {
|
||||
// Check if the network changed, if not, return cache.
|
||||
if !nameserversNetworkChangedFlag.IsSet() {
|
||||
return nameservers
|
||||
}
|
||||
// update cache expiry when finished
|
||||
defer func() {
|
||||
nameserversExpires = time.Now().Add(nameserversRecheck)
|
||||
}()
|
||||
nameserversNetworkChangedFlag.Refresh()
|
||||
|
||||
// logic
|
||||
// TODO: try:
|
||||
|
|
11
netenv/environment_linux_test.go
Normal file
11
netenv/environment_linux_test.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package netenv
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLinuxEnvironment(t *testing.T) {
|
||||
nameserversTest, err := getNameserversFromResolvconf()
|
||||
if err != nil {
|
||||
t.Errorf("failed to get namerservers from resolvconf: %s", err)
|
||||
}
|
||||
t.Logf("nameservers from resolvconf: %+v", nameserversTest)
|
||||
}
|
|
@ -1,21 +1,11 @@
|
|||
// +build linux
|
||||
|
||||
package netenv
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEnvironment(t *testing.T) {
|
||||
|
||||
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)
|
||||
nameserversTest := Nameservers()
|
||||
t.Logf("nameservers: %+v", nameserversTest)
|
||||
|
||||
gatewaysTest := Gateways()
|
||||
t.Logf("gateways: %v", gatewaysTest)
|
||||
|
||||
t.Logf("gateways: %+v", gatewaysTest)
|
||||
}
|
||||
|
|
|
@ -3,93 +3,182 @@ package netenv
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/utils/osdetail"
|
||||
)
|
||||
|
||||
const (
|
||||
nameserversRecheck = 2 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
nameservers = make([]Nameserver, 0)
|
||||
nameserversLock sync.Mutex
|
||||
nameserversExpires = time.Now()
|
||||
)
|
||||
|
||||
// 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)
|
||||
}()
|
||||
|
||||
// reset
|
||||
nameservers = make([]Nameserver, 0)
|
||||
|
||||
// This is a preliminary workaround until we have more time for proper interface using iphlpapi.dll
|
||||
// TODO: make nice implementation
|
||||
|
||||
var output = make(chan []byte, 1)
|
||||
module.StartWorker("get assigned nameservers", func(ctx context.Context) error {
|
||||
cmd := exec.CommandContext(ctx, "nslookup", "localhost")
|
||||
data, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Debugf("netenv: failed to get assigned nameserves: %s", err)
|
||||
output <- nil
|
||||
} else {
|
||||
output <- data
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
select {
|
||||
case data := <-output:
|
||||
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
// check if we found the correct line
|
||||
if !strings.HasPrefix(scanner.Text(), "Address:") {
|
||||
continue
|
||||
}
|
||||
// split into fields, check if we have enough fields
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
// parse nameserver, return if valid IP found
|
||||
ns := net.ParseIP(fields[1])
|
||||
if ns != nil {
|
||||
nameservers = append(nameservers, Nameserver{
|
||||
IP: ns,
|
||||
})
|
||||
return nameservers
|
||||
}
|
||||
}
|
||||
log.Debug("netenv: could not find assigned nameserver")
|
||||
return nameservers
|
||||
|
||||
case <-time.After(5 * time.Second):
|
||||
log.Debug("netenv: timed out while getting assigned nameserves")
|
||||
}
|
||||
|
||||
return nameservers
|
||||
}
|
||||
|
||||
// Gateways returns the currently active gateways.
|
||||
func Gateways() []net.IP {
|
||||
return nil
|
||||
defaultIf := getDefaultInterface()
|
||||
if defaultIf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Collect gateways.
|
||||
var gw []net.IP
|
||||
if defaultIf.IPv4DefaultGateway != nil {
|
||||
gw = append(gw, defaultIf.IPv4DefaultGateway)
|
||||
}
|
||||
if defaultIf.IPv6DefaultGateway != nil {
|
||||
gw = append(gw, defaultIf.IPv6DefaultGateway)
|
||||
}
|
||||
|
||||
return gw
|
||||
}
|
||||
|
||||
// Nameservers returns the currently active nameservers.
|
||||
func Nameservers() []Nameserver {
|
||||
defaultIf := getDefaultInterface()
|
||||
if defaultIf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compile search list.
|
||||
var search []string
|
||||
if defaultIf.DNSServerConfig != nil {
|
||||
if defaultIf.DNSServerConfig.Suffix != "" {
|
||||
search = append(search, defaultIf.DNSServerConfig.Suffix)
|
||||
}
|
||||
if len(defaultIf.DNSServerConfig.SuffixSearchList) > 0 {
|
||||
search = append(search, defaultIf.DNSServerConfig.SuffixSearchList...)
|
||||
}
|
||||
}
|
||||
|
||||
// Compile nameservers.
|
||||
var ns []Nameserver
|
||||
for _, nsIP := range defaultIf.DNSServer {
|
||||
ns = append(ns, Nameserver{
|
||||
IP: nsIP,
|
||||
Search: search,
|
||||
})
|
||||
}
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
const (
|
||||
defaultInterfaceRecheck = 2 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
defaultInterface *defaultNetInterface
|
||||
defaultInterfaceLock sync.Mutex
|
||||
defaultInterfaceNetworkChangedFlag = GetNetworkChangedFlag()
|
||||
)
|
||||
|
||||
type defaultNetInterface struct {
|
||||
InterfaceIndex string
|
||||
IPv6Address net.IP
|
||||
IPv4Address net.IP
|
||||
IPv6DefaultGateway net.IP
|
||||
IPv4DefaultGateway net.IP
|
||||
DNSServer []net.IP
|
||||
DNSServerConfig *dnsServerConfig
|
||||
}
|
||||
|
||||
type dnsServerConfig struct {
|
||||
Suffix string
|
||||
SuffixSearchList []string
|
||||
}
|
||||
|
||||
func getDefaultInterface() *defaultNetInterface {
|
||||
defaultInterfaceLock.Lock()
|
||||
defer defaultInterfaceLock.Unlock()
|
||||
// Check if the network changed, if not, return cache.
|
||||
if !defaultInterfaceNetworkChangedFlag.IsSet() {
|
||||
return defaultInterface
|
||||
}
|
||||
defaultInterfaceNetworkChangedFlag.Refresh()
|
||||
|
||||
// Get interface data from Windows.
|
||||
interfaceData, err := osdetail.RunPowershellCmd("Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Select-Object -First 1 | Get-NetIPConfiguration | Format-List")
|
||||
if err != nil {
|
||||
log.Warningf("netenv: failed to get interface data: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: It would be great to get this as json. Powershell can do this,
|
||||
// but it just spits out lots of weird data instead of the same strings
|
||||
// seen in the list.
|
||||
newIf := &defaultNetInterface{}
|
||||
|
||||
// Scan data for needed fields.
|
||||
scanner := bufio.NewScanner(bytes.NewBufferString(interfaceData))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
var segmentKey, segmentValue, previousKey string
|
||||
for scanner.Scan() {
|
||||
segments := strings.SplitN(scanner.Text(), " : ", 2)
|
||||
|
||||
// Check what the line gives us.
|
||||
switch len(segments) {
|
||||
case 2:
|
||||
// This is a new key and value.
|
||||
segmentKey = strings.TrimSpace(segments[0])
|
||||
segmentValue = strings.TrimSpace(segments[1])
|
||||
previousKey = segmentKey
|
||||
case 1:
|
||||
// This is another value for the previous key.
|
||||
segmentKey = previousKey
|
||||
segmentValue = strings.TrimSpace(segments[0])
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// Ignore empty lines.
|
||||
if segmentValue == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse and assign value to struct.
|
||||
switch segmentKey {
|
||||
case "InterfaceIndex":
|
||||
newIf.InterfaceIndex = segmentValue
|
||||
case "IPv6Address":
|
||||
newIf.IPv6Address = net.ParseIP(segmentValue)
|
||||
case "IPv4Address":
|
||||
newIf.IPv4Address = net.ParseIP(segmentValue)
|
||||
case "IPv6DefaultGateway":
|
||||
newIf.IPv6DefaultGateway = net.ParseIP(segmentValue)
|
||||
case "IPv4DefaultGateway":
|
||||
newIf.IPv4DefaultGateway = net.ParseIP(segmentValue)
|
||||
case "DNSServer":
|
||||
newIP := net.ParseIP(segmentValue)
|
||||
if newIP != nil {
|
||||
newIf.DNSServer = append(newIf.DNSServer, newIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get Search Scopes for this interface.
|
||||
if newIf.InterfaceIndex != "" {
|
||||
dnsConfigData, err := osdetail.RunPowershellCmd(fmt.Sprintf(
|
||||
"Get-DnsClient -InterfaceIndex %s | ConvertTo-Json -Depth 1",
|
||||
newIf.InterfaceIndex,
|
||||
))
|
||||
if err != nil {
|
||||
log.Warningf("netenv: failed to get dns server config data: %s", err)
|
||||
} else {
|
||||
// Parse data into struct.
|
||||
dnsConfig := &dnsServerConfig{}
|
||||
err := json.Unmarshal([]byte(dnsConfigData), dnsConfig)
|
||||
if err != nil {
|
||||
log.Warningf("netenv: failed to get dns server config data: %s", err)
|
||||
} else {
|
||||
newIf.DNSServerConfig = dnsConfig
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Warning("netenv: could not get dns server config data, because default interface index is missing")
|
||||
}
|
||||
|
||||
// Assign new value to cache and return.
|
||||
defaultInterface = newIf
|
||||
return defaultInterface
|
||||
}
|
||||
|
|
11
netenv/environment_windows_test.go
Normal file
11
netenv/environment_windows_test.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package netenv
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestWindowsEnvironment(t *testing.T) {
|
||||
defaultIf := getDefaultInterface()
|
||||
if defaultIf == nil {
|
||||
t.Error("failed to get default interface")
|
||||
}
|
||||
t.Logf("default interface: %+v", defaultIf)
|
||||
}
|
100
netenv/icmp_listener.go
Normal file
100
netenv/icmp_listener.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package netenv
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/tevino/abool"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
)
|
||||
|
||||
/*
|
||||
This ICMP listening system is a simple system for components to listen to ICMP
|
||||
packets via the firewall.
|
||||
|
||||
The main use case for this is to receive ICMP packets that are not always
|
||||
delivered correctly, or need special permissions and or sockets to receive
|
||||
them. This is the case when doing a traceroute.
|
||||
|
||||
In order to keep it simple, the system is only designed to be used by one
|
||||
"user" at at time. Further calls to ListenToICMP will wait for the previous
|
||||
operation to complete.
|
||||
*/
|
||||
|
||||
var (
|
||||
// listenICMPLock locks the ICMP listening system for one user at a time.
|
||||
listenICMPLock sync.Mutex
|
||||
|
||||
// listenICMPEnabled defines whether or not the firewall should submit ICMP
|
||||
// packets to this interface.
|
||||
listenICMPEnabled = abool.New()
|
||||
|
||||
// listenICMPInput is created for every use of the ICMP listenting system.
|
||||
listenICMPInput chan packet.Packet
|
||||
listenICMPInputLock sync.Mutex
|
||||
)
|
||||
|
||||
// ListenToICMP returns a new channel for listenting to icmp packets. Please
|
||||
// note that any icmp packet will be passed and filtering must be done on
|
||||
// the side of the caller. The caller must call the returned done function when
|
||||
// done with the listener.
|
||||
func ListenToICMP() (packets chan packet.Packet, done func()) {
|
||||
// Lock for single use.
|
||||
listenICMPLock.Lock()
|
||||
|
||||
// Create new input channel.
|
||||
listenICMPInputLock.Lock()
|
||||
listenICMPInput = make(chan packet.Packet, 100)
|
||||
listenICMPEnabled.Set()
|
||||
listenICMPInputLock.Unlock()
|
||||
|
||||
return listenICMPInput, func() {
|
||||
// Release for someone else to use.
|
||||
defer listenICMPLock.Unlock()
|
||||
|
||||
// Close input channel.
|
||||
listenICMPInputLock.Lock()
|
||||
listenICMPEnabled.UnSet()
|
||||
close(listenICMPInput)
|
||||
listenICMPInputLock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitPacketToICMPListener checks if an ICMP packet should be submitted to
|
||||
// the listener. If so, it is submitted right away. The function returns
|
||||
// whether or not the packet should be submitted, not if it was successful.
|
||||
func SubmitPacketToICMPListener(pkt packet.Packet) (submitted bool) {
|
||||
// Hot path.
|
||||
if !listenICMPEnabled.IsSet() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Slow path.
|
||||
submitPacketToICMPListenerSlow(pkt)
|
||||
return true
|
||||
}
|
||||
|
||||
func submitPacketToICMPListenerSlow(pkt packet.Packet) {
|
||||
// Make sure the payload is available.
|
||||
if err := pkt.LoadPacketData(); err != nil {
|
||||
log.Warningf("netenv: failed to get payload for ICMP listener: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send to input channel.
|
||||
listenICMPInputLock.Lock()
|
||||
defer listenICMPInputLock.Unlock()
|
||||
|
||||
// Check if still enabled.
|
||||
if !listenICMPEnabled.IsSet() {
|
||||
return
|
||||
}
|
||||
|
||||
// Send to channel, if possible.
|
||||
select {
|
||||
case listenICMPInput <- pkt:
|
||||
default:
|
||||
log.Warning("netenv: failed to send packet payload to ICMP listener: channel full")
|
||||
}
|
||||
}
|
|
@ -2,23 +2,32 @@ package netenv
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/rng"
|
||||
"github.com/safing/portmaster/intel/geoip"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
)
|
||||
|
||||
var (
|
||||
locationTestingIPv4 = "1.1.1.1"
|
||||
locationTestingIPv4Addr *net.IPAddr
|
||||
|
||||
locations = &DeviceLocations{
|
||||
All: make(map[string]*DeviceLocation),
|
||||
}
|
||||
locationsLock sync.Mutex
|
||||
gettingLocationsLock sync.Mutex
|
||||
locationNetworkChangedFlag = GetNetworkChangedFlag()
|
||||
)
|
||||
|
||||
func prepLocation() (err error) {
|
||||
|
@ -26,62 +35,250 @@ func prepLocation() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// GetApproximateInternetLocation returns the nearest detectable IP address. If one or more global IP addresses are configured, one of them is returned. Currently only support IPv4. Else, the IP address of the nearest ping-answering internet node is returned.
|
||||
func GetApproximateInternetLocation() (net.IP, error) { //nolint:gocognit
|
||||
// TODO: Create IPv6 version of GetApproximateInternetLocation
|
||||
type DeviceLocations struct {
|
||||
Best *DeviceLocation
|
||||
All map[string]*DeviceLocation
|
||||
}
|
||||
|
||||
// First check if we have an assigned IPv6 address. Return that if available.
|
||||
globalIPv4, _, err := GetAssignedGlobalAddresses()
|
||||
if err != nil {
|
||||
log.Warningf("netenv: location approximation: failed to get assigned global addresses: %s", err)
|
||||
} else if len(globalIPv4) > 0 {
|
||||
return globalIPv4[0], nil
|
||||
func copyDeviceLocations() *DeviceLocations {
|
||||
locationsLock.Lock()
|
||||
defer locationsLock.Unlock()
|
||||
|
||||
// Create a copy of the locations, but not the entries.
|
||||
cp := *locations
|
||||
cp.All = make(map[string]*DeviceLocation, len(locations.All))
|
||||
for k, v := range locations.All {
|
||||
cp.All[k] = v
|
||||
}
|
||||
|
||||
// Create OS specific ICMP Listener.
|
||||
conn, err := newICMPListener(locationTestingIPv4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to listen: %s", err)
|
||||
return &cp
|
||||
}
|
||||
|
||||
// DeviceLocation represents a single IP and metadata. It must not be changed
|
||||
// once created.
|
||||
type DeviceLocation struct {
|
||||
IP net.IP
|
||||
Continent string
|
||||
Country string
|
||||
ASN uint
|
||||
ASOrg string
|
||||
Source DeviceLocationSource
|
||||
SourceAccuracy int
|
||||
}
|
||||
|
||||
// IsMoreAccurateThan checks if the device location is more accurate than the
|
||||
// given one.
|
||||
func (dl *DeviceLocation) IsMoreAccurateThan(other *DeviceLocation) bool {
|
||||
switch {
|
||||
case dl.SourceAccuracy > other.SourceAccuracy:
|
||||
// Higher accuracy is better.
|
||||
return true
|
||||
case dl.ASN != 0 && other.ASN == 0:
|
||||
// Having an ASN is better than having none.
|
||||
return true
|
||||
case dl.Country == "" && other.Country != "":
|
||||
// Having a Country is better than having none.
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type DeviceLocationSource string
|
||||
|
||||
const (
|
||||
SourceInterface DeviceLocationSource = "interface"
|
||||
SourcePeer DeviceLocationSource = "peer"
|
||||
SourceUPNP DeviceLocationSource = "upnp"
|
||||
SourceTraceroute DeviceLocationSource = "traceroute"
|
||||
SourceOther DeviceLocationSource = "other"
|
||||
)
|
||||
|
||||
func (dls DeviceLocationSource) Accuracy() int {
|
||||
switch dls {
|
||||
case SourceInterface:
|
||||
return 5
|
||||
case SourcePeer:
|
||||
return 4
|
||||
case SourceUPNP:
|
||||
return 3
|
||||
case SourceTraceroute:
|
||||
return 2
|
||||
case SourceOther:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func SetInternetLocation(ip net.IP, source DeviceLocationSource) (ok bool) {
|
||||
// Check if IP is global.
|
||||
if netutils.GetIPScope(ip) != netutils.Global {
|
||||
return false
|
||||
}
|
||||
|
||||
// Create new location.
|
||||
loc := &DeviceLocation{
|
||||
IP: ip,
|
||||
Source: source,
|
||||
SourceAccuracy: source.Accuracy(),
|
||||
}
|
||||
|
||||
// Get geoip information, but continue if it fails.
|
||||
geoLoc, err := geoip.GetLocation(ip)
|
||||
if err != nil {
|
||||
log.Warningf("netenv: failed to get geolocation data of %s (from %s): %s", ip, source, err)
|
||||
} else {
|
||||
loc.Continent = geoLoc.Continent.Code
|
||||
loc.Country = geoLoc.Country.ISOCode
|
||||
loc.ASN = geoLoc.AutonomousSystemNumber
|
||||
loc.ASOrg = geoLoc.AutonomousSystemOrganization
|
||||
}
|
||||
|
||||
locationsLock.Lock()
|
||||
defer locationsLock.Unlock()
|
||||
|
||||
// Add to locations, if better.
|
||||
key := loc.IP.String()
|
||||
existing, ok := locations.All[key]
|
||||
if ok && existing.IsMoreAccurateThan(loc) {
|
||||
// Existing entry is more accurate, abort adding.
|
||||
// Return true, because the IP address is already part of the locations.
|
||||
return true
|
||||
}
|
||||
locations.All[key] = loc
|
||||
|
||||
// Find best location.
|
||||
best := loc
|
||||
for _, dl := range locations.All {
|
||||
if dl.IsMoreAccurateThan(best) {
|
||||
best = dl
|
||||
}
|
||||
}
|
||||
locations.Best = best
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// DEPRECATED: Please use GetInternetLocation instead.
|
||||
func GetApproximateInternetLocation() (net.IP, error) {
|
||||
loc, ok := GetInternetLocation()
|
||||
if !ok {
|
||||
return nil, errors.New("no location data available")
|
||||
}
|
||||
return loc.Best.IP, nil
|
||||
}
|
||||
|
||||
func GetInternetLocation() (deviceLocations *DeviceLocations, ok bool) {
|
||||
gettingLocationsLock.Lock()
|
||||
defer gettingLocationsLock.Unlock()
|
||||
|
||||
// Check if the network changed, if not, return cache.
|
||||
if !locationNetworkChangedFlag.IsSet() {
|
||||
return copyDeviceLocations(), true
|
||||
}
|
||||
locationNetworkChangedFlag.Refresh()
|
||||
|
||||
// Check different sources, return on first success.
|
||||
switch {
|
||||
case getLocationFromInterfaces():
|
||||
case getLocationFromTraceroute():
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Return gathered locations.
|
||||
cp := copyDeviceLocations()
|
||||
return cp, cp.Best != nil
|
||||
}
|
||||
|
||||
func getLocationFromInterfaces() (ok bool) {
|
||||
globalIPv4, globalIPv6, err := GetAssignedGlobalAddresses()
|
||||
if err != nil {
|
||||
log.Warningf("netenv: location: failed to get assigned global addresses: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, ip := range globalIPv4 {
|
||||
if SetInternetLocation(ip, SourceInterface) {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
for _, ip := range globalIPv6 {
|
||||
if SetInternetLocation(ip, SourceInterface) {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// TODO: Check feasibility of getting the external IP via UPnP.
|
||||
/*
|
||||
func getLocationFromUPnP() (ok bool) {
|
||||
// Endoint: urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress
|
||||
// A first test showed that a router did offer that endpoint, but did not
|
||||
// return an IP addres.
|
||||
return false
|
||||
}
|
||||
*/
|
||||
|
||||
func getLocationFromTraceroute() (ok bool) {
|
||||
// Create connection.
|
||||
conn, err := net.ListenPacket("ip4:icmp", "")
|
||||
if err != nil {
|
||||
log.Warningf("netenv: location: failed to open icmp conn: %s", err)
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
v4Conn := ipv4.NewPacketConn(conn)
|
||||
|
||||
// Generate a random ID for the ICMP packets.
|
||||
msgID, err := rng.Number(0xFFFF) // uint16
|
||||
generatedID, err := rng.Number(0xFFFF) // uint16
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate ID: %s", err)
|
||||
log.Warningf("netenv: location: failed to generate icmp msg ID: %s", err)
|
||||
return false
|
||||
}
|
||||
msgID := int(generatedID)
|
||||
var msgSeq int
|
||||
|
||||
// Create ICMP message body
|
||||
// Create ICMP message body.
|
||||
pingMessage := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho,
|
||||
Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: int(msgID),
|
||||
Seq: 0, // increased before marshal
|
||||
ID: msgID,
|
||||
Seq: msgSeq, // Is increased before marshalling.
|
||||
Data: []byte{},
|
||||
},
|
||||
}
|
||||
recvBuffer := make([]byte, 1500)
|
||||
maxHops := 4 // add one for every reply that is not global
|
||||
|
||||
next:
|
||||
// Get additional listener for ICMP messages via the firewall.
|
||||
icmpPacketsViaFirewall, doneWithListeningToICMP := ListenToICMP()
|
||||
defer doneWithListeningToICMP()
|
||||
|
||||
nextHop:
|
||||
for i := 1; i <= maxHops; i++ {
|
||||
minSeq := msgSeq + 1
|
||||
|
||||
repeat:
|
||||
for j := 1; j <= 2; j++ { // Try every hop twice.
|
||||
// Increase sequence number.
|
||||
pingMessage.Body.(*icmp.Echo).Seq++
|
||||
msgSeq++
|
||||
pingMessage.Body.(*icmp.Echo).Seq = msgSeq
|
||||
|
||||
// Make packet data.
|
||||
pingPacket, err := pingMessage.Marshal(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Warningf("netenv: location: failed to build icmp packet: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Set TTL on IP packet.
|
||||
err = v4Conn.SetTTL(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Warningf("netenv: location: failed to set icmp packet TTL: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Send ICMP packet.
|
||||
|
@ -91,72 +288,106 @@ next:
|
|||
continue
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
log.Warningf("netenv: location: failed to send icmp packet: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Listen for replies to the ICMP packet.
|
||||
// Listen for replies of the ICMP packet.
|
||||
listen:
|
||||
for {
|
||||
// Set read timeout.
|
||||
err = conn.SetReadDeadline(
|
||||
time.Now().Add(
|
||||
time.Duration(i*2+30) * time.Millisecond,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read next packet.
|
||||
n, src, err := conn.ReadFrom(recvBuffer)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
// Continue with next packet if we timeout
|
||||
continue repeat
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse remote IP address.
|
||||
addr, ok := src.(*net.IPAddr)
|
||||
remoteIP, icmpPacket, ok := recvICMP(i, icmpPacketsViaFirewall)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to parse IP: %s", src.String())
|
||||
continue repeat
|
||||
}
|
||||
|
||||
// Continue if we receive a packet from ourself. This is specific to Windows.
|
||||
if me, err := IsMyIP(addr.IP); err == nil && me {
|
||||
log.Tracef("netenv: location approximation: ignoring own message from %s", src)
|
||||
// Pre-filter by message type.
|
||||
switch icmpPacket.TypeCode.Type() {
|
||||
case layers.ICMPv4TypeEchoReply:
|
||||
// Check if the ID and sequence match.
|
||||
if icmpPacket.Id != uint16(msgID) {
|
||||
continue listen
|
||||
}
|
||||
if icmpPacket.Seq < uint16(minSeq) {
|
||||
continue listen
|
||||
}
|
||||
// We received a reply, so we did not trigger a time exceeded response on the way.
|
||||
// This means we were not able to find the nearest router to us.
|
||||
return false
|
||||
case layers.ICMPv4TypeDestinationUnreachable,
|
||||
layers.ICMPv4TypeTimeExceeded:
|
||||
// Continue processing.
|
||||
default:
|
||||
continue listen
|
||||
}
|
||||
|
||||
// If we received something from a global IP address, we have succeeded and can return immediately.
|
||||
if netutils.GetIPScope(addr.IP).IsGlobal() {
|
||||
return addr.IP, nil
|
||||
// Parse copy of origin icmp packet that triggered the error.
|
||||
if len(icmpPacket.Payload) != ipv4.HeaderLen+8 {
|
||||
continue listen
|
||||
}
|
||||
|
||||
// For everey non-global reply received, increase the maximum hops to try.
|
||||
maxHops++
|
||||
|
||||
// Parse the ICMP message.
|
||||
icmpReply, err := icmp.ParseMessage(1, recvBuffer[:n])
|
||||
originalMessage, err := icmp.ParseMessage(1, icmpPacket.Payload[ipv4.HeaderLen:])
|
||||
if err != nil {
|
||||
log.Warningf("netenv: location approximation: failed to parse ICMP message: %s", err)
|
||||
continue listen
|
||||
}
|
||||
originalEcho, ok := originalMessage.Body.(*icmp.Echo)
|
||||
if !ok {
|
||||
continue listen
|
||||
}
|
||||
// Check if the ID and sequence match.
|
||||
if originalEcho.ID != int(msgID) {
|
||||
continue listen
|
||||
}
|
||||
if originalEcho.Seq < minSeq {
|
||||
continue listen
|
||||
}
|
||||
|
||||
// React based on message type.
|
||||
switch icmpReply.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded, ipv4.ICMPTypeEchoReply:
|
||||
log.Tracef("netenv: location approximation: receveived %q from %s", icmpReply.Type, addr.IP)
|
||||
continue next
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
return nil, fmt.Errorf("destination unreachable")
|
||||
default:
|
||||
log.Tracef("netenv: location approximation: unexpected ICMP reply: received %q from %s", icmpReply.Type, addr.IP)
|
||||
switch icmpPacket.TypeCode.Type() {
|
||||
case layers.ICMPv4TypeDestinationUnreachable:
|
||||
// We have received a valid destination unreachable response, abort.
|
||||
return false
|
||||
|
||||
case layers.ICMPv4TypeTimeExceeded:
|
||||
// We have received a valid time exceeded error.
|
||||
// If message came from a global unicast, us it!
|
||||
if netutils.GetIPScope(remoteIP) == netutils.Global {
|
||||
return SetInternetLocation(remoteIP, SourceTraceroute)
|
||||
}
|
||||
|
||||
// Otherwise, continue.
|
||||
continue nextHop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("no usable response to any icmp message")
|
||||
// We did not receive anything actionable.
|
||||
return false
|
||||
}
|
||||
|
||||
func recvICMP(currentHop int, icmpPacketsViaFirewall chan packet.Packet) (
|
||||
remoteIP net.IP, imcpPacket *layers.ICMPv4, ok bool) {
|
||||
|
||||
for {
|
||||
select {
|
||||
case pkt := <-icmpPacketsViaFirewall:
|
||||
if pkt.IsOutbound() {
|
||||
continue
|
||||
}
|
||||
if pkt.Layers() == nil {
|
||||
continue
|
||||
}
|
||||
icmpLayer := pkt.Layers().Layer(layers.LayerTypeICMPv4)
|
||||
if icmpLayer == nil {
|
||||
continue
|
||||
}
|
||||
icmp4, ok := icmpLayer.(*layers.ICMPv4)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
return pkt.Info().RemoteIP(), icmp4, true
|
||||
|
||||
case <-time.After(time.Duration(currentHop*10+50) * time.Millisecond):
|
||||
return nil, nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ func TestGetApproximateInternetLocation(t *testing.T) {
|
|||
t.Skip("skipping privileged test, active with -privileged argument")
|
||||
}
|
||||
|
||||
ip, err := GetApproximateInternetLocation()
|
||||
loc, err := GetInternetLocation()
|
||||
if err != nil {
|
||||
t.Fatalf("GetApproximateInternetLocation failed: %s", err)
|
||||
}
|
||||
t.Logf("GetApproximateInternetLocation: %s", ip.String())
|
||||
t.Logf("GetApproximateInternetLocation: %+v", loc)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ func prep() error {
|
|||
}
|
||||
|
||||
func start() error {
|
||||
if err := registerAPIEndpoints(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
module.StartServiceWorker(
|
||||
"monitor network changes",
|
||||
0,
|
||||
|
|
|
@ -9,12 +9,23 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
networkChangeCheckTrigger = make(chan struct{}, 1)
|
||||
networkChangeCheckTrigger = make(chan struct{}, 1)
|
||||
networkChangedBroadcastFlag = utils.NewBroadcastFlag()
|
||||
)
|
||||
|
||||
func GetNetworkChangedFlag() *utils.Flag {
|
||||
return networkChangedBroadcastFlag.NewFlag()
|
||||
}
|
||||
|
||||
func notifyOfNetworkChange() {
|
||||
networkChangedBroadcastFlag.NotifyAndReset()
|
||||
module.TriggerEvent(NetworkChangedEvent, nil)
|
||||
}
|
||||
|
||||
func triggerNetworkChangeCheck() {
|
||||
select {
|
||||
case networkChangeCheckTrigger <- struct{}{}:
|
||||
|
@ -82,7 +93,7 @@ serviceLoop:
|
|||
if trigger {
|
||||
triggerOnlineStatusInvestigation()
|
||||
}
|
||||
module.TriggerEvent(NetworkChangedEvent, nil)
|
||||
notifyOfNetworkChange()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,14 +4,18 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
)
|
||||
|
||||
// Base is a base structure for satisfying the Packet interface.
|
||||
type Base struct {
|
||||
ctx context.Context
|
||||
info Info
|
||||
connID string
|
||||
Payload []byte
|
||||
ctx context.Context
|
||||
info Info
|
||||
connID string
|
||||
layers gopacket.Packet
|
||||
layer3Data []byte
|
||||
layer5Data []byte
|
||||
}
|
||||
|
||||
// SetCtx sets the packet context.
|
||||
|
@ -65,9 +69,24 @@ func (pkt *Base) HasPorts() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// GetPayload returns the packet payload. In some cases, this will fetch the payload from the os integration system.
|
||||
func (pkt *Base) GetPayload() ([]byte, error) {
|
||||
return pkt.Payload, ErrFailedToLoadPayload
|
||||
// LoadPacketData loads packet data from the integration, if not yet done.
|
||||
func (pkt *Base) LoadPacketData() error {
|
||||
return ErrFailedToLoadPayload
|
||||
}
|
||||
|
||||
// Layers returns the parsed layer data.
|
||||
func (pkt *Base) Layers() gopacket.Packet {
|
||||
return pkt.layers
|
||||
}
|
||||
|
||||
// Raw returns the raw Layer 3 Network Data.
|
||||
func (pkt *Base) Raw() []byte {
|
||||
return pkt.layer3Data
|
||||
}
|
||||
|
||||
// Payload returns the raw Layer 5 Network Data.
|
||||
func (pkt *Base) Payload() []byte {
|
||||
return pkt.layer5Data
|
||||
}
|
||||
|
||||
// GetConnectionID returns the link ID for this packet.
|
||||
|
@ -214,9 +233,14 @@ type Packet interface {
|
|||
SetInbound()
|
||||
SetOutbound()
|
||||
HasPorts() bool
|
||||
GetPayload() ([]byte, error)
|
||||
GetConnectionID() string
|
||||
|
||||
// PAYLOAD
|
||||
LoadPacketData() error
|
||||
Layers() gopacket.Packet
|
||||
Raw() []byte
|
||||
Payload() []byte
|
||||
|
||||
// MATCHING
|
||||
MatchesAddress(bool, IPProtocol, *net.IPNet, uint16) bool
|
||||
MatchesIP(bool, *net.IPNet) bool
|
||||
|
|
|
@ -102,10 +102,11 @@ func checkError(packet gopacket.Packet, _ *Info) error {
|
|||
}
|
||||
|
||||
// Parse parses an IP packet and saves the information in the given packet object.
|
||||
func Parse(packetData []byte, pktInfo *Info) error {
|
||||
func Parse(packetData []byte, pktBase *Base) (err error) {
|
||||
if len(packetData) == 0 {
|
||||
return errors.New("empty packet")
|
||||
}
|
||||
pktBase.layer3Data = packetData
|
||||
|
||||
ipVersion := packetData[0] >> 4
|
||||
var networkLayerType gopacket.LayerType
|
||||
|
@ -137,11 +138,15 @@ func Parse(packetData []byte, pktInfo *Info) error {
|
|||
}
|
||||
|
||||
for _, dec := range availableDecoders {
|
||||
if err := dec(packet, pktInfo); err != nil {
|
||||
if err := dec(packet, pktBase.Info()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pktBase.layers = packet
|
||||
if transport := packet.TransportLayer(); transport != nil {
|
||||
pktBase.layer5Data = transport.LayerPayload()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -58,8 +58,13 @@ func (p *Process) GetProfile(ctx context.Context) (changed bool, err error) {
|
|||
// Check if this is the system resolver.
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
if (p.Path == `C:\Windows\System32\svchost.exe` || p.Path == `C:\Windows\system32\svchost.exe`) &&
|
||||
(strings.Contains(p.SpecialDetail, "Dnscache") || strings.Contains(p.CmdLine, "-k NetworkService")) {
|
||||
// Depending on the OS version System32 may be capitalized or not.
|
||||
if (p.Path == `C:\Windows\System32\svchost.exe` ||
|
||||
p.Path == `C:\Windows\system32\svchost.exe`) &&
|
||||
// This comes from the windows tasklist command and should be pretty consistent.
|
||||
(strings.Contains(p.SpecialDetail, "Dnscache") ||
|
||||
// As an alternative in case of failure, we try to match the svchost.exe service parameter.
|
||||
strings.Contains(p.CmdLine, "-s Dnscache")) {
|
||||
profileID = profile.SystemResolverProfileID
|
||||
}
|
||||
case "linux":
|
||||
|
|
Loading…
Add table
Reference in a new issue