Skip to content

Commit

Permalink
Add ReceiveBufferSize and force option to *Subscribe
Browse files Browse the repository at this point in the history
When there are a large number of existing results for the link, neighbor, and
address subscribe functions with ListExisting are likely to fail with ENOBUFS.
This takes the AddrSubscribeOptions ReceiveBufferSize, already applied
to LinkSubscribeOptions, and applies it to NeighSubscribeOptions and
RouteSubscribeOptions. The ReceiveTimeout option was also added to each.

Added a SetReceiveBufferSize to the nl_linux socket API.

The existing addr_linux subscribe function was modified so instead of setting
the ReceiveBufferSize on the netlink pkghandle, it is set on the socket
associated with the subscription. The new implementations also only change the
receive buffer size on the socket.

Lastly, a new ReceiveBufferForceSize option was applied to all four of the
modified Subscribe functions.
  • Loading branch information
daveset authored and aboch committed Sep 22, 2023
1 parent 3cc961e commit 229a102
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 41 deletions.
34 changes: 18 additions & 16 deletions addr_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,23 +296,24 @@ 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 addrSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil)
return addrSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil, false)
}

// 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 addrSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil)
return addrSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil, false)
}

// AddrSubscribeOptions contains a set of options to use with
// AddrSubscribeWithOptions.
type AddrSubscribeOptions struct {
Namespace *netns.NsHandle
ErrorCallback func(error)
ListExisting bool
ReceiveBufferSize int
ReceiveTimeout *unix.Timeval
Namespace *netns.NsHandle
ErrorCallback func(error)
ListExisting bool
ReceiveBufferSize int
ReceiveBufferForceSize bool
ReceiveTimeout *unix.Timeval
}

// AddrSubscribeWithOptions work like AddrSubscribe but enable to
Expand All @@ -323,10 +324,12 @@ func AddrSubscribeWithOptions(ch chan<- AddrUpdate, done <-chan struct{}, option
none := netns.None()
options.Namespace = &none
}
return addrSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting, options.ReceiveBufferSize, options.ReceiveTimeout)
return addrSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting,
options.ReceiveBufferSize, options.ReceiveTimeout, options.ReceiveBufferForceSize)
}

func addrSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}, cberr func(error), listExisting bool, rcvbuf int, rcvTimeout *unix.Timeval) error {
func addrSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}, cberr func(error), listExisting bool,
rcvbuf int, rcvTimeout *unix.Timeval, rcvBufForce bool) error {
s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_IPV4_IFADDR, unix.RTNLGRP_IPV6_IFADDR)
if err != nil {
return err
Expand All @@ -336,19 +339,18 @@ func addrSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-c
return err
}
}

if rcvbuf != 0 {
err = s.SetReceiveBufferSize(rcvbuf, rcvBufForce)
if err != nil {
return err
}
}
if done != nil {
go func() {
<-done
s.Close()
}()
}
if rcvbuf != 0 {
err = pkgHandle.SetSocketReceiveBufferSize(rcvbuf, false)
if err != nil {
return err
}
}
if listExisting {
req := pkgHandle.newNetlinkRequest(unix.RTM_GETADDR,
unix.NLM_F_DUMP)
Expand Down
37 changes: 23 additions & 14 deletions link_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -2276,22 +2276,24 @@ 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 linkSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0)
return linkSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil, false)
}

// 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 linkSubscribeAt(ns, netns.None(), ch, done, nil, false, 0)
return linkSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil, false)
}

// LinkSubscribeOptions contains a set of options to use with
// LinkSubscribeWithOptions.
type LinkSubscribeOptions struct {
Namespace *netns.NsHandle
ErrorCallback func(error)
ListExisting bool
ReceiveBufferSize int
Namespace *netns.NsHandle
ErrorCallback func(error)
ListExisting bool
ReceiveBufferSize int
ReceiveBufferForceSize bool
ReceiveTimeout *unix.Timeval
}

// LinkSubscribeWithOptions work like LinkSubscribe but enable to
Expand All @@ -2302,14 +2304,27 @@ func LinkSubscribeWithOptions(ch chan<- LinkUpdate, done <-chan struct{}, option
none := netns.None()
options.Namespace = &none
}
return linkSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting, options.ReceiveBufferSize)
return linkSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting,
options.ReceiveBufferSize, options.ReceiveTimeout, options.ReceiveBufferForceSize)
}

func linkSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}, cberr func(error), listExisting bool, rcvbuf int) error {
func linkSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}, cberr func(error), listExisting bool,
rcvbuf int, rcvTimeout *unix.Timeval, rcvbufForce bool) error {
s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_LINK)
if err != nil {
return err
}
if rcvTimeout != nil {
if err := s.SetReceiveTimeout(rcvTimeout); err != nil {
return err
}
}
if rcvbuf != 0 {
err = s.SetReceiveBufferSize(rcvbuf, rcvbufForce)
if err != nil {
return err
}
}
if done != nil {
go func() {
<-done
Expand All @@ -2325,12 +2340,6 @@ func linkSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-c
return err
}
}
if rcvbuf != 0 {
err = pkgHandle.SetSocketReceiveBufferSize(rcvbuf, false)
if err != nil {
return err
}
}
go func() {
defer close(ch)
for {
Expand Down
26 changes: 22 additions & 4 deletions neigh_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,13 +339,13 @@ func NeighDeserialize(m []byte) (*Neigh, error) {
// NeighSubscribe takes a chan down which notifications will be sent
// when neighbors are added or deleted. Close the 'done' chan to stop subscription.
func NeighSubscribe(ch chan<- NeighUpdate, done <-chan struct{}) error {
return neighSubscribeAt(netns.None(), netns.None(), ch, done, nil, false)
return neighSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil, false)
}

// NeighSubscribeAt works like NeighSubscribe plus it allows the caller
// to choose the network namespace in which to subscribe (ns).
func NeighSubscribeAt(ns netns.NsHandle, ch chan<- NeighUpdate, done <-chan struct{}) error {
return neighSubscribeAt(ns, netns.None(), ch, done, nil, false)
return neighSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil, false)
}

// NeighSubscribeOptions contains a set of options to use with
Expand All @@ -354,6 +354,11 @@ type NeighSubscribeOptions struct {
Namespace *netns.NsHandle
ErrorCallback func(error)
ListExisting bool

// max size is based on value of /proc/sys/net/core/rmem_max
ReceiveBufferSize int
ReceiveBufferForceSize bool
ReceiveTimeout *unix.Timeval
}

// NeighSubscribeWithOptions work like NeighSubscribe but enable to
Expand All @@ -364,10 +369,12 @@ func NeighSubscribeWithOptions(ch chan<- NeighUpdate, done <-chan struct{}, opti
none := netns.None()
options.Namespace = &none
}
return neighSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting)
return neighSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting,
options.ReceiveBufferSize, options.ReceiveTimeout, options.ReceiveBufferForceSize)
}

func neighSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- NeighUpdate, done <-chan struct{}, cberr func(error), listExisting bool) error {
func neighSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- NeighUpdate, done <-chan struct{}, cberr func(error), listExisting bool,
rcvbuf int, rcvTimeout *unix.Timeval, rcvbufForce 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)
Expand All @@ -381,6 +388,17 @@ func neighSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- NeighUpdate, done <
if err != nil {
return err
}
if rcvTimeout != nil {
if err := s.SetReceiveTimeout(rcvTimeout); err != nil {
return err
}
}
if rcvbuf != 0 {
err = s.SetReceiveBufferSize(rcvbuf, rcvbufForce)
if err != nil {
return err
}
}
if done != nil {
go func() {
<-done
Expand Down
9 changes: 9 additions & 0 deletions nl/nl_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,15 @@ func (s *NetlinkSocket) SetReceiveTimeout(timeout *unix.Timeval) error {
return unix.SetsockoptTimeval(int(s.fd), unix.SOL_SOCKET, unix.SO_RCVTIMEO, timeout)
}

// SetReceiveBufferSize allows to set a receive buffer size on the socket
func (s *NetlinkSocket) SetReceiveBufferSize(size int, force bool) error {
opt := unix.SO_RCVBUF
if force {
opt = unix.SO_RCVBUFFORCE
}
return unix.SetsockoptInt(int(s.fd), unix.SOL_SOCKET, opt, size)
}

// SetExtAck requests error messages to be reported on the socket
func (s *NetlinkSocket) SetExtAck(enable bool) error {
var enableN int
Expand Down
30 changes: 23 additions & 7 deletions route_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -1458,21 +1458,24 @@ func (h *Handle) RouteGet(destination net.IP) ([]Route, error) {
// RouteSubscribe takes a chan down which notifications will be sent
// when routes are added or deleted. Close the 'done' chan to stop subscription.
func RouteSubscribe(ch chan<- RouteUpdate, done <-chan struct{}) error {
return routeSubscribeAt(netns.None(), netns.None(), ch, done, nil, false)
return routeSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil, false)
}

// RouteSubscribeAt works like RouteSubscribe plus it allows the caller
// to choose the network namespace in which to subscribe (ns).
func RouteSubscribeAt(ns netns.NsHandle, ch chan<- RouteUpdate, done <-chan struct{}) error {
return routeSubscribeAt(ns, netns.None(), ch, done, nil, false)
return routeSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil, false)
}

// RouteSubscribeOptions contains a set of options to use with
// RouteSubscribeWithOptions.
type RouteSubscribeOptions struct {
Namespace *netns.NsHandle
ErrorCallback func(error)
ListExisting bool
Namespace *netns.NsHandle
ErrorCallback func(error)
ListExisting bool
ReceiveBufferSize int
ReceiveBufferForceSize bool
ReceiveTimeout *unix.Timeval
}

// RouteSubscribeWithOptions work like RouteSubscribe but enable to
Expand All @@ -1483,14 +1486,27 @@ func RouteSubscribeWithOptions(ch chan<- RouteUpdate, done <-chan struct{}, opti
none := netns.None()
options.Namespace = &none
}
return routeSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting)
return routeSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting,
options.ReceiveBufferSize, options.ReceiveTimeout, options.ReceiveBufferForceSize)
}

func routeSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RouteUpdate, done <-chan struct{}, cberr func(error), listExisting bool) error {
func routeSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RouteUpdate, done <-chan struct{}, cberr func(error), listExisting bool,
rcvbuf int, rcvTimeout *unix.Timeval, rcvbufForce bool) error {
s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_IPV4_ROUTE, unix.RTNLGRP_IPV6_ROUTE)
if err != nil {
return err
}
if rcvTimeout != nil {
if err := s.SetReceiveTimeout(rcvTimeout); err != nil {
return err
}
}
if rcvbuf != 0 {
err = s.SetReceiveBufferSize(rcvbuf, rcvbufForce)
if err != nil {
return err
}
}
if done != nil {
go func() {
<-done
Expand Down

0 comments on commit 229a102

Please sign in to comment.