Skip to content
This repository was archived by the owner on Mar 21, 2025. It is now read-only.

Commit 8dcf671

Browse files
authored
Synchronize DNS records with network member IP address assignments (#167)
* Handle AAAA and A records together when setting network member names * Add a button to force-update a member's AAAA & A records * Decompose the accordion panels of device.partial.tmpl * Detect when DNS records need updating, list changes needed * Avoid collapsing device panel accordions when we don't need to * Update device panels when domain names or DNS update plans change * Move DNS updates section to Advanced accordion * Move code from .../routes/networks to .../client for background worker * Automatically update network member DNS records in background worker * Upgrade dependencies
1 parent c66ae3b commit 8dcf671

31 files changed

+1559
-768
lines changed

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/gorilla/csrf v1.7.1
1111
github.com/gorilla/sessions v1.2.1
1212
github.com/gorilla/websocket v1.5.0
13-
github.com/labstack/echo/v4 v4.10.0
13+
github.com/labstack/echo/v4 v4.10.2
1414
github.com/labstack/gommon v0.4.0
1515
github.com/pkg/errors v0.9.1
1616
github.com/sargassum-world/godest v0.5.1
@@ -47,10 +47,10 @@ require (
4747
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
4848
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
4949
golang.org/x/crypto v0.6.0 // indirect
50-
golang.org/x/net v0.6.0 // indirect
50+
golang.org/x/net v0.7.0 // indirect
5151
golang.org/x/sys v0.5.0 // indirect
5252
golang.org/x/text v0.7.0 // indirect
53-
golang.org/x/time v0.2.0 // indirect
53+
golang.org/x/time v0.3.0 // indirect
5454
modernc.org/libc v1.21.5 // indirect
5555
modernc.org/mathutil v1.5.0 // indirect
5656
modernc.org/memory v1.4.0 // indirect

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPci
6565
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
6666
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
6767
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
68-
github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA=
69-
github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ=
68+
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
69+
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
7070
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
7171
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
7272
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
@@ -141,8 +141,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
141141
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
142142
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
143143
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
144-
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
145-
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
144+
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
145+
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
146146
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
147147
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
148148
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -175,8 +175,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
175175
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
176176
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
177177
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
178-
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
179-
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
178+
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
179+
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
180180
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
181181
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
182182
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=

internal/app/fluitans/client/dns.go

Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package client
22

33
import (
44
"context"
5-
"sort"
6-
"strings"
5+
6+
"github.com/pkg/errors"
77

88
desecc "github.com/sargassum-world/fluitans/internal/clients/desec"
99
ztc "github.com/sargassum-world/fluitans/internal/clients/zerotier"
@@ -12,14 +12,6 @@ import (
1212
"github.com/sargassum-world/fluitans/pkg/zerotier"
1313
)
1414

15-
func GetReverseDomainNameFragments(domainName string) []string {
16-
fragments := strings.Split(domainName, ".")
17-
for i, j := 0, len(fragments)-1; i < j; i, j = i+1, j-1 {
18-
fragments[i], fragments[j] = fragments[j], fragments[i]
19-
}
20-
return fragments
21-
}
22-
2315
func GetNetworkIDs(subnameRRsets map[string][]desec.RRset) map[string]string {
2416
networkIDs := make(map[string]string)
2517
for subname, rrsets := range subnameRRsets {
@@ -36,39 +28,6 @@ func GetNetworkIDs(subnameRRsets map[string][]desec.RRset) map[string]string {
3628
return networkIDs
3729
}
3830

39-
func CompareSubnames(first, second string) bool {
40-
a := GetReverseDomainNameFragments(first)
41-
b := GetReverseDomainNameFragments(second)
42-
k := 0
43-
for k = 0; k < len(a) && k < len(b); k++ {
44-
if a[k] < b[k] {
45-
return true
46-
}
47-
48-
if a[k] > b[k] {
49-
return false
50-
}
51-
}
52-
return len(a) < len(b)
53-
}
54-
55-
func SortSubnameRRsets(
56-
rrsets map[string][]desec.RRset, filterRecordTypes []string,
57-
) (subnames []string, sorted [][]desec.RRset) {
58-
subnames = make([]string, 0, len(rrsets))
59-
for subname := range rrsets {
60-
subnames = append(subnames, subname)
61-
}
62-
sort.Slice(subnames, func(i, j int) bool {
63-
return CompareSubnames(subnames[i], subnames[j])
64-
})
65-
sorted = make([][]desec.RRset, 0, len(subnames))
66-
for _, subname := range subnames {
67-
sorted = append(sorted, desecc.FilterAndSortRRsets(rrsets[subname], filterRecordTypes))
68-
}
69-
return subnames, sorted
70-
}
71-
7231
type Subdomain struct {
7332
Subname string
7433
RRsets []desec.RRset
@@ -82,7 +41,7 @@ func GetSubdomains(
8241
c *desecc.Client, zc *ztc.Client, zcc *ztcontrollers.Client,
8342
) ([]Subdomain, error) {
8443
ids := GetNetworkIDs(subnameRRsets)
85-
sortedKeys, sortedSubnameRRsets := SortSubnameRRsets(subnameRRsets, c.Cache.RecordTypes)
44+
sortedKeys, sortedSubnameRRsets := desecc.SortSubnameRRsets(subnameRRsets, c.Cache.RecordTypes)
8645
networks, controllers, err := GetNetworks(ctx, ids, zc, zcc)
8746
if err != nil {
8847
return nil, err
@@ -101,3 +60,20 @@ func GetSubdomains(
10160
}
10261
return subnames, nil
10362
}
63+
64+
func GetRecordsOfType(
65+
subnameRRsets map[string][]desec.RRset, rrsetType string,
66+
) (records map[string][]string, err error) {
67+
records = make(map[string][]string)
68+
// Look up potential domain names of network members
69+
for subname, rrsets := range subnameRRsets {
70+
filtered := desecc.FilterAndSortRRsets(rrsets, []string{rrsetType})
71+
if len(filtered) > 1 {
72+
return nil, errors.Errorf("unexpected number of RRsets for record")
73+
}
74+
if len(filtered) == 1 {
75+
records[subname] = filtered[0].Records
76+
}
77+
}
78+
return records, nil
79+
}

internal/app/fluitans/client/ip.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package client
2+
3+
import (
4+
"net/netip"
5+
6+
"github.com/pkg/errors"
7+
)
8+
9+
func SplitIPAddresses(rawAddresses []string) (ipv4 []string, ipv6 []string, err error) {
10+
ipv4 = make([]string, 0, len(rawAddresses))
11+
ipv6 = make([]string, 0, len(rawAddresses))
12+
for _, rawAddress := range rawAddresses {
13+
address, err := netip.ParseAddr(rawAddress)
14+
if err != nil {
15+
return nil, nil, errors.Wrapf(err, "couldn't parse IP address %s", rawAddress)
16+
}
17+
if address.Is4() {
18+
ipv4 = append(ipv4, address.String())
19+
continue
20+
}
21+
if address.Is6() {
22+
ipv6 = append(ipv6, address.String())
23+
continue
24+
}
25+
}
26+
return ipv4, ipv6, nil
27+
}

internal/app/fluitans/client/networks.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"golang.org/x/sync/errgroup"
77

8+
desecc "github.com/sargassum-world/fluitans/internal/clients/desec"
89
ztc "github.com/sargassum-world/fluitans/internal/clients/zerotier"
910
"github.com/sargassum-world/fluitans/internal/clients/ztcontrollers"
1011
"github.com/sargassum-world/fluitans/pkg/zerotier"
@@ -17,7 +18,7 @@ func CompareSubnamesAndAddresses(
1718
secondNamed := len(secondSubnames) > 0
1819
if firstNamed && secondNamed {
1920
// fmt.Println("Comparing subnames", firstSubnames[0], secondSubnames[0])
20-
return CompareSubnames(firstSubnames[0], secondSubnames[0])
21+
return desecc.CompareSubnames(firstSubnames[0], secondSubnames[0])
2122
}
2223
if firstNamed {
2324
return true
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package client
2+
3+
// StringSet
4+
5+
type StringSet map[string]struct{}
6+
7+
func NewStringSet(strings []string) StringSet {
8+
set := make(map[string]struct{})
9+
for _, s := range strings {
10+
set[s] = struct{}{}
11+
}
12+
return set
13+
}
14+
15+
func (ss StringSet) Contains(set StringSet) bool {
16+
if ss == nil || set == nil {
17+
return false
18+
}
19+
if len(set) > len(ss) {
20+
return false
21+
}
22+
23+
for s := range set {
24+
if _, ok := ss[s]; !ok {
25+
return false
26+
}
27+
}
28+
return true
29+
}
30+
31+
func (ss StringSet) Equals(set StringSet) bool {
32+
if ss == nil || set == nil {
33+
return false
34+
}
35+
if len(set) != len(ss) {
36+
return false
37+
}
38+
39+
// This might not be the most efficient algorithm, but it's fine for now
40+
return ss.Contains(set) && set.Contains(ss)
41+
}
42+
43+
func (ss StringSet) Difference(set StringSet) StringSet {
44+
difference := make(map[string]struct{})
45+
for s := range ss {
46+
if _, ok := set[s]; !ok {
47+
difference[s] = struct{}{}
48+
}
49+
}
50+
return difference
51+
}

0 commit comments

Comments
 (0)