Repository: kumahq/kuma
Pull request #1485 (2021-01-27 14:27:30)

chore(*) generate both DNS compliant and non-compliant names
### Summary

Add a resolve for `.mesh` names with `.` instead of `_`. The latter is not compliant with the canonical DNS RFCs and therefore not supported by Kubernetes and other dev frameworks to be referred to.

Example: `kuma-test_namespace_svc_80.mesh` will resolve to the same VIP as `kuma-test.namespace.svc.80.mesh`

### Documentation

- [x] Link to the website [documentation PR](https://github.com/kumahq/kuma/pull/1519)
 -[x] #1519 



Patch:
diff --git a/api/go.mod b/api/go.mod
index 7a260c329..4bb000d39 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -7,8 +7,8 @@ require (
 	github.com/envoyproxy/protoc-gen-validate v0.4.1
 	github.com/ghodss/yaml v1.0.0
 	github.com/golang/protobuf v1.4.3
-	github.com/onsi/ginkgo v1.14.2
-	github.com/onsi/gomega v1.10.4
+	github.com/onsi/ginkgo v1.15.0
+	github.com/onsi/gomega v1.10.5
 	github.com/pkg/errors v0.9.1
 	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
 	google.golang.org/grpc v1.34.0
diff --git a/api/go.sum b/api/go.sum
index e083d924b..0314f935e 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -48,12 +48,12 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
-github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
-github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -67,6 +67,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -78,6 +79,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -87,6 +89,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -94,6 +97,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -102,11 +106,10 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -117,10 +120,12 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
diff --git a/app/kuma-dp/cmd/context.go b/app/kuma-dp/cmd/context.go
index 47ba7efac..dc547cecd 100644
--- a/app/kuma-dp/cmd/context.go
+++ b/app/kuma-dp/cmd/context.go
@@ -13,9 +13,10 @@ import (
 
 // RootContext contains variables, functions and components that can be overridden when extending kuma-dp or running the test.
 type RootContext struct {
-	ComponentManager   component.Manager
-	BootstrapGenerator envoy.BootstrapConfigFactoryFunc
-	Config             *kumadp.Config
+	ComponentManager         component.Manager
+	BootstrapGenerator       envoy.BootstrapConfigFactoryFunc
+	BootstrapDynamicMetadata map[string]string
+	Config                   *kumadp.Config
 }
 
 func DefaultRootContext() *RootContext {
@@ -26,6 +27,7 @@ func DefaultRootContext() *RootContext {
 			Timeout:   10 * time.Second,
 			Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
 		}),
-		Config: &config,
+		Config:                   &config,
+		BootstrapDynamicMetadata: map[string]string{},
 	}
 }
diff --git a/app/kuma-dp/cmd/run.go b/app/kuma-dp/cmd/run.go
index 30e595f60..e155ac406 100644
--- a/app/kuma-dp/cmd/run.go
+++ b/app/kuma-dp/cmd/run.go
@@ -116,12 +116,13 @@ func newRunCmd(rootCtx *RootContext) *cobra.Command {
 			shouldQuit := setupQuitChannel()
 
 			dataplane, err := envoy.New(envoy.Opts{
-				Config:    *cfg,
-				Generator: rootCtx.BootstrapGenerator,
-				Dataplane: dp,
-				Stdout:    cmd.OutOrStdout(),
-				Stderr:    cmd.OutOrStderr(),
-				Quit:      shouldQuit,
+				Config:          *cfg,
+				Generator:       rootCtx.BootstrapGenerator,
+				Dataplane:       dp,
+				DynamicMetadata: rootCtx.BootstrapDynamicMetadata,
+				Stdout:          cmd.OutOrStdout(),
+				Stderr:          cmd.OutOrStderr(),
+				Quit:            shouldQuit,
 			})
 			if err != nil {
 				return err
diff --git a/app/kuma-dp/pkg/dataplane/envoy/envoy.go b/app/kuma-dp/pkg/dataplane/envoy/envoy.go
index fe9426771..173e4dd85 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/envoy.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/envoy.go
@@ -25,15 +25,23 @@ var (
 	runLog = core.Log.WithName("kuma-dp").WithName("run").WithName("envoy")
 )
 
-type BootstrapConfigFactoryFunc func(url string, cfg kuma_dp.Config, dp *rest.Resource, bootstrapVersion types.BootstrapVersion, ev EnvoyVersion) ([]byte, types.BootstrapVersion, error)
+type BootstrapParams struct {
+	Dataplane        *rest.Resource
+	BootstrapVersion types.BootstrapVersion
+	EnvoyVersion     EnvoyVersion
+	DynamicMetadata  map[string]string
+}
+
+type BootstrapConfigFactoryFunc func(url string, cfg kuma_dp.Config, params BootstrapParams) ([]byte, types.BootstrapVersion, error)
 
 type Opts struct {
-	Config    kuma_dp.Config
-	Generator BootstrapConfigFactoryFunc
-	Dataplane *rest.Resource
-	Stdout    io.Writer
-	Stderr    io.Writer
-	Quit      chan struct{}
+	Config          kuma_dp.Config
+	Generator       BootstrapConfigFactoryFunc
+	Dataplane       *rest.Resource
+	DynamicMetadata map[string]string
+	Stdout          io.Writer
+	Stderr          io.Writer
+	Quit            chan struct{}
 }
 
 func New(opts Opts) (*Envoy, error) {
@@ -109,7 +117,12 @@ func (e *Envoy) Start(stop <-chan struct{}) error {
 	}
 	runLog.Info("fetched Envoy version", "version", envoyVersion)
 	runLog.Info("generating bootstrap configuration")
-	bootstrapConfig, version, err := e.opts.Generator(e.opts.Config.ControlPlane.URL, e.opts.Config, e.opts.Dataplane, types.BootstrapVersion(e.opts.Config.Dataplane.BootstrapVersion), *envoyVersion)
+	bootstrapConfig, version, err := e.opts.Generator(e.opts.Config.ControlPlane.URL, e.opts.Config, BootstrapParams{
+		Dataplane:        e.opts.Dataplane,
+		BootstrapVersion: types.BootstrapVersion(e.opts.Config.Dataplane.BootstrapVersion),
+		EnvoyVersion:     *envoyVersion,
+		DynamicMetadata:  e.opts.DynamicMetadata,
+	})
 	if err != nil {
 		return errors.Errorf("Failed to generate Envoy bootstrap config. %v", err)
 	}
diff --git a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
index 460ee5c30..544e02ccd 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
@@ -16,7 +16,6 @@ import (
 
 	kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
 	"github.com/kumahq/kuma/pkg/core"
-	rest_types "github.com/kumahq/kuma/pkg/core/resources/model/rest"
 	kuma_version "github.com/kumahq/kuma/pkg/version"
 	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 )
@@ -43,7 +42,7 @@ func IsInvalidRequestErr(err error) bool {
 	return strings.HasPrefix(err.Error(), "Invalid request: ")
 }
 
-func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_types.Resource, bootstrapVersion types.BootstrapVersion, ev EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, params BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 	bootstrapUrl, err := net_url.Parse(url)
 	if err != nil {
 		return nil, "", err
@@ -74,7 +73,7 @@ func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_type
 	var version types.BootstrapVersion
 	err = retry.Do(context.Background(), backoff, func(ctx context.Context) error {
 		log.Info("trying to fetch bootstrap configuration from the Control Plane")
-		respBytes, version, err = b.requestForBootstrap(bootstrapUrl, cfg, dp, bootstrapVersion, ev)
+		respBytes, version, err = b.requestForBootstrap(bootstrapUrl, cfg, params)
 		if err == nil {
 			return nil
 		}
@@ -96,11 +95,11 @@ func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_type
 	return respBytes, version, nil
 }
 
-func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Config, dp *rest_types.Resource, bootstrapVersion types.BootstrapVersion, ev EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Config, params BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 	url.Path = "/bootstrap"
 	var dataplaneResource string
-	if dp != nil {
-		dpJSON, err := json.Marshal(dp)
+	if params.Dataplane != nil {
+		dpJSON, err := json.Marshal(params.Dataplane)
 		if err != nil {
 			return nil, "", err
 		}
@@ -114,7 +113,7 @@ func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Conf
 		AdminPort:          cfg.Dataplane.AdminPort.Lowest(),
 		DataplaneTokenPath: cfg.DataplaneRuntime.TokenPath,
 		DataplaneResource:  dataplaneResource,
-		BootstrapVersion:   bootstrapVersion,
+		BootstrapVersion:   params.BootstrapVersion,
 		Version: types.Version{
 			KumaDp: types.KumaDpVersion{
 				Version:   kuma_version.Build.Version,
@@ -123,10 +122,11 @@ func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Conf
 				BuildDate: kuma_version.Build.BuildDate,
 			},
 			Envoy: types.EnvoyVersion{
-				Version: ev.Version,
-				Build:   ev.Build,
+				Version: params.EnvoyVersion.Version,
+				Build:   params.EnvoyVersion.Build,
 			},
 		},
+		DynamicMetadata: params.DynamicMetadata,
 	}
 	jsonBytes, err := json.Marshal(request)
 	if err != nil {
diff --git a/docs/proposals/dns-naming-compliance.md b/docs/proposals/dns-naming-compliance.md
new file mode 100644
index 000000000..896f44345
--- /dev/null
+++ b/docs/proposals/dns-naming-compliance.md
@@ -0,0 +1,90 @@
+# DNS compliant names in Kuma
+
+## Current state
+
+### Service Naming
+
+In a broader sense, Kuma uses the `kuma.io/service` tag to mark a certain combination of ip/address and port as a particular service, which then
+can be used in the Policies selectors.
+
+On Kubernetes, this tag is generated automatically and is an underscore delimited combination of the K8s Service Name, Namespace, and Port: `<name>_<namespace>_svc_<port>`.
+
+On Universal, one still has to generate the name in an arbitrary form.
+
+### Cross-zone communication
+
+To account for multizone and hybrid deployments, we have extended the naming scheme through a DNS resolving service by adding a `.mesh` suffix.
+This unifies the naming of services in a way that it does not matter where the service was instantiated. Of course the naming scheme in Kubernetes
+is still applied, but now we can also also call `<name>_<namespace>_svc_<port>.mesh`.
+
+By adding the transparent proxy capabilities to Universal, we can now refer to services in the `.mesh` domain too.
+
+The DNS server in the kuma-cp will generate and assign virtual IPs (VIPs) so that any service name resolves to it, and that
+the side-cars get an additional listener for the service cluster. The default VIPs CIDR is `240.0.0.1`.
+
+### Exposing services outside the mesh
+
+The typical way to expose services from within the mesh to the external consumers is through a gateway dataplane proxy (one the Kuma side) and
+a reverse proxy on the customer-facing side. In this architecture, the traffic routes on the proxy should refer to Kuma services so that the
+listeners on the side-car Envoy, are hit with the traffic and then load-balanced through the available local and remote endpoints.
+
+On Kubernetes this happens using an Ingress resource of the form:
+
+```yaml
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: marketplace
+  namespace: default
+  annotations:
+    kubernetes.io/ingress.class: kong
+    kubernetes.io/ingress.global-static-ip-name: "kong-demo-ip"
+spec:
+  rules:
+  - host: localhost
+    http:
+      paths:
+      - path: /
+        backend:
+          serviceName: frontend
+          servicePort: 80
+```
+
+Where `serviceName: frontend` is the name of a Kubernetes Service object, which will be used by the proxy to redirect the incoming requests.
+This same service in Kuma namespace will be called `frontend_default_svc_80` and can also be resolved as a DNS name `frontend_default_svc_80.mesh`.
+
+However, the problem is that the Kubernetes Ingress Resource enforces 2 rules here:
+* `serviceName` can refer only to a Kubernetes Service
+* this service can not contain `_` or `.`
+
+### Illustrating the problem
+To illustrate the problem further, let's look at the option to manually create a Service of type `ExternalName`
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: frontend
+  namespace: default
+  annotations:
+    ingress.kubernetes.io/service-upstream: "true"
+spec:
+  type: ExternalName
+  externalName: frontend_default_svc_80.mesh
+```
+
+```shell
+The Service "frontend" is invalid: spec.externalName: Invalid value: "frontend_default_svc_80.mesh": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
+```
+
+The DNS name we try to supply is invalid according to the relevant RFCs and the Kubernetes embedded checks. So we are not even able to apply that resource.
+
+## Proposal for improvements
+
+In addition to the already existing Kubernetes generated `frontend_default_svc_80.mesh`, we can add a couple of compliant aliases:
+* use dashes as separators `frontend-default-svc-80.mesh`. This can get compllicated, cosider a service name `my-complex-service`
+  which naturally translates to `my-complex-service_default_svc_80.mesh`, and then the all dashes version `my-complex-service-default-svc-80.mesh`
+* use dots as separators `frontend.default.svc.80.mesh`. The complex service version then will be slighly more readable `my-complex-service.default.svc.80.mesh`
+
+All three of these should refer to the same VIP. We can have a `kumactl` command that enumerates the services and ServiceInsights and expose
+the DNS names associated with it.
diff --git a/go.mod b/go.mod
index 49ed0caed..56fb62ff0 100644
--- a/go.mod
+++ b/go.mod
@@ -29,8 +29,8 @@ require (
 	github.com/kumahq/kuma/pkg/transparentproxy/istio v0.0.0-00010101000000-000000000000
 	github.com/lib/pq v1.9.0
 	github.com/miekg/dns v1.1.35
-	github.com/onsi/ginkgo v1.14.2
-	github.com/onsi/gomega v1.10.4
+	github.com/onsi/ginkgo v1.15.0
+	github.com/onsi/gomega v1.10.5
 	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.9.0
diff --git a/go.sum b/go.sum
index 8ccb8a504..e8d425078 100644
--- a/go.sum
+++ b/go.sum
@@ -888,8 +888,8 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
 github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
-github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
-github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@@ -899,8 +899,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
 github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
 github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
-github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/open-policy-agent/opa v0.8.2/go.mod h1:rlfeSeHuZmMEpmrcGla42AjkOUjP4rGIpS96H12un3o=
 github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@@ -1416,8 +1416,9 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
 golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1500,6 +1501,7 @@ golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210111221946-d33bae441459 h1:/pJfBBZw1zeMQA6vRpalDcuw4VJ/B6+N5obtmG0Xiv8=
 golang.org/x/tools v0.0.0-20210111221946-d33bae441459/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/pkg/core/xds/metadata.go b/pkg/core/xds/metadata.go
index 8cd2d3941..79a976315 100644
--- a/pkg/core/xds/metadata.go
+++ b/pkg/core/xds/metadata.go
@@ -19,6 +19,7 @@ const (
 	fieldDataplaneTokenPath         = "dataplaneTokenPath"
 	fieldDataplaneAdminPort         = "dataplane.admin.port"
 	fieldDataplaneDataplaneResource = "dataplane.resource"
+	fieldDynamicMetadata            = "dynamicMetadata"
 )
 
 // DataplaneMetadata represents environment-specific part of a dataplane configuration.
@@ -39,6 +40,7 @@ type DataplaneMetadata struct {
 	DataplaneTokenPath string
 	DataplaneResource  *core_mesh.DataplaneResource
 	AdminPort          uint32
+	DynamicMetadata    map[string]string
 }
 
 func (m *DataplaneMetadata) GetDataplaneTokenPath() string {
@@ -62,6 +64,13 @@ func (m *DataplaneMetadata) GetAdminPort() uint32 {
 	return m.AdminPort
 }
 
+func (m *DataplaneMetadata) GetDynamicMetadata(key string) string {
+	if m == nil || m.DynamicMetadata == nil {
+		return ""
+	}
+	return m.DynamicMetadata[key]
+}
+
 func DataplaneMetadataFromXdsMetadata(xdsMetadata *_struct.Struct) *DataplaneMetadata {
 	metadata := DataplaneMetadata{}
 	if xdsMetadata == nil {
@@ -88,5 +97,12 @@ func DataplaneMetadataFromXdsMetadata(xdsMetadata *_struct.Struct) *DataplaneMet
 		}
 		metadata.DataplaneResource = dp
 	}
+	if value := xdsMetadata.Fields[fieldDynamicMetadata]; value != nil {
+		dynamicMetadata := map[string]string{}
+		for field, val := range value.GetStructValue().GetFields() {
+			dynamicMetadata[field] = val.GetStringValue()
+		}
+		metadata.DynamicMetadata = dynamicMetadata
+	}
 	return &metadata
 }
diff --git a/pkg/dns/components.go b/pkg/dns/components.go
index e489f0738..6c679a88e 100644
--- a/pkg/dns/components.go
+++ b/pkg/dns/components.go
@@ -19,6 +19,7 @@ func Setup(rt runtime.Runtime) error {
 		rt.Config().DNSServer.Port,
 		rt.DNSResolver(),
 		rt.Metrics(),
+		DnsNameToKumaCompliant,
 	)
 	if err != nil {
 		return err
diff --git a/pkg/dns/name_modifier.go b/pkg/dns/name_modifier.go
new file mode 100644
index 000000000..67ae4b7a7
--- /dev/null
+++ b/pkg/dns/name_modifier.go
@@ -0,0 +1,26 @@
+package dns
+
+import "strings"
+
+func DnsNameToKumaCompliant(name string) (string, error) {
+	// the request might be of the form:
+	//  service-name.namespace.something-else.mesh.
+	// it will always end with a dot, as specified by https://tools.ietf.org/html/rfc1034#section-3.1
+	//  `Since a complete domain name ends with the root label, this leads to a printed form which ends in a dot.`
+	countDots := strings.Count(name, ".")
+	toReplace := countDots
+	switch countDots {
+	case 0:
+		return name, nil
+	case 1:
+		if name[len(name)-1] == '.' {
+			return name, nil
+		}
+	default:
+		if name[len(name)-1] == '.' {
+			toReplace--
+		}
+	}
+
+	return strings.Replace(name, ".", "_", toReplace-1), nil
+}
diff --git a/pkg/dns/server.go b/pkg/dns/server.go
index 26c9b76a9..186e9b9f9 100644
--- a/pkg/dns/server.go
+++ b/pkg/dns/server.go
@@ -21,15 +21,18 @@ type DNSServer interface {
 	NeedLeaderElection() bool
 }
 
+type NameModifier = func(qName string) (string, error)
+
 type SimpleDNSServer struct {
 	address  string
 	resolver resolver.DNSResolver
 
 	latencyMetric    prometheus.Summary
 	resolutionMetric *prometheus.CounterVec
+	nameModifier     NameModifier
 }
 
-func NewDNSServer(port uint32, resolver resolver.DNSResolver, metrics core_metrics.Metrics) (DNSServer, error) {
+func NewDNSServer(port uint32, resolver resolver.DNSResolver, metrics core_metrics.Metrics, modifier NameModifier) (DNSServer, error) {
 	handler := &SimpleDNSServer{
 		address:  fmt.Sprintf("0.0.0.0:%d", port),
 		resolver: resolver,
@@ -42,6 +45,7 @@ func NewDNSServer(port uint32, resolver resolver.DNSResolver, metrics core_metri
 			Name: "dns_server_resolution",
 			Help: "Counter for DNS Server resolutions",
 		}, []string{"result"}),
+		nameModifier: modifier,
 	}
 	if err := metrics.Register(handler.latencyMetric); err != nil {
 		return nil, err
@@ -57,8 +61,8 @@ func (h *SimpleDNSServer) parseQuery(m *dns.Msg) {
 	for _, q := range m.Question {
 		switch q.Qtype {
 		case dns.TypeA:
-			serverLog.V(1).Info("query for " + q.Name)
-			ip, err := h.resolver.ForwardLookupFQDN(q.Name)
+			serverLog.V(1).Info("received a query for " + q.Name)
+			ip, err := h.lookup(q.Name)
 			if err != nil {
 				serverLog.V(1).Info("unable to resolve", "Name", q.Name, "error", err.Error())
 				h.resolutionMetric.WithLabelValues("unresolved").Inc()
@@ -132,3 +136,24 @@ func (h *SimpleDNSServer) registerDNSHandler() {
 		h.handleDNSRequest(writer, msg)
 	})
 }
+
+func (h *SimpleDNSServer) lookup(qName string) (string, error) {
+	ip, err := h.resolver.ForwardLookupFQDN(qName)
+	if err != nil {
+		if h.nameModifier == nil {
+			return "", err
+		}
+
+		modifiedName, err := h.nameModifier(qName)
+		if err != nil {
+			return "", err
+		}
+
+		ip, err = h.resolver.ForwardLookupFQDN(modifiedName)
+		if err != nil {
+			return "", err
+		}
+	}
+
+	return ip, nil
+}
diff --git a/pkg/plugins/resources/k8s/native/go.mod b/pkg/plugins/resources/k8s/native/go.mod
index 357af2197..6f3227327 100644
--- a/pkg/plugins/resources/k8s/native/go.mod
+++ b/pkg/plugins/resources/k8s/native/go.mod
@@ -6,8 +6,8 @@ require (
 	github.com/go-logr/logr v0.1.0
 	github.com/golang/protobuf v1.4.3
 	github.com/kumahq/kuma/api v0.0.0-00010101000000-000000000000
-	github.com/onsi/ginkgo v1.14.2
-	github.com/onsi/gomega v1.10.4
+	github.com/onsi/ginkgo v1.15.0
+	github.com/onsi/gomega v1.10.5
 	github.com/pkg/errors v0.9.1
 	golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb
 	k8s.io/apimachinery v0.18.14
diff --git a/pkg/plugins/resources/k8s/native/go.sum b/pkg/plugins/resources/k8s/native/go.sum
index c62fd831e..7c2ce3762 100644
--- a/pkg/plugins/resources/k8s/native/go.sum
+++ b/pkg/plugins/resources/k8s/native/go.sum
@@ -247,14 +247,14 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
-github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
-github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -317,6 +317,7 @@ github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
@@ -350,6 +351,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -369,6 +371,7 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -381,6 +384,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -401,9 +405,9 @@ golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -430,10 +434,12 @@ golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
 gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
diff --git a/pkg/xds/bootstrap/generator.go b/pkg/xds/bootstrap/generator.go
index 1819641ca..8b0410e62 100644
--- a/pkg/xds/bootstrap/generator.go
+++ b/pkg/xds/bootstrap/generator.go
@@ -207,6 +207,7 @@ func (b *bootstrapGenerator) generateFor(proxyId core_xds.ProxyId, dataplane *co
 		EnvoyVersion:       request.Version.Envoy.Version,
 		EnvoyBuild:         request.Version.Envoy.Build,
 		HdsEnabled:         b.hdsEnabled,
+		DynamicMetadata:    request.DynamicMetadata,
 	}
 	log.WithValues("params", params).Info("Generating bootstrap config")
 	return b.configForParameters(params, request.BootstrapVersion)
diff --git a/pkg/xds/bootstrap/parameters.go b/pkg/xds/bootstrap/parameters.go
index bdc5adc9e..3a7b4ad3c 100644
--- a/pkg/xds/bootstrap/parameters.go
+++ b/pkg/xds/bootstrap/parameters.go
@@ -22,4 +22,5 @@ type configParameters struct {
 	EnvoyVersion       string
 	EnvoyBuild         string
 	HdsEnabled         bool
+	DynamicMetadata    map[string]string
 }
diff --git a/pkg/xds/bootstrap/template_v2.go b/pkg/xds/bootstrap/template_v2.go
index fa42790d0..2e2407199 100644
--- a/pkg/xds/bootstrap/template_v2.go
+++ b/pkg/xds/bootstrap/template_v2.go
@@ -23,6 +23,12 @@ node:
       envoy:
         version: "{{ .EnvoyVersion }}"
         build: "{{ .EnvoyBuild }}"
+{{if .DynamicMetadata }}
+    dynamicMetadata:
+{{ range $key, $value := .DynamicMetadata }}
+      {{ $key }}: "{{ $value }}"
+{{ end }}
+{{ end }}
 
 {{if .AdminPort }}
 admin:
diff --git a/pkg/xds/bootstrap/template_v3.go b/pkg/xds/bootstrap/template_v3.go
index adcd4fcf1..ff648aeea 100644
--- a/pkg/xds/bootstrap/template_v3.go
+++ b/pkg/xds/bootstrap/template_v3.go
@@ -23,6 +23,12 @@ node:
       envoy:
         version: "{{ .EnvoyVersion }}"
         build: "{{ .EnvoyBuild }}"
+{{if .DynamicMetadata }}
+    dynamicMetadata:
+{{ range $key, $value := .DynamicMetadata }}
+      {{ $key }}: "{{ $value }}"
+{{ end }}
+{{ end }}
 
 {{if .AdminPort }}
 admin:
diff --git a/pkg/xds/bootstrap/types/bootstrap_request.go b/pkg/xds/bootstrap/types/bootstrap_request.go
index 66fa2239d..4d4a6d31f 100644
--- a/pkg/xds/bootstrap/types/bootstrap_request.go
+++ b/pkg/xds/bootstrap/types/bootstrap_request.go
@@ -13,13 +13,14 @@ const (
 const BootstrapVersionHeader = "kuma-bootstrap-version"
 
 type BootstrapRequest struct {
-	Mesh               string  `json:"mesh"`
-	Name               string  `json:"name"`
-	AdminPort          uint32  `json:"adminPort,omitempty"`
-	DataplaneTokenPath string  `json:"dataplaneTokenPath,omitempty"`
-	DataplaneResource  string  `json:"dataplaneResource,omitempty"`
-	Host               string  `json:"-"`
-	Version            Version `json:"version"`
+	Mesh               string            `json:"mesh"`
+	Name               string            `json:"name"`
+	AdminPort          uint32            `json:"adminPort,omitempty"`
+	DataplaneTokenPath string            `json:"dataplaneTokenPath,omitempty"`
+	DataplaneResource  string            `json:"dataplaneResource,omitempty"`
+	Host               string            `json:"-"`
+	Version            Version           `json:"version"`
+	DynamicMetadata    map[string]string `json:"dynamicMetadata"`
 	// BootstrapVersion is an optional version to override the control plane's default setting
 	BootstrapVersion BootstrapVersion `json:"bootstrapVersion"`
 }


Test patch:
diff --git a/app/kuma-dp/cmd/run_test.go b/app/kuma-dp/cmd/run_test.go
index 90afe22f2..140dcdcb1 100644
--- a/app/kuma-dp/cmd/run_test.go
+++ b/app/kuma-dp/cmd/run_test.go
@@ -13,7 +13,6 @@ import (
 	"strings"
 	"syscall"
 
-	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
 	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 
 	. "github.com/onsi/ginkgo"
@@ -106,7 +105,7 @@ var _ = Describe("run", func() {
 
 			// given
 			rootCtx := DefaultRootContext()
-			rootCtx.BootstrapGenerator = func(_ string, cfg kumadp.Config, _ *rest.Resource, _ types.BootstrapVersion, version envoy.EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+			rootCtx.BootstrapGenerator = func(_ string, cfg kumadp.Config, _ envoy.BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 				respBytes, err := ioutil.ReadFile(filepath.Join("testdata", "bootstrap-config.golden.yaml"))
 				Expect(err).ToNot(HaveOccurred())
 				return respBytes, "", nil
diff --git a/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go b/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
index 93d792e57..502aab88c 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
@@ -16,7 +16,6 @@ import (
 	. "github.com/onsi/gomega"
 
 	kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
-	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
 	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 )
 
@@ -69,7 +68,7 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, types.BootstrapVersion, EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+			sampleConfig := func(string, kuma_dp.Config, BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 				return []byte(`node:
   id: example`), types.BootstrapV2, nil
 			}
@@ -137,7 +136,7 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, types.BootstrapVersion, EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+			sampleConfig := func(string, kuma_dp.Config, BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 				return nil, "", nil
 			}
 
@@ -179,7 +178,7 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, types.BootstrapVersion, EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+			sampleConfig := func(string, kuma_dp.Config, BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 				return nil, "", nil
 			}
 
diff --git a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go
index cf8f3cd35..d9bcb2107 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go
@@ -29,6 +29,7 @@ var _ = Describe("Remote Bootstrap", func() {
 		config                   kuma_dp.Config
 		bootstrapVersion         types.BootstrapVersion
 		dataplane                *rest.Resource
+		dynamicMetadata          map[string]string
 		expectedBootstrapRequest string
 	}
 
@@ -65,10 +66,16 @@ var _ = Describe("Remote Bootstrap", func() {
 		generator := NewRemoteBootstrapGenerator(http.DefaultClient)
 
 		// when
-		config, version, err := generator(fmt.Sprintf("http://localhost:%d", port), given.config, given.dataplane, given.bootstrapVersion, EnvoyVersion{
-			Build:   "hash/1.15.0/RELEASE",
-			Version: "1.15.0",
-		})
+		params := BootstrapParams{
+			Dataplane:        given.dataplane,
+			BootstrapVersion: given.bootstrapVersion,
+			EnvoyVersion: EnvoyVersion{
+				Build:   "hash/1.15.0/RELEASE",
+				Version: "1.15.0",
+			},
+			DynamicMetadata: given.dynamicMetadata,
+		}
+		config, version, err := generator(fmt.Sprintf("http://localhost:%d", port), given.config, params)
 
 		// then
 		Expect(version).To(Equal(given.bootstrapVersion))
@@ -93,6 +100,9 @@ var _ = Describe("Remote Bootstrap", func() {
 							Name: "sample",
 						},
 					},
+					dynamicMetadata: map[string]string{
+						"test": "value",
+					},
 					expectedBootstrapRequest: `
 					{
 					  "mesh": "demo",
@@ -112,7 +122,10 @@ var _ = Describe("Remote Bootstrap", func() {
 						  "build": "hash/1.15.0/RELEASE"
 						}
 					  },
-					  "bootstrapVersion": "2"
+					  "bootstrapVersion": "2",
+					  "dynamicMetadata": {
+					    "test": "value"
+					  }
 					}`,
 				}
 			}()),
@@ -154,7 +167,8 @@ var _ = Describe("Remote Bootstrap", func() {
                           "build": "hash/1.15.0/RELEASE"
                         }
                       },
-                      "bootstrapVersion": "3"
+                      "bootstrapVersion": "3",
+					  "dynamicMetadata": null
                     }`,
 				}
 			}()),
@@ -193,7 +207,8 @@ var _ = Describe("Remote Bootstrap", func() {
                           "build": "hash/1.15.0/RELEASE"
                         }
                       },
-                      "bootstrapVersion": ""
+                      "bootstrapVersion": "",
+					  "dynamicMetadata": null
                     }`,
 				}
 			}()),
@@ -226,13 +241,16 @@ var _ = Describe("Remote Bootstrap", func() {
 		// when
 		cfg := kuma_dp.DefaultConfig()
 		cfg.ControlPlane.Retry.Backoff = 10 * time.Millisecond
-		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), cfg, &rest.Resource{
-			Meta: rest.ResourceMeta{
-				Type: "Dataplane",
-				Mesh: "default",
-				Name: "dp-1",
+		params := BootstrapParams{
+			Dataplane: &rest.Resource{
+				Meta: rest.ResourceMeta{
+					Type: "Dataplane",
+					Mesh: "default",
+					Name: "dp-1",
+				},
 			},
-		}, "", EnvoyVersion{})
+		}
+		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), cfg, params)
 
 		// then
 		Expect(err).ToNot(HaveOccurred())
@@ -258,9 +276,16 @@ var _ = Describe("Remote Bootstrap", func() {
 		config := kuma_dp.DefaultConfig()
 		config.ControlPlane.Retry.Backoff = 10 * time.Millisecond
 		config.ControlPlane.Retry.MaxDuration = 100 * time.Millisecond
-		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), config, &rest.Resource{
-			Meta: rest.ResourceMeta{Mesh: "default", Name: "dp-1"},
-		}, "", EnvoyVersion{})
+		params := BootstrapParams{
+			Dataplane: &rest.Resource{
+				Meta: rest.ResourceMeta{
+					Type: "Dataplane",
+					Mesh: "default",
+					Name: "dp-1",
+				},
+			},
+		}
+		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), config, params)
 
 		// then
 		Expect(err).To(MatchError("retryable: Dataplane entity not found. If you are running on Universal please create a Dataplane entity on kuma-cp before starting kuma-dp. If you are running on Kubernetes, please check the kuma-cp logs to determine why the Dataplane entity could not be created by the automatic sidecar injection."))
diff --git a/pkg/dns/name_modifier_test.go b/pkg/dns/name_modifier_test.go
new file mode 100644
index 000000000..b0cc65989
--- /dev/null
+++ b/pkg/dns/name_modifier_test.go
@@ -0,0 +1,57 @@
+package dns_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/ginkgo/extensions/table"
+	. "github.com/onsi/gomega"
+
+	"github.com/kumahq/kuma/pkg/dns"
+)
+
+var _ = Describe("DNS name modifier", func() {
+	type testCase struct {
+		input    string
+		expected string
+	}
+
+	DescribeTable("should modify names",
+		func(given testCase) {
+			// when
+			actual, err := dns.DnsNameToKumaCompliant(given.input)
+
+			// then
+			Expect(err).ToNot(HaveOccurred())
+			// and
+			Expect(actual).To(MatchYAML(given.expected))
+		},
+		Entry("empty", testCase{
+			input:    "",
+			expected: "",
+		}),
+		Entry("one last dot", testCase{
+			input:    "mesh.",
+			expected: "mesh.",
+		}),
+		Entry("one dot", testCase{
+			input:    ".mesh",
+			expected: ".mesh",
+		}),
+		Entry("two dots with last", testCase{
+			input:    "a.mesh.",
+			expected: "a.mesh.",
+		}),
+		Entry("two dots", testCase{
+			input:    "a.b.mesh",
+			expected: "a_b.mesh",
+		}),
+		Entry("three dots with last", testCase{
+			input:    "a.b.mesh.",
+			expected: "a_b.mesh.",
+		}),
+		Entry("three dots", testCase{
+			input:    "a.b.c.mesh",
+			expected: "a_b_c.mesh",
+		}),
+	)
+
+})
diff --git a/pkg/dns/server_test.go b/pkg/dns/server_test.go
index b3ecc9c4c..f9e3e3a2c 100644
--- a/pkg/dns/server_test.go
+++ b/pkg/dns/server_test.go
@@ -37,7 +37,7 @@ var _ = Describe("DNS server", func() {
 			m, err := core_metrics.NewMetrics("Standalone")
 			metrics = m
 			Expect(err).ToNot(HaveOccurred())
-			server, err := NewDNSServer(port, dnsResolver, metrics)
+			server, err := NewDNSServer(port, dnsResolver, metrics, DnsNameToKumaCompliant)
 			Expect(err).ToNot(HaveOccurred())
 
 			// given
@@ -190,5 +190,32 @@ var _ = Describe("DNS server", func() {
 			Expect(test_metrics.FindMetric(metrics, "dns_server")).ToNot(BeNil())
 			Expect(test_metrics.FindMetric(metrics, "dns_server_resolution", "result", "resolved").Counter.GetValue()).To(Equal(1.0))
 		})
+
+		It("should resolve converted services with '.'", func() {
+			// given
+			var err error
+			dnsResolver.SetVIPs(vips.List{
+				"my-service_test-namespace_svc_80": "240.0.0.1",
+			})
+			ip, err = dnsResolver.ForwardLookupFQDN("my-service_test-namespace_svc_80.mesh")
+			Expect(err).ToNot(HaveOccurred())
+
+			// when
+			client := new(dns.Client)
+			message := new(dns.Msg)
+			_ = message.SetQuestion("my-service.test-namespace.svc.80.mesh.", dns.TypeA)
+			var response *dns.Msg
+			Eventually(func() error {
+				response, _, err = client.Exchange(message, fmt.Sprintf("127.0.0.1:%d", port))
+				return err
+			}).ShouldNot(HaveOccurred())
+
+			// then
+			Expect(response.Answer[0].String()).To(Equal(fmt.Sprintf("my-service.test-namespace.svc.80.mesh.\t60\tIN\tA\t%s", ip)))
+
+			// and metrics are published
+			Expect(test_metrics.FindMetric(metrics, "dns_server")).ToNot(BeNil())
+			Expect(test_metrics.FindMetric(metrics, "dns_server_resolution", "result", "resolved").Counter.GetValue()).To(Equal(1.0))
+		})
 	})
 })
diff --git a/pkg/xds/bootstrap/generator_test.go b/pkg/xds/bootstrap/generator_test.go
index 1e8d80a28..b92506958 100644
--- a/pkg/xds/bootstrap/generator_test.go
+++ b/pkg/xds/bootstrap/generator_test.go
@@ -192,6 +192,9 @@ var _ = Describe("bootstrapGenerator", func() {
 				Name:               "name.namespace",
 				AdminPort:          1234,
 				DataplaneTokenPath: "/tmp/token",
+				DynamicMetadata: map[string]string{
+					"test": "value",
+				},
 				DataplaneResource: `
 {
   "type": "Dataplane",
diff --git a/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml b/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml
index 29bfe62e7..a00bf6e79 100644
--- a/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml
@@ -59,6 +59,8 @@ node:
       8443, "tags": { "kuma.io/protocol": "http2", "kuma.io/service": "backend" }
       }, ] } }'
     dataplaneTokenPath: /tmp/token
+    dynamicMetadata:
+      test: value
     version:
       envoy:
         build: hash/1.15.0/RELEASE
diff --git a/test/e2e/kuma_deploy_universal_transparent_proxy_test.go b/test/e2e/kuma_deploy_universal_transparent_proxy_test.go
index c7e8c13f6..4c072444c 100644
--- a/test/e2e/kuma_deploy_universal_transparent_proxy_test.go
+++ b/test/e2e/kuma_deploy_universal_transparent_proxy_test.go
@@ -14,7 +14,7 @@ import (
 
 var _ = Describe("Test Universal Transparent Proxy deployment", func() {
 
-	const iterations = 100
+	const iterations = 10
 
 	var cluster Cluster
 	var deployOptsFuncs []DeployOptionsFunc
@@ -65,6 +65,19 @@ var _ = Describe("Test Universal Transparent Proxy deployment", func() {
 					}
 					return "should retry", errors.Errorf("should retry")
 				})
+			retry.DoWithRetry(cluster.GetTesting(), "curl remote service with dots",
+				DefaultRetries, DefaultTimeout,
+				func() (string, error) {
+					stdout, _, err := cluster.ExecWithRetries("", "", "demo-client",
+						"curl", "-v", "-m", "3", "echo-server.kuma-test.svc.8080.mesh")
+					if err != nil {
+						return "should retry", err
+					}
+					if strings.Contains(stdout, "HTTP/1.1 200 OK") {
+						return "Accessing service successful", nil
+					}
+					return "should retry", errors.Errorf("should retry")
+				})
 		}
 	})
 })