mirror of
https://github.com/safing/portmaster
synced 2025-09-04 03:29:12 +00:00
Switch to powershell for getting gateways and nameservers on Windows
This commit is contained in:
parent
5518e952b2
commit
d16810c0e9
1 changed files with 168 additions and 79 deletions
|
@ -3,93 +3,182 @@ package netenv
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/safing/portbase/log"
|
"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.
|
// Gateways returns the currently active gateways.
|
||||||
func Gateways() []net.IP {
|
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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue