netlink/neigh_test.go
Nicolas Belouin a1c9a648f7 neighSubscribeAt: AF_BRIDGE entries not listed when listExisting is true
When subscribing to neigh updates, the updates for all neigh
protocol families are received. However when listExisting is set,
the request is made with AF_UNSPEC family, this request does not
include AF_BRIDGE entries.

This patch add a second request for AF_BRIDGE entries.

Add test for existing AF_BRIDGE entry and make expectNeighUpdate
take a slice of expected updates

Creates a VXLAN interface for this test as its AF_BRIDGE entries
looks a lot like usual ones

Also add support for latest (2014+) neighbour attributes

NDA_MASTER was added back in 2014, it indicates whether a neigh
entry is linked to a master interface and index of this interface.

The other entries, namely NDA_LINK_NETNSID and NDA_SRC_VNI were
added later and will need extra handling.

Signed-off-by: Nicolas Belouin <nicolas.belouin@gandi.net>
2019-08-23 11:29:04 -07:00

548 lines
12 KiB
Go

// +build linux
package netlink
import (
"net"
"syscall"
"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, "ip_gre")
tearDown := setUpNetlinkTest(t)
defer tearDown()
dummy := Gretun{
LinkAttrs: LinkAttrs{Name: "neigh0"},
Local: net.IPv4(127, 0, 0, 1),
IKey: 1234,
OKey: 1234}
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)
}
}
// expectNeighUpdate returns whether the expected updates are received within one second.
func expectNeighUpdate(ch <-chan NeighUpdate, expected []NeighUpdate) bool {
for {
timeout := time.After(time.Second)
select {
case update := <-ch:
var toDelete []int
for index, elem := range expected {
if update.Type == elem.Type &&
update.Neigh.State == elem.Neigh.State &&
update.Neigh.IP != nil &&
update.Neigh.IP.Equal(elem.Neigh.IP) {
toDelete = append(toDelete, index)
}
}
for done, index := range toDelete {
expected = append(expected[:index-done], expected[index-done+1:]...)
}
if len(expected) == 0 {
return true
}
case <-timeout:
return false
}
}
}
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 !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: *entry,
}}) {
t.Fatalf("Add update not received as expected")
}
if err := NeighDel(entry); err != nil {
t.Fatal(err)
}
if !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: Neigh{
State: NUD_FAILED,
IP: entry.IP},
}}) {
t.Fatalf("Del update not received as expected")
}
}
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 !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: *entry,
}}) {
t.Fatalf("Add update not received as expected")
}
}
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 !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: *entry,
}}) {
t.Fatalf("Add update not received as expected")
}
}
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)
}
}()
vxlani := &Vxlan{LinkAttrs: LinkAttrs{Name: "neigh1"}, VxlanId: 1}
if err := nh.LinkAdd(vxlani); err != nil {
t.Errorf("Failed to create link: %v", err)
}
ensureIndex(vxlani.Attrs())
defer func() {
if err := nh.LinkDel(vxlani); 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"),
}
entryBr := &Neigh{
Family: syscall.AF_BRIDGE,
LinkIndex: vxlani.Index,
State: NUD_PERMANENT,
Flags: NTF_SELF,
IP: net.IPv4(198, 51, 100, 3),
HardwareAddr: parseMAC("aa:bb:cc:dd:00:03"),
}
err = nh.NeighAdd(entry1)
if err != nil {
t.Errorf("Failed to NeighAdd: %v", err)
}
err = nh.NeighAppend(entryBr)
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 !expectNeighUpdate(ch, []NeighUpdate{
NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: *entry1,
},
NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: *entryBr,
},
}) {
t.Fatalf("Existing add update not received as expected")
}
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 !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: *entry2,
}}) {
t.Fatalf("Existing add update not received as expected")
}
}