//go:build integration package multinet import ( "net" "net/netip" "runtime" "testing" "github.com/stretchr/testify/require" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" ) func TestDialer(t *testing.T) { runInNewNamespace(t, "2 interfaces with multiple routes in different subnets", func(t *testing.T, ns netns.NsHandle) { setup(t, map[string][]string{ "testdev1": {"1.2.30.10/23", "4.4.4.4/8"}, "testdev2": {"1.2.30.11/23", "4.4.4.5/8"}, }) // Do not use `t.Run` because everything should be executed in a single OS thread. { // Restrict to a single subnet. d, err := NewDialer(Config{ Subnets: []string{"1.2.30.0/23"}, }) require.NoError(t, err) require.Equal(t, []Subnet{ { Mask: netip.MustParsePrefix("1.2.30.0/23"), Interfaces: []Source{ {Name: "testdev1", LocalAddr: &net.TCPAddr{IP: net.IP{1, 2, 30, 10}}}, {Name: "testdev2", LocalAddr: &net.TCPAddr{IP: net.IP{1, 2, 30, 11}}}, }, }, }, d.(*dialer).subnets) } { // Restrict to two subnets. d, err := NewDialer(Config{ Subnets: []string{"1.2.30.0/23", "4.0.0.0/8"}, }) require.NoError(t, err) require.Equal(t, []Subnet{ { Mask: netip.MustParsePrefix("1.2.30.0/23"), Interfaces: []Source{ {Name: "testdev1", LocalAddr: &net.TCPAddr{IP: net.IP{1, 2, 30, 10}}}, {Name: "testdev2", LocalAddr: &net.TCPAddr{IP: net.IP{1, 2, 30, 11}}}, }, }, { Mask: netip.MustParsePrefix("4.0.0.0/8"), Interfaces: []Source{ {Name: "testdev1", LocalAddr: &net.TCPAddr{IP: net.IP{4, 4, 4, 4}}}, {Name: "testdev2", LocalAddr: &net.TCPAddr{IP: net.IP{4, 4, 4, 5}}}, }, }, }, d.(*dialer).subnets) } }) runInNewNamespace(t, "4 interfaces, 2 for data, 2 internal", func(t *testing.T, ns netns.NsHandle) { setup(t, map[string][]string{ "internal1": {"192.168.0.1/16"}, "internal2": {"192.168.0.2/16"}, "data1": {"10.11.12.101/24"}, "data2": {"10.11.12.102/24"}, }) d, err := NewDialer(Config{ Subnets: []string{"10.11.12.0/24", "192.168.0.0/16"}, }) require.NoError(t, err) require.Equal(t, []Subnet{ { Mask: netip.MustParsePrefix("10.11.12.0/24"), Interfaces: []Source{ {Name: "data1", LocalAddr: &net.TCPAddr{IP: net.IP{10, 11, 12, 101}}}, {Name: "data2", LocalAddr: &net.TCPAddr{IP: net.IP{10, 11, 12, 102}}}, }, }, { Mask: netip.MustParsePrefix("192.168.0.0/16"), Interfaces: []Source{ {Name: "internal1", LocalAddr: &net.TCPAddr{IP: net.IP{192, 168, 0, 1}}}, {Name: "internal2", LocalAddr: &net.TCPAddr{IP: net.IP{192, 168, 0, 2}}}, }, }, }, d.(*dialer).subnets) }) runInNewNamespace(t, "with ipv6", func(t *testing.T, ns netns.NsHandle) { addr1 := "2001:db8:85a3:8d3:1319:8a2e:370:7348/64" addr2 := "2001:db8:85a3:8d3:1319:8a2e:370:8192/64" setup(t, map[string][]string{ "testdev1": {addr1}, "testdev2": {addr2}, }) // Do not use `t.Run` because everything should be executed in a single OS thread. { // Restrict to a single subnet. d, err := NewDialer(Config{ Subnets: []string{"2001:db8:85a3:8d3::/64"}, }) require.NoError(t, err) require.Equal(t, []Subnet{ { Mask: netip.MustParsePrefix("2001:db8:85a3:8d3::/64"), Interfaces: []Source{ {Name: "testdev1", LocalAddr: mustParseIPv6(t, addr1)}, {Name: "testdev2", LocalAddr: mustParseIPv6(t, addr2)}, }, }, }, d.(*dialer).subnets) } }) } func mustParseIPv6(t *testing.T, s string) *net.TCPAddr { ip, _, err := net.ParseCIDR(s) require.NoError(t, err) return &net.TCPAddr{IP: ip} } func setup(t *testing.T, config map[string][]string) { for name, ips := range config { link := createLink(t, name) for i := range ips { ip, err := netlink.ParseIPNet(ips[i]) require.NoError(t, err) require.NoError(t, netlink.AddrAdd(link, &netlink.Addr{IPNet: ip})) } } } func createLink(t *testing.T, name string) netlink.Link { require.NoError(t, netlink.LinkAdd(&netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: name}})) link, err := netlink.LinkByName(name) require.NoError(t, err) require.NoError(t, netlink.LinkSetUp(link)) return link } func runInNewNamespace(t *testing.T, name string, f func(t *testing.T, ns netns.NsHandle)) { t.Run(name, func(t *testing.T) { // To avoid messing with host network settings, // we create a new names space and execute tests in it. // Switching thread can move us to a different namespace, thus this line. runtime.LockOSThread() defer runtime.UnlockOSThread() origns, err := netns.Get() require.NoError(t, err) defer origns.Close() defer netns.Set(origns) newns, err := netns.New() require.NoError(t, err) defer newns.Close() f(t, newns) }) }