mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-05-27 12:52:27 +02:00
internal/controld: check if ipv4 is available before connect to API
Updates #53
This commit is contained in:
committed by
Cuong Manh Le
parent
45f827a2c5
commit
83b551fb2d
+2
-1
@@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld"
|
"github.com/Control-D-Inc/ctrld"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/controld"
|
"github.com/Control-D-Inc/ctrld/internal/controld"
|
||||||
|
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -105,7 +106,7 @@ func initCLI() {
|
|||||||
log.Fatalf("failed to unmarshal config: %v", err)
|
log.Fatalf("failed to unmarshal config: %v", err)
|
||||||
}
|
}
|
||||||
// Wait for network up.
|
// Wait for network up.
|
||||||
if !netUp() {
|
if !ctrldnet.Up() {
|
||||||
log.Fatal("network is not up yet")
|
log.Fatal("network is not up yet")
|
||||||
}
|
}
|
||||||
processLogAndCacheFlags()
|
processLogAndCacheFlags()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld"
|
"github.com/Control-D-Inc/ctrld"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
||||||
|
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const staleTTL = 60 * time.Second
|
const staleTTL = 60 * time.Second
|
||||||
@@ -55,7 +56,7 @@ func (p *prog) serveUDP(listenerNum string) error {
|
|||||||
|
|
||||||
// On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can
|
// On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can
|
||||||
// listen on ::1, then spawn a listener for receiving DNS requests.
|
// listen on ::1, then spawn a listener for receiving DNS requests.
|
||||||
if runtime.GOOS == "windows" && supportsIPv6ListenLocal() {
|
if runtime.GOOS == "windows" && ctrldnet.SupportsIPv6() {
|
||||||
go func() {
|
go func() {
|
||||||
s := &dns.Server{
|
s := &dns.Server{
|
||||||
Addr: net.JoinHostPort("::1", strconv.Itoa(listenerConfig.Port)),
|
Addr: net.JoinHostPort("::1", strconv.Itoa(listenerConfig.Port)),
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tailscale.com/logtail/backoff"
|
|
||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld/internal/controld"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
controldIPv6Test = "ipv6.controld.io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
stackOnce sync.Once
|
|
||||||
ipv6Enabled bool
|
|
||||||
canListenIPv6Local bool
|
|
||||||
hasNetworkUp bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func probeStack() {
|
|
||||||
b := backoff.NewBackoff("probeStack", func(format string, args ...any) {}, time.Minute)
|
|
||||||
for {
|
|
||||||
if _, err := controld.Dialer.Dial("udp", net.JoinHostPort(bootstrapDNS, "53")); err == nil {
|
|
||||||
hasNetworkUp = true
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
b.BackOff(context.Background(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := controld.Dialer.Dial("tcp6", net.JoinHostPort(controldIPv6Test, "80")); err == nil {
|
|
||||||
ipv6Enabled = true
|
|
||||||
}
|
|
||||||
if ln, err := net.Listen("tcp6", "[::1]:53"); err == nil {
|
|
||||||
ln.Close()
|
|
||||||
canListenIPv6Local = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func netUp() bool {
|
|
||||||
stackOnce.Do(probeStack)
|
|
||||||
return hasNetworkUp
|
|
||||||
}
|
|
||||||
|
|
||||||
func supportsIPv6() bool {
|
|
||||||
stackOnce.Do(probeStack)
|
|
||||||
return ipv6Enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func supportsIPv6ListenLocal() bool {
|
|
||||||
stackOnce.Do(probeStack)
|
|
||||||
return canListenIPv6Local
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"tailscale.com/util/dnsname"
|
"tailscale.com/util/dnsname"
|
||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld/internal/dns"
|
"github.com/Control-D-Inc/ctrld/internal/dns"
|
||||||
|
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/resolvconffile"
|
"github.com/Control-D-Inc/ctrld/internal/resolvconffile"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ func resetDNS(iface *net.Interface) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(cuonglm): handle DHCPv6 properly.
|
// TODO(cuonglm): handle DHCPv6 properly.
|
||||||
if supportsIPv6() {
|
if ctrldnet.SupportsIPv6() {
|
||||||
c := client6.NewClient()
|
c := client6.NewClient()
|
||||||
conversation, err := c.Exchange(iface.Name)
|
conversation, err := c.Exchange(iface.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||||
|
|
||||||
|
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(cuonglm): implement.
|
// TODO(cuonglm): implement.
|
||||||
@@ -39,7 +41,7 @@ func setDNS(iface *net.Interface, nameservers []string) error {
|
|||||||
|
|
||||||
// TODO(cuonglm): should we use system API?
|
// TODO(cuonglm): should we use system API?
|
||||||
func resetDNS(iface *net.Interface) error {
|
func resetDNS(iface *net.Interface) error {
|
||||||
if supportsIPv6ListenLocal() {
|
if ctrldnet.SupportsIPv6ListenLocal() {
|
||||||
if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil {
|
if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil {
|
||||||
mainLog.Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output))
|
mainLog.Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output))
|
||||||
}
|
}
|
||||||
@@ -54,7 +56,7 @@ func resetDNS(iface *net.Interface) error {
|
|||||||
|
|
||||||
func setPrimaryDNS(iface *net.Interface, dns string) error {
|
func setPrimaryDNS(iface *net.Interface, dns string) error {
|
||||||
ipVer := "ipv4"
|
ipVer := "ipv4"
|
||||||
if isIPv6(dns) {
|
if ctrldnet.IsIPv6(dns) {
|
||||||
ipVer = "ipv6"
|
ipVer = "ipv6"
|
||||||
}
|
}
|
||||||
idx := strconv.Itoa(iface.Index)
|
idx := strconv.Itoa(iface.Index)
|
||||||
@@ -73,7 +75,7 @@ func setPrimaryDNS(iface *net.Interface, dns string) error {
|
|||||||
|
|
||||||
func addSecondaryDNS(iface *net.Interface, dns string) error {
|
func addSecondaryDNS(iface *net.Interface, dns string) error {
|
||||||
ipVer := "ipv4"
|
ipVer := "ipv4"
|
||||||
if isIPv6(dns) {
|
if ctrldnet.IsIPv6(dns) {
|
||||||
ipVer = "ipv6"
|
ipVer = "ipv6"
|
||||||
}
|
}
|
||||||
output, err := netsh("interface", ipVer, "add", "dns", strconv.Itoa(iface.Index), dns, "index=2")
|
output, err := netsh("interface", ipVer, "add", "dns", strconv.Itoa(iface.Index), dns, "index=2")
|
||||||
|
|||||||
+2
-1
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld"
|
"github.com/Control-D-Inc/ctrld"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
||||||
|
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errWindowsAddrInUse = syscall.Errno(0x2740)
|
var errWindowsAddrInUse = syscall.Errno(0x2740)
|
||||||
@@ -64,7 +65,7 @@ func (p *prog) run() {
|
|||||||
// resolve it manually and set the bootstrap ip
|
// resolve it manually and set the bootstrap ip
|
||||||
c := new(dns.Client)
|
c := new(dns.Client)
|
||||||
for _, dnsType := range []uint16{dns.TypeAAAA, dns.TypeA} {
|
for _, dnsType := range []uint16{dns.TypeAAAA, dns.TypeA} {
|
||||||
if !supportsIPv6() && dnsType == dns.TypeAAAA {
|
if !ctrldnet.SupportsIPv6() && dnsType == dns.TypeAAAA {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -15,20 +17,6 @@ const (
|
|||||||
InvalidConfigCode = 40401
|
InvalidConfigCode = 40401
|
||||||
)
|
)
|
||||||
|
|
||||||
const 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)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolverConfig represents Control D resolver data.
|
// ResolverConfig represents Control D resolver data.
|
||||||
type ResolverConfig struct {
|
type ResolverConfig struct {
|
||||||
DOH string `json:"doh"`
|
DOH string `json:"doh"`
|
||||||
@@ -70,7 +58,13 @@ func FetchResolverConfig(uid string) (*ResolverConfig, error) {
|
|||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return Dialer.DialContext(ctx, "tcp4", addr)
|
// We experiment hanging in TLS handshake when connecting to ControlD API
|
||||||
|
// with ipv6. So prefer ipv4 if available.
|
||||||
|
network = "tcp6"
|
||||||
|
if ctrldnet.SupportsIPv4() {
|
||||||
|
network = "tcp4"
|
||||||
|
}
|
||||||
|
return ctrldnet.Dialer.DialContext(ctx, network, addr)
|
||||||
}
|
}
|
||||||
client := http.Client{
|
client := http.Client{
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"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 sync.Once
|
||||||
|
ipv4Enabled bool
|
||||||
|
ipv6Enabled bool
|
||||||
|
canListenIPv6Local bool
|
||||||
|
hasNetworkUp bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func probeStack() {
|
||||||
|
b := backoff.NewBackoff("probeStack", func(format string, args ...any) {}, time.Minute)
|
||||||
|
for {
|
||||||
|
if _, err := Dialer.Dial("udp", net.JoinHostPort(bootstrapDNS, "53")); err == nil {
|
||||||
|
hasNetworkUp = true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
b.BackOff(context.Background(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := Dialer.Dial("tcp4", net.JoinHostPort(controldIPv4Test, "80")); err == nil {
|
||||||
|
ipv4Enabled = true
|
||||||
|
}
|
||||||
|
if _, err := Dialer.Dial("tcp6", net.JoinHostPort(controldIPv6Test, "80")); err == nil {
|
||||||
|
ipv6Enabled = true
|
||||||
|
}
|
||||||
|
if ln, err := net.Listen("tcp6", "[::1]:53"); err == nil {
|
||||||
|
ln.Close()
|
||||||
|
canListenIPv6Local = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Up() bool {
|
||||||
|
stackOnce.Do(probeStack)
|
||||||
|
return hasNetworkUp
|
||||||
|
}
|
||||||
|
|
||||||
|
func SupportsIPv4() bool {
|
||||||
|
stackOnce.Do(probeStack)
|
||||||
|
return ipv4Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func SupportsIPv6() bool {
|
||||||
|
stackOnce.Do(probeStack)
|
||||||
|
return ipv6Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func SupportsIPv6ListenLocal() bool {
|
||||||
|
stackOnce.Do(probeStack)
|
||||||
|
return canListenIPv6Local
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user