diff --git a/link.go b/link.go index 0b42224..db05edb 100644 --- a/link.go +++ b/link.go @@ -35,6 +35,7 @@ type LinkAttrs struct { Promisc int Xdp *LinkXdp EncapType string + Protinfo *Protinfo } // NewLinkAttrs returns LinkAttrs structure filled with default values diff --git a/link_linux.go b/link_linux.go index 9fdb45a..df37cda 100644 --- a/link_linux.go +++ b/link_linux.go @@ -953,7 +953,7 @@ func execGetLink(req *nl.NetlinkRequest) (Link, error) { return nil, fmt.Errorf("Link not found") case len(msgs) == 1: - return LinkDeserialize(msgs[0]) + return LinkDeserialize(nil, msgs[0]) default: return nil, fmt.Errorf("More than one link found") @@ -962,7 +962,7 @@ func execGetLink(req *nl.NetlinkRequest) (Link, error) { // linkDeserialize deserializes a raw message received from netlink into // a link object. -func LinkDeserialize(m []byte) (Link, error) { +func LinkDeserialize(hdr *syscall.NlMsghdr, m []byte) (Link, error) { msg := nl.DeserializeIfInfomsg(m) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) @@ -1074,6 +1074,15 @@ func LinkDeserialize(m []byte) (Link, error) { return nil, err } base.Xdp = xdp + case syscall.IFLA_PROTINFO | syscall.NLA_F_NESTED: + if hdr != nil && hdr.Type == syscall.RTM_NEWLINK && + msg.Family == syscall.AF_BRIDGE { + attrs, err := nl.ParseRouteAttr(attr.Value[:]) + if err != nil { + return nil, err + } + base.Protinfo = parseProtinfo(attrs) + } } } // Links that don't have IFLA_INFO_KIND are hardware devices @@ -1108,7 +1117,7 @@ func (h *Handle) LinkList() ([]Link, error) { var res []Link for _, m := range msgs { - link, err := LinkDeserialize(m) + link, err := LinkDeserialize(nil, m) if err != nil { return nil, err } @@ -1157,7 +1166,7 @@ func linkSubscribe(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-cha } for _, m := range msgs { ifmsg := nl.DeserializeIfInfomsg(m.Data) - link, err := LinkDeserialize(m.Data) + link, err := LinkDeserialize(&m.Header, m.Data) if err != nil { return } diff --git a/link_test.go b/link_test.go index bf79231..4a4fa24 100644 --- a/link_test.go +++ b/link_test.go @@ -1006,3 +1006,55 @@ func TestLinkAddDelVti(t *testing.T) { Local: net.IPv4(127, 0, 0, 1), Remote: net.IPv4(127, 0, 0, 1)}) } + +func TestLinkSubscribeWithProtinfo(t *testing.T) { + tearDown := setUpNetlinkTest(t) + defer tearDown() + + master := &Bridge{LinkAttrs{Name: "foo"}} + if err := LinkAdd(master); err != nil { + t.Fatal(err) + } + + slave := &Veth{ + LinkAttrs: LinkAttrs{ + Name: "bar", + TxQLen: testTxQLen, + MTU: 1400, + MasterIndex: master.Attrs().Index, + }, + PeerName: "bar-peer", + } + if err := LinkAdd(slave); err != nil { + t.Fatal(err) + } + + ch := make(chan LinkUpdate) + done := make(chan struct{}) + defer close(done) + if err := LinkSubscribe(ch, done); err != nil { + t.Fatal(err) + } + + if err := LinkSetHairpin(slave, true); err != nil { + t.Fatal(err) + } + + select { + case update := <-ch: + if !(update.Attrs().Name == "bar" && update.Attrs().Protinfo != nil && + update.Attrs().Protinfo.Hairpin) { + t.Fatal("Hairpin update not received as expected") + } + case <-time.After(time.Minute): + t.Fatal("Hairpin update timed out") + } + + if err := LinkDel(slave); err != nil { + t.Fatal(err) + } + + if err := LinkDel(master); err != nil { + t.Fatal(err) + } +} diff --git a/protinfo_linux.go b/protinfo_linux.go index 5b95d76..ea72695 100644 --- a/protinfo_linux.go +++ b/protinfo_linux.go @@ -40,25 +40,31 @@ func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) { if err != nil { return pi, err } - var pi Protinfo - for _, info := range infos { - switch info.Attr.Type { - case nl.IFLA_BRPORT_MODE: - pi.Hairpin = byteToBool(info.Value[0]) - case nl.IFLA_BRPORT_GUARD: - pi.Guard = byteToBool(info.Value[0]) - case nl.IFLA_BRPORT_FAST_LEAVE: - pi.FastLeave = byteToBool(info.Value[0]) - case nl.IFLA_BRPORT_PROTECT: - pi.RootBlock = byteToBool(info.Value[0]) - case nl.IFLA_BRPORT_LEARNING: - pi.Learning = byteToBool(info.Value[0]) - case nl.IFLA_BRPORT_UNICAST_FLOOD: - pi.Flood = byteToBool(info.Value[0]) - } - } + pi = *parseProtinfo(infos) + return pi, nil } } return pi, fmt.Errorf("Device with index %d not found", base.Index) } + +func parseProtinfo(infos []syscall.NetlinkRouteAttr) *Protinfo { + var pi Protinfo + for _, info := range infos { + switch info.Attr.Type { + case nl.IFLA_BRPORT_MODE: + pi.Hairpin = byteToBool(info.Value[0]) + case nl.IFLA_BRPORT_GUARD: + pi.Guard = byteToBool(info.Value[0]) + case nl.IFLA_BRPORT_FAST_LEAVE: + pi.FastLeave = byteToBool(info.Value[0]) + case nl.IFLA_BRPORT_PROTECT: + pi.RootBlock = byteToBool(info.Value[0]) + case nl.IFLA_BRPORT_LEARNING: + pi.Learning = byteToBool(info.Value[0]) + case nl.IFLA_BRPORT_UNICAST_FLOOD: + pi.Flood = byteToBool(info.Value[0]) + } + } + return &pi +}