Browse Source

:gear: support more complex dns entries

Ettore Di Giacinto 3 years ago
parent
commit
bdb425f04d
5 changed files with 69 additions and 23 deletions
  1. 12 0
      pkg/blockchain/ledger.go
  2. 1 0
      pkg/protocol/protocol.go
  3. 32 22
      pkg/services/dns.go
  4. 4 1
      pkg/services/dns_test.go
  5. 20 0
      pkg/types/dns.go

+ 12 - 0
pkg/blockchain/ledger.go

@@ -185,6 +185,18 @@ func (l *Ledger) AnnounceDeleteBucketKey(ctx context.Context, interval, timeout
 	})
 }
 
+// AnnounceUpdate Keeps announcing something into the blockchain if state is differing
+func (l *Ledger) AnnounceUpdate(ctx context.Context, interval time.Duration, bucket, key string, value interface{}) {
+	l.Announce(ctx, interval, func() {
+		v, exists := l.CurrentData()[bucket][key]
+		realv, _ := json.Marshal(value)
+		switch {
+		case !exists || string(v) != string(realv):
+			l.Add(bucket, map[string]interface{}{key: value})
+		}
+	})
+}
+
 // Persist Keeps announcing something into the blockchain until it is reconciled
 func (l *Ledger) Persist(ctx context.Context, interval, timeout time.Duration, bucket, key string, value interface{}) {
 	put, cancel := context.WithTimeout(ctx, timeout)

+ 1 - 0
pkg/protocol/protocol.go

@@ -31,6 +31,7 @@ const (
 	ServicesLedgerKey = "services"
 	UsersLedgerKey    = "users"
 	HealthCheckKey    = "healthcheck"
+	DNSKey            = "dns"
 )
 
 type Protocol string

+ 32 - 22
pkg/services/dns.go

@@ -19,19 +19,18 @@ import (
 	"context"
 	"fmt"
 	"net"
+	"regexp"
 	"time"
 
 	lru "github.com/hashicorp/golang-lru"
 	"github.com/miekg/dns"
 	"github.com/mudler/edgevpn/pkg/blockchain"
 	"github.com/mudler/edgevpn/pkg/node"
+	"github.com/mudler/edgevpn/pkg/protocol"
+	"github.com/mudler/edgevpn/pkg/types"
 	"github.com/pkg/errors"
 )
 
-const (
-	DNSKey string = "dns"
-)
-
 // DNS returns a network service binding a dns blockchain resolver on listenAddr.
 // Takes an associated name for the addresses in the blockchain
 func DNS(listenAddr string, forwarder bool, forward []string, cacheSize int) []node.Option {
@@ -60,10 +59,17 @@ func DNS(listenAddr string, forwarder bool, forward []string, cacheSize int) []n
 	}
 }
 
-func AnnounceDomain(ctx context.Context, b *blockchain.Ledger, announcetime, timeout time.Duration, record, ip string) {
-	b.Announce(ctx, announcetime, func() {
-		b.Add(DNSKey, map[string]interface{}{fmt.Sprintf("%s.", record): ip})
-	})
+// PersistDNSRecord is syntatic sugar around the ledger
+// It persists a DNS record to the blockchain until it sees it reconciled.
+// It automatically stop announcing and it is not *guaranteed* to persist data.
+func PersistDNSRecord(ctx context.Context, b *blockchain.Ledger, announcetime, timeout time.Duration, regex string, record types.DNS) {
+	b.Persist(ctx, announcetime, timeout, protocol.DNSKey, regex, record)
+}
+
+// AnnounceDNSRecord is syntatic sugar around the ledger
+// Announces a DNS record binding to the blockchain, and keeps announcing for the ctx lifecycle
+func AnnounceDNSRecord(ctx context.Context, b *blockchain.Ledger, announcetime time.Duration, regex string, record types.DNS) {
+	b.AnnounceUpdate(ctx, announcetime, protocol.DNSKey, regex, record)
 }
 
 type dnsHandler struct {
@@ -75,24 +81,27 @@ type dnsHandler struct {
 }
 
 func (d dnsHandler) parseQuery(m *dns.Msg) {
-	for _, q := range m.Question {
+	if len(m.Question) > 0 {
+		q := m.Question[0]
 		// Resolve the entry to an IP from the blockchain data
-		switch q.Qtype {
-		case dns.TypeA, dns.TypeAAAA:
-			if v, exists := d.b.GetKey(DNSKey, q.Name); exists {
-				var res string
+		for k, v := range d.b.CurrentData()[protocol.DNSKey] {
+			r, err := regexp.Compile(k)
+			if err == nil && r.MatchString(q.Name) {
+				var res types.DNS
 				v.Unmarshal(&res)
-				rr, err := dns.NewRR(fmt.Sprintf("%s %s %s", q.Name, dns.TypeToString[q.Qtype], res))
-				if err == nil {
-					m.Answer = append(m.Answer, rr)
-				}
-			} else if d.forwarder {
-				r, err := d.forwardQuery(m)
-				if err == nil {
-					m.Answer = r.Answer
+				if val, exists := res[dns.Type(q.Qtype)]; exists {
+					rr, err := dns.NewRR(fmt.Sprintf("%s %s %s", q.Name, dns.TypeToString[q.Qtype], val))
+					if err == nil {
+						m.Answer = append(m.Answer, rr)
+						return
+					}
 				}
 			}
 		}
+		r, err := d.forwardQuery(m)
+		if err == nil {
+			m.Answer = r.Answer
+		}
 	}
 }
 
@@ -134,7 +143,8 @@ func (d dnsHandler) forwardQuery(dnsMessage *dns.Msg) (*dns.Msg, error) {
 	return nil, errors.New("not available")
 }
 
-// Queries a dns server with a dns message
+// QueryDNS queries a dns server with a dns message and return the answer
+// it is blocking.
 func QueryDNS(ctx context.Context, msg *dns.Msg, dnsServer string) (*dns.Msg, error) {
 	c := new(dns.Conn)
 	cc, _ := (&net.Dialer{Timeout: 35 * time.Second}).DialContext(ctx, "udp", dnsServer)

+ 4 - 1
pkg/services/dns_test.go

@@ -29,6 +29,7 @@ import (
 	"github.com/mudler/edgevpn/pkg/logger"
 	node "github.com/mudler/edgevpn/pkg/node"
 	. "github.com/mudler/edgevpn/pkg/services"
+	"github.com/mudler/edgevpn/pkg/types"
 )
 
 var _ = Describe("DNS service", func() {
@@ -55,7 +56,9 @@ var _ = Describe("DNS service", func() {
 
 			ll, _ := e2.Ledger()
 
-			AnnounceDomain(ctx, ll, 15*time.Second, 10*time.Second, "test.foo", "2.2.2.2")
+			AnnounceDNSRecord(ctx, ll, 15*time.Second, `test.foo.`, types.DNS{
+				dns.Type(dns.TypeA): "2.2.2.2",
+			})
 
 			searchDomain := func(d string) func() string {
 				return func() string {

+ 20 - 0
pkg/types/dns.go

@@ -0,0 +1,20 @@
+// Copyright © 2021 Ettore Di Giacinto <[email protected]>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, see <http://www.gnu.org/licenses/>.
+
+package types
+
+import "github.com/miekg/dns"
+
+type DNS map[dns.Type]string