// +build linux package netlink import ( "fmt" "net" "testing" "time" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) type arpEntry struct { ip net.IP mac net.HardwareAddr } type proxyEntry struct { ip net.IP dev int } func parseMAC(s string) net.HardwareAddr { m, err := net.ParseMAC(s) if err != nil { panic(err) } return m } func dumpContains(dump []Neigh, e arpEntry) bool { for _, n := range dump { if n.IP.Equal(e.ip) && (n.State&NUD_INCOMPLETE) == 0 { return true } } return false } func dumpContainsNeigh(dump []Neigh, ne Neigh) bool { for _, n := range dump { if n.IP.Equal(ne.IP) && n.LLIPAddr.Equal(ne.LLIPAddr) { return true } } return false } func dumpContainsProxy(dump []Neigh, p proxyEntry) bool { for _, n := range dump { if n.IP.Equal(p.ip) && (n.LinkIndex == p.dev) && (n.Flags&NTF_PROXY) == NTF_PROXY { return true } } return false } func TestNeighAddDelLLIPAddr(t *testing.T) { setUpNetlinkTestWithKModule(t, "ipip") tearDown := setUpNetlinkTest(t) defer tearDown() dummy := Iptun{ LinkAttrs: LinkAttrs{Name: "neigh0"}, PMtuDisc: 1, Local: net.IPv4(127, 0, 0, 1), Remote: net.IPv4(127, 0, 0, 1)} if err := LinkAdd(&dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) entry := Neigh{ LinkIndex: dummy.Index, State: NUD_PERMANENT, IP: net.IPv4(198, 51, 100, 2), LLIPAddr: net.IPv4(198, 51, 100, 1), } err := NeighAdd(&entry) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } // Dump and see that all added entries are there dump, err := NeighList(dummy.Index, 0) if err != nil { t.Errorf("Failed to NeighList: %v", err) } if !dumpContainsNeigh(dump, entry) { t.Errorf("Dump does not contain: %v: %v", entry, dump) } // Delete the entry err = NeighDel(&entry) if err != nil { t.Errorf("Failed to NeighDel: %v", err) } if err := LinkDel(&dummy); err != nil { t.Fatal(err) } } func TestNeighAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() dummy := Dummy{LinkAttrs{Name: "neigh0"}} if err := LinkAdd(&dummy); err != nil { t.Fatal(err) } ensureIndex(dummy.Attrs()) arpTable := []arpEntry{ {net.ParseIP("10.99.0.1"), parseMAC("aa:bb:cc:dd:00:01")}, {net.ParseIP("10.99.0.2"), parseMAC("aa:bb:cc:dd:00:02")}, {net.ParseIP("10.99.0.3"), parseMAC("aa:bb:cc:dd:00:03")}, {net.ParseIP("10.99.0.4"), parseMAC("aa:bb:cc:dd:00:04")}, {net.ParseIP("10.99.0.5"), parseMAC("aa:bb:cc:dd:00:05")}, } // Add the arpTable for _, entry := range arpTable { err := NeighAdd(&Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: entry.ip, HardwareAddr: entry.mac, }) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } } // Dump and see that all added entries are there dump, err := NeighList(dummy.Index, 0) if err != nil { t.Errorf("Failed to NeighList: %v", err) } for _, entry := range arpTable { if !dumpContains(dump, entry) { t.Errorf("Dump does not contain: %v", entry) } } // Delete the arpTable for _, entry := range arpTable { err := NeighDel(&Neigh{ LinkIndex: dummy.Index, IP: entry.ip, HardwareAddr: entry.mac, }) if err != nil { t.Errorf("Failed to NeighDel: %v", err) } } // TODO: seems not working because of cache //// Dump and see that none of deleted entries are there //dump, err = NeighList(dummy.Index, 0) //if err != nil { //t.Errorf("Failed to NeighList: %v", err) //} //for _, entry := range arpTable { //if dumpContains(dump, entry) { //t.Errorf("Dump contains: %v", entry) //} //} if err := LinkDel(&dummy); err != nil { t.Fatal(err) } } func TestNeighAddDelProxy(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() dummy := Dummy{LinkAttrs{Name: "neigh0"}} if err := LinkAdd(&dummy); err != nil { t.Fatal(err) } ensureIndex(dummy.Attrs()) proxyTable := []proxyEntry{ {net.ParseIP("10.99.0.1"), dummy.Index}, {net.ParseIP("10.99.0.2"), dummy.Index}, {net.ParseIP("10.99.0.3"), dummy.Index}, {net.ParseIP("10.99.0.4"), dummy.Index}, {net.ParseIP("10.99.0.5"), dummy.Index}, } // Add the proxyTable for _, entry := range proxyTable { err := NeighAdd(&Neigh{ LinkIndex: dummy.Index, Flags: NTF_PROXY, IP: entry.ip, }) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } } // Dump and see that all added entries are there dump, err := NeighProxyList(dummy.Index, 0) if err != nil { t.Errorf("Failed to NeighList: %v", err) } for _, entry := range proxyTable { if !dumpContainsProxy(dump, entry) { t.Errorf("Dump does not contain: %v", entry) } } // Delete the proxyTable for _, entry := range proxyTable { err := NeighDel(&Neigh{ LinkIndex: dummy.Index, Flags: NTF_PROXY, IP: entry.ip, }) if err != nil { t.Errorf("Failed to NeighDel: %v", err) } } // Dump and see that none of deleted entries are there dump, err = NeighProxyList(dummy.Index, 0) if err != nil { t.Errorf("Failed to NeighList: %v", err) } for _, entry := range proxyTable { if dumpContainsProxy(dump, entry) { t.Errorf("Dump contains: %v", entry) } } if err := LinkDel(&dummy); err != nil { t.Fatal(err) } } func expectNeighUpdate(ch <-chan NeighUpdate, t uint16, state int, ip net.IP) error { for { timeout := time.After(time.Second) select { case update := <-ch: if update.Type != t { return fmt.Errorf("want %d got %d", t, update.Type) } if update.Neigh.State != state { return fmt.Errorf("want %d got %d", state, update.State) } if update.Neigh.IP == nil { return fmt.Errorf("Neigh.IP is nil") } if !update.Neigh.IP.Equal(ip) { return fmt.Errorf("Neigh.IP: want %s got %s", ip, update.Neigh.IP) } return nil case <-timeout: return fmt.Errorf("timeout") } } } func TestNeighSubscribe(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() dummy := &Dummy{LinkAttrs{Name: "neigh0"}} if err := LinkAdd(dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) defer func() { if err := LinkDel(dummy); err != nil { t.Fatal(err) } }() ch := make(chan NeighUpdate) done := make(chan struct{}) defer close(done) if err := NeighSubscribe(ch, done); err != nil { t.Fatal(err) } entry := &Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: net.IPv4(10, 99, 0, 1), HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), } if err := NeighAdd(entry); err != nil { t.Errorf("Failed to NeighAdd: %v", err) } if err := expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_REACHABLE, entry.IP); err != nil { t.Fatalf("Add update not received as expected: %s", err) } if err := NeighDel(entry); err != nil { t.Fatal(err) } if err := expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_FAILED, entry.IP); err != nil { t.Fatalf("Del update not received as expected: %s", err) } } func TestNeighSubscribeWithOptions(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ch := make(chan NeighUpdate) done := make(chan struct{}) defer close(done) var lastError error defer func() { if lastError != nil { t.Fatalf("Fatal error received during subscription: %v", lastError) } }() if err := NeighSubscribeWithOptions(ch, done, NeighSubscribeOptions{ ErrorCallback: func(err error) { lastError = err }, }); err != nil { t.Fatal(err) } dummy := &Dummy{LinkAttrs{Name: "neigh0"}} if err := LinkAdd(dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) defer func() { if err := LinkDel(dummy); err != nil { t.Fatal(err) } }() entry := &Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: net.IPv4(10, 99, 0, 1), HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), } err := NeighAdd(entry) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } if err := expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_REACHABLE, entry.IP); err != nil { t.Fatalf("Add update not received as expected: %s", err) } } func TestNeighSubscribeAt(t *testing.T) { skipUnlessRoot(t) // Create an handle on a custom netns newNs, err := netns.New() if err != nil { t.Fatal(err) } defer newNs.Close() nh, err := NewHandleAt(newNs) if err != nil { t.Fatal(err) } defer nh.Delete() // Subscribe for Neigh events on the custom netns ch := make(chan NeighUpdate) done := make(chan struct{}) defer close(done) if err := NeighSubscribeAt(newNs, ch, done); err != nil { t.Fatal(err) } dummy := &Dummy{LinkAttrs{Name: "neigh0"}} if err := nh.LinkAdd(dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) defer func() { if err := nh.LinkDel(dummy); err != nil { t.Fatal(err) } }() entry := &Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: net.IPv4(198, 51, 100, 1), HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), } err = nh.NeighAdd(entry) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } if err := expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_REACHABLE, entry.IP); err != nil { t.Fatalf("Add update not received as expected: %s", err) } } func TestNeighSubscribeListExisting(t *testing.T) { skipUnlessRoot(t) // Create an handle on a custom netns newNs, err := netns.New() if err != nil { t.Fatal(err) } defer newNs.Close() nh, err := NewHandleAt(newNs) if err != nil { t.Fatal(err) } defer nh.Delete() dummy := &Dummy{LinkAttrs{Name: "neigh0"}} if err := nh.LinkAdd(dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) defer func() { if err := nh.LinkDel(dummy); err != nil { t.Fatal(err) } }() entry1 := &Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: net.IPv4(198, 51, 100, 1), HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), } err = nh.NeighAdd(entry1) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } // Subscribe for Neigh events including existing neighbors ch := make(chan NeighUpdate) done := make(chan struct{}) defer close(done) if err := NeighSubscribeWithOptions(ch, done, NeighSubscribeOptions{ Namespace: &newNs, ListExisting: true}, ); err != nil { t.Fatal(err) } if err := expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_REACHABLE, entry1.IP); err != nil { t.Fatalf("Existing add update not received as expected: %s", err) } entry2 := &Neigh{ LinkIndex: dummy.Index, State: NUD_PERMANENT, IP: net.IPv4(198, 51, 100, 2), HardwareAddr: parseMAC("aa:bb:cc:dd:00:02"), } err = nh.NeighAdd(entry2) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } if err := expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_PERMANENT, entry2.IP); err != nil { t.Fatalf("Existing add update not received as expected: %s", err) } }