From 9329cbac0ae610e6df50c74f392685a8a7d72c3d Mon Sep 17 00:00:00 2001 From: Aestek Date: Fri, 17 Jan 2020 15:54:17 +0100 Subject: [PATCH] Add support for dual stack IPv4/IPv6 network (#6640) * Use consts for well known tagged adress keys * Add ipv4 and ipv6 tagged addresses for node lan and wan * Add ipv4 and ipv6 tagged addresses for service lan and wan * Use IPv4 and IPv6 address in DNS --- agent/agent.go | 48 ++++++++ agent/agent_endpoint_test.go | 17 +-- agent/agent_test.go | 141 ++++++++++++++++++++++- agent/catalog_endpoint.go | 6 +- agent/config/builder.go | 51 ++++++++- agent/config/config.go | 4 + agent/config/runtime_test.go | 87 +++++++++----- agent/dns.go | 69 +++++------ agent/dns_test.go | 190 +++++++++++++++++++++++++++++++ agent/health_endpoint.go | 2 +- agent/prepared_query_endpoint.go | 2 +- agent/proxycfg/testing.go | 4 +- agent/service_manager_test.go | 52 +++++---- agent/structs/structs.go | 13 ++- agent/structs/testing_catalog.go | 4 +- agent/translate_addr.go | 99 ++++++++++++---- api/catalog_test.go | 6 +- api/txn_test.go | 6 +- command/connect/envoy/envoy.go | 5 +- 19 files changed, 670 insertions(+), 136 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 0bff3863e..ca44d1c9f 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -2258,6 +2258,26 @@ func (a *Agent) addServiceInternal(req *addServiceRequest) error { a.PauseSync() defer a.ResumeSync() + // Set default tagged addresses + serviceIP := net.ParseIP(service.Address) + serviceAddressIs4 := serviceIP != nil && serviceIP.To4() != nil + serviceAddressIs6 := serviceIP != nil && serviceIP.To4() == nil + if service.TaggedAddresses == nil { + service.TaggedAddresses = map[string]structs.ServiceAddress{} + } + if _, ok := service.TaggedAddresses[structs.TaggedAddressLANIPv4]; !ok && serviceAddressIs4 { + service.TaggedAddresses[structs.TaggedAddressLANIPv4] = structs.ServiceAddress{Address: service.Address, Port: service.Port} + } + if _, ok := service.TaggedAddresses[structs.TaggedAddressWANIPv4]; !ok && serviceAddressIs4 { + service.TaggedAddresses[structs.TaggedAddressWANIPv4] = structs.ServiceAddress{Address: service.Address, Port: service.Port} + } + if _, ok := service.TaggedAddresses[structs.TaggedAddressLANIPv6]; !ok && serviceAddressIs6 { + service.TaggedAddresses[structs.TaggedAddressLANIPv6] = structs.ServiceAddress{Address: service.Address, Port: service.Port} + } + if _, ok := service.TaggedAddresses[structs.TaggedAddressWANIPv6]; !ok && serviceAddressIs6 { + service.TaggedAddresses[structs.TaggedAddressWANIPv6] = structs.ServiceAddress{Address: service.Address, Port: service.Port} + } + // Take a snapshot of the current state of checks (if any), and when adding // a check that already existed carry over the state before resuming // anti-entropy. @@ -2453,6 +2473,34 @@ func (a *Agent) validateService(service *structs.NodeService, chkTypes []*struct } } + // Check IPv4/IPv6 tagged addresses + if service.TaggedAddresses != nil { + if sa, ok := service.TaggedAddresses[structs.TaggedAddressLANIPv4]; ok { + ip := net.ParseIP(sa.Address) + if ip == nil || ip.To4() == nil { + return fmt.Errorf("Service tagged address %q must be a valid ipv4 address", structs.TaggedAddressLANIPv4) + } + } + if sa, ok := service.TaggedAddresses[structs.TaggedAddressWANIPv4]; ok { + ip := net.ParseIP(sa.Address) + if ip == nil || ip.To4() == nil { + return fmt.Errorf("Service tagged address %q must be a valid ipv4 address", structs.TaggedAddressWANIPv4) + } + } + if sa, ok := service.TaggedAddresses[structs.TaggedAddressLANIPv6]; ok { + ip := net.ParseIP(sa.Address) + if ip == nil || ip.To4() != nil { + return fmt.Errorf("Service tagged address %q must be a valid ipv6 address", structs.TaggedAddressLANIPv6) + } + } + if sa, ok := service.TaggedAddresses[structs.TaggedAddressLANIPv6]; ok { + ip := net.ParseIP(sa.Address) + if ip == nil || ip.To4() != nil { + return fmt.Errorf("Service tagged address %q must be a valid ipv6 address", structs.TaggedAddressLANIPv6) + } + } + } + return nil } diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index a6a1776de..18149bdb0 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -3031,10 +3031,11 @@ func testAgent_RegisterService_UnmanagedConnectProxy(t *testing.T, extraHCL stri func testDefaultSidecar(svc string, port int, fns ...func(*structs.NodeService)) *structs.NodeService { ns := &structs.NodeService{ - ID: svc + "-sidecar-proxy", - Kind: structs.ServiceKindConnectProxy, - Service: svc + "-sidecar-proxy", - Port: 2222, + ID: svc + "-sidecar-proxy", + Kind: structs.ServiceKindConnectProxy, + Service: svc + "-sidecar-proxy", + Port: 2222, + TaggedAddresses: map[string]structs.ServiceAddress{}, Weights: &structs.Weights{ Passing: 1, Warning: 1, @@ -3396,9 +3397,10 @@ func testAgent_RegisterServiceDeregisterService_Sidecar(t *testing.T, extraHCL s // Note here that although the registration here didn't register it, we // should still see the NodeService we pre-registered here. wantNS: &structs.NodeService{ - ID: "web-sidecar-proxy", - Service: "fake-sidecar", - Port: 9999, + ID: "web-sidecar-proxy", + Service: "fake-sidecar", + Port: 9999, + TaggedAddresses: map[string]structs.ServiceAddress{}, Weights: &structs.Weights{ Passing: 1, Warning: 1, @@ -3439,6 +3441,7 @@ func testAgent_RegisterServiceDeregisterService_Sidecar(t *testing.T, extraHCL s Service: "web-sidecar-proxy", LocallyRegisteredAsSidecar: true, Port: 6666, + TaggedAddresses: map[string]structs.ServiceAddress{}, Weights: &structs.Weights{ Passing: 1, Warning: 1, diff --git a/agent/agent_test.go b/agent/agent_test.go index abc502643..1c92c84fe 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -718,6 +718,134 @@ func testAgent_AddServiceNoRemoteExec(t *testing.T, extraHCL string) { } } +func TestAddServiceIPv4TaggedDefault(t *testing.T) { + t.Helper() + + a := NewTestAgent(t, t.Name(), "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + srv := &structs.NodeService{ + Service: "my_service", + ID: "my_service_id", + Port: 8100, + Address: "10.0.1.2", + } + + err := a.AddService(srv, []*structs.CheckType{}, false, "", ConfigSourceRemote) + require.Nil(t, err) + + ns := a.State.Service(structs.NewServiceID("my_service_id", nil)) + require.NotNil(t, ns) + + svcAddr := structs.ServiceAddress{Address: srv.Address, Port: srv.Port} + require.Equal(t, svcAddr, ns.TaggedAddresses[structs.TaggedAddressLANIPv4]) + require.Equal(t, svcAddr, ns.TaggedAddresses[structs.TaggedAddressWANIPv4]) + _, ok := ns.TaggedAddresses[structs.TaggedAddressLANIPv6] + require.False(t, ok) + _, ok = ns.TaggedAddresses[structs.TaggedAddressWANIPv6] + require.False(t, ok) +} + +func TestAddServiceIPv6TaggedDefault(t *testing.T) { + t.Helper() + + a := NewTestAgent(t, t.Name(), "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + srv := &structs.NodeService{ + Service: "my_service", + ID: "my_service_id", + Port: 8100, + Address: "::5", + } + + err := a.AddService(srv, []*structs.CheckType{}, false, "", ConfigSourceRemote) + require.Nil(t, err) + + ns := a.State.Service(structs.NewServiceID("my_service_id", nil)) + require.NotNil(t, ns) + + svcAddr := structs.ServiceAddress{Address: srv.Address, Port: srv.Port} + require.Equal(t, svcAddr, ns.TaggedAddresses[structs.TaggedAddressLANIPv6]) + require.Equal(t, svcAddr, ns.TaggedAddresses[structs.TaggedAddressWANIPv6]) + _, ok := ns.TaggedAddresses[structs.TaggedAddressLANIPv4] + require.False(t, ok) + _, ok = ns.TaggedAddresses[structs.TaggedAddressWANIPv4] + require.False(t, ok) +} + +func TestAddServiceIPv4TaggedSet(t *testing.T) { + t.Helper() + + a := NewTestAgent(t, t.Name(), "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + srv := &structs.NodeService{ + Service: "my_service", + ID: "my_service_id", + Port: 8100, + Address: "10.0.1.2", + TaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressWANIPv4: { + Address: "10.100.200.5", + Port: 8100, + }, + }, + } + + err := a.AddService(srv, []*structs.CheckType{}, false, "", ConfigSourceRemote) + require.Nil(t, err) + + ns := a.State.Service(structs.NewServiceID("my_service_id", nil)) + require.NotNil(t, ns) + + svcAddr := structs.ServiceAddress{Address: srv.Address, Port: srv.Port} + require.Equal(t, svcAddr, ns.TaggedAddresses[structs.TaggedAddressLANIPv4]) + require.Equal(t, structs.ServiceAddress{Address: "10.100.200.5", Port: 8100}, ns.TaggedAddresses[structs.TaggedAddressWANIPv4]) + _, ok := ns.TaggedAddresses[structs.TaggedAddressLANIPv6] + require.False(t, ok) + _, ok = ns.TaggedAddresses[structs.TaggedAddressWANIPv6] + require.False(t, ok) +} + +func TestAddServiceIPv6TaggedSet(t *testing.T) { + t.Helper() + + a := NewTestAgent(t, t.Name(), "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + srv := &structs.NodeService{ + Service: "my_service", + ID: "my_service_id", + Port: 8100, + Address: "::5", + TaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressWANIPv6: { + Address: "::6", + Port: 8100, + }, + }, + } + + err := a.AddService(srv, []*structs.CheckType{}, false, "", ConfigSourceRemote) + require.Nil(t, err) + + ns := a.State.Service(structs.NewServiceID("my_service_id", nil)) + require.NotNil(t, ns) + + svcAddr := structs.ServiceAddress{Address: srv.Address, Port: srv.Port} + require.Equal(t, svcAddr, ns.TaggedAddresses[structs.TaggedAddressLANIPv6]) + require.Equal(t, structs.ServiceAddress{Address: "::6", Port: 8100}, ns.TaggedAddresses[structs.TaggedAddressWANIPv6]) + _, ok := ns.TaggedAddresses[structs.TaggedAddressLANIPv4] + require.False(t, ok) + _, ok = ns.TaggedAddresses[structs.TaggedAddressWANIPv4] + require.False(t, ok) +} + func TestAgent_RemoveService(t *testing.T) { t.Run("normal", func(t *testing.T) { t.Parallel() @@ -1876,12 +2004,13 @@ func testAgent_persistedService_compat(t *testing.T, extraHCL string) { defer a.Shutdown() svc := &structs.NodeService{ - ID: "redis", - Service: "redis", - Tags: []string{"foo"}, - Port: 8000, - Weights: &structs.Weights{Passing: 1, Warning: 1}, - EnterpriseMeta: *structs.DefaultEnterpriseMeta(), + ID: "redis", + Service: "redis", + Tags: []string{"foo"}, + Port: 8000, + TaggedAddresses: map[string]structs.ServiceAddress{}, + Weights: &structs.Weights{Passing: 1, Warning: 1}, + EnterpriseMeta: *structs.DefaultEnterpriseMeta(), } // Encode the NodeService directly. This is what previous versions diff --git a/agent/catalog_endpoint.go b/agent/catalog_endpoint.go index a2860ed1c..dba5f4871 100644 --- a/agent/catalog_endpoint.go +++ b/agent/catalog_endpoint.go @@ -138,7 +138,7 @@ RETRY_ONCE: } out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() - s.agent.TranslateAddresses(args.Datacenter, out.Nodes) + s.agent.TranslateAddresses(args.Datacenter, out.Nodes, TranslateAddressAcceptAny) // Use empty list instead of nil if out.Nodes == nil { @@ -284,7 +284,7 @@ func (s *HTTPServer) catalogServiceNodes(resp http.ResponseWriter, req *http.Req } out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() - s.agent.TranslateAddresses(args.Datacenter, out.ServiceNodes) + s.agent.TranslateAddresses(args.Datacenter, out.ServiceNodes, TranslateAddressAcceptAny) // Use empty list instead of nil if out.ServiceNodes == nil { @@ -340,7 +340,7 @@ RETRY_ONCE: } out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() if out.NodeServices != nil { - s.agent.TranslateAddresses(args.Datacenter, out.NodeServices) + s.agent.TranslateAddresses(args.Datacenter, out.NodeServices, TranslateAddressAcceptAny) } // TODO: The NodeServices object in IndexedNodeServices is a pointer to diff --git a/agent/config/builder.go b/agent/config/builder.go index 6ab5bde61..667bddc4b 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -415,6 +415,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { bindAddr := bindAddrs[0].(*net.IPAddr) advertiseAddr := b.makeIPAddr(b.expandFirstIP("advertise_addr", c.AdvertiseAddrLAN), bindAddr) + if ipaddr.IsAny(advertiseAddr) { var addrtyp string @@ -460,7 +461,39 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { // derive other advertise addresses from the advertise address advertiseAddrLAN := b.makeIPAddr(b.expandFirstIP("advertise_addr", c.AdvertiseAddrLAN), advertiseAddr) + advertiseAddrIsV6 := advertiseAddr.IP.To4() == nil + var advertiseAddrV4, advertiseAddrV6 *net.IPAddr + if !advertiseAddrIsV6 { + advertiseAddrV4 = advertiseAddr + } else { + advertiseAddrV6 = advertiseAddr + } + advertiseAddrLANIPv4 := b.makeIPAddr(b.expandFirstIP("advertise_addr_ipv4", c.AdvertiseAddrLANIPv4), advertiseAddrV4) + if advertiseAddrLANIPv4 != nil && advertiseAddrLANIPv4.IP.To4() == nil { + return RuntimeConfig{}, fmt.Errorf("advertise_addr_ipv4 must be an ipv4 address") + } + advertiseAddrLANIPv6 := b.makeIPAddr(b.expandFirstIP("advertise_addr_ipv6", c.AdvertiseAddrLANIPv6), advertiseAddrV6) + if advertiseAddrLANIPv6 != nil && advertiseAddrLANIPv6.IP.To4() != nil { + return RuntimeConfig{}, fmt.Errorf("advertise_addr_ipv6 must be an ipv6 address") + } + advertiseAddrWAN := b.makeIPAddr(b.expandFirstIP("advertise_addr_wan", c.AdvertiseAddrWAN), advertiseAddrLAN) + advertiseAddrWANIsV6 := advertiseAddrWAN.IP.To4() == nil + var advertiseAddrWANv4, advertiseAddrWANv6 *net.IPAddr + if !advertiseAddrWANIsV6 { + advertiseAddrWANv4 = advertiseAddrWAN + } else { + advertiseAddrWANv6 = advertiseAddrWAN + } + advertiseAddrWANIPv4 := b.makeIPAddr(b.expandFirstIP("advertise_addr_wan_ipv4", c.AdvertiseAddrWANIPv4), advertiseAddrWANv4) + if advertiseAddrWANIPv4 != nil && advertiseAddrWANIPv4.IP.To4() == nil { + return RuntimeConfig{}, fmt.Errorf("advertise_addr_wan_ipv4 must be an ipv4 address") + } + advertiseAddrWANIPv6 := b.makeIPAddr(b.expandFirstIP("advertise_addr_wan_ipv6", c.AdvertiseAddrWANIPv6), advertiseAddrWANv6) + if advertiseAddrWANIPv6 != nil && advertiseAddrWANIPv6.IP.To4() != nil { + return RuntimeConfig{}, fmt.Errorf("advertise_addr_wan_ipv6 must be an ipv6 address") + } + rpcAdvertiseAddr := &net.TCPAddr{IP: advertiseAddrLAN.IP, Port: serverPort} serfAdvertiseAddrLAN := &net.TCPAddr{IP: advertiseAddrLAN.IP, Port: serfPortLAN} // Only initialize serf WAN advertise address when its enabled @@ -509,8 +542,22 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { if c.TaggedAddresses == nil { c.TaggedAddresses = make(map[string]string) } - c.TaggedAddresses["lan"] = advertiseAddrLAN.IP.String() - c.TaggedAddresses["wan"] = advertiseAddrWAN.IP.String() + + c.TaggedAddresses[structs.TaggedAddressLAN] = advertiseAddrLAN.IP.String() + if advertiseAddrLANIPv4 != nil { + c.TaggedAddresses[structs.TaggedAddressLANIPv4] = advertiseAddrLANIPv4.IP.String() + } + if advertiseAddrLANIPv6 != nil { + c.TaggedAddresses[structs.TaggedAddressLANIPv6] = advertiseAddrLANIPv6.IP.String() + } + + c.TaggedAddresses[structs.TaggedAddressWAN] = advertiseAddrWAN.IP.String() + if advertiseAddrWANIPv4 != nil { + c.TaggedAddresses[structs.TaggedAddressWANIPv4] = advertiseAddrWANIPv4.IP.String() + } + if advertiseAddrWANIPv6 != nil { + c.TaggedAddresses[structs.TaggedAddressWANIPv6] = advertiseAddrWANIPv6.IP.String() + } // segments var segments []structs.NetworkSegment diff --git a/agent/config/config.go b/agent/config/config.go index ab2284158..f5cac9401 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -182,7 +182,11 @@ type Config struct { ACL ACL `json:"acl,omitempty" hcl:"acl" mapstructure:"acl"` Addresses Addresses `json:"addresses,omitempty" hcl:"addresses" mapstructure:"addresses"` AdvertiseAddrLAN *string `json:"advertise_addr,omitempty" hcl:"advertise_addr" mapstructure:"advertise_addr"` + AdvertiseAddrLANIPv4 *string `json:"advertise_addr_ipv4,omitempty" hcl:"advertise_addr_ipv4" mapstructure:"advertise_addr_ipv4"` + AdvertiseAddrLANIPv6 *string `json:"advertise_addr_ipv6,omitempty" hcl:"advertise_addr_ipv6" mapstructure:"advertise_addr_ipv6"` AdvertiseAddrWAN *string `json:"advertise_addr_wan,omitempty" hcl:"advertise_addr_wan" mapstructure:"advertise_addr_wan"` + AdvertiseAddrWANIPv4 *string `json:"advertise_addr_wan_ipv4,omitempty" hcl:"advertise_addr_wan_ipv4" mapstructure:"advertise_addr_wan_ipv4"` + AdvertiseAddrWANIPv6 *string `json:"advertise_addr_wan_ipv6,omitempty" hcl:"advertise_addr_wan_ipv6" mapstructure:"advertise_addr_ipv6"` Autopilot Autopilot `json:"autopilot,omitempty" hcl:"autopilot" mapstructure:"autopilot"` BindAddr *string `json:"bind_addr,omitempty" hcl:"bind_addr" mapstructure:"bind_addr"` Bootstrap *bool `json:"bootstrap,omitempty" hcl:"bootstrap" mapstructure:"bootstrap"` diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index 2d506fb8a..2901072ec 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -70,8 +70,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301") rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302") rt.TaggedAddresses = map[string]string{ - "lan": "1.2.3.4", - "wan": "1.2.3.4", + "lan": "1.2.3.4", + "lan_ipv4": "1.2.3.4", + "wan": "1.2.3.4", + "wan_ipv4": "1.2.3.4", } rt.DataDir = dataDir }, @@ -86,8 +88,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.AdvertiseAddrWAN = ipAddr("1.2.3.4") rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302") rt.TaggedAddresses = map[string]string{ - "lan": "10.0.0.1", - "wan": "1.2.3.4", + "lan": "10.0.0.1", + "lan_ipv4": "10.0.0.1", + "wan": "1.2.3.4", + "wan_ipv4": "1.2.3.4", } rt.DataDir = dataDir }, @@ -106,8 +110,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301") rt.SerfAdvertiseAddrWAN = tcpAddr("5.6.7.8:8302") rt.TaggedAddresses = map[string]string{ - "lan": "1.2.3.4", - "wan": "5.6.7.8", + "lan": "1.2.3.4", + "lan_ipv4": "1.2.3.4", + "wan": "5.6.7.8", + "wan_ipv4": "5.6.7.8", } rt.DataDir = dataDir }, @@ -129,8 +135,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfBindAddrLAN = tcpAddr("1.2.3.4:8301") rt.SerfBindAddrWAN = tcpAddr("1.2.3.4:8302") rt.TaggedAddresses = map[string]string{ - "lan": "1.2.3.4", - "wan": "1.2.3.4", + "lan": "1.2.3.4", + "lan_ipv4": "1.2.3.4", + "wan": "1.2.3.4", + "wan_ipv4": "1.2.3.4", } rt.DataDir = dataDir }, @@ -282,7 +290,12 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfBindAddrWAN = tcpAddr("127.0.0.1:8302") rt.ServerMode = true rt.SkipLeaveOnInt = true - rt.TaggedAddresses = map[string]string{"lan": "127.0.0.1", "wan": "127.0.0.1"} + rt.TaggedAddresses = map[string]string{ + "lan": "127.0.0.1", + "lan_ipv4": "127.0.0.1", + "wan": "127.0.0.1", + "wan_ipv4": "127.0.0.1", + } rt.ConsulCoordinateUpdatePeriod = 100 * time.Millisecond rt.ConsulRaftElectionTimeout = 52 * time.Millisecond rt.ConsulRaftHeartbeatTimeout = 35 * time.Millisecond @@ -836,8 +849,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:8301") rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:8302") rt.TaggedAddresses = map[string]string{ - "lan": "10.0.0.1", - "wan": "10.0.0.1", + "lan": "10.0.0.1", + "lan_ipv4": "10.0.0.1", + "wan": "10.0.0.1", + "wan_ipv4": "10.0.0.1", } rt.DataDir = dataDir }, @@ -858,8 +873,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfBindAddrLAN = tcpAddr("[::]:8301") rt.SerfBindAddrWAN = tcpAddr("[::]:8302") rt.TaggedAddresses = map[string]string{ - "lan": "dead:beef::1", - "wan": "dead:beef::1", + "lan": "dead:beef::1", + "lan_ipv6": "dead:beef::1", + "wan": "dead:beef::1", + "wan_ipv6": "dead:beef::1", } rt.DataDir = dataDir }, @@ -883,8 +900,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:8301") rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:8302") rt.TaggedAddresses = map[string]string{ - "lan": "1.2.3.4", - "wan": "1.2.3.4", + "lan": "1.2.3.4", + "lan_ipv4": "1.2.3.4", + "wan": "1.2.3.4", + "wan_ipv4": "1.2.3.4", } rt.DataDir = dataDir }, @@ -1101,8 +1120,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301") rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302") rt.TaggedAddresses = map[string]string{ - "lan": "1.2.3.4", - "wan": "1.2.3.4", + "lan": "1.2.3.4", + "lan_ipv4": "1.2.3.4", + "wan": "1.2.3.4", + "wan_ipv4": "1.2.3.4", } rt.DataDir = dataDir }, @@ -1116,8 +1137,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.AdvertiseAddrWAN = ipAddr("1.2.3.4") rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302") rt.TaggedAddresses = map[string]string{ - "lan": "10.0.0.1", - "wan": "1.2.3.4", + "lan": "10.0.0.1", + "lan_ipv4": "10.0.0.1", + "wan": "1.2.3.4", + "wan_ipv4": "1.2.3.4", } rt.DataDir = dataDir }, @@ -1154,8 +1177,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfPortWAN = 3000 rt.ServerPort = 1000 rt.TaggedAddresses = map[string]string{ - "lan": "1.2.3.4", - "wan": "1.2.3.4", + "lan": "1.2.3.4", + "lan_ipv4": "1.2.3.4", + "wan": "1.2.3.4", + "wan_ipv4": "1.2.3.4", } rt.DataDir = dataDir }, @@ -1192,8 +1217,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfPortWAN = 3000 rt.ServerPort = 1000 rt.TaggedAddresses = map[string]string{ - "lan": "10.0.0.1", - "wan": "1.2.3.4", + "lan": "10.0.0.1", + "lan_ipv4": "10.0.0.1", + "wan": "1.2.3.4", + "wan_ipv4": "1.2.3.4", } rt.DataDir = dataDir }, @@ -1218,8 +1245,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfAdvertiseAddrWAN = nil rt.SerfBindAddrWAN = nil rt.TaggedAddresses = map[string]string{ - "lan": "10.0.0.1", - "wan": "1.2.3.4", + "lan": "10.0.0.1", + "lan_ipv4": "10.0.0.1", + "wan": "1.2.3.4", + "wan_ipv4": "1.2.3.4", } rt.DataDir = dataDir rt.SerfPortWAN = -1 @@ -1430,8 +1459,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.SerfBindAddrWAN = tcpAddr("4.4.4.4:8302") rt.StartJoinAddrsLAN = []string{"c", "d", "a", "b"} rt.TaggedAddresses = map[string]string{ - "lan": "1.1.1.1", - "wan": "2.2.2.2", + "lan": "1.1.1.1", + "lan_ipv4": "1.1.1.1", + "wan": "2.2.2.2", + "wan_ipv4": "2.2.2.2", } rt.DataDir = dataDir }, @@ -5464,7 +5495,9 @@ func TestFullConfig(t *testing.T) { "7MYgHrYH": "dALJAhLD", "h6DdBy6K": "ebrr9zZ8", "lan": "17.99.29.16", + "lan_ipv4": "17.99.29.16", "wan": "78.63.37.19", + "wan_ipv4": "78.63.37.19", }, TranslateWANAddrs: true, UIContentPath: "/consul/", diff --git a/agent/dns.go b/agent/dns.go index dee4ecb4a..4cb496739 100644 --- a/agent/dns.go +++ b/agent/dns.go @@ -1308,24 +1308,25 @@ RPC: // serviceNodeRecords is used to add the node records for a service lookup func (d *DNSServer) serviceNodeRecords(cfg *dnsConfig, dc string, nodes structs.CheckServiceNodes, req, resp *dns.Msg, ttl time.Duration, maxRecursionLevel int) { - qName := req.Question[0].Name handled := make(map[string]struct{}) var answerCNAME []dns.RR = nil count := 0 for _, node := range nodes { - addr := d.serviceNodeAddr(node, dc, qName) - - // Avoid duplicate entries, possible if a node has - // the same service on multiple ports, etc. - if _, ok := handled[addr]; ok { - continue - } - handled[addr] = struct{}{} - // Add the node record had_answer := false records, _ := d.nodeServiceRecords(dc, node, req, ttl, cfg, maxRecursionLevel) + if len(records) == 0 { + continue + } + + // Avoid duplicate entries, possible if a node has + // the same service on multiple ports, etc. + if _, ok := handled[records[0].String()]; ok { + continue + } + handled[records[0].String()] = struct{}{} + if records != nil { switch records[0].(type) { case *dns.CNAME: @@ -1399,25 +1400,6 @@ func findWeight(node structs.CheckServiceNode) int { } } -// serviceNodeAddr is used to identify target service address -func (d *DNSServer) serviceNodeAddr(serviceNode structs.CheckServiceNode, dc string, dnsQuery string) string { - nodeAddress := d.agent.TranslateAddress(dc, serviceNode.Node.Address, serviceNode.Node.TaggedAddresses) - serviceAddress := d.agent.TranslateServiceAddress(dc, serviceNode.Service.Address, serviceNode.Service.TaggedAddresses) - addr := nodeAddress - - if serviceAddress != "" { - addr = serviceAddress - } - - // If the service address is a CNAME for the service we are looking - // for then use the node address. - if dnsQuery == strings.TrimSuffix(addr, ".")+"." { - addr = nodeAddress - } - - return addr -} - func (d *DNSServer) encodeIPAsFqdn(dc string, ip net.IP) string { ipv4 := ip.To4() if ipv4 != nil { @@ -1460,7 +1442,16 @@ func makeARecord(qType uint16, ip net.IP, ttl time.Duration) dns.RR { // In case of an SRV query the answer will be a IN SRV and additional data will store an IN A to the node IP // Otherwise it will return a IN A record func (d *DNSServer) makeRecordFromNode(dc string, node *structs.Node, qType uint16, qName string, ttl time.Duration, maxRecursionLevel int) []dns.RR { - addr := d.agent.TranslateAddress(node.Datacenter, node.Address, node.TaggedAddresses) + addrTranslate := TranslateAddressAcceptDomain + if qType == dns.TypeA { + addrTranslate |= TranslateAddressAcceptIPv4 + } else if qType == dns.TypeAAAA { + addrTranslate |= TranslateAddressAcceptIPv6 + } else { + addrTranslate |= TranslateAddressAcceptAny + } + + addr := d.agent.TranslateAddress(node.Datacenter, node.Address, node.TaggedAddresses, addrTranslate) ip := net.ParseIP(addr) var res []dns.RR @@ -1621,8 +1612,20 @@ MORE_REC: } func (d *DNSServer) nodeServiceRecords(dc string, node structs.CheckServiceNode, req *dns.Msg, ttl time.Duration, cfg *dnsConfig, maxRecursionLevel int) ([]dns.RR, []dns.RR) { - serviceAddr := d.agent.TranslateServiceAddress(dc, node.Service.Address, node.Service.TaggedAddresses) - nodeAddr := d.agent.TranslateAddress(node.Node.Datacenter, node.Node.Address, node.Node.TaggedAddresses) + addrTranslate := TranslateAddressAcceptDomain + if req.Question[0].Qtype == dns.TypeA { + addrTranslate |= TranslateAddressAcceptIPv4 + } else if req.Question[0].Qtype == dns.TypeAAAA { + addrTranslate |= TranslateAddressAcceptIPv6 + } else { + addrTranslate |= TranslateAddressAcceptAny + } + + serviceAddr := d.agent.TranslateServiceAddress(dc, node.Service.Address, node.Service.TaggedAddresses, addrTranslate) + nodeAddr := d.agent.TranslateAddress(node.Node.Datacenter, node.Node.Address, node.Node.TaggedAddresses, addrTranslate) + if serviceAddr == "" && nodeAddr == "" { + return nil, nil + } nodeIPAddr := net.ParseIP(nodeAddr) serviceIPAddr := net.ParseIP(serviceAddr) @@ -1685,7 +1688,7 @@ func (d *DNSServer) serviceSRVRecords(cfg *dnsConfig, dc string, nodes structs.C for _, node := range nodes { // Avoid duplicate entries, possible if a node has // the same service the same port, etc. - serviceAddress := d.agent.TranslateServiceAddress(dc, node.Service.Address, node.Service.TaggedAddresses) + serviceAddress := d.agent.TranslateServiceAddress(dc, node.Service.Address, node.Service.TaggedAddresses, TranslateAddressAcceptAny) servicePort := d.agent.TranslateServicePort(dc, node.Service.Port, node.Service.TaggedAddresses) tuple := fmt.Sprintf("%s:%s:%d", node.Node.Node, serviceAddress, servicePort) if _, ok := handled[tuple]; ok { diff --git a/agent/dns_test.go b/agent/dns_test.go index 9b9bb4986..c04e53fa2 100644 --- a/agent/dns_test.go +++ b/agent/dns_test.go @@ -2518,6 +2518,196 @@ func TestDNS_ServiceLookup_WanTranslation(t *testing.T) { } } +func TestDNS_Lookup_TaggedIPAddresses(t *testing.T) { + t.Parallel() + a := NewTestAgent(t, t.Name(), "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + require.NoError(t, a.RPC("PreparedQuery.Apply", args, &id)) + } + + type testCase struct { + nodeAddress string + nodeTaggedAddresses map[string]string + serviceAddress string + serviceTaggedAddresses map[string]structs.ServiceAddress + + expectedServiceIPv4Address string + expectedServiceIPv6Address string + expectedNodeIPv4Address string + expectedNodeIPv6Address string + } + + cases := map[string]testCase{ + "simple-ipv4": testCase{ + serviceAddress: "127.0.0.2", + nodeAddress: "127.0.0.1", + + expectedServiceIPv4Address: "127.0.0.2", + expectedServiceIPv6Address: "", + expectedNodeIPv4Address: "127.0.0.1", + expectedNodeIPv6Address: "", + }, + "simple-ipv6": testCase{ + serviceAddress: "::2", + nodeAddress: "::1", + + expectedServiceIPv6Address: "::2", + expectedServiceIPv4Address: "", + expectedNodeIPv6Address: "::1", + expectedNodeIPv4Address: "", + }, + "ipv4-with-tagged-ipv6": testCase{ + serviceAddress: "127.0.0.2", + nodeAddress: "127.0.0.1", + + serviceTaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressLANIPv6: {Address: "::2"}, + }, + nodeTaggedAddresses: map[string]string{ + structs.TaggedAddressLANIPv6: "::1", + }, + + expectedServiceIPv4Address: "127.0.0.2", + expectedServiceIPv6Address: "::2", + expectedNodeIPv4Address: "127.0.0.1", + expectedNodeIPv6Address: "::1", + }, + "ipv6-with-tagged-ipv4": testCase{ + serviceAddress: "::2", + nodeAddress: "::1", + + serviceTaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressLANIPv4: {Address: "127.0.0.2"}, + }, + nodeTaggedAddresses: map[string]string{ + structs.TaggedAddressLANIPv4: "127.0.0.1", + }, + + expectedServiceIPv4Address: "127.0.0.2", + expectedServiceIPv6Address: "::2", + expectedNodeIPv4Address: "127.0.0.1", + expectedNodeIPv6Address: "::1", + }, + } + + for name, tc := range cases { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: tc.nodeAddress, + TaggedAddresses: tc.nodeTaggedAddresses, + Service: &structs.NodeService{ + Service: "db", + Address: tc.serviceAddress, + Port: 8080, + TaggedAddresses: tc.serviceTaggedAddresses, + }, + } + + var out struct{} + require.NoError(t, a.RPC("Catalog.Register", args, &out)) + + // Look up the SRV record via service and prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) + + c := new(dns.Client) + addr := a.config.DNSAddrs[0].String() + in, _, err := c.Exchange(m, addr) + require.NoError(t, err) + + if tc.expectedServiceIPv4Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, question, aRec.Hdr.Name) + require.Equal(t, tc.expectedServiceIPv4Address, aRec.A.String()) + } else { + require.Len(t, in.Answer, 0) + } + + m = new(dns.Msg) + m.SetQuestion(question, dns.TypeAAAA) + + c = new(dns.Client) + addr = a.config.DNSAddrs[0].String() + in, _, err = c.Exchange(m, addr) + require.NoError(t, err) + + if tc.expectedServiceIPv6Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.AAAA) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, question, aRec.Hdr.Name) + require.Equal(t, tc.expectedServiceIPv6Address, aRec.AAAA.String()) + } else { + require.Len(t, in.Answer, 0) + } + } + + // Look up node + m := new(dns.Msg) + m.SetQuestion("foo.node.consul.", dns.TypeA) + + c := new(dns.Client) + addr := a.config.DNSAddrs[0].String() + in, _, err := c.Exchange(m, addr) + require.NoError(t, err) + + if tc.expectedNodeIPv4Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, "foo.node.consul.", aRec.Hdr.Name) + require.Equal(t, tc.expectedNodeIPv4Address, aRec.A.String()) + } else { + require.Len(t, in.Answer, 0) + } + + m = new(dns.Msg) + m.SetQuestion("foo.node.consul.", dns.TypeAAAA) + + c = new(dns.Client) + addr = a.config.DNSAddrs[0].String() + in, _, err = c.Exchange(m, addr) + require.NoError(t, err) + + if tc.expectedNodeIPv6Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.AAAA) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, "foo.node.consul.", aRec.Hdr.Name) + require.Equal(t, tc.expectedNodeIPv6Address, aRec.AAAA.String()) + } else { + require.Len(t, in.Answer, 0) + } + }) + } +} + func TestDNS_CaseInsensitiveServiceLookup(t *testing.T) { t.Parallel() a := NewTestAgent(t, t.Name(), "") diff --git a/agent/health_endpoint.go b/agent/health_endpoint.go index ebf253c52..3ab1a2cb9 100644 --- a/agent/health_endpoint.go +++ b/agent/health_endpoint.go @@ -247,7 +247,7 @@ func (s *HTTPServer) healthServiceNodes(resp http.ResponseWriter, req *http.Requ } // Translate addresses after filtering so we don't waste effort. - s.agent.TranslateAddresses(args.Datacenter, out.Nodes) + s.agent.TranslateAddresses(args.Datacenter, out.Nodes, TranslateAddressAcceptAny) // Use empty list instead of nil if out.Nodes == nil { diff --git a/agent/prepared_query_endpoint.go b/agent/prepared_query_endpoint.go index 8bccb5b93..5e1afbd5b 100644 --- a/agent/prepared_query_endpoint.go +++ b/agent/prepared_query_endpoint.go @@ -163,7 +163,7 @@ func (s *HTTPServer) preparedQueryExecute(id string, resp http.ResponseWriter, r // a query can fail over to a different DC than where the execute request // was sent to. That's why we use the reply's DC and not the one from // the args. - s.agent.TranslateAddresses(reply.Datacenter, reply.Nodes) + s.agent.TranslateAddresses(reply.Datacenter, reply.Nodes, TranslateAddressAcceptAny) // Use empty list instead of nil. if reply.Nodes == nil { diff --git a/agent/proxycfg/testing.go b/agent/proxycfg/testing.go index 9643d12f1..19f85ec94 100644 --- a/agent/proxycfg/testing.go +++ b/agent/proxycfg/testing.go @@ -986,11 +986,11 @@ func testConfigSnapshotMeshGateway(t testing.T, populateServices bool) *ConfigSn Config: map[string]interface{}{}, }, TaggedAddresses: map[string]structs.ServiceAddress{ - "lan": structs.ServiceAddress{ + structs.TaggedAddressLAN: structs.ServiceAddress{ Address: "1.2.3.4", Port: 8443, }, - "wan": structs.ServiceAddress{ + structs.TaggedAddressWAN: structs.ServiceAddress{ Address: "198.18.0.1", Port: 443, }, diff --git a/agent/service_manager_test.go b/agent/service_manager_test.go index 2092da300..285e98810 100644 --- a/agent/service_manager_test.go +++ b/agent/service_manager_test.go @@ -50,9 +50,10 @@ func TestServiceManager_RegisterService(t *testing.T) { redisService := a.State.Service(structs.NewServiceID("redis", nil)) require.NotNil(redisService) require.Equal(&structs.NodeService{ - ID: "redis", - Service: "redis", - Port: 8000, + ID: "redis", + Service: "redis", + Port: 8000, + TaggedAddresses: map[string]structs.ServiceAddress{}, Weights: &structs.Weights{ Passing: 1, Warning: 1, @@ -116,10 +117,11 @@ func TestServiceManager_RegisterSidecar(t *testing.T) { sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) require.NotNil(sidecarService) require.Equal(&structs.NodeService{ - Kind: structs.ServiceKindConnectProxy, - ID: "web-sidecar-proxy", - Service: "web-sidecar-proxy", - Port: 21000, + Kind: structs.ServiceKindConnectProxy, + ID: "web-sidecar-proxy", + Service: "web-sidecar-proxy", + Port: 21000, + TaggedAddresses: map[string]structs.ServiceAddress{}, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "web", DestinationServiceID: "web", @@ -184,10 +186,11 @@ func TestServiceManager_RegisterMeshGateway(t *testing.T) { gateway := a.State.Service(structs.NewServiceID("mesh-gateway", nil)) require.NotNil(gateway) require.Equal(&structs.NodeService{ - Kind: structs.ServiceKindMeshGateway, - ID: "mesh-gateway", - Service: "mesh-gateway", - Port: 443, + Kind: structs.ServiceKindMeshGateway, + ID: "mesh-gateway", + Service: "mesh-gateway", + Port: 443, + TaggedAddresses: map[string]structs.ServiceAddress{}, Proxy: structs.ConnectProxyConfig{ Config: map[string]interface{}{ "foo": int64(1), @@ -276,10 +279,11 @@ func TestServiceManager_PersistService_API(t *testing.T) { } expectState := &structs.NodeService{ - Kind: structs.ServiceKindConnectProxy, - ID: "web-sidecar-proxy", - Service: "web-sidecar-proxy", - Port: 21000, + Kind: structs.ServiceKindConnectProxy, + ID: "web-sidecar-proxy", + Service: "web-sidecar-proxy", + Port: 21000, + TaggedAddresses: map[string]structs.ServiceAddress{}, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "web", DestinationServiceID: "web", @@ -487,10 +491,11 @@ func TestServiceManager_PersistService_ConfigFiles(t *testing.T) { svcID := "web-sidecar-proxy" expectState := &structs.NodeService{ - Kind: structs.ServiceKindConnectProxy, - ID: "web-sidecar-proxy", - Service: "web-sidecar-proxy", - Port: 21000, + Kind: structs.ServiceKindConnectProxy, + ID: "web-sidecar-proxy", + Service: "web-sidecar-proxy", + Port: 21000, + TaggedAddresses: map[string]structs.ServiceAddress{}, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "web", DestinationServiceID: "web", @@ -637,10 +642,11 @@ func TestServiceManager_Disabled(t *testing.T) { sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) require.NotNil(sidecarService) require.Equal(&structs.NodeService{ - Kind: structs.ServiceKindConnectProxy, - ID: "web-sidecar-proxy", - Service: "web-sidecar-proxy", - Port: 21000, + Kind: structs.ServiceKindConnectProxy, + ID: "web-sidecar-proxy", + Service: "web-sidecar-proxy", + Port: 21000, + TaggedAddresses: map[string]structs.ServiceAddress{}, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "web", DestinationServiceID: "web", diff --git a/agent/structs/structs.go b/agent/structs/structs.go index ec0bac0ee..e3340165e 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -119,6 +119,15 @@ var ( NodeMaintCheckID = NewCheckID(NodeMaint, nil) ) +const ( + TaggedAddressWAN = "wan" + TaggedAddressWANIPv4 = "wan_ipv4" + TaggedAddressWANIPv6 = "wan_ipv6" + TaggedAddressLAN = "lan" + TaggedAddressLANIPv4 = "lan_ipv4" + TaggedAddressLANIPv6 = "lan_ipv6" +) + // metaKeyFormat checks if a metadata key string is valid var metaKeyFormat = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString @@ -612,7 +621,7 @@ type Node struct { func (n *Node) BestAddress(wan bool) string { if wan { - if addr, ok := n.TaggedAddresses["wan"]; ok { + if addr, ok := n.TaggedAddresses[TaggedAddressWAN]; ok { return addr } } @@ -919,7 +928,7 @@ func (ns *NodeService) BestAddress(wan bool) (string, int) { port := ns.Port if wan { - if wan, ok := ns.TaggedAddresses["wan"]; ok { + if wan, ok := ns.TaggedAddresses[TaggedAddressWAN]; ok { addr = wan.Address if wan.Port != 0 { port = wan.Port diff --git a/agent/structs/testing_catalog.go b/agent/structs/testing_catalog.go index f25c4ef5b..1eca4a2e2 100644 --- a/agent/structs/testing_catalog.go +++ b/agent/structs/testing_catalog.go @@ -97,8 +97,8 @@ func TestNodeServiceMeshGatewayWithAddrs(t testing.T, address string, port int, }, }, TaggedAddresses: map[string]ServiceAddress{ - "lan": lanAddr, - "wan": wanAddr, + TaggedAddressLAN: lanAddr, + TaggedAddressWAN: wanAddr, }, } } diff --git a/agent/translate_addr.go b/agent/translate_addr.go index a0ba41972..45bf8c912 100644 --- a/agent/translate_addr.go +++ b/agent/translate_addr.go @@ -2,16 +2,27 @@ package agent import ( "fmt" + "net" "github.com/hashicorp/consul/agent/structs" ) +type TranslateAddressAccept int + +const ( + TranslateAddressAcceptDomain TranslateAddressAccept = 1 << iota + TranslateAddressAcceptIPv4 + TranslateAddressAcceptIPv6 + + TranslateAddressAcceptAny TranslateAddressAccept = ^0 +) + // TranslateServicePort is used to provide the final, translated port for a service, // depending on how the agent and the other node are configured. The dc // parameter is the dc the datacenter this node is from. func (a *Agent) TranslateServicePort(dc string, port int, taggedAddresses map[string]structs.ServiceAddress) int { if a.config.TranslateWANAddrs && (a.config.Datacenter != dc) { - if wanAddr, ok := taggedAddresses["wan"]; ok && wanAddr.Port != 0 { + if wanAddr, ok := taggedAddresses[structs.TaggedAddressWAN]; ok && wanAddr.Port != 0 { return wanAddr.Port } } @@ -21,32 +32,78 @@ func (a *Agent) TranslateServicePort(dc string, port int, taggedAddresses map[st // TranslateServiceAddress is used to provide the final, translated address for a node, // depending on how the agent and the other node are configured. The dc // parameter is the dc the datacenter this node is from. -func (a *Agent) TranslateServiceAddress(dc string, addr string, taggedAddresses map[string]structs.ServiceAddress) string { - if a.config.TranslateWANAddrs && (a.config.Datacenter != dc) { - if wanAddr, ok := taggedAddresses["wan"]; ok && wanAddr.Address != "" { - return wanAddr.Address +func (a *Agent) TranslateServiceAddress(dc string, addr string, taggedAddresses map[string]structs.ServiceAddress, accept TranslateAddressAccept) string { + def := addr + v4 := taggedAddresses[structs.TaggedAddressLANIPv4].Address + v6 := taggedAddresses[structs.TaggedAddressLANIPv6].Address + + shouldUseWan := a.config.TranslateWANAddrs && (a.config.Datacenter != dc) + if shouldUseWan { + if v, ok := taggedAddresses[structs.TaggedAddressWAN]; ok { + def = v.Address + } + if v, ok := taggedAddresses[structs.TaggedAddressWANIPv4]; ok { + v4 = v.Address + } + if v, ok := taggedAddresses[structs.TaggedAddressWANIPv6]; ok { + v6 = v.Address } } - return addr + + return translateAddressAccept(accept, def, v4, v6) } // TranslateAddress is used to provide the final, translated address for a node, // depending on how the agent and the other node are configured. The dc // parameter is the dc the datacenter this node is from. -func (a *Agent) TranslateAddress(dc string, addr string, taggedAddresses map[string]string) string { - if a.config.TranslateWANAddrs && (a.config.Datacenter != dc) { - wanAddr := taggedAddresses["wan"] - if wanAddr != "" { - addr = wanAddr +func (a *Agent) TranslateAddress(dc string, addr string, taggedAddresses map[string]string, accept TranslateAddressAccept) string { + def := addr + v4 := taggedAddresses[structs.TaggedAddressLANIPv4] + v6 := taggedAddresses[structs.TaggedAddressLANIPv6] + + shouldUseWan := a.config.TranslateWANAddrs && (a.config.Datacenter != dc) + if shouldUseWan { + if v, ok := taggedAddresses[structs.TaggedAddressWAN]; ok { + def = v + } + if v, ok := taggedAddresses[structs.TaggedAddressWANIPv4]; ok { + v4 = v + } + if v, ok := taggedAddresses[structs.TaggedAddressWANIPv6]; ok { + v6 = v } } - return addr + + return translateAddressAccept(accept, def, v4, v6) +} + +func translateAddressAccept(accept TranslateAddressAccept, def, v4, v6 string) string { + switch { + case accept&TranslateAddressAcceptIPv6 > 0 && v6 != "": + return v6 + case accept&TranslateAddressAcceptIPv4 > 0 && v4 != "": + return v4 + case accept&TranslateAddressAcceptAny > 0 && def != "": + return def + default: + defIP := net.ParseIP(def) + switch { + case defIP != nil && defIP.To4() != nil && accept&TranslateAddressAcceptIPv4 > 0: + return def + case defIP != nil && defIP.To4() == nil && accept&TranslateAddressAcceptIPv6 > 0: + return def + case defIP == nil && accept&TranslateAddressAcceptDomain > 0: + return def + } + } + + return "" } // TranslateAddresses translates addresses in the given structure into the // final, translated address, depending on how the agent and the other node are // configured. The dc parameter is the datacenter this structure is from. -func (a *Agent) TranslateAddresses(dc string, subj interface{}) { +func (a *Agent) TranslateAddresses(dc string, subj interface{}, accept TranslateAddressAccept) { // CAUTION - SUBTLE! An agent running on a server can, in some cases, // return pointers directly into the immutable state store for // performance (it's via the in-memory RPC mechanism). It's never safe @@ -68,28 +125,28 @@ func (a *Agent) TranslateAddresses(dc string, subj interface{}) { switch v := subj.(type) { case structs.CheckServiceNodes: for _, entry := range v { - entry.Node.Address = a.TranslateAddress(dc, entry.Node.Address, entry.Node.TaggedAddresses) - entry.Service.Address = a.TranslateServiceAddress(dc, entry.Service.Address, entry.Service.TaggedAddresses) + entry.Node.Address = a.TranslateAddress(dc, entry.Node.Address, entry.Node.TaggedAddresses, accept) + entry.Service.Address = a.TranslateServiceAddress(dc, entry.Service.Address, entry.Service.TaggedAddresses, accept) entry.Service.Port = a.TranslateServicePort(dc, entry.Service.Port, entry.Service.TaggedAddresses) } case *structs.Node: - v.Address = a.TranslateAddress(dc, v.Address, v.TaggedAddresses) + v.Address = a.TranslateAddress(dc, v.Address, v.TaggedAddresses, accept) case structs.Nodes: for _, node := range v { - node.Address = a.TranslateAddress(dc, node.Address, node.TaggedAddresses) + node.Address = a.TranslateAddress(dc, node.Address, node.TaggedAddresses, accept) } case structs.ServiceNodes: for _, entry := range v { - entry.Address = a.TranslateAddress(dc, entry.Address, entry.TaggedAddresses) - entry.ServiceAddress = a.TranslateServiceAddress(dc, entry.ServiceAddress, entry.ServiceTaggedAddresses) + entry.Address = a.TranslateAddress(dc, entry.Address, entry.TaggedAddresses, accept) + entry.ServiceAddress = a.TranslateServiceAddress(dc, entry.ServiceAddress, entry.ServiceTaggedAddresses, accept) entry.ServicePort = a.TranslateServicePort(dc, entry.ServicePort, entry.ServiceTaggedAddresses) } case *structs.NodeServices: if v.Node != nil { - v.Node.Address = a.TranslateAddress(dc, v.Node.Address, v.Node.TaggedAddresses) + v.Node.Address = a.TranslateAddress(dc, v.Node.Address, v.Node.TaggedAddresses, accept) } for _, entry := range v.Services { - entry.Address = a.TranslateServiceAddress(dc, entry.Address, entry.TaggedAddresses) + entry.Address = a.TranslateServiceAddress(dc, entry.Address, entry.TaggedAddresses, accept) entry.Port = a.TranslateServicePort(dc, entry.Port, entry.TaggedAddresses) } default: diff --git a/api/catalog_test.go b/api/catalog_test.go index 273baff82..e0c4f9073 100644 --- a/api/catalog_test.go +++ b/api/catalog_test.go @@ -52,8 +52,10 @@ func TestAPI_CatalogNodes(t *testing.T) { Address: "127.0.0.1", Datacenter: "dc1", TaggedAddresses: map[string]string{ - "lan": "127.0.0.1", - "wan": "127.0.0.1", + "lan": "127.0.0.1", + "lan_ipv4": "127.0.0.1", + "wan": "127.0.0.1", + "wan_ipv4": "127.0.0.1", }, Meta: map[string]string{ "consul-network-segment": "", diff --git a/api/txn_test.go b/api/txn_test.go index a41c1b801..e5dac60ba 100644 --- a/api/txn_test.go +++ b/api/txn_test.go @@ -269,8 +269,10 @@ func TestAPI_ClientTxn(t *testing.T) { Address: "127.0.0.1", Datacenter: "dc1", TaggedAddresses: map[string]string{ - "lan": s.Config.Bind, - "wan": s.Config.Bind, + "lan": s.Config.Bind, + "lan_ipv4": s.Config.Bind, + "wan": s.Config.Bind, + "wan_ipv4": s.Config.Bind, }, Meta: map[string]string{"consul-network-segment": ""}, CreateIndex: ret.Results[1].Node.CreateIndex, diff --git a/command/connect/envoy/envoy.go b/command/connect/envoy/envoy.go index 2a6fee819..a7443e960 100644 --- a/command/connect/envoy/envoy.go +++ b/command/connect/envoy/envoy.go @@ -13,6 +13,7 @@ import ( "github.com/mitchellh/mapstructure" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/xds" "github.com/hashicorp/consul/api" proxyCmd "github.com/hashicorp/consul/command/connect/proxy" @@ -244,7 +245,7 @@ func (c *cmd) Run(args []string) int { taggedAddrs := make(map[string]api.ServiceAddress) if lanAddr != "" { - taggedAddrs["lan"] = api.ServiceAddress{Address: lanAddr, Port: lanPort} + taggedAddrs[structs.TaggedAddressLAN] = api.ServiceAddress{Address: lanAddr, Port: lanPort} } wanAddr := "" @@ -255,7 +256,7 @@ func (c *cmd) Run(args []string) int { c.UI.Error(fmt.Sprintf("Failed to parse the -wan-address parameter: %v", err)) return 1 } - taggedAddrs["wan"] = api.ServiceAddress{Address: wanAddr, Port: wanPort} + taggedAddrs[structs.TaggedAddressWAN] = api.ServiceAddress{Address: wanAddr, Port: wanPort} } tcpCheckAddr := lanAddr