diff --git a/addr_linux.go b/addr_linux.go index d171827..8808b42 100644 --- a/addr_linux.go +++ b/addr_linux.go @@ -236,16 +236,34 @@ type AddrUpdate struct { // AddrSubscribe takes a chan down which notifications will be sent // when addresses change. Close the 'done' chan to stop subscription. func AddrSubscribe(ch chan<- AddrUpdate, done <-chan struct{}) error { - return addrSubscribe(netns.None(), netns.None(), ch, done, nil) + return addrSubscribeAt(netns.None(), netns.None(), ch, done, nil) } // AddrSubscribeAt works like AddrSubscribe plus it allows the caller // to choose the network namespace in which to subscribe (ns). func AddrSubscribeAt(ns netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}) error { - return addrSubscribe(ns, netns.None(), ch, done, nil) + return addrSubscribeAt(ns, netns.None(), ch, done, nil) } -func addrSubscribe(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}, cberr func(error)) error { +// AddrSubscribeOptions contains a set of options to use with +// AddrSubscribeWithOptions. +type AddrSubscribeOptions struct { + Namespace *netns.NsHandle + ErrorCallback func(error) +} + +// AddrSubscribeWithOptions work like AddrSubscribe but enable to +// provide additional options to modify the behavior. Currently, the +// namespace can be provided as well as an error callback. +func AddrSubscribeWithOptions(ch chan<- AddrUpdate, done <-chan struct{}, options AddrSubscribeOptions) error { + if options.Namespace == nil { + none := netns.None() + options.Namespace = &none + } + return addrSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback) +} + +func addrSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}, cberr func(error)) error { s, err := nl.SubscribeAt(newNs, curNs, syscall.NETLINK_ROUTE, syscall.RTNLGRP_IPV4_IFADDR, syscall.RTNLGRP_IPV6_IFADDR) if err != nil { return err diff --git a/addr_test.go b/addr_test.go index cd7fed4..1c4021f 100644 --- a/addr_test.go +++ b/addr_test.go @@ -7,6 +7,7 @@ import ( "os" "syscall" "testing" + "time" ) func TestAddrAdd(t *testing.T) { @@ -191,3 +192,55 @@ func TestAddrAddReplace(t *testing.T) { t.Fatal("Address not removed properly") } } + +func expectAddrUpdate(ch <-chan AddrUpdate, add bool, dst net.IP) bool { + for { + timeout := time.After(time.Minute) + select { + case update := <-ch: + if update.NewAddr == add && update.LinkAddress.IP.Equal(dst) { + return true + } + case <-timeout: + return false + } + } +} + +func TestAddrSubscribeWithOptions(t *testing.T) { + tearDown := setUpNetlinkTest(t) + defer tearDown() + + ch := make(chan AddrUpdate) + 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 := AddrSubscribeWithOptions(ch, done, AddrSubscribeOptions{ + ErrorCallback: func(err error) { + lastError = err + }, + }); err != nil { + t.Fatal(err) + } + + // get loopback interface + link, err := LinkByName("lo") + if err != nil { + t.Fatal(err) + } + + // bring the interface up + if err = LinkSetUp(link); err != nil { + t.Fatal(err) + } + + ip := net.IPv4(127, 0, 0, 1) + if !expectAddrUpdate(ch, true, ip) { + t.Fatal("Add update not received as expected") + } +} diff --git a/link_linux.go b/link_linux.go index 931055e..2c9c6ca 100644 --- a/link_linux.go +++ b/link_linux.go @@ -1328,16 +1328,34 @@ type LinkUpdate struct { // LinkSubscribe takes a chan down which notifications will be sent // when links change. Close the 'done' chan to stop subscription. func LinkSubscribe(ch chan<- LinkUpdate, done <-chan struct{}) error { - return linkSubscribe(netns.None(), netns.None(), ch, done, nil) + return linkSubscribeAt(netns.None(), netns.None(), ch, done, nil) } // LinkSubscribeAt works like LinkSubscribe plus it allows the caller // to choose the network namespace in which to subscribe (ns). func LinkSubscribeAt(ns netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}) error { - return linkSubscribe(ns, netns.None(), ch, done, nil) + return linkSubscribeAt(ns, netns.None(), ch, done, nil) } -func linkSubscribe(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}, cberr func(error)) error { +// LinkSubscribeOptions contains a set of options to use with +// LinkSubscribeWithOptions. +type LinkSubscribeOptions struct { + Namespace *netns.NsHandle + ErrorCallback func(error) +} + +// LinkSubscribeWithOptions work like LinkSubscribe but enable to +// provide additional options to modify the behavior. Currently, the +// namespace can be provided as well as an error callback. +func LinkSubscribeWithOptions(ch chan<- LinkUpdate, done <-chan struct{}, options LinkSubscribeOptions) error { + if options.Namespace == nil { + none := netns.None() + options.Namespace = &none + } + return linkSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback) +} + +func linkSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}, cberr func(error)) error { s, err := nl.SubscribeAt(newNs, curNs, syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK) if err != nil { return err diff --git a/link_test.go b/link_test.go index f5850ab..a78e09d 100644 --- a/link_test.go +++ b/link_test.go @@ -1038,6 +1038,37 @@ func TestLinkSubscribe(t *testing.T) { } } +func TestLinkSubscribeWithOptions(t *testing.T) { + tearDown := setUpNetlinkTest(t) + defer tearDown() + + ch := make(chan LinkUpdate) + 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 := LinkSubscribeWithOptions(ch, done, LinkSubscribeOptions{ + ErrorCallback: func(err error) { + lastError = err + }, + }); err != nil { + t.Fatal(err) + } + + link := &Veth{LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1400}, "bar"} + if err := LinkAdd(link); err != nil { + t.Fatal(err) + } + + if !expectLinkUpdate(ch, "foo", false) { + t.Fatal("Add update not received as expected") + } +} + func TestLinkSubscribeAt(t *testing.T) { skipUnlessRoot(t) diff --git a/route_linux.go b/route_linux.go index ce19549..9234c69 100644 --- a/route_linux.go +++ b/route_linux.go @@ -687,6 +687,24 @@ func RouteSubscribeAt(ns netns.NsHandle, ch chan<- RouteUpdate, done <-chan stru return routeSubscribeAt(ns, netns.None(), ch, done, nil) } +// RouteSubscribeOptions contains a set of options to use with +// RouteSubscribeWithOptions. +type RouteSubscribeOptions struct { + Namespace *netns.NsHandle + ErrorCallback func(error) +} + +// RouteSubscribeWithOptions work like RouteSubscribe but enable to +// provide additional options to modify the behavior. Currently, the +// namespace can be provided as well as an error callback. +func RouteSubscribeWithOptions(ch chan<- RouteUpdate, done <-chan struct{}, options RouteSubscribeOptions) error { + if options.Namespace == nil { + none := netns.None() + options.Namespace = &none + } + return routeSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback) +} + func routeSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RouteUpdate, done <-chan struct{}, cberr func(error)) error { s, err := nl.SubscribeAt(newNs, curNs, syscall.NETLINK_ROUTE, syscall.RTNLGRP_IPV4_ROUTE, syscall.RTNLGRP_IPV6_ROUTE) if err != nil { diff --git a/route_test.go b/route_test.go index 4655a15..acbada4 100644 --- a/route_test.go +++ b/route_test.go @@ -210,6 +210,55 @@ func TestRouteSubscribe(t *testing.T) { } } +func TestRouteSubscribeWithOptions(t *testing.T) { + tearDown := setUpNetlinkTest(t) + defer tearDown() + + ch := make(chan RouteUpdate) + 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 := RouteSubscribeWithOptions(ch, done, RouteSubscribeOptions{ + ErrorCallback: func(err error) { + lastError = err + }, + }); err != nil { + t.Fatal(err) + } + + // get loopback interface + link, err := LinkByName("lo") + if err != nil { + t.Fatal(err) + } + + // bring the interface up + if err = LinkSetUp(link); err != nil { + t.Fatal(err) + } + + // add a gateway route + dst := &net.IPNet{ + IP: net.IPv4(192, 168, 0, 0), + Mask: net.CIDRMask(24, 32), + } + + ip := net.IPv4(127, 1, 1, 1) + route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} + if err := RouteAdd(&route); err != nil { + t.Fatal(err) + } + + if !expectRouteUpdate(ch, syscall.RTM_NEWROUTE, dst.IP) { + t.Fatal("Add update not received as expected") + } +} + func TestRouteSubscribeAt(t *testing.T) { skipUnlessRoot(t)