diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index e9038403..6f013947 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -514,6 +514,12 @@ func tunnelFlags(shouldHide bool) []cli.Flag { Usage: "Cloudflare Edge region to connect to. Omit or set to empty to connect to the global region.", EnvVars: []string{"TUNNEL_REGION"}, }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "edge-ip-version", + Usage: "Cloudflare Edge ip address version to connect with. {4, 6, auto}", + EnvVars: []string{"TUNNEL_EDGE_IP_VERSION"}, + Hidden: true, + }), altsrc.NewStringFlag(&cli.StringFlag{ Name: tlsconfig.CaCertFlag, Usage: "Certificate Authority authenticating connections with Cloudflare's edge network.", diff --git a/cmd/cloudflared/tunnel/configuration.go b/cmd/cloudflared/tunnel/configuration.go index 9a0728c5..9376d520 100644 --- a/cmd/cloudflared/tunnel/configuration.go +++ b/cmd/cloudflared/tunnel/configuration.go @@ -18,6 +18,7 @@ import ( "golang.org/x/crypto/ssh/terminal" "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" + "github.com/cloudflare/cloudflared/edgediscovery/allregions" "github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/connection" @@ -324,6 +325,10 @@ func prepareTunnelConfig( CompressionSetting: h2mux.CompressionSetting(uint64(c.Int("compression-quality"))), MetricsUpdateFreq: c.Duration("metrics-update-freq"), } + edgeIPVersion, err := parseConfigIPVersion(c.String("edge-ip-version")) + if err != nil { + return nil, nil, err + } tunnelConfig := &supervisor.TunnelConfig{ GracePeriod: gracePeriod, @@ -332,6 +337,7 @@ func prepareTunnelConfig( ClientID: clientID, EdgeAddrs: c.StringSlice("edge"), Region: c.String("region"), + EdgeIPVersion: edgeIPVersion, HAConnections: c.Int("ha-connections"), IncidentLookup: supervisor.NewIncidentLookup(), IsAutoupdated: c.Bool("is-autoupdated"), @@ -404,3 +410,18 @@ func dedup(slice []string) []string { } return keys } + +// ParseConfigIPVersion returns the IP version from possible expected values from config +func parseConfigIPVersion(version string) (v allregions.ConfigIPVersion, err error) { + switch version { + case "4": + v = allregions.IPv4Only + case "6": + v = allregions.IPv6Only + case "auto": + v = allregions.Auto + default: // unspecified or invalid + err = fmt.Errorf("invalid value for edge-ip-version: %s", version) + } + return +} diff --git a/connection/errors.go b/connection/errors.go index df3cfe97..98eececd 100644 --- a/connection/errors.go +++ b/connection/errors.go @@ -18,6 +18,15 @@ func (e DupConnRegisterTunnelError) Error() string { return "already connected to this server, trying another address" } +// Dial to edge server with quic failed +type EdgeQuicDialError struct { + Cause error +} + +func (e EdgeQuicDialError) Error() string { + return "failed to dial to edge with quic: " + e.Cause.Error() +} + // RegisterTunnel error from server type ServerRegisterTunnelError struct { Cause error diff --git a/connection/observer.go b/connection/observer.go index 5c2bf06c..1e8b62b5 100644 --- a/connection/observer.go +++ b/connection/observer.go @@ -1,6 +1,7 @@ package connection import ( + "net" "strings" "github.com/rs/zerolog" @@ -8,6 +9,7 @@ import ( const ( LogFieldLocation = "location" + LogFieldIPAddress = "ip" observerChannelBufferSize = 16 ) @@ -41,11 +43,12 @@ func (o *Observer) RegisterSink(sink EventSink) { o.addSinkChan <- sink } -func (o *Observer) logServerInfo(connIndex uint8, location, msg string) { +func (o *Observer) logServerInfo(connIndex uint8, location string, address net.IP, msg string) { o.sendEvent(Event{Index: connIndex, EventType: Connected, Location: location}) o.log.Info(). Uint8(LogFieldConnIndex, connIndex). Str(LogFieldLocation, location). + IPAddr(LogFieldIPAddress, address). Msg(msg) o.metrics.registerServerLocation(uint8ToString(connIndex), location) } diff --git a/connection/quic.go b/connection/quic.go index 4e2f4681..607a17c7 100644 --- a/connection/quic.go +++ b/connection/quic.go @@ -55,7 +55,7 @@ func NewQUICConnection( ) (*QUICConnection, error) { session, err := quic.DialAddr(edgeAddr.String(), tlsConfig, quicConfig) if err != nil { - return nil, fmt.Errorf("failed to dial to edge: %w", err) + return nil, EdgeQuicDialError{Cause: err} } datagramMuxer, err := quicpogs.NewDatagramMuxer(session, logger) diff --git a/connection/rpc.go b/connection/rpc.go index 4565f396..a4444ea3 100644 --- a/connection/rpc.go +++ b/connection/rpc.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "net" "time" "github.com/rs/zerolog" @@ -114,7 +115,7 @@ func (rsc *registrationServerClient) RegisterConnection( observer.metrics.regSuccess.WithLabelValues("registerConnection").Inc() - observer.logServerInfo(connIndex, conn.Location, fmt.Sprintf("Connection %s registered", conn.UUID)) + observer.logServerInfo(connIndex, conn.Location, options.OriginLocalIP, fmt.Sprintf("Connection %s registered", conn.UUID)) observer.sendConnectedEvent(connIndex, conn.Location) return conn, nil @@ -274,7 +275,7 @@ func (h *h2muxConnection) logServerInfo(ctx context.Context, rpcClient *tunnelSe h.observer.log.Err(err).Msg("Failed to retrieve server information") return err } - h.observer.logServerInfo(h.connIndex, serverInfo.LocationName, "Connection established") + h.observer.logServerInfo(h.connIndex, serverInfo.LocationName, net.IP{}, "Connection established") return nil } diff --git a/edgediscovery/allregions/discovery.go b/edgediscovery/allregions/discovery.go index 39a3b00b..e6a4103b 100644 --- a/edgediscovery/allregions/discovery.go +++ b/edgediscovery/allregions/discovery.go @@ -32,10 +32,40 @@ var ( netLookupIP = net.LookupIP ) +// ConfigIPVersion is the selection of IP versions from config +type ConfigIPVersion int8 + +const ( + Auto ConfigIPVersion = 2 + IPv4Only ConfigIPVersion = 4 + IPv6Only ConfigIPVersion = 6 +) + +// IPVersion is the IP version of an EdgeAddr +type EdgeIPVersion int8 + +const ( + V4 EdgeIPVersion = 4 + V6 EdgeIPVersion = 6 +) + +// String returns the enum's constant name. +func (c EdgeIPVersion) String() string { + switch c { + case V4: + return "4" + case V6: + return "6" + default: + return "" + } +} + // EdgeAddr is a representation of possible ways to refer an edge location. type EdgeAddr struct { - TCP *net.TCPAddr - UDP *net.UDPAddr + TCP *net.TCPAddr + UDP *net.UDPAddr + IPVersion EdgeIPVersion } // If the call to net.LookupSRV fails, try to fall back to DoT from Cloudflare directly. @@ -120,9 +150,14 @@ func resolveSRV(srv *net.SRV) ([]*EdgeAddr, error) { } addrs := make([]*EdgeAddr, len(ips)) for i, ip := range ips { + version := V6 + if ip.To4() != nil { + version = V4 + } addrs[i] = &EdgeAddr{ - TCP: &net.TCPAddr{IP: ip, Port: int(srv.Port)}, - UDP: &net.UDPAddr{IP: ip, Port: int(srv.Port)}, + TCP: &net.TCPAddr{IP: ip, Port: int(srv.Port)}, + UDP: &net.UDPAddr{IP: ip, Port: int(srv.Port)}, + IPVersion: version, } } return addrs, nil @@ -143,9 +178,14 @@ func ResolveAddrs(addrs []string, log *zerolog.Logger) (resolved []*EdgeAddr) { log.Error().Str(logFieldAddress, addr).Err(err).Msg("failed to resolve to UDP address") continue } + version := V6 + if udpAddr.IP.To4() != nil { + version = V4 + } resolved = append(resolved, &EdgeAddr{ - TCP: tcpAddr, - UDP: udpAddr, + TCP: tcpAddr, + UDP: udpAddr, + IPVersion: version, }) } diff --git a/edgediscovery/allregions/mocks_for_test.go b/edgediscovery/allregions/mocks_for_test.go index 36298be2..ab99c94f 100644 --- a/edgediscovery/allregions/mocks_for_test.go +++ b/edgediscovery/allregions/mocks_for_test.go @@ -36,7 +36,7 @@ func newMockAddrs(port uint16, numRegions uint8, numAddrsPerRegion uint8) mockAd IP: net.ParseIP(fmt.Sprintf("10.0.%v.%v", r, a)), Port: int(port), } - addrs = append(addrs, &EdgeAddr{tcpAddr, udpAddr}) + addrs = append(addrs, &EdgeAddr{tcpAddr, udpAddr, V4}) } addrMap[srv] = addrs numAddrs += len(addrs) diff --git a/supervisor/supervisor.go b/supervisor/supervisor.go index 8f33e279..62bf6ec3 100644 --- a/supervisor/supervisor.go +++ b/supervisor/supervisor.go @@ -63,7 +63,6 @@ var errEarlyShutdown = errors.New("shutdown started") type tunnelError struct { index int - addr *allregions.EdgeAddr err error } @@ -235,7 +234,7 @@ func (s *Supervisor) startFirstTunnel( ) const firstConnIndex = 0 defer func() { - s.tunnelErrors <- tunnelError{index: firstConnIndex, addr: addr, err: err} + s.tunnelErrors <- tunnelError{index: firstConnIndex, err: err} }() addr, err = s.edgeIPs.GetAddr(firstConnIndex) @@ -306,7 +305,7 @@ func (s *Supervisor) startTunnel( err error ) defer func() { - s.tunnelErrors <- tunnelError{index: index, addr: addr, err: err} + s.tunnelErrors <- tunnelError{index: index, err: err} }() addr, err = s.edgeIPs.GetDifferentAddr(index) diff --git a/supervisor/tunnel.go b/supervisor/tunnel.go index 8683c929..45a1ffba 100644 --- a/supervisor/tunnel.go +++ b/supervisor/tunnel.go @@ -43,6 +43,7 @@ type TunnelConfig struct { CloseConnOnce *sync.Once // Used to close connectedSignal no more than once EdgeAddrs []string Region string + EdgeIPVersion allregions.ConfigIPVersion HAConnections int IncidentLookup IncidentLookup IsAutoupdated bool