diff --git a/go.mod b/go.mod index ae74aa21c..3247ac633 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.6 github.com/google/uuid v1.1.2 + github.com/miekg/dns v1.1.49 github.com/nats-io/nats-streaming-server v0.24.3 github.com/nats-io/stan.go v0.10.2 github.com/networkservicemesh/api v1.3.2-0.20220516230921-edaa6f46d6ab @@ -83,10 +84,13 @@ require ( go.opentelemetry.io/otel/sdk/export/metric v0.26.0 // indirect go.opentelemetry.io/proto/otlp v0.11.0 // indirect golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect + golang.org/x/mod v0.4.2 // indirect golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect + golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 // indirect gopkg.in/square/go-jose.v2 v2.4.1 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/go.sum b/go.sum index 8db8810a6..12b2f6b1c 100644 --- a/go.sum +++ b/go.sum @@ -171,6 +171,8 @@ github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW1 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8= +github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mna/pigeon v0.0.0-20180808201053-bb0192cfc2ae/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04= @@ -328,6 +330,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/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= @@ -346,6 +349,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -359,6 +363,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -383,6 +388,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE= golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -410,8 +416,9 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/networkservice/chains/nsmgr/utils_test.go b/pkg/networkservice/chains/nsmgr/utils_test.go index b267dd516..a71ee07b6 100644 --- a/pkg/networkservice/chains/nsmgr/utils_test.go +++ b/pkg/networkservice/chains/nsmgr/utils_test.go @@ -52,6 +52,7 @@ func defaultRequest(nsName string) *networkservice.NetworkServiceRequest { Id: "1", NetworkService: nsName, Context: &networkservice.ConnectionContext{}, + Labels: make(map[string]string), }, } } diff --git a/pkg/networkservice/chains/nsmgr/vl3_test.go b/pkg/networkservice/chains/nsmgr/vl3_test.go new file mode 100644 index 000000000..0a48591aa --- /dev/null +++ b/pkg/networkservice/chains/nsmgr/vl3_test.go @@ -0,0 +1,212 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !windows +// +build !windows + +package nsmgr_test + +import ( + "context" + "fmt" + "net" + "testing" + "time" + + "github.com/google/uuid" + "github.com/networkservicemesh/api/pkg/api/ipam" + "github.com/stretchr/testify/require" + "go.uber.org/goleak" + + "github.com/networkservicemesh/sdk/pkg/networkservice/chains/client" + "github.com/networkservicemesh/sdk/pkg/networkservice/connectioncontext/dnscontext/vl3dns" + "github.com/networkservicemesh/sdk/pkg/networkservice/connectioncontext/ipcontext/vl3" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/memory" + "github.com/networkservicemesh/sdk/pkg/tools/sandbox" +) + +func Test_NSC_ConnectsTo_vl3NSE(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + domain := sandbox.NewBuilder(ctx, t). + SetNodesCount(1). + SetNSMgrProxySupplier(nil). + SetRegistryProxySupplier(nil). + Build() + + nsRegistryClient := domain.NewNSRegistryClient(ctx, sandbox.GenerateTestToken) + + nsReg, err := nsRegistryClient.Register(ctx, defaultRegistryService("vl3")) + require.NoError(t, err) + + nseReg := defaultRegistryEndpoint(nsReg.Name) + + var serverPrefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(serverPrefixCh) + + serverPrefixCh <- &ipam.PrefixResponse{Prefix: "10.0.0.1/24"} + + _ = domain.Nodes[0].NewEndpoint( + ctx, + nseReg, + sandbox.GenerateTestToken, + vl3.NewServer(ctx, serverPrefixCh), + vl3dns.NewServer(ctx, vl3dns.WithDomainSchemes("{{ index .Labels \"podName\" }}.{{ .NetworkService }}."), vl3dns.WithDNSPort(40053)), + ) + + resolver := net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + var dialer net.Dialer + return dialer.DialContext(ctx, network, "127.0.0.1:40053") + }, + } + + for i := 0; i < 10; i++ { + nsc := domain.Nodes[0].NewClient(ctx, sandbox.GenerateTestToken) + + reqCtx, reqClose := context.WithTimeout(ctx, time.Second) + defer reqClose() + + req := defaultRequest(nsReg.Name) + req.Connection.Id = uuid.New().String() + + req.Connection.Labels["podName"] = "nsc" + fmt.Sprint(i) + + resp, err := nsc.Request(reqCtx, req) + + require.NoError(t, err) + require.Len(t, resp.GetContext().GetDnsContext().GetConfigs(), 1) + require.Len(t, resp.GetContext().GetDnsContext().GetConfigs()[0].DnsServerIps, 1) + require.Equal(t, "10.0.0.0", resp.GetContext().GetDnsContext().GetConfigs()[0].DnsServerIps[0]) + + req.Connection = resp.Clone() + + requireIPv4Lookup(ctx, t, &resolver, "nsc"+fmt.Sprint(i)+".vl3", "10.0.0.1") + + resp, err = nsc.Request(reqCtx, req) + require.NoError(t, err) + + requireIPv4Lookup(ctx, t, &resolver, "nsc"+fmt.Sprint(i)+".vl3", "10.0.0.1") + + _, err = nsc.Close(reqCtx, resp) + require.NoError(t, err) + + _, err = resolver.LookupIP(reqCtx, "ip4", "nsc"+fmt.Sprint(i)+".vl3") + require.Error(t, err) + } +} + +func requireIPv4Lookup(ctx context.Context, t *testing.T, r *net.Resolver, host, expected string) { + addrs, err := r.LookupIP(ctx, "ip4", host) + require.NoError(t, err) + require.Len(t, addrs, 1) + require.Equal(t, expected, addrs[0].String()) +} + +func Test_vl3NSE_ConnectsTo_vl3NSE(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + domain := sandbox.NewBuilder(ctx, t). + SetNodesCount(1). + SetNSMgrProxySupplier(nil). + SetRegistryProxySupplier(nil). + Build() + + var records memory.Map + var dnsServer = memory.NewDNSHandler(&records) + + records.Store("nsc1.vl3.", []net.IP{net.ParseIP("1.1.1.1")}) + + dnsutils.ListenAndServe(ctx, dnsServer, ":40053") + + nsRegistryClient := domain.NewNSRegistryClient(ctx, sandbox.GenerateTestToken) + + nsReg, err := nsRegistryClient.Register(ctx, defaultRegistryService("vl3")) + require.NoError(t, err) + + nseReg := defaultRegistryEndpoint(nsReg.Name) + + var serverPrefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(serverPrefixCh) + + serverPrefixCh <- &ipam.PrefixResponse{Prefix: "10.0.0.1/24"} + + _ = domain.Nodes[0].NewEndpoint( + ctx, + nseReg, + sandbox.GenerateTestToken, + vl3.NewServer(ctx, serverPrefixCh), + vl3dns.NewServer(ctx, vl3dns.WithDomainSchemes("{{ index .Labels \"podName\" }}.{{ .NetworkService }}."), vl3dns.WithDNSListenAndServeFunc(func(ctx context.Context, handler dnsutils.Handler, listenOn string) { + dnsutils.ListenAndServe(ctx, handler, ":50053") + }), vl3dns.WithDNSPort(40053)), + ) + + resolver := net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + var dialer net.Dialer + return dialer.DialContext(ctx, network, "127.0.0.1:50053") + }, + } + + var clientPrefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(clientPrefixCh) + + clientPrefixCh <- &ipam.PrefixResponse{Prefix: "127.0.0.1/32"} + nsc := domain.Nodes[0].NewClient(ctx, sandbox.GenerateTestToken, client.WithAdditionalFunctionality(vl3.NewClient(ctx, clientPrefixCh))) + + req := defaultRequest(nsReg.Name) + req.Connection.Id = uuid.New().String() + + req.Connection.Labels["podName"] = "nsc" + + resp, err := nsc.Request(ctx, req) + require.NoError(t, err) + require.Len(t, resp.GetContext().GetDnsContext().GetConfigs(), 1) + require.Len(t, resp.GetContext().GetDnsContext().GetConfigs()[0].DnsServerIps, 1) + require.Equal(t, "10.0.0.0", resp.GetContext().GetDnsContext().GetConfigs()[0].DnsServerIps[0]) + + require.Equal(t, "127.0.0.1/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + req.Connection = resp.Clone() + + requireIPv4Lookup(ctx, t, &resolver, "nsc.vl3", "127.0.0.1") + + requireIPv4Lookup(ctx, t, &resolver, "nsc1.vl3", "1.1.1.1") // we can lookup this ip address only and only if fanout is working + + resp, err = nsc.Request(ctx, req) + require.NoError(t, err) + + requireIPv4Lookup(ctx, t, &resolver, "nsc.vl3", "127.0.0.1") + + requireIPv4Lookup(ctx, t, &resolver, "nsc1.vl3", "1.1.1.1") // we can lookup this ip address only and only if fanout is working + + _, err = nsc.Close(ctx, resp) + require.NoError(t, err) + + _, err = resolver.LookupIP(ctx, "ip4", "nsc.vl3") + require.Error(t, err) + + _, err = resolver.LookupIP(ctx, "ip4", "nsc1.vl3") + require.Error(t, err) +} diff --git a/pkg/networkservice/connectioncontext/dnscontext/vl3dns/options.go b/pkg/networkservice/connectioncontext/dnscontext/vl3dns/options.go new file mode 100644 index 000000000..c58c72d93 --- /dev/null +++ b/pkg/networkservice/connectioncontext/dnscontext/vl3dns/options.go @@ -0,0 +1,70 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vl3dns + +import ( + "context" + "fmt" + "net/url" + "text/template" + + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" +) + +// Option configures vl3DNSServer +type Option func(*vl3DNSServer) + +// WithInitialFanoutList sets initial list to fanout queries +func WithInitialFanoutList(initialFanoutList []url.URL) Option { + return func(vd *vl3DNSServer) { + vd.initialFanoutList = initialFanoutList + } +} + +// WithDNSServerHandler replaces default dns handler to specific one +func WithDNSServerHandler(handler dnsutils.Handler) Option { + return func(vd *vl3DNSServer) { + vd.dnsServer = handler + } +} + +// WithDomainSchemes sets domain schemes for vl3 dns server. Schemes are using to get dns names for clients +func WithDomainSchemes(domainSchemes ...string) Option { + return func(vd *vl3DNSServer) { + vd.domainSchemeTemplates = nil + for i, domainScheme := range domainSchemes { + vd.domainSchemeTemplates = append(vd.domainSchemeTemplates, template.Must(template.New(fmt.Sprintf("dnsScheme%d", i)).Parse(domainScheme))) + } + } +} + +// WithDNSListenAndServeFunc replaces default listen and serve behavior for inner dns server +func WithDNSListenAndServeFunc(listenAndServeDNS func(ctx context.Context, handler dnsutils.Handler, listenOn string)) Option { + if listenAndServeDNS == nil { + panic("listenAndServeDNS cannot be nil") + } + return func(vd *vl3DNSServer) { + vd.listenAndServeDNS = listenAndServeDNS + } +} + +// WithDNSPort replaces default dns port for the inner dns server. +func WithDNSPort(dnsPort int) Option { + return func(vd *vl3DNSServer) { + vd.dnsPort = dnsPort + } +} diff --git a/pkg/networkservice/connectioncontext/dnscontext/vl3dns/server.go b/pkg/networkservice/connectioncontext/dnscontext/vl3dns/server.go new file mode 100644 index 000000000..a7548ec11 --- /dev/null +++ b/pkg/networkservice/connectioncontext/dnscontext/vl3dns/server.go @@ -0,0 +1,198 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package vl3dns provides a possible for vl3 networkservice endpoint to use distributed dns +package vl3dns + +import ( + "context" + "fmt" + "net" + "net/url" + "strings" + "sync" + "text/template" + + "github.com/golang/protobuf/ptypes/empty" + "github.com/networkservicemesh/api/pkg/api/networkservice" + + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/fanout" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/memory" + dnsnext "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/next" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/noloop" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/norecursion" + "github.com/networkservicemesh/sdk/pkg/tools/ippool" +) + +type vl3DNSServer struct { + dnsServerRecords memory.Map + fanoutAddresses sync.Map + domainSchemeTemplates []*template.Template + initialFanoutList []url.URL + dnsPort int + dnsServer dnsutils.Handler + listenAndServeDNS func(ctx context.Context, handler dnsutils.Handler, listenOn string) +} + +type clientDNSNameKey struct{} + +// NewServer creates a new vl3dns netwrokservice server. +// It starts dns server on the passed port/url. By default listens ":53". +// By default is using fanout dns handler to connect to other vl3 nses. +// chanCtx is using for signal to stop dns server. +// opts confugre vl3dns networkservice instance with specific behavior. +func NewServer(chanCtx context.Context, opts ...Option) networkservice.NetworkServiceServer { + var result = &vl3DNSServer{ + dnsPort: 53, + listenAndServeDNS: dnsutils.ListenAndServe, + } + + for _, opt := range opts { + opt(result) + } + + if result.dnsServer == nil { + result.dnsServer = dnsnext.NewDNSHandler( + noloop.NewDNSHandler(), + norecursion.NewDNSHandler(), + memory.NewDNSHandler(&result.dnsServerRecords), + fanout.NewDNSHandler(result.getFanoutAddresses), + ) + } + + result.listenAndServeDNS(chanCtx, result.dnsServer, fmt.Sprintf(":%v", result.dnsPort)) + + return result +} + +func (n *vl3DNSServer) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) { + if request.GetConnection().GetContext().GetDnsContext() == nil { + request.Connection.Context.DnsContext = new(networkservice.DNSContext) + } + + var ipContext = request.GetConnection().GetContext().GetIpContext() + + for _, dstIPNet := range ipContext.GetDstIPNets() { + request.GetConnection().GetContext().GetDnsContext().Configs = append(request.GetConnection().GetContext().GetDnsContext().Configs, &networkservice.DNSConfig{ + DnsServerIps: []string{dstIPNet.IP.String()}, + }) + } + + var recordNames, err = n.buildSrcDNSRecords(request.GetConnection()) + + if err != nil { + return nil, err + } + + var ips []net.IP + for _, srcIPNet := range request.GetConnection().GetContext().GetIpContext().GetSrcIPNets() { + ips = append(ips, srcIPNet.IP) + } + + if v, ok := metadata.Map(ctx, false).LoadAndDelete(clientDNSNameKey{}); ok { + var previousNames = v.([]string) + if !compareStringSlices(previousNames, recordNames) { + for _, prevName := range previousNames { + n.dnsServerRecords.Delete(prevName) + } + } + } + if len(ips) > 0 { + for _, recordName := range recordNames { + n.dnsServerRecords.Store(recordName, ips) + } + + metadata.Map(ctx, false).Store(clientDNSNameKey{}, recordNames) + } + + if n.shouldAddToFanoutList(ipContext) { + for _, srcIPNet := range ipContext.GetSrcIPNets() { + var u = url.URL{Scheme: "tcp", Host: fmt.Sprintf("%v:%v", srcIPNet.IP.String(), n.dnsPort)} + n.fanoutAddresses.Store(u, struct{}{}) + } + } + + return next.Server(ctx).Request(ctx, request) +} + +func (n *vl3DNSServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) { + for _, srcIPNet := range conn.Context.GetIpContext().GetSrcIPNets() { + var u = url.URL{Scheme: "tcp", Host: fmt.Sprintf("%v:%v", srcIPNet.IP.String(), n.dnsPort)} + n.fanoutAddresses.Delete(u) + } + + if v, ok := metadata.Map(ctx, false).LoadAndDelete(clientDNSNameKey{}); ok { + var names = v.([]string) + for _, name := range names { + n.dnsServerRecords.Delete(name) + } + } + + return next.Server(ctx).Close(ctx, conn) +} + +func (n *vl3DNSServer) buildSrcDNSRecords(c *networkservice.Connection) ([]string, error) { + var result []string + for _, templ := range n.domainSchemeTemplates { + var recordBuilder = new(strings.Builder) + if err := templ.Execute(recordBuilder, c); err != nil { + return nil, err + } + result = append(result, recordBuilder.String()) + } + return result, nil +} + +func (n *vl3DNSServer) getFanoutAddresses() []url.URL { + var result = n.initialFanoutList + n.fanoutAddresses.Range(func(key, _ interface{}) bool { + result = append(result, key.(url.URL)) + return true + }) + return result +} + +func (n *vl3DNSServer) shouldAddToFanoutList(ipContext *networkservice.IPContext) bool { + if len(ipContext.SrcRoutes) > 0 { + var lastSrcRoute = ipContext.SrcRoutes[len(ipContext.SrcRoutes)-1] + _, ipNet, err := net.ParseCIDR(lastSrcRoute.Prefix) + if err != nil { + return false + } + var pool = ippool.NewWithNet(ipNet) + for _, srcIP := range ipContext.GetSrcIpAddrs() { + if !pool.ContainsNetString(srcIP) { + return true + } + } + } + return false +} + +func compareStringSlices(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} diff --git a/pkg/tools/dnsutils/adapt/handler.go b/pkg/tools/dnsutils/adapt/handler.go new file mode 100644 index 000000000..958f626f2 --- /dev/null +++ b/pkg/tools/dnsutils/adapt/handler.go @@ -0,0 +1,42 @@ +// Copyright (c) 2022 Cisco Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package adapt provides possible to adapt dns.Handler to dnsutils.Handler +package adapt + +import ( + "context" + + "github.com/miekg/dns" + + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/next" +) + +// NewDNSHandler adapts dns.Handler to dnsutils.Handler. +// handler is candidate for adaption. +func NewDNSHandler(handler dns.Handler) dnsutils.Handler { + return &adaptedHandler{original: handler} +} + +type adaptedHandler struct { + original dns.Handler +} + +func (h *adaptedHandler) ServeDNS(ctx context.Context, rp dns.ResponseWriter, m *dns.Msg) { + h.original.ServeDNS(rp, m) + next.Handler(ctx).ServeDNS(ctx, rp, m) +} diff --git a/pkg/tools/dnsutils/connect/handler.go b/pkg/tools/dnsutils/connect/handler.go new file mode 100644 index 000000000..b19b7bbc8 --- /dev/null +++ b/pkg/tools/dnsutils/connect/handler.go @@ -0,0 +1,59 @@ +// Copyright (c) 2022 Cisco Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package connect simply connects to the concrete endpoint +package connect + +import ( + "context" + "net/url" + + "github.com/miekg/dns" + + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/next" + "github.com/networkservicemesh/sdk/pkg/tools/log" +) + +// NewDNSHandler creates a new dnshandler that simply connects to the ednpoint by passed url +// connectTO is endpoint url +func NewDNSHandler(connectTO *url.URL) dnsutils.Handler { + return &connectDNSHandler{connectTO: connectTO} +} + +type connectDNSHandler struct{ connectTO *url.URL } + +func (c *connectDNSHandler) ServeDNS(ctx context.Context, rp dns.ResponseWriter, msg *dns.Msg) { + var client = dns.Client{ + Net: c.connectTO.Scheme, + } + + var resp, _, err = client.Exchange(msg, c.connectTO.Host) + + if err != nil { + log.FromContext(ctx).Warnf("got an error during exchanging: %v", err.Error()) + dns.HandleFailed(rp, msg) + return + } + + if err = rp.WriteMsg(resp); err != nil { + log.FromContext(ctx).Warnf("got an error during write the message: %v", err.Error()) + dns.HandleFailed(rp, msg) + return + } + + next.Handler(ctx).ServeDNS(ctx, rp, resp) +} diff --git a/pkg/tools/dnsutils/dnsutils.go b/pkg/tools/dnsutils/dnsutils.go new file mode 100644 index 000000000..9a4fd1c9c --- /dev/null +++ b/pkg/tools/dnsutils/dnsutils.go @@ -0,0 +1,58 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package dnsutils provides dns specific utils functions and packages +package dnsutils + +import ( + "context" + "time" + + "github.com/miekg/dns" + + "github.com/networkservicemesh/sdk/pkg/tools/log" +) + +// ListenAndServe starts dns server with specific handler. Listens both udp/tcp networks. +// ctx is using for keeping the server alive. As soon as <-ctx.Done() happens it stops dns server. +// handler is using for hanlding dns queries. +// listenOn is using for listen. Expects {ip}:{port} to listen. Examples: "127.0.0.1:53", ":53". +func ListenAndServe(ctx context.Context, handler Handler, listenOn string) { + var networks = []string{"tcp", "udp"} + + for _, network := range networks { + var server = &dns.Server{Addr: listenOn, Net: network, Handler: dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) { + var timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second) + defer cancel() + + handler.ServeDNS(timeoutCtx, w, m) + })} + + go func() { + <-ctx.Done() + _ = server.Shutdown() + }() + + go func() { + for ; ctx.Err() == nil; time.Sleep(time.Millisecond / 100) { + var err = server.ListenAndServe() + if err != nil { + log.FromContext(ctx).Errorf("an error during serve dns: %v", err.Error()) + } + } + }() + } +} diff --git a/pkg/tools/dnsutils/fanout/fanout.go b/pkg/tools/dnsutils/fanout/fanout.go new file mode 100644 index 000000000..6003e22d1 --- /dev/null +++ b/pkg/tools/dnsutils/fanout/fanout.go @@ -0,0 +1,127 @@ +// Copyright (c) 2022 Cisco Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package fanout sends incoming queries in parallel to few endpoints +package fanout + +import ( + "context" + "net/url" + + "github.com/miekg/dns" + + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/next" + "github.com/networkservicemesh/sdk/pkg/tools/log" +) + +type fanoutHandler struct { + getAddressesFn GetAddressesFn +} + +func (f *fanoutHandler) ServeDNS(ctx context.Context, rw dns.ResponseWriter, msg *dns.Msg) { + var connectTO = f.getAddressesFn() + var responseCh = make(chan *dns.Msg, len(connectTO)) + + if len(connectTO) == 0 { + log.FromContext(ctx).Error("no urls to fanout") + dns.HandleFailed(rw, msg) + return + } + + for i := 0; i < len(connectTO); i++ { + go func(u *url.URL, msg *dns.Msg) { + var client = dns.Client{ + Net: u.Scheme, + } + + var resp, _, err = client.Exchange(msg, u.Host) + + if err != nil { + log.FromContext(ctx).Warnf("got an error during exchanging: %v", err.Error()) + responseCh <- nil + return + } + + responseCh <- resp + }(&connectTO[i], msg.Copy()) + } + + var resp = f.waitResponse(ctx, responseCh) + + if resp == nil { + dns.HandleFailed(rw, msg) + return + } + + if err := rw.WriteMsg(resp); err != nil { + log.FromContext(ctx).Warnf("got an error during write the message: %v", err.Error()) + dns.HandleFailed(rw, msg) + return + } + + next.Handler(ctx).ServeDNS(ctx, rw, resp) +} + +func (f *fanoutHandler) waitResponse(ctx context.Context, respCh <-chan *dns.Msg) *dns.Msg { + var respCount = cap(respCh) + for { + select { + case resp, ok := <-respCh: + if !ok { + return nil + } + respCount-- + if resp == nil { + if respCount == 0 { + return nil + } + continue + } + if resp.Rcode == dns.RcodeSuccess { + return resp + } + if respCount == 0 { + return nil + } + + case <-ctx.Done(): + return nil + } + } +} + +// WithStaticAddresses sets endpoints as static addresses +func WithStaticAddresses(addresses ...url.URL) GetAddressesFn { + if len(addresses) == 0 { + panic("zero addresses are not supported") + } + return func() []url.URL { + return addresses + } +} + +// NewDNSHandler creates a new dns handler instance that sends incoming queries in parallel to few endpoints +// getAddressesFn gets endpoints for fanout +func NewDNSHandler(getAddressesFn GetAddressesFn) dnsutils.Handler { + if getAddressesFn == nil { + panic("no addresses provided") + } + return &fanoutHandler{getAddressesFn: getAddressesFn} +} + +// GetAddressesFn is alias for urls getter function +type GetAddressesFn = func() []url.URL diff --git a/pkg/tools/dnsutils/handler.go b/pkg/tools/dnsutils/handler.go new file mode 100644 index 000000000..6b2af21d4 --- /dev/null +++ b/pkg/tools/dnsutils/handler.go @@ -0,0 +1,28 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dnsutils + +import ( + "context" + + "github.com/miekg/dns" +) + +// Handler handles dns queries +type Handler interface { + ServeDNS(ctx context.Context, rp dns.ResponseWriter, m *dns.Msg) +} diff --git a/pkg/tools/dnsutils/memory/gen.go b/pkg/tools/dnsutils/memory/gen.go new file mode 100644 index 000000000..b276ba453 --- /dev/null +++ b/pkg/tools/dnsutils/memory/gen.go @@ -0,0 +1,27 @@ +// Copyright (c) 2022 Cisco Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memory + +import ( + "sync" +) + +//go:generate go-syncmap -output sync_map.gen.go -type Map + +// Map is like a Go map[string][]net.IP but is safe for concurrent use +// by multiple goroutines without additional locking or coordination +type Map sync.Map diff --git a/pkg/tools/dnsutils/memory/handler.go b/pkg/tools/dnsutils/memory/handler.go new file mode 100644 index 000000000..b234de5a7 --- /dev/null +++ b/pkg/tools/dnsutils/memory/handler.go @@ -0,0 +1,104 @@ +// Copyright (c) 2022 Cisco Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package memory provides a/aaaa memory storage +package memory + +import ( + "context" + "net" + + "github.com/miekg/dns" + + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/next" +) + +const defaultTTL = 3600 + +type memoryHandler struct { + recoreds *Map +} + +func (f *memoryHandler) ServeDNS(ctx context.Context, rw dns.ResponseWriter, msg *dns.Msg) { + if msg == nil || len(msg.Question) == 0 { + next.Handler(ctx).ServeDNS(ctx, rw, msg) + return + } + + var name = dns.Name(msg.Question[0].Name).String() + var records, ok = f.recoreds.Load(name) + + if !ok { + next.Handler(ctx).ServeDNS(ctx, rw, msg) + return + } + + var resp = new(dns.Msg) + resp.SetReply(msg) + resp.Authoritative = true + + switch msg.Question[0].Qtype { + case dns.TypeAAAA: + resp.Answer = append(resp.Answer, aaaa(name, records)...) + case dns.TypeA: + resp.Answer = append(resp.Answer, a(name, records)...) + } + + if len(resp.Answer) == 0 { + next.Handler(ctx).ServeDNS(ctx, rw, msg) + return + } + + if err := rw.WriteMsg(resp); err != nil { + dns.HandleFailed(rw, msg) + } +} + +// NewDNSHandler creates a new dns handler instance that stores a/aaaa answers +func NewDNSHandler(records *Map) dnsutils.Handler { + if records == nil { + panic("records cannot be nil") + } + return &memoryHandler{recoreds: records} +} +func a(domain string, ips []net.IP) []dns.RR { + answers := make([]dns.RR, len(ips)) + for i, ip := range ips { + if ip.To4() == nil { + continue + } + r := new(dns.A) + r.Hdr = dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: defaultTTL} + r.A = ip + answers[i] = r + } + return answers +} + +func aaaa(domain string, ips []net.IP) []dns.RR { + answers := make([]dns.RR, len(ips)) + for i, ip := range ips { + if ip.To16() == nil { + continue + } + r := new(dns.AAAA) + r.Hdr = dns.RR_Header{Name: domain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: defaultTTL} + r.AAAA = ip + answers[i] = r + } + return answers +} diff --git a/pkg/tools/dnsutils/memory/sync_map.gen.go b/pkg/tools/dnsutils/memory/sync_map.gen.go new file mode 100644 index 000000000..aa8f83d90 --- /dev/null +++ b/pkg/tools/dnsutils/memory/sync_map.gen.go @@ -0,0 +1,74 @@ +// Code generated by "-output sync_map.gen.go -type Map -output sync_map.gen.go -type Map"; DO NOT EDIT. +package memory + +import ( + "net" + "sync" // Used by sync.Map. +) + +// Generate code that will fail if the constants change value. +func _() { + // An "cannot convert Map literal (type Map) to type sync.Map" compiler error signifies that the base type have changed. + // Re-run the go-syncmap command to generate them again. + _ = (sync.Map)(Map{}) +} + +var _nil_Map_net_IP_value = func() (val []net.IP) { return }() + +// Load returns the value stored in the map for a key, or nil if no +// value is present. +// The ok result indicates whether value was found in the map. +func (m *Map) Load(key string) ([]net.IP, bool) { + value, ok := (*sync.Map)(m).Load(key) + if value == nil { + return _nil_Map_net_IP_value, ok + } + return value.([]net.IP), ok +} + +// Store sets the value for a key. +func (m *Map) Store(key string, value []net.IP) { + (*sync.Map)(m).Store(key, value) +} + +// LoadOrStore returns the existing value for the key if present. +// Otherwise, it stores and returns the given value. +// The loaded result is true if the value was loaded, false if stored. +func (m *Map) LoadOrStore(key string, value []net.IP) ([]net.IP, bool) { + actual, loaded := (*sync.Map)(m).LoadOrStore(key, value) + if actual == nil { + return _nil_Map_net_IP_value, loaded + } + return actual.([]net.IP), loaded +} + +// LoadAndDelete deletes the value for a key, returning the previous value if any. +// The loaded result reports whether the key was present. +func (m *Map) LoadAndDelete(key string) (value []net.IP, loaded bool) { + actual, loaded := (*sync.Map)(m).LoadAndDelete(key) + if actual == nil { + return _nil_Map_net_IP_value, loaded + } + return actual.([]net.IP), loaded +} + +// Delete deletes the value for a key. +func (m *Map) Delete(key string) { + (*sync.Map)(m).Delete(key) +} + +// Range calls f sequentially for each key and value present in the map. +// If f returns false, range stops the iteration. +// +// Range does not necessarily correspond to any consistent snapshot of the Map's +// contents: no key will be visited more than once, but if the value for any key +// is stored or deleted concurrently, Range may reflect any mapping for that key +// from any point during the Range call. +// +// Range may be O(N) with the number of elements in the map even if f returns +// false after a constant number of calls. +func (m *Map) Range(f func(key string, value []net.IP) bool) { + (*sync.Map)(m).Range(func(key, value interface{}) bool { + return f(key.(string), value.([]net.IP)) + }) +} diff --git a/pkg/tools/dnsutils/next/context.go b/pkg/tools/dnsutils/next/context.go new file mode 100644 index 000000000..43a236100 --- /dev/null +++ b/pkg/tools/dnsutils/next/context.go @@ -0,0 +1,47 @@ +// Copyright (c) 2022 Cisco Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package next + +import ( + "context" + + "github.com/miekg/dns" + + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" +) + +type key struct{} + +func withNextHandler(parent context.Context, next dnsutils.Handler) context.Context { + if parent == nil { + parent = context.Background() + } + return context.WithValue(parent, key{}, next) +} + +// Handler returns next handler or tail +func Handler(ctx context.Context) dnsutils.Handler { + rv, ok := ctx.Value(key{}).(dnsutils.Handler) + if ok && rv != nil { + return rv + } + return &tailHandler{} +} + +type tailHandler struct{} + +func (*tailHandler) ServeDNS(ctx context.Context, rp dns.ResponseWriter, m *dns.Msg) {} diff --git a/pkg/tools/dnsutils/next/next.go b/pkg/tools/dnsutils/next/next.go new file mode 100644 index 000000000..0fff08f70 --- /dev/null +++ b/pkg/tools/dnsutils/next/next.go @@ -0,0 +1,56 @@ +// Copyright (c) 2022 Cisco Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package next allows to dns handlers be joined into chain +package next + +import ( + "context" + + "github.com/miekg/dns" + + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" +) + +// NewDNSHandler creates a new chain of dns handlers +func NewDNSHandler(handlers ...dnsutils.Handler) dnsutils.Handler { + return &nextDNSHandler{handlers: handlers} +} + +type nextDNSHandler struct { + handlers []dnsutils.Handler + index int + nextParent dnsutils.Handler +} + +func (n *nextDNSHandler) ServeDNS(ctx context.Context, rp dns.ResponseWriter, m *dns.Msg) { + nextHandler, nextCtx := n.getClientAndContext(ctx) + nextHandler.ServeDNS(nextCtx, rp, m) +} + +func (n *nextDNSHandler) getClientAndContext(ctx context.Context) (dnsutils.Handler, context.Context) { + nextParent := n.nextParent + if n.index == 0 { + nextParent = Handler(ctx) + if len(n.handlers) == 0 { + return nextParent, ctx + } + } + if n.index+1 < len(n.handlers) { + return n.handlers[n.index], withNextHandler(ctx, &nextDNSHandler{nextParent: nextParent, handlers: n.handlers, index: n.index + 1}) + } + return n.handlers[n.index], withNextHandler(ctx, nextParent) +} diff --git a/pkg/tools/dnsutils/noloop/handler.go b/pkg/tools/dnsutils/noloop/handler.go new file mode 100644 index 000000000..2aa44f61a --- /dev/null +++ b/pkg/tools/dnsutils/noloop/handler.go @@ -0,0 +1,51 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package noloop prevents loops +package noloop + +import ( + "context" + + "sync" + + "github.com/miekg/dns" + + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/next" + "github.com/networkservicemesh/sdk/pkg/tools/log" +) + +type noloopDNSHandler struct{ ids sync.Map } + +func (n *noloopDNSHandler) ServeDNS(ctx context.Context, rp dns.ResponseWriter, m *dns.Msg) { + if m == nil { + dns.HandleFailed(rp, m) + return + } + if _, loaded := n.ids.LoadOrStore(m.Id, struct{}{}); loaded { + log.FromContext(ctx).Errorf("loop is not allowed: query: %v", m.String()) + dns.HandleFailed(rp, m) + return + } + defer n.ids.Delete(m.Id) + next.Handler(ctx).ServeDNS(ctx, rp, m) +} + +// NewDNSHandler creates a new dns handelr that prevents loops +func NewDNSHandler() dnsutils.Handler { + return new(noloopDNSHandler) +} diff --git a/pkg/tools/dnsutils/norecursion/hanlder.go b/pkg/tools/dnsutils/norecursion/hanlder.go new file mode 100644 index 000000000..8a78ace34 --- /dev/null +++ b/pkg/tools/dnsutils/norecursion/hanlder.go @@ -0,0 +1,40 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package norecursion disables recursion for the incomming query. +package norecursion + +import ( + "context" + + "github.com/miekg/dns" + + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils" + "github.com/networkservicemesh/sdk/pkg/tools/dnsutils/next" +) + +type norecursionDNSHandler struct{} + +func (n *norecursionDNSHandler) ServeDNS(ctx context.Context, rp dns.ResponseWriter, m *dns.Msg) { + m.RecursionDesired = false + m.RecursionAvailable = false + next.Handler(ctx).ServeDNS(ctx, rp, m) +} + +// NewDNSHandler creates a new dns handler that simply disables recursions flags {rd, ra} from dns msg header. +func NewDNSHandler() dnsutils.Handler { + return new(norecursionDNSHandler) +}