pkg: deduplicate peer IP addresses
We need to defensively deduplicate peer allowed IPs. If two peers claim the same IP, the WireGuard configuration could flap, causing the interface to churn.
This commit is contained in:
@@ -138,7 +138,9 @@ func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Gra
|
||||
sort.Slice(t.peers, func(i, j int) bool {
|
||||
return t.peers[i].Name < t.peers[j].Name
|
||||
})
|
||||
|
||||
// We need to defensively deduplicate peer allowed IPs. If two peers claim the same IP,
|
||||
// the WireGuard configuration could flap, causing the interface to churn.
|
||||
t.peers = deduplicatePeerIPs(t.peers)
|
||||
// Allocate IPs to the segment leaders in a stable, coordination-free manner.
|
||||
a := newAllocator(*subnet)
|
||||
for _, segment := range t.segments {
|
||||
@@ -417,3 +419,27 @@ func findLeader(nodes []*Node) int {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func deduplicatePeerIPs(peers []*Peer) []*Peer {
|
||||
ps := make([]*Peer, len(peers))
|
||||
ips := make(map[string]struct{})
|
||||
for i, peer := range peers {
|
||||
p := Peer{
|
||||
Name: peer.Name,
|
||||
Peer: wireguard.Peer{
|
||||
Endpoint: peer.Endpoint,
|
||||
PersistentKeepalive: peer.PersistentKeepalive,
|
||||
PublicKey: peer.PublicKey,
|
||||
},
|
||||
}
|
||||
for _, ip := range peer.AllowedIPs {
|
||||
if _, ok := ips[ip.String()]; ok {
|
||||
continue
|
||||
}
|
||||
p.AllowedIPs = append(p.AllowedIPs, ip)
|
||||
ips[ip.String()] = struct{}{}
|
||||
}
|
||||
ps[i] = &p
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
@@ -1246,3 +1246,147 @@ func TestFindLeader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeduplicatePeerIPs(t *testing.T) {
|
||||
p1 := &Peer{
|
||||
Name: "1",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key1"),
|
||||
AllowedIPs: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)},
|
||||
{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
},
|
||||
}
|
||||
p2 := &Peer{
|
||||
Name: "2",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key2"),
|
||||
AllowedIPs: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)},
|
||||
{IP: net.ParseIP("10.0.0.3"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
},
|
||||
}
|
||||
p3 := &Peer{
|
||||
Name: "3",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key3"),
|
||||
AllowedIPs: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)},
|
||||
{IP: net.ParseIP("10.0.0.3"), Mask: net.CIDRMask(24, 32)},
|
||||
{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
p4 := &Peer{
|
||||
Name: "4",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key4"),
|
||||
AllowedIPs: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.3"), Mask: net.CIDRMask(24, 32)},
|
||||
{IP: net.ParseIP("10.0.0.3"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
peers []*Peer
|
||||
out []*Peer
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
peers: nil,
|
||||
out: nil,
|
||||
},
|
||||
{
|
||||
name: "simple dupe",
|
||||
peers: []*Peer{p1, p2},
|
||||
out: []*Peer{
|
||||
p1,
|
||||
{
|
||||
Name: "2",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key2"),
|
||||
AllowedIPs: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.3"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple dupe reversed",
|
||||
peers: []*Peer{p2, p1},
|
||||
out: []*Peer{
|
||||
p2,
|
||||
{
|
||||
Name: "1",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key1"),
|
||||
AllowedIPs: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one duplicates all",
|
||||
peers: []*Peer{p3, p2, p1, p4},
|
||||
out: []*Peer{
|
||||
p3,
|
||||
{
|
||||
Name: "2",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "1",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "4",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one duplicates itself",
|
||||
peers: []*Peer{p4, p1},
|
||||
out: []*Peer{
|
||||
{
|
||||
Name: "4",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key4"),
|
||||
AllowedIPs: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.3"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "1",
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("key1"),
|
||||
AllowedIPs: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)},
|
||||
{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
out := deduplicatePeerIPs(tc.peers)
|
||||
if diff := pretty.Compare(out, tc.out); diff != "" {
|
||||
t.Errorf("test case %q: got diff: %v", tc.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user