Files
ctrld/internal/net/net.go
Cuong Manh Le fa50cd4df4 all: another rework on discovering bootstrap IPs
Instead of re-query DNS record for upstream when re-bootstrapping, just
query all records on startup, then selecting the next bootstrap ip
depends on the current network stack.
2023-03-10 09:25:17 +07:00

108 lines
2.1 KiB
Go

package net
import (
"context"
"net"
"sync"
"sync/atomic"
"time"
"tailscale.com/logtail/backoff"
)
const (
controldIPv6Test = "ipv6.controld.io"
controldIPv4Test = "ipv4.controld.io"
bootstrapDNS = "76.76.2.0:53"
)
var Dialer = &net.Dialer{
Resolver: &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: 10 * time.Second,
}
return d.DialContext(ctx, "udp", bootstrapDNS)
},
},
}
var (
stackOnce atomic.Pointer[sync.Once]
ipv4Enabled bool
ipv6Enabled bool
canListenIPv6Local bool
hasNetworkUp bool
)
func init() {
stackOnce.Store(new(sync.Once))
}
func supportIPv4() bool {
_, err := Dialer.Dial("tcp4", net.JoinHostPort(controldIPv4Test, "80"))
return err == nil
}
func supportIPv6() bool {
_, err := Dialer.Dial("tcp6", net.JoinHostPort(controldIPv6Test, "80"))
return err == nil
}
func supportListenIPv6Local() bool {
if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil {
ln.Close()
return true
}
return false
}
func probeStack() {
b := backoff.NewBackoff("probeStack", func(format string, args ...any) {}, time.Minute)
for {
if _, err := Dialer.Dial("udp", bootstrapDNS); err == nil {
hasNetworkUp = true
break
} else {
b.BackOff(context.Background(), err)
}
}
ipv4Enabled = supportIPv4()
ipv6Enabled = supportIPv6()
canListenIPv6Local = supportListenIPv6Local()
}
func Up() bool {
stackOnce.Load().Do(probeStack)
return hasNetworkUp
}
func SupportsIPv4() bool {
stackOnce.Load().Do(probeStack)
return ipv4Enabled
}
func SupportsIPv6() bool {
stackOnce.Load().Do(probeStack)
return ipv6Enabled
}
func SupportsIPv6ListenLocal() bool {
stackOnce.Load().Do(probeStack)
return canListenIPv6Local
}
// IPv6Available is like SupportsIPv6, but always do the check without caching.
func IPv6Available() bool {
return supportIPv6()
}
// IsIPv6 checks if the provided IP is v6.
//
//lint:ignore U1000 use in os_windows.go
func IsIPv6(ip string) bool {
parsedIP := net.ParseIP(ip)
return parsedIP != nil && parsedIP.To4() == nil && parsedIP.To16() != nil
}