diff --git a/api/api.go b/api/api.go
index b0fe6d83b7..dd811fde4b 100644
--- a/api/api.go
+++ b/api/api.go
@@ -80,6 +80,9 @@ type QueryMeta struct {
// How long did the request take
RequestTime time.Duration
+
+ // Is address translation enabled for HTTP responses on this agent
+ AddressTranslationEnabled bool
}
// WriteMeta is used to return meta data about a write
@@ -542,6 +545,15 @@ func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
default:
q.KnownLeader = false
}
+
+ // Parse X-Consul-Translate-Addresses
+ switch header.Get("X-Consul-Translate-Addresses") {
+ case "true":
+ q.AddressTranslationEnabled = true
+ default:
+ q.AddressTranslationEnabled = false
+ }
+
return nil
}
diff --git a/api/api_test.go b/api/api_test.go
index 71f01c483a..b00101bd31 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -306,6 +306,7 @@ func TestParseQueryMeta(t *testing.T) {
resp.Header.Set("X-Consul-Index", "12345")
resp.Header.Set("X-Consul-LastContact", "80")
resp.Header.Set("X-Consul-KnownLeader", "true")
+ resp.Header.Set("X-Consul-Translate-Addresses", "true")
qm := &QueryMeta{}
if err := parseQueryMeta(resp, qm); err != nil {
@@ -321,6 +322,9 @@ func TestParseQueryMeta(t *testing.T) {
if !qm.KnownLeader {
t.Fatalf("Bad: %v", qm)
}
+ if !qm.AddressTranslationEnabled {
+ t.Fatalf("Bad: %v", qm)
+ }
}
func TestAPI_UnixSocket(t *testing.T) {
diff --git a/api/catalog.go b/api/catalog.go
index 52a00b3043..337772ec0b 100644
--- a/api/catalog.go
+++ b/api/catalog.go
@@ -1,13 +1,15 @@
package api
type Node struct {
- Node string
- Address string
+ Node string
+ Address string
+ TaggedAddresses map[string]string
}
type CatalogService struct {
Node string
Address string
+ TaggedAddresses map[string]string
ServiceID string
ServiceName string
ServiceAddress string
@@ -22,11 +24,12 @@ type CatalogNode struct {
}
type CatalogRegistration struct {
- Node string
- Address string
- Datacenter string
- Service *AgentService
- Check *AgentCheck
+ Node string
+ Address string
+ TaggedAddresses map[string]string
+ Datacenter string
+ Service *AgentService
+ Check *AgentCheck
}
type CatalogDeregistration struct {
diff --git a/api/catalog_test.go b/api/catalog_test.go
index 3a3395ae6e..9ca8323047 100644
--- a/api/catalog_test.go
+++ b/api/catalog_test.go
@@ -51,6 +51,10 @@ func TestCatalog_Nodes(t *testing.T) {
return false, fmt.Errorf("Bad: %v", nodes)
}
+ if _, ok := nodes[0].TaggedAddresses["wan"]; !ok {
+ return false, fmt.Errorf("Bad: %v", nodes)
+ }
+
return true, nil
}, func(err error) {
t.Fatalf("err: %s", err)
@@ -128,10 +132,15 @@ func TestCatalog_Node(t *testing.T) {
if meta.LastIndex == 0 {
return false, fmt.Errorf("Bad: %v", meta)
}
+
if len(info.Services) == 0 {
return false, fmt.Errorf("Bad: %v", info)
}
+ if _, ok := info.Node.TaggedAddresses["wan"]; !ok {
+ return false, fmt.Errorf("Bad: %v", info)
+ }
+
return true, nil
}, func(err error) {
t.Fatalf("err: %s", err)
diff --git a/api/health_test.go b/api/health_test.go
index d80a4693ae..b6f463d563 100644
--- a/api/health_test.go
+++ b/api/health_test.go
@@ -94,6 +94,9 @@ func TestHealth_Service(t *testing.T) {
if len(checks) == 0 {
return false, fmt.Errorf("Bad: %v", checks)
}
+ if _, ok := checks[0].Node.TaggedAddresses["wan"]; !ok {
+ return false, fmt.Errorf("Bad: %v", checks)
+ }
return true, nil
}, func(err error) {
t.Fatalf("err: %s", err)
diff --git a/api/prepared_query_test.go b/api/prepared_query_test.go
index 011bfb195d..d16f007c9d 100644
--- a/api/prepared_query_test.go
+++ b/api/prepared_query_test.go
@@ -17,6 +17,9 @@ func TestPreparedQuery(t *testing.T) {
Datacenter: "dc1",
Node: "foobar",
Address: "192.168.10.10",
+ TaggedAddresses: map[string]string{
+ "wan": "127.0.0.1",
+ },
Service: &AgentService{
ID: "redis1",
Service: "redis",
@@ -96,6 +99,9 @@ func TestPreparedQuery(t *testing.T) {
if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" {
t.Fatalf("bad: %v", results)
}
+ if wan, ok := results.Nodes[0].Node.TaggedAddresses["wan"]; !ok || wan != "127.0.0.1" {
+ t.Fatalf("bad: %v", results)
+ }
// Execute by name.
results, _, err = query.Execute("my-query", nil)
@@ -105,6 +111,9 @@ func TestPreparedQuery(t *testing.T) {
if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" {
t.Fatalf("bad: %v", results)
}
+ if wan, ok := results.Nodes[0].Node.TaggedAddresses["wan"]; !ok || wan != "127.0.0.1" {
+ t.Fatalf("bad: %v", results)
+ }
// Delete it.
_, err = query.Delete(def.ID, nil)
diff --git a/command/agent/agent.go b/command/agent/agent.go
index 79b94d55db..d22cc92f1c 100644
--- a/command/agent/agent.go
+++ b/command/agent/agent.go
@@ -170,6 +170,7 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) {
// Create the default set of tagged addresses.
config.TaggedAddresses = map[string]string{
+ "lan": config.AdvertiseAddr,
"wan": config.AdvertiseAddrWan,
}
diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go
index bab39a7d08..6b94c119d6 100644
--- a/command/agent/agent_test.go
+++ b/command/agent/agent_test.go
@@ -183,6 +183,7 @@ func TestAgent_CheckAdvertiseAddrsSettings(t *testing.T) {
t.Fatalf("RPC is not properly set to %v: %s", c.AdvertiseAddrs.RPC, rpc)
}
expected := map[string]string{
+ "lan": agent.config.AdvertiseAddr,
"wan": agent.config.AdvertiseAddrWan,
}
if !reflect.DeepEqual(agent.config.TaggedAddresses, expected) {
diff --git a/command/agent/http.go b/command/agent/http.go
index cd36204544..5d7dcce7c6 100644
--- a/command/agent/http.go
+++ b/command/agent/http.go
@@ -329,6 +329,7 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) {
f := func(resp http.ResponseWriter, req *http.Request) {
setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders)
+ setTranslateAddr(resp, s.agent.config.TranslateWanAddrs)
// Obfuscate any tokens from appearing in the logs
formVals, err := url.ParseQuery(req.URL.RawQuery)
@@ -373,6 +374,7 @@ func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Reque
if strings.Contains(errMsg, "Permission denied") || strings.Contains(errMsg, "ACL not found") {
code = http.StatusForbidden // 403
}
+
resp.WriteHeader(code)
resp.Write([]byte(err.Error()))
return
@@ -452,6 +454,14 @@ func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error)
return mapstructure.Decode(raw, out)
}
+// setTranslateAddr is used to set the address translation header. This is only
+// present if the feature is active.
+func setTranslateAddr(resp http.ResponseWriter, active bool) {
+ if active {
+ resp.Header().Set("X-Consul-Translate-Addresses", "true")
+ }
+}
+
// setIndex is used to set the index response header
func setIndex(resp http.ResponseWriter, index uint64) {
resp.Header().Set("X-Consul-Index", strconv.FormatUint(index, 10))
diff --git a/command/agent/http_test.go b/command/agent/http_test.go
index b6618977f3..7cc80cb240 100644
--- a/command/agent/http_test.go
+++ b/command/agent/http_test.go
@@ -223,6 +223,51 @@ func TestSetMeta(t *testing.T) {
}
}
+func TestHTTPAPI_TranslateAddrHeader(t *testing.T) {
+ // Header should not be present if address translation is off.
+ {
+ dir, srv := makeHTTPServer(t)
+ defer os.RemoveAll(dir)
+ defer srv.Shutdown()
+ defer srv.agent.Shutdown()
+
+ resp := httptest.NewRecorder()
+ handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
+ return nil, nil
+ }
+
+ req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
+ srv.wrap(handler)(resp, req)
+
+ translate := resp.Header().Get("X-Consul-Translate-Addresses")
+ if translate != "" {
+ t.Fatalf("bad: expected %q, got %q", "", translate)
+ }
+ }
+
+ // Header should be set to true if it's turned on.
+ {
+ dir, srv := makeHTTPServer(t)
+ srv.agent.config.TranslateWanAddrs = true
+ defer os.RemoveAll(dir)
+ defer srv.Shutdown()
+ defer srv.agent.Shutdown()
+
+ resp := httptest.NewRecorder()
+ handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
+ return nil, nil
+ }
+
+ req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
+ srv.wrap(handler)(resp, req)
+
+ translate := resp.Header().Get("X-Consul-Translate-Addresses")
+ if translate != "true" {
+ t.Fatalf("bad: expected %q, got %q", "true", translate)
+ }
+ }
+}
+
func TestHTTPAPIResponseHeaders(t *testing.T) {
dir, srv := makeHTTPServer(t)
srv.agent.config.HTTPAPIResponseHeaders = map[string]string{
diff --git a/website/source/docs/agent/http.html.markdown b/website/source/docs/agent/http.html.markdown
index 6ea2b03f01..0f226ed846 100644
--- a/website/source/docs/agent/http.html.markdown
+++ b/website/source/docs/agent/http.html.markdown
@@ -96,3 +96,12 @@ configuration option. However, the token can also be specified per-request
by using the `X-Consul-Token` request header or the `token` querystring
parameter. The request header takes precedence over the default token, and
the querystring parameter takes precedence over everything.
+
+
+## Translated Addresses
+
+Consul 0.7 added the ability to translate addresses in HTTP response based on the configuration
+setting for [`translate_wan_addrs`](/docs/agent/options.html#translate_wan_addrs). In order to
+allow clients to know if address translation is in effect, the `X-Consul-Translate-Addresses`
+header will be added if translation is enabled, and will have a value of `true`. If translation
+is not enabled then this header will not be present.
diff --git a/website/source/docs/agent/http/catalog.html.markdown b/website/source/docs/agent/http/catalog.html.markdown
index 55879a8ba6..327daf9359 100644
--- a/website/source/docs/agent/http/catalog.html.markdown
+++ b/website/source/docs/agent/http/catalog.html.markdown
@@ -41,7 +41,8 @@ body must look something like:
"Node": "foobar",
"Address": "192.168.10.10",
"TaggedAddresses": {
- "wan": "127.0.0.1"
+ "lan": "192.168.10.10",
+ "wan": "10.0.10.10"
},
"Service": {
"ID": "redis1",
@@ -69,7 +70,8 @@ requires `Node` and `Address` to be provided while `Datacenter` will be defaulte
to match that of the agent. If only those are provided, the endpoint will register
the node with the catalog. `TaggedAddresses` can be used in conjunction with the
[`translate_wan_addrs`](/docs/agent/options.html#translate_wan_addrs) configuration
-option. Currently only the "wan" tag is supported.
+option and the "wan" address. The "lan" address was added in Consul 0.7 to help find
+the LAN address if address translation is enabled.
If the `Service` key is provided, the service will also be registered. If
`ID` is not provided, it will be defaulted to the value of the `Service.Service` property.
@@ -200,6 +202,7 @@ It returns a JSON body like this:
"Node": "baz",
"Address": "10.1.10.11",
"TaggedAddresses": {
+ "lan": "10.1.10.11",
"wan": "10.1.10.11"
}
},
@@ -207,6 +210,7 @@ It returns a JSON body like this:
"Node": "foobar",
"Address": "10.1.10.12",
"TaggedAddresses": {
+ "lan": "10.1.10.11",
"wan": "10.1.10.12"
}
}
@@ -287,6 +291,7 @@ It returns a JSON body like this:
"Node": "foobar",
"Address": "10.1.10.12",
"TaggedAddresses": {
+ "lan": "10.1.10.12",
"wan": "10.1.10.12"
}
},
diff --git a/website/source/docs/agent/http/health.html.markdown b/website/source/docs/agent/http/health.html.markdown
index 6f0c4a99ef..0825a1f47d 100644
--- a/website/source/docs/agent/http/health.html.markdown
+++ b/website/source/docs/agent/http/health.html.markdown
@@ -129,6 +129,7 @@ It returns a JSON body like this:
"Node": "foobar",
"Address": "10.1.10.12",
"TaggedAddresses": {
+ "lan": "10.1.10.12",
"wan": "10.1.10.12"
}
},
diff --git a/website/source/docs/agent/http/query.html.markdown b/website/source/docs/agent/http/query.html.markdown
index d2737384d9..5eaccdb11d 100644
--- a/website/source/docs/agent/http/query.html.markdown
+++ b/website/source/docs/agent/http/query.html.markdown
@@ -399,7 +399,11 @@ a JSON body will be returned like this:
{
"Node": {
"Node": "foobar",
- "Address": "10.1.10.12"
+ "Address": "10.1.10.12",
+ "TaggedAddresses": {
+ "lan": "10.1.10.12",
+ "wan": "10.1.10.12"
+ }
},
"Service": {
"ID": "redis",
diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown
index 506a361ff3..4409cbf711 100644
--- a/website/source/docs/agent/options.html.markdown
+++ b/website/source/docs/agent/options.html.markdown
@@ -756,9 +756,14 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
default.
- Starting in Consul 0.7 and later, node addresses in responses to the following HTTP endpoints will
- prefer a node's configured WAN address when querying for a node in a
- remote datacenter:
+ Starting in Consul 0.7 and later, node addresses in responses to HTTP requests will also prefer a
+ node's configured WAN address when querying for a node in a remote
+ datacenter. An [`X-Consul-Translate-Addresses`](/docs/agent/http.html#translate_header) header
+ will be present on all responses when translation is enabled to help clients know that the addresses
+ may be translated. The `TaggedAddresses` field in responses also have a `lan` address for clients that
+ need knowledge of that address, regardless of translation.
+
+
The following endpoints translate addresses:
* [`/v1/catalog/nodes`](/docs/agent/http/catalog.html#catalog_nodes)
* [`/v1/catalog/node/`](/docs/agent/http/catalog.html#catalog_node)