Nodes without private IPs

Allow nodes to have no private IPs.
Nodes without private IPs will automatically be put into
their own location.
This commit is contained in:
leonnicolas 2021-01-24 14:19:01 +01:00
parent 92825ba0c7
commit 3a201ba0fa
No known key found for this signature in database
GPG Key ID: 088D0743E2B65C07
11 changed files with 519 additions and 50 deletions

59
pkg/encapsulation/noop.go Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2021 the Kilo authors
//
// 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 encapsulation
import (
"net"
"github.com/squat/kilo/pkg/iptables"
)
// Noop is an encapsulation that does nothing.
type Noop Strategy
// CleanUp will also do nothing.
func (n Noop) CleanUp() error {
return nil
}
// Gw will also do nothing.
func (n Noop) Gw(_ net.IP, _ net.IP, _ *net.IPNet) net.IP {
return nil
}
// Index will also do nothing.
func (n Noop) Index() int {
return 0
}
// Init will also do nothing.
func (n Noop) Init(_ int) error {
return nil
}
// Rules will also do nothing.
func (n Noop) Rules(_ []*net.IPNet) []iptables.Rule {
return nil
}
// Set will also do nothing.
func (n Noop) Set(_ *net.IPNet) error {
return nil
}
// Strategy will finally do nothing.
func (n Noop) Strategy() Strategy {
return Strategy(n)
}

View File

@ -209,7 +209,11 @@ func (nb *nodeBackend) Set(name string, node *mesh.Node) error {
}
n := old.DeepCopy()
n.ObjectMeta.Annotations[endpointAnnotationKey] = node.Endpoint.String()
n.ObjectMeta.Annotations[internalIPAnnotationKey] = node.InternalIP.String()
if node.InternalIP == nil {
n.ObjectMeta.Annotations[internalIPAnnotationKey] = ""
} else {
n.ObjectMeta.Annotations[internalIPAnnotationKey] = node.InternalIP.String()
}
n.ObjectMeta.Annotations[keyAnnotationKey] = string(node.Key)
n.ObjectMeta.Annotations[lastSeenAnnotationKey] = strconv.FormatInt(node.LastSeen, 10)
if node.WireGuardIP == nil {
@ -289,6 +293,8 @@ func translateNode(node *v1.Node, topologyLabel string) *mesh.Node {
// remote node's agent has not yet set its IP address;
// in this case the IP will be nil and
// the mesh can wait for the node to be updated.
// It is valid for the InternalIP to be nil,
// if the given node only has public IP addresses.
Endpoint: endpoint,
InternalIP: internalIP,
Key: []byte(node.ObjectMeta.Annotations[keyAnnotationKey]),

View File

@ -187,6 +187,33 @@ func TestTranslateNode(t *testing.T) {
},
subnet: "10.2.1.0/24",
},
{
name: "no InternalIP",
annotations: map[string]string{
endpointAnnotationKey: "10.0.0.1:51820",
internalIPAnnotationKey: "",
keyAnnotationKey: "foo",
lastSeenAnnotationKey: "1000000000",
locationAnnotationKey: "b",
persistentKeepaliveKey: "25",
wireGuardIPAnnotationKey: "10.4.0.1/16",
},
labels: map[string]string{
RegionLabelKey: "a",
},
out: &mesh.Node{
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("10.0.0.1")}, Port: 51820},
InternalIP: nil,
Key: []byte("foo"),
LastSeen: 1000000000,
Leader: false,
Location: "b",
PersistentKeepalive: 25,
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1"), Mask: net.CIDRMask(16, 32)},
},
subnet: "10.2.1.0/24",
},
} {
n := &v1.Node{}
n.ObjectMeta.Annotations = tc.annotations

View File

@ -70,7 +70,7 @@ type Node struct {
// Ready indicates whether or not the node is ready.
func (n *Node) Ready() bool {
// Nodes that are not leaders will not have WireGuardIPs, so it is not required.
return n != nil && n.Endpoint != nil && !(n.Endpoint.IP == nil && n.Endpoint.DNS == "") && n.Endpoint.Port != 0 && n.Key != nil && n.InternalIP != nil && n.Subnet != nil && time.Now().Unix()-n.LastSeen < int64(resyncPeriod)*2/int64(time.Second)
return n != nil && n.Endpoint != nil && !(n.Endpoint.IP == nil && n.Endpoint.DNS == "") && n.Endpoint.Port != 0 && n.Key != nil && n.Subnet != nil && time.Now().Unix()-n.LastSeen < int64(resyncPeriod)*2/int64(time.Second)
}
// Peer represents a peer in the network.

View File

@ -30,9 +30,7 @@ import (
// - private IP to which hostname resolves
// - private IP assigned to interface of default route
// - private IP assigned to local interface
// - public IP to which hostname resolves
// - public IP assigned to interface of default route
// - public IP assigned to local interface
// - nil if no private IP was found
// It selects the public IP address in the following order:
// - public IP to which hostname resolves
// - public IP assigned to interface of default route
@ -153,7 +151,8 @@ func getIP(hostname string, ignoreIfaces ...int) (*net.IPNet, *net.IPNet, error)
return nil, nil, errors.New("no valid IP was found")
}
if len(priv) == 0 {
priv = pub
// If no private IPs were found, use nil.
priv = append(priv, nil)
}
if len(pub) == 0 {
pub = priv

View File

@ -70,7 +70,11 @@ func (t *Topology) Dot() (string, error) {
return "", fmt.Errorf("failed to add rank to node")
}
}
if err := g.Nodes.Lookup[graphEscape(s.hostnames[j])].Attrs.Add(string(gographviz.Label), nodeLabel(s.location, s.hostnames[j], s.cidrs[j], s.privateIPs[j], wg, endpoint)); err != nil {
var priv net.IP
if s.privateIPs != nil {
priv = s.privateIPs[j]
}
if err := g.Nodes.Lookup[graphEscape(s.hostnames[j])].Attrs.Add(string(gographviz.Label), nodeLabel(s.location, s.hostnames[j], s.cidrs[j], priv, wg, endpoint)); err != nil {
return "", fmt.Errorf("failed to add label to node")
}
}
@ -155,7 +159,9 @@ func nodeLabel(location, name string, cidr *net.IPNet, priv, wgIP net.IP, endpoi
location,
name,
cidr.String(),
priv.String(),
}
if priv != nil {
label = append(label, priv.String())
}
if wgIP != nil {
label = append(label, wgIP.String())

View File

@ -71,7 +71,7 @@ type Mesh struct {
wireGuardIP *net.IPNet
// nodes and peers are mutable fields in the struct
// and needs to be guarded.
// and need to be guarded.
nodes map[string]*Node
peers map[string]*Peer
mu sync.Mutex
@ -125,17 +125,23 @@ func New(backend Backend, enc encapsulation.Encapsulator, granularity Granularit
if err != nil {
return nil, fmt.Errorf("failed to find public IP: %v", err)
}
ifaces, err := interfacesForIP(privateIP)
if err != nil {
return nil, fmt.Errorf("failed to find interface for private IP: %v", err)
}
privIface := ifaces[0].Index
if enc.Strategy() != encapsulation.Never {
if err := enc.Init(privIface); err != nil {
return nil, fmt.Errorf("failed to initialize encapsulator: %v", err)
var privIface int
if privateIP != nil {
ifaces, err := interfacesForIP(privateIP)
if err != nil {
return nil, fmt.Errorf("failed to find interface for private IP: %v", err)
}
privIface := ifaces[0].Index
if enc.Strategy() != encapsulation.Never {
if err := enc.Init(privIface); err != nil {
return nil, fmt.Errorf("failed to initialize encapsulator: %v", err)
}
}
level.Debug(logger).Log("msg", fmt.Sprintf("using %s as the private IP address", privateIP.String()))
} else {
enc = encapsulation.Noop(enc.Strategy())
level.Debug(logger).Log("msg", "running without a private IP address")
}
level.Debug(logger).Log("msg", fmt.Sprintf("using %s as the private IP address", privateIP.String()))
level.Debug(logger).Log("msg", fmt.Sprintf("using %s as the public IP address", publicIP.String()))
ipTables, err := iptables.New()
if err != nil {

View File

@ -97,9 +97,9 @@ func (t *Topology) Routes(kiloIfaceName string, kiloIface, privIface, tunlIface
LinkIndex: privIface,
Protocol: unix.RTPROT_STATIC,
}, enc.Strategy(), t.privateIP, tunlIface))
}
for i := range segment.privateIPs {
// Add routes to the private IPs of nodes in other segments.
// Number of CIDRs and private IPs always match so
// we can reuse the loop.
routes = append(routes, encapsulateRoute(&netlink.Route{
Dst: oneAddressCIDR(segment.privateIPs[i]),
Flags: int(netlink.FLAG_ONLINK),
@ -126,7 +126,9 @@ func (t *Topology) Routes(kiloIfaceName string, kiloIface, privIface, tunlIface
for _, segment := range t.segments {
// Add routes for the current segment if local is true.
if segment.location == t.location {
if local {
// If the local node does not have a private IP address,
// then skip adding routes, because the node is in its own location.
if local && t.privateIP != nil {
for i := range segment.cidrs {
// Don't add routes for the local node.
if segment.privateIPs[i].Equal(t.privateIP.IP) {
@ -165,6 +167,8 @@ func (t *Topology) Routes(kiloIfaceName string, kiloIface, privIface, tunlIface
}
}
}
// Continuing here prevents leaders form adding routes via WireGuard to
// nodes in their own location.
continue
}
for i := range segment.cidrs {
@ -180,7 +184,7 @@ func (t *Topology) Routes(kiloIfaceName string, kiloIface, privIface, tunlIface
// equals the external IP. This means that the node
// is only accessible through an external IP and we
// cannot encapsulate traffic to an IP through the IP.
if segment.privateIPs[i].Equal(segment.endpoint.IP) {
if segment.privateIPs == nil || segment.privateIPs[i].Equal(segment.endpoint.IP) {
continue
}
// Add routes to the private IPs of nodes in other segments.

View File

@ -74,6 +74,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[2].cidrs[0],
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[2].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -110,6 +117,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[2].cidrs[0],
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[2].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -160,6 +174,20 @@ func TestRoutes(t *testing.T) {
LinkIndex: privIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: oneAddressCIDR(mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[2].wireGuardIP),
Flags: int(netlink.FLAG_ONLINK),
Gw: nodes["b"].InternalIP.IP,
LinkIndex: privIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[2].cidrs[0],
Flags: int(netlink.FLAG_ONLINK),
Gw: nodes["b"].InternalIP.IP,
LinkIndex: privIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
Flags: int(netlink.FLAG_ONLINK),
@ -183,6 +211,70 @@ func TestRoutes(t *testing.T) {
},
},
},
{
name: "logical from d",
topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name),
strategy: encapsulation.Never,
routes: []*netlink.Route{
{
Dst: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name).segments[0].cidrs[0],
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name).segments[0].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: oneAddressCIDR(nodes["a"].InternalIP.IP),
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name).segments[0].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name).segments[1].cidrs[0],
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name).segments[1].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: oneAddressCIDR(nodes["b"].InternalIP.IP),
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name).segments[1].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name).segments[1].cidrs[1],
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name).segments[1].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: oneAddressCIDR(nodes["c"].InternalIP.IP),
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["d"].Name).segments[1].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[1],
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["b"].AllowedIPs[0],
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
},
},
{
name: "full from a",
topology: mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name),
@ -216,6 +308,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[3].cidrs[0],
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[3].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -266,6 +365,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[3].cidrs[0],
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[3].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -316,6 +422,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[3].cidrs[0],
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[3].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -367,6 +480,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: nodes["d"].Subnet,
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[2].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -418,6 +538,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: nodes["d"].Subnet,
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[2].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -462,6 +589,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: privIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: nodes["d"].Subnet,
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[2].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -514,6 +648,13 @@ func TestRoutes(t *testing.T) {
Protocol: unix.RTPROT_STATIC,
Table: kiloTableIndex,
},
{
Dst: nodes["d"].Subnet,
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[2].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -584,6 +725,20 @@ func TestRoutes(t *testing.T) {
LinkIndex: privIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: oneAddressCIDR(mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[2].wireGuardIP),
Flags: int(netlink.FLAG_ONLINK),
Gw: nodes["b"].InternalIP.IP,
LinkIndex: privIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: nodes["d"].Subnet,
Flags: int(netlink.FLAG_ONLINK),
Gw: nodes["b"].InternalIP.IP,
LinkIndex: privIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
Flags: int(netlink.FLAG_ONLINK),
@ -656,6 +811,20 @@ func TestRoutes(t *testing.T) {
Protocol: unix.RTPROT_STATIC,
Table: kiloTableIndex,
},
{
Dst: oneAddressCIDR(mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[2].wireGuardIP),
Flags: int(netlink.FLAG_ONLINK),
Gw: nodes["b"].InternalIP.IP,
LinkIndex: tunlIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: nodes["d"].Subnet,
Flags: int(netlink.FLAG_ONLINK),
Gw: nodes["b"].InternalIP.IP,
LinkIndex: tunlIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
Flags: int(netlink.FLAG_ONLINK),
@ -720,6 +889,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: nodes["d"].Subnet,
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[3].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -771,6 +947,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: nodes["d"].Subnet,
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[3].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,
@ -822,6 +1005,13 @@ func TestRoutes(t *testing.T) {
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: nodes["d"].Subnet,
Flags: int(netlink.FLAG_ONLINK),
Gw: mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[3].wireGuardIP,
LinkIndex: kiloIface,
Protocol: unix.RTPROT_STATIC,
},
{
Dst: peers["a"].AllowedIPs[0],
LinkIndex: kiloIface,

View File

@ -22,6 +22,11 @@ import (
"github.com/squat/kilo/pkg/wireguard"
)
const (
logicalLocationPrefix = "location:"
nodeLocationPrefix = "node:"
)
// Topology represents the logical structure of the overlay network.
type Topology struct {
// key is the private key of the node creating the topology.
@ -77,18 +82,24 @@ func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Gra
var location string
switch granularity {
case LogicalGranularity:
location = node.Location
location = logicalLocationPrefix + node.Location
if node.InternalIP == nil {
location = nodeLocationPrefix + node.Name
}
case FullGranularity:
location = node.Name
location = nodeLocationPrefix + node.Name
}
topoMap[location] = append(topoMap[location], node)
}
var localLocation string
switch granularity {
case LogicalGranularity:
localLocation = nodes[hostname].Location
localLocation = logicalLocationPrefix + nodes[hostname].Location
if nodes[hostname].InternalIP == nil {
localLocation = nodeLocationPrefix + hostname
}
case FullGranularity:
localLocation = hostname
localLocation = nodeLocationPrefix + hostname
}
t := Topology{key: key, port: port, hostname: hostname, location: localLocation, persistentKeepalive: persistentKeepalive, privateIP: nodes[hostname].InternalIP, subnet: nodes[hostname].Subnet}
@ -110,10 +121,13 @@ func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Gra
// - the node's allocated subnet
// - the node's WireGuard IP
// - the node's internal IP
allowedIPs = append(allowedIPs, node.Subnet, oneAddressCIDR(node.InternalIP.IP))
allowedIPs = append(allowedIPs, node.Subnet)
if node.InternalIP != nil {
allowedIPs = append(allowedIPs, oneAddressCIDR(node.InternalIP.IP))
privateIPs = append(privateIPs, node.InternalIP.IP)
}
cidrs = append(cidrs, node.Subnet)
hostnames = append(hostnames, node.Name)
privateIPs = append(privateIPs, node.InternalIP.IP)
}
t.segments = append(t.segments, &segment{
allowedIPs: allowedIPs,

View File

@ -33,6 +33,7 @@ func setup(t *testing.T) (map[string]*Node, map[string]*Peer, []byte, uint32) {
e1 := &net.IPNet{IP: net.ParseIP("10.1.0.1").To4(), Mask: net.CIDRMask(16, 32)}
e2 := &net.IPNet{IP: net.ParseIP("10.1.0.2").To4(), Mask: net.CIDRMask(16, 32)}
e3 := &net.IPNet{IP: net.ParseIP("10.1.0.3").To4(), Mask: net.CIDRMask(16, 32)}
e4 := &net.IPNet{IP: net.ParseIP("10.1.0.4").To4(), Mask: net.CIDRMask(16, 32)}
i1 := &net.IPNet{IP: net.ParseIP("192.168.0.1").To4(), Mask: net.CIDRMask(32, 32)}
i2 := &net.IPNet{IP: net.ParseIP("192.168.0.2").To4(), Mask: net.CIDRMask(32, 32)}
nodes := map[string]*Node{
@ -57,11 +58,19 @@ func setup(t *testing.T) (map[string]*Node, map[string]*Peer, []byte, uint32) {
Name: "c",
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: e3.IP}, Port: DefaultKiloPort},
InternalIP: i2,
// Same location a node b.
// Same location as node b.
Location: "2",
Subnet: &net.IPNet{IP: net.ParseIP("10.2.3.0"), Mask: net.CIDRMask(24, 32)},
Key: []byte("key3"),
},
"d": {
Name: "d",
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: e4.IP}, Port: DefaultKiloPort},
// Same location as node a, but without private IP
Location: "1",
Subnet: &net.IPNet{IP: net.ParseIP("10.2.4.0"), Mask: net.CIDRMask(24, 32)},
Key: []byte("key4"),
},
}
peers := map[string]*Peer{
"a": {
@ -97,6 +106,7 @@ func TestNewTopology(t *testing.T) {
w1 := net.ParseIP("10.4.0.1").To4()
w2 := net.ParseIP("10.4.0.2").To4()
w3 := net.ParseIP("10.4.0.3").To4()
w4 := net.ParseIP("10.4.0.4").To4()
for _, tc := range []struct {
name string
granularity Granularity
@ -110,7 +120,7 @@ func TestNewTopology(t *testing.T) {
result: &Topology{
hostname: nodes["a"].Name,
leader: true,
location: nodes["a"].Location,
location: logicalLocationPrefix + nodes["a"].Location,
subnet: nodes["a"].Subnet,
privateIP: nodes["a"].InternalIP,
wireGuardCIDR: &net.IPNet{IP: w1, Mask: net.CIDRMask(16, 32)},
@ -119,7 +129,7 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].Endpoint,
key: nodes["a"].Key,
location: nodes["a"].Location,
location: logicalLocationPrefix + nodes["a"].Location,
cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
@ -129,12 +139,22 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["b"].Endpoint,
key: nodes["b"].Key,
location: nodes["b"].Location,
location: logicalLocationPrefix + nodes["b"].Location,
cidrs: []*net.IPNet{nodes["b"].Subnet, nodes["c"].Subnet},
hostnames: []string{"b", "c"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP, nodes["c"].InternalIP.IP},
wireGuardIP: w2,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w3, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["d"].Endpoint,
key: nodes["d"].Key,
location: nodeLocationPrefix + nodes["d"].Name,
cidrs: []*net.IPNet{nodes["d"].Subnet},
hostnames: []string{"d"},
privateIPs: nil,
wireGuardIP: w3,
},
},
peers: []*Peer{peers["a"], peers["b"]},
},
@ -146,7 +166,7 @@ func TestNewTopology(t *testing.T) {
result: &Topology{
hostname: nodes["b"].Name,
leader: true,
location: nodes["b"].Location,
location: logicalLocationPrefix + nodes["b"].Location,
subnet: nodes["b"].Subnet,
privateIP: nodes["b"].InternalIP,
wireGuardCIDR: &net.IPNet{IP: w2, Mask: net.CIDRMask(16, 32)},
@ -155,7 +175,7 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].Endpoint,
key: nodes["a"].Key,
location: nodes["a"].Location,
location: logicalLocationPrefix + nodes["a"].Location,
cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
@ -165,12 +185,22 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["b"].Endpoint,
key: nodes["b"].Key,
location: nodes["b"].Location,
location: logicalLocationPrefix + nodes["b"].Location,
cidrs: []*net.IPNet{nodes["b"].Subnet, nodes["c"].Subnet},
hostnames: []string{"b", "c"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP, nodes["c"].InternalIP.IP},
wireGuardIP: w2,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w3, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["d"].Endpoint,
key: nodes["d"].Key,
location: nodeLocationPrefix + nodes["d"].Name,
cidrs: []*net.IPNet{nodes["d"].Subnet},
hostnames: []string{"d"},
privateIPs: nil,
wireGuardIP: w3,
},
},
peers: []*Peer{peers["a"], peers["b"]},
},
@ -182,7 +212,7 @@ func TestNewTopology(t *testing.T) {
result: &Topology{
hostname: nodes["c"].Name,
leader: false,
location: nodes["b"].Location,
location: logicalLocationPrefix + nodes["b"].Location,
subnet: nodes["c"].Subnet,
privateIP: nodes["c"].InternalIP,
wireGuardCIDR: nil,
@ -191,7 +221,7 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].Endpoint,
key: nodes["a"].Key,
location: nodes["a"].Location,
location: logicalLocationPrefix + nodes["a"].Location,
cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
@ -201,12 +231,22 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["b"].Endpoint,
key: nodes["b"].Key,
location: nodes["b"].Location,
location: logicalLocationPrefix + nodes["b"].Location,
cidrs: []*net.IPNet{nodes["b"].Subnet, nodes["c"].Subnet},
hostnames: []string{"b", "c"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP, nodes["c"].InternalIP.IP},
wireGuardIP: w2,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w3, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["d"].Endpoint,
key: nodes["d"].Key,
location: nodeLocationPrefix + nodes["d"].Name,
cidrs: []*net.IPNet{nodes["d"].Subnet},
hostnames: []string{"d"},
privateIPs: nil,
wireGuardIP: w3,
},
},
peers: []*Peer{peers["a"], peers["b"]},
},
@ -218,7 +258,7 @@ func TestNewTopology(t *testing.T) {
result: &Topology{
hostname: nodes["a"].Name,
leader: true,
location: nodes["a"].Name,
location: nodeLocationPrefix + nodes["a"].Name,
subnet: nodes["a"].Subnet,
privateIP: nodes["a"].InternalIP,
wireGuardCIDR: &net.IPNet{IP: w1, Mask: net.CIDRMask(16, 32)},
@ -227,7 +267,7 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].Endpoint,
key: nodes["a"].Key,
location: nodes["a"].Name,
location: nodeLocationPrefix + nodes["a"].Name,
cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
@ -237,7 +277,7 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["b"].Endpoint,
key: nodes["b"].Key,
location: nodes["b"].Name,
location: nodeLocationPrefix + nodes["b"].Name,
cidrs: []*net.IPNet{nodes["b"].Subnet},
hostnames: []string{"b"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
@ -247,12 +287,22 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w3, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["c"].Endpoint,
key: nodes["c"].Key,
location: nodes["c"].Name,
location: nodeLocationPrefix + nodes["c"].Name,
cidrs: []*net.IPNet{nodes["c"].Subnet},
hostnames: []string{"c"},
privateIPs: []net.IP{nodes["c"].InternalIP.IP},
wireGuardIP: w3,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w4, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["d"].Endpoint,
key: nodes["d"].Key,
location: nodeLocationPrefix + nodes["d"].Name,
cidrs: []*net.IPNet{nodes["d"].Subnet},
hostnames: []string{"d"},
privateIPs: nil,
wireGuardIP: w4,
},
},
peers: []*Peer{peers["a"], peers["b"]},
},
@ -264,7 +314,7 @@ func TestNewTopology(t *testing.T) {
result: &Topology{
hostname: nodes["b"].Name,
leader: true,
location: nodes["b"].Name,
location: nodeLocationPrefix + nodes["b"].Name,
subnet: nodes["b"].Subnet,
privateIP: nodes["b"].InternalIP,
wireGuardCIDR: &net.IPNet{IP: w2, Mask: net.CIDRMask(16, 32)},
@ -273,7 +323,7 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].Endpoint,
key: nodes["a"].Key,
location: nodes["a"].Name,
location: nodeLocationPrefix + nodes["a"].Name,
cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
@ -283,7 +333,7 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["b"].Endpoint,
key: nodes["b"].Key,
location: nodes["b"].Name,
location: nodeLocationPrefix + nodes["b"].Name,
cidrs: []*net.IPNet{nodes["b"].Subnet},
hostnames: []string{"b"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
@ -293,12 +343,22 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w3, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["c"].Endpoint,
key: nodes["c"].Key,
location: nodes["c"].Name,
location: nodeLocationPrefix + nodes["c"].Name,
cidrs: []*net.IPNet{nodes["c"].Subnet},
hostnames: []string{"c"},
privateIPs: []net.IP{nodes["c"].InternalIP.IP},
wireGuardIP: w3,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w4, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["d"].Endpoint,
key: nodes["d"].Key,
location: nodeLocationPrefix + nodes["d"].Name,
cidrs: []*net.IPNet{nodes["d"].Subnet},
hostnames: []string{"d"},
privateIPs: nil,
wireGuardIP: w4,
},
},
peers: []*Peer{peers["a"], peers["b"]},
},
@ -310,7 +370,7 @@ func TestNewTopology(t *testing.T) {
result: &Topology{
hostname: nodes["c"].Name,
leader: true,
location: nodes["c"].Name,
location: nodeLocationPrefix + nodes["c"].Name,
subnet: nodes["c"].Subnet,
privateIP: nodes["c"].InternalIP,
wireGuardCIDR: &net.IPNet{IP: w3, Mask: net.CIDRMask(16, 32)},
@ -319,7 +379,7 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].Endpoint,
key: nodes["a"].Key,
location: nodes["a"].Name,
location: nodeLocationPrefix + nodes["a"].Name,
cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
@ -329,7 +389,7 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["b"].Endpoint,
key: nodes["b"].Key,
location: nodes["b"].Name,
location: nodeLocationPrefix + nodes["b"].Name,
cidrs: []*net.IPNet{nodes["b"].Subnet},
hostnames: []string{"b"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
@ -339,12 +399,78 @@ func TestNewTopology(t *testing.T) {
allowedIPs: []*net.IPNet{nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w3, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["c"].Endpoint,
key: nodes["c"].Key,
location: nodes["c"].Name,
location: nodeLocationPrefix + nodes["c"].Name,
cidrs: []*net.IPNet{nodes["c"].Subnet},
hostnames: []string{"c"},
privateIPs: []net.IP{nodes["c"].InternalIP.IP},
wireGuardIP: w3,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w4, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["d"].Endpoint,
key: nodes["d"].Key,
location: nodeLocationPrefix + nodes["d"].Name,
cidrs: []*net.IPNet{nodes["d"].Subnet},
hostnames: []string{"d"},
privateIPs: nil,
wireGuardIP: w4,
},
},
peers: []*Peer{peers["a"], peers["b"]},
},
},
{
name: "full from d",
granularity: FullGranularity,
hostname: nodes["d"].Name,
result: &Topology{
hostname: nodes["d"].Name,
leader: true,
location: nodeLocationPrefix + nodes["d"].Name,
subnet: nodes["d"].Subnet,
privateIP: nil,
wireGuardCIDR: &net.IPNet{IP: w4, Mask: net.CIDRMask(16, 32)},
segments: []*segment{
{
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].Endpoint,
key: nodes["a"].Key,
location: nodeLocationPrefix + nodes["a"].Name,
cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
wireGuardIP: w1,
},
{
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["b"].Endpoint,
key: nodes["b"].Key,
location: nodeLocationPrefix + nodes["b"].Name,
cidrs: []*net.IPNet{nodes["b"].Subnet},
hostnames: []string{"b"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
wireGuardIP: w2,
},
{
allowedIPs: []*net.IPNet{nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w3, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["c"].Endpoint,
key: nodes["c"].Key,
location: nodeLocationPrefix + nodes["c"].Name,
cidrs: []*net.IPNet{nodes["c"].Subnet},
hostnames: []string{"c"},
privateIPs: []net.IP{nodes["c"].InternalIP.IP},
wireGuardIP: w3,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w4, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["d"].Endpoint,
key: nodes["d"].Key,
location: nodeLocationPrefix + nodes["d"].Name,
cidrs: []*net.IPNet{nodes["d"].Subnet},
hostnames: []string{"d"},
privateIPs: nil,
wireGuardIP: w4,
},
},
peers: []*Peer{peers["a"], peers["b"]},
},
@ -390,6 +516,12 @@ Endpoint = 10.1.0.2:51820
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
PersistentKeepalive = 25
[Peer]
PublicKey = key4
Endpoint = 10.1.0.4:51820
AllowedIPs = 10.2.4.0/24, 10.4.0.3/32
PersistentKeepalive = 25
[Peer]
PublicKey = key4
AllowedIPs = 10.5.0.1/24, 10.5.0.2/24
@ -414,6 +546,11 @@ PersistentKeepalive = 25
Endpoint = 10.1.0.1:51820
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
[Peer]
PublicKey = key4
Endpoint = 10.1.0.4:51820
AllowedIPs = 10.2.4.0/24, 10.4.0.3/32
[Peer]
PublicKey = key4
AllowedIPs = 10.5.0.1/24, 10.5.0.2/24
@ -436,6 +573,11 @@ PersistentKeepalive = 25
Endpoint = 10.1.0.1:51820
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
[Peer]
PublicKey = key4
Endpoint = 10.1.0.4:51820
AllowedIPs = 10.2.4.0/24, 10.4.0.3/32
[Peer]
PublicKey = key4
AllowedIPs = 10.5.0.1/24, 10.5.0.2/24
@ -465,6 +607,12 @@ PersistentKeepalive = 25
AllowedIPs = 10.2.3.0/24, 192.168.0.2/32, 10.4.0.3/32
PersistentKeepalive = 25
[Peer]
PublicKey = key4
Endpoint = 10.1.0.4:51820
AllowedIPs = 10.2.4.0/24, 10.4.0.4/32
PersistentKeepalive = 25
[Peer]
PublicKey = key4
AllowedIPs = 10.5.0.1/24, 10.5.0.2/24
@ -494,6 +642,11 @@ PersistentKeepalive = 25
Endpoint = 10.1.0.3:51820
AllowedIPs = 10.2.3.0/24, 192.168.0.2/32, 10.4.0.3/32
[Peer]
PublicKey = key4
Endpoint = 10.1.0.4:51820
AllowedIPs = 10.2.4.0/24, 10.4.0.4/32
[Peer]
PublicKey = key4
AllowedIPs = 10.5.0.1/24, 10.5.0.2/24
@ -521,6 +674,11 @@ PersistentKeepalive = 25
Endpoint = 10.1.0.2:51820
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.4.0.2/32
[Peer]
PublicKey = key4
Endpoint = 10.1.0.4:51820
AllowedIPs = 10.2.4.0/24, 10.4.0.4/32
[Peer]
PublicKey = key4
AllowedIPs = 10.5.0.1/24, 10.5.0.2/24