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>
This commit is contained in:
Nicolas Belouin 2018-12-21 09:18:13 +01:00 committed by Alessandro Boch
parent 941b4de9e1
commit a1c9a648f7
3 changed files with 108 additions and 15 deletions

View File

@ -17,6 +17,7 @@ type Neigh struct {
LLIPAddr net.IP //Used in the case of NHRP
Vlan int
VNI int
MasterIndex int
}
// String returns $ip/$hwaddr $label

View File

@ -21,7 +21,10 @@ const (
NDA_PORT
NDA_VNI
NDA_IFINDEX
NDA_MAX = NDA_IFINDEX
NDA_MASTER
NDA_LINK_NETNSID
NDA_SRC_VNI
NDA_MAX = NDA_SRC_VNI
)
// Neighbor Cache Entry States.
@ -174,6 +177,11 @@ func neighHandle(neigh *Neigh, req *nl.NetlinkRequest) error {
req.AddData(vniData)
}
if neigh.MasterIndex != 0 {
masterData := nl.NewRtAttr(NDA_MASTER, nl.Uint32Attr(uint32(neigh.MasterIndex)))
req.AddData(masterData)
}
_, err := req.Execute(unix.NETLINK_ROUTE, 0)
return err
}
@ -289,6 +297,8 @@ func NeighDeserialize(m []byte) (*Neigh, error) {
neigh.Vlan = int(native.Uint16(attr.Value[0:2]))
case NDA_VNI:
neigh.VNI = int(native.Uint32(attr.Value[0:4]))
case NDA_MASTER:
neigh.MasterIndex = int(native.Uint32(attr.Value[0:4]))
}
}
@ -328,6 +338,16 @@ func NeighSubscribeWithOptions(ch chan<- NeighUpdate, done <-chan struct{}, opti
func neighSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- NeighUpdate, done <-chan struct{}, cberr func(error), listExisting bool) error {
s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_NEIGH)
makeRequest := func(family int) error {
req := pkgHandle.newNetlinkRequest(unix.RTM_GETNEIGH,
unix.NLM_F_DUMP)
infmsg := nl.NewIfInfomsg(family)
req.AddData(infmsg)
if err := s.Send(req); err != nil {
return err
}
return nil
}
if err != nil {
return err
}
@ -338,13 +358,10 @@ func neighSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- NeighUpdate, done <
}()
}
if listExisting {
req := pkgHandle.newNetlinkRequest(unix.RTM_GETNEIGH,
unix.NLM_F_DUMP)
infmsg := nl.NewIfInfomsg(unix.AF_UNSPEC)
req.AddData(infmsg)
if err := s.Send(req); err != nil {
if err := makeRequest(unix.AF_UNSPEC); err != nil {
return err
}
// We have to wait for NLMSG_DONE before making AF_BRIDGE request
}
go func() {
defer close(ch)
@ -364,6 +381,18 @@ func neighSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- NeighUpdate, done <
}
for _, m := range msgs {
if m.Header.Type == unix.NLMSG_DONE {
if listExisting {
// This will be called after handling AF_UNSPEC
// list request, we have to wait for NLMSG_DONE
// before making another request
if err := makeRequest(unix.AF_BRIDGE); err != nil {
if cberr != nil {
cberr(err)
}
return
}
listExisting = false
}
continue
}
if m.Header.Type == unix.NLMSG_ERROR {

View File

@ -4,6 +4,7 @@ package netlink
import (
"net"
"syscall"
"testing"
"time"
@ -255,13 +256,25 @@ func TestNeighAddDelProxy(t *testing.T) {
}
}
// expectNeighUpdate returns whether the expected updated is received within one second.
func expectNeighUpdate(ch <-chan NeighUpdate, t uint16, state int, ip net.IP) bool {
// 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:
if update.Type == t && update.Neigh.State == state && update.Neigh.IP != nil && update.Neigh.IP.Equal(ip) {
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:
@ -302,13 +315,21 @@ func TestNeighSubscribe(t *testing.T) {
if err := NeighAdd(entry); err != nil {
t.Errorf("Failed to NeighAdd: %v", err)
}
if !expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_REACHABLE, entry.IP) {
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, unix.RTM_NEWNEIGH, NUD_FAILED, entry.IP) {
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")
}
}
@ -356,7 +377,10 @@ func TestNeighSubscribeWithOptions(t *testing.T) {
if err != nil {
t.Errorf("Failed to NeighAdd: %v", err)
}
if !expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_REACHABLE, entry.IP) {
if !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: *entry,
}}) {
t.Fatalf("Add update not received as expected")
}
}
@ -407,7 +431,10 @@ func TestNeighSubscribeAt(t *testing.T) {
if err != nil {
t.Errorf("Failed to NeighAdd: %v", err)
}
if !expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_REACHABLE, entry.IP) {
if !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: *entry,
}}) {
t.Fatalf("Add update not received as expected")
}
}
@ -439,6 +466,17 @@ func TestNeighSubscribeListExisting(t *testing.T) {
}
}()
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,
@ -446,10 +484,23 @@ func TestNeighSubscribeListExisting(t *testing.T) {
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)
@ -462,7 +513,16 @@ func TestNeighSubscribeListExisting(t *testing.T) {
t.Fatal(err)
}
if !expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_REACHABLE, entry1.IP) {
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")
}
@ -478,7 +538,10 @@ func TestNeighSubscribeListExisting(t *testing.T) {
t.Errorf("Failed to NeighAdd: %v", err)
}
if !expectNeighUpdate(ch, unix.RTM_NEWNEIGH, NUD_PERMANENT, entry2.IP) {
if !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{
Type: unix.RTM_NEWNEIGH,
Neigh: *entry2,
}}) {
t.Fatalf("Existing add update not received as expected")
}
}