Improve captive portal handling
This commit is contained in:
@@ -6,7 +6,7 @@ func Nameservers() []Nameserver {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Gateways() []*net.IP {
|
||||
func Gateways() []net.IP {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
gateways = make([]*net.IP, 0)
|
||||
gateways = make([]net.IP, 0)
|
||||
gatewaysLock sync.Mutex
|
||||
gatewaysExpires = time.Now()
|
||||
|
||||
@@ -31,7 +31,7 @@ var (
|
||||
)
|
||||
|
||||
// Gateways returns the currently active gateways.
|
||||
func Gateways() []*net.IP {
|
||||
func Gateways() []net.IP {
|
||||
// locking
|
||||
gatewaysLock.Lock()
|
||||
defer gatewaysLock.Unlock()
|
||||
@@ -45,7 +45,7 @@ func Gateways() []*net.IP {
|
||||
}()
|
||||
// logic
|
||||
|
||||
newGateways := make([]*net.IP, 0)
|
||||
newGateways := make([]net.IP, 0)
|
||||
var decoded []byte
|
||||
|
||||
// open file
|
||||
@@ -77,7 +77,7 @@ func Gateways() []*net.IP {
|
||||
continue
|
||||
}
|
||||
gate := net.IPv4(decoded[3], decoded[2], decoded[1], decoded[0])
|
||||
newGateways = append(newGateways, &gate)
|
||||
newGateways = append(newGateways, gate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ func Gateways() []*net.IP {
|
||||
continue
|
||||
}
|
||||
gate := net.IP(decoded)
|
||||
newGateways = append(newGateways, &gate)
|
||||
newGateways = append(newGateways, gate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,95 @@
|
||||
package netenv
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
|
||||
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 {
|
||||
return nil
|
||||
// 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 {
|
||||
func Gateways() []net.IP {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package netenv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -41,6 +42,8 @@ const (
|
||||
ResolverTestFqdn = "one.one.one.one."
|
||||
ResolverTestRRType = dns.TypeA
|
||||
ResolverTestExpectedResponse = "1.1.1.1"
|
||||
|
||||
SpecialCaptivePortalDomain = "captiveportal.local."
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -62,12 +65,12 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// IsOnlineStatusTestDomain checks whether the given fqdn is used for testing online status.
|
||||
func IsOnlineStatusTestDomain(domain string) bool {
|
||||
// IsConnectivityDomain checks whether the given domain (fqdn) is used for any connectivity related network connections and should always be resolved using the network assigned DNS server.
|
||||
func IsConnectivityDomain(domain string) bool {
|
||||
switch domain {
|
||||
case "detectportal.firefox.com.":
|
||||
return true
|
||||
case "one.one.one.one.":
|
||||
case "detectportal.firefox.com.",
|
||||
"one.one.one.one.",
|
||||
GetCaptivePortal().Domain:
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -104,10 +107,34 @@ var (
|
||||
onlineStatusInvestigationInProgress = abool.NewBool(false)
|
||||
onlineStatusInvestigationWg sync.WaitGroup
|
||||
|
||||
captivePortalURL string
|
||||
captivePortal = &CaptivePortal{}
|
||||
captivePortalLock sync.Mutex
|
||||
)
|
||||
|
||||
type CaptivePortal struct {
|
||||
URL string
|
||||
Domain string
|
||||
IP net.IP
|
||||
}
|
||||
|
||||
// IPasRR returns the captive portal IP as a DNS resource record.
|
||||
func (p *CaptivePortal) IPasRR() (rr dns.RR, err error) {
|
||||
switch {
|
||||
case p.IP == nil:
|
||||
return nil, errors.New("no portal IP present")
|
||||
case p.Domain == "":
|
||||
return nil, errors.New("no portal domain present")
|
||||
case p.IP.To4() != nil:
|
||||
rr, err = dns.NewRR(p.Domain + " 17 IN A " + p.IP.String())
|
||||
default:
|
||||
rr, err = dns.NewRR(p.Domain + " 17 IN AAAA " + p.IP.String())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
var onlineStatusValue int32
|
||||
onlineStatus = &onlineStatusValue
|
||||
@@ -147,18 +174,16 @@ func updateOnlineStatus(status OnlineStatus, portalURL, comment string) {
|
||||
}
|
||||
|
||||
// captive portal
|
||||
captivePortalLock.Lock()
|
||||
defer captivePortalLock.Unlock()
|
||||
if portalURL != captivePortalURL {
|
||||
captivePortalURL = portalURL
|
||||
changed = true
|
||||
// delete if offline, update only if there is a new value
|
||||
if status == StatusOffline || portalURL != "" {
|
||||
setCaptivePortal(portalURL)
|
||||
}
|
||||
|
||||
// trigger event
|
||||
if changed {
|
||||
module.TriggerEvent(OnlineStatusChangedEvent, nil)
|
||||
if status == StatusPortal {
|
||||
log.Infof(`network: setting online status to %s at "%s" (%s)`, status, captivePortalURL, comment)
|
||||
log.Infof(`network: setting online status to %s at "%s" (%s)`, status, portalURL, comment)
|
||||
} else {
|
||||
log.Infof("network: setting online status to %s (%s)", status, comment)
|
||||
}
|
||||
@@ -166,12 +191,56 @@ func updateOnlineStatus(status OnlineStatus, portalURL, comment string) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetCaptivePortalURL returns the current captive portal url as a string.
|
||||
func GetCaptivePortalURL() string {
|
||||
func setCaptivePortal(portalURL string) {
|
||||
captivePortalLock.Lock()
|
||||
defer captivePortalLock.Unlock()
|
||||
|
||||
return captivePortalURL
|
||||
// delete
|
||||
if portalURL == "" {
|
||||
captivePortal = &CaptivePortal{}
|
||||
return
|
||||
}
|
||||
|
||||
// set
|
||||
captivePortal = &CaptivePortal{
|
||||
URL: portalURL,
|
||||
}
|
||||
parsedURL, err := url.Parse(portalURL)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Debugf(`netenv: failed to parse captive portal URL "%s": %s`, portalURL, err)
|
||||
return
|
||||
case parsedURL.Hostname() == "":
|
||||
log.Debugf(`netenv: captive portal URL "%s" has no domain or IP`, portalURL)
|
||||
return
|
||||
default:
|
||||
// try to parse an IP
|
||||
portalIP := net.ParseIP(parsedURL.Hostname())
|
||||
if portalIP != nil {
|
||||
captivePortal.IP = portalIP
|
||||
captivePortal.Domain = SpecialCaptivePortalDomain
|
||||
return
|
||||
}
|
||||
|
||||
// try to parse domain
|
||||
// ensure fqdn format
|
||||
domain := dns.Fqdn(parsedURL.Hostname())
|
||||
// check validity
|
||||
if !netutils.IsValidFqdn(domain) {
|
||||
log.Debugf(`netenv: captive portal domain/IP "%s" is invalid`, parsedURL.Hostname())
|
||||
return
|
||||
}
|
||||
// set domain
|
||||
captivePortal.Domain = domain
|
||||
}
|
||||
}
|
||||
|
||||
// GetCaptivePortal returns the current captive portal. The returned struct must not be edited.
|
||||
func GetCaptivePortal() *CaptivePortal {
|
||||
captivePortalLock.Lock()
|
||||
defer captivePortalLock.Unlock()
|
||||
|
||||
return captivePortal
|
||||
}
|
||||
|
||||
// ReportSuccessfulConnection hints the online status monitoring system that a connection attempt was successful.
|
||||
|
||||
@@ -8,5 +8,5 @@ import (
|
||||
func TestCheckOnlineStatus(t *testing.T) {
|
||||
checkOnlineStatus(context.Background())
|
||||
t.Logf("online status: %s", GetOnlineStatus())
|
||||
t.Logf("captive portal: %s", GetCaptivePortalURL())
|
||||
t.Logf("captive portal: %+v", GetCaptivePortal())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user