// Copyright 2019 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 mesh

import (
	"testing"

	"github.com/kylelemons/godebug/pretty"
	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"

	"github.com/squat/kilo/pkg/encapsulation"
)

func TestRoutes(t *testing.T) {
	nodes, peers, key, port := setup(t)
	kiloIface := 0
	privIface := 1
	tunlIface := 2
	mustTopoForGranularityAndHost := func(granularity Granularity, hostname string) *Topology {
		return mustTopo(t, nodes, peers, granularity, hostname, port, key, DefaultKiloSubnet, 0)
	}

	for _, tc := range []struct {
		name     string
		local    bool
		topology *Topology
		strategy encapsulation.Strategy
		routes   []*netlink.Route
		rules    []*netlink.Rule
	}{
		{
			name:     "logical from a",
			topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].cidrs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["b"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].cidrs[1],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["c"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &nodes["b"].AllowedLocationIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					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,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "logical from b",
			topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[0].cidrs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					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,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "logical from c",
			topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       oneAddressCIDR(mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[0].wireGuardIP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[0].cidrs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[1].wireGuardIP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					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),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			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:       &nodes["b"].AllowedLocationIPs[0],
					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),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[1].cidrs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["b"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &nodes["b"].AllowedLocationIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[2].cidrs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["c"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					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,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "full from b",
			topology: mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[0].cidrs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[2].cidrs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["c"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					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,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "full from c",
			topology: mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[0].cidrs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[1].cidrs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["b"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &nodes["b"].AllowedLocationIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					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,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "logical from a local",
			local:    true,
			topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       nodes["b"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["b"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["c"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["c"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &nodes["b"].AllowedLocationIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["d"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "logical from a local always",
			local:    true,
			topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name),
			strategy: encapsulation.Always,
			routes: []*netlink.Route{
				{
					Dst:       nodes["b"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["b"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["c"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["c"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &nodes["b"].AllowedLocationIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["d"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["a"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "logical from b local",
			local:    true,
			topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       nodes["a"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["c"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["c"].InternalIP.IP,
					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,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "logical from b local always",
			local:    true,
			topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name),
			strategy: encapsulation.Always,
			routes: []*netlink.Route{
				{
					Dst:       nodes["a"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(LogicalGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["c"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["c"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["c"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["c"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["b"].InternalIP.IP,
					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,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
			rules: []*netlink.Rule{
				defaultRule(&netlink.Rule{
					Src:   nodes["b"].Subnet,
					Dst:   nodes["c"].InternalIP,
					Table: kiloTableIndex,
				}),
				defaultRule(&netlink.Rule{
					Dst:     nodes["c"].InternalIP,
					IifName: DefaultKiloInterface,
					Table:   kiloTableIndex,
				}),
			},
		},
		{
			name:     "logical from c local",
			local:    true,
			topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       oneAddressCIDR(mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[0].wireGuardIP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["a"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[1].wireGuardIP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["b"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					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),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: privIface,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "logical from c local always",
			local:    true,
			topology: mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name),
			strategy: encapsulation.Always,
			routes: []*netlink.Route{
				{
					Dst:       oneAddressCIDR(mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[0].wireGuardIP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["a"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(mustTopoForGranularityAndHost(LogicalGranularity, nodes["c"].Name).segments[1].wireGuardIP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["b"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["b"].InternalIP,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					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,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["d"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        nodes["b"].InternalIP.IP,
					LinkIndex: tunlIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
			rules: []*netlink.Rule{
				defaultRule(&netlink.Rule{
					Src:   nodes["c"].Subnet,
					Dst:   nodes["b"].InternalIP,
					Table: kiloTableIndex,
				}),
			},
		},
		{
			name:     "full from a local",
			local:    true,
			topology: mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       nodes["b"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["b"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &nodes["b"].AllowedLocationIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["c"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["c"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["d"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["a"].Name).segments[3].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["a"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "full from b local",
			local:    true,
			topology: mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       nodes["a"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["c"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["c"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[2].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["d"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["b"].Name).segments[3].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["b"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
		{
			name:     "full from c local",
			local:    true,
			topology: mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name),
			strategy: encapsulation.Never,
			routes: []*netlink.Route{
				{
					Dst:       nodes["a"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["a"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[0].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["b"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       oneAddressCIDR(nodes["b"].InternalIP.IP),
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &nodes["b"].AllowedLocationIPs[0],
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[1].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       nodes["d"].Subnet,
					Flags:     int(netlink.FLAG_ONLINK),
					Gw:        mustTopoForGranularityAndHost(FullGranularity, nodes["c"].Name).segments[3].wireGuardIP,
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["a"].AllowedIPs[1],
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
				{
					Dst:       &peers["b"].AllowedIPs[0],
					LinkIndex: kiloIface,
					Src:       nodes["c"].InternalIP.IP,
					Protocol:  unix.RTPROT_STATIC,
				},
			},
		},
	} {
		routes, rules := tc.topology.Routes(DefaultKiloInterface, kiloIface, privIface, tunlIface, tc.local, encapsulation.NewIPIP(tc.strategy))
		if diff := pretty.Compare(routes, tc.routes); diff != "" {
			t.Errorf("test case %q: got diff: %v", tc.name, diff)
		}
		if diff := pretty.Compare(rules, tc.rules); diff != "" {
			t.Errorf("test case %q: got diff: %v", tc.name, diff)
		}
	}
}
