From 5cfa69a3d38ec69dc8c1c8546ae6f467ec904f00 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Tue, 12 Mar 2024 10:39:12 +0000 Subject: [PATCH] feat: Handle OobData Adds OobData []byte to the Message struct. During the Recieve() function, this field is populated with the raw bytes of any control messages recieved from the recvmsg call. An example of how to process the OobData field has been included. This feature requires the user to set EnableControlMessages in the Config. This is to avoid allocating an additional page per recvmsg call unless it's absolutely necessary. Signed-off-by: Dave Tucker --- align.go | 5 +++++ conn.go | 6 ++++++ conn_linux.go | 27 +++++++++++++++-------- example_test.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ message.go | 6 ++++-- 5 files changed, 90 insertions(+), 11 deletions(-) diff --git a/align.go b/align.go index 20892c7..2aaa4ef 100644 --- a/align.go +++ b/align.go @@ -35,3 +35,8 @@ const sizeofAttribute = 4 // #define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr))) var nlaHeaderLen = nlaAlign(sizeofAttribute) + +// #define CMSG_ALIGN(len) ( ((len)+sizeof(long)-1) & ~(sizeof(long)-1) ) +func cmsgAlign(len int) int { + return ((len) + int(unsafe.Sizeof(int(0))-1)) & ^(int(unsafe.Sizeof(int(0)) - 1)) +} diff --git a/conn.go b/conn.go index 7138665..c814eb9 100644 --- a/conn.go +++ b/conn.go @@ -590,4 +590,10 @@ type Config struct { // When possible, setting Strict to true is recommended for applications // running on modern Linux kernels. Strict bool + + // EnableControlMessages enables the use of control messages for + // netlink. This option is intended for advanced use cases where the + // caller needs to receive control messages that contain ancillary data. + // For example, when using options like `ListenAllNSID`. + EnableControlMessages bool } diff --git a/conn_linux.go b/conn_linux.go index 4af18c9..d380c0b 100644 --- a/conn_linux.go +++ b/conn_linux.go @@ -19,7 +19,8 @@ var _ Socket = &conn{} // A conn is the Linux implementation of a netlink sockets connection. type conn struct { - s *socket.Conn + s *socket.Conn + enableControlMessages bool } // dial is the entry point for Dial. dial opens a netlink socket using @@ -70,7 +71,7 @@ func newConn(s *socket.Conn, config *Config) (*conn, uint32, error) { return nil, 0, err } - c := &conn{s: s} + c := &conn{s: s, enableControlMessages: config.EnableControlMessages} if config.Strict { // The caller has requested the strict option set. Historically we have // recommended checking for ENOPROTOOPT if the kernel does not support @@ -124,9 +125,6 @@ func (c *conn) Receive() ([]Message, error) { b := make([]byte, os.Getpagesize()) for { // Peek at the buffer to see how many bytes are available. - // - // TODO(mdlayher): deal with OOB message data if available, such as - // when PacketInfo ConnOption is true. n, _, _, _, err := c.s.Recvmsg(context.Background(), b, nil, unix.MSG_PEEK) if err != nil { return nil, err @@ -141,12 +139,23 @@ func (c *conn) Receive() ([]Message, error) { b = make([]byte, len(b)*2) } + // Only allocate a buffer for control messages if they are enabled. + var oob []byte + if c.enableControlMessages { + oob = make([]byte, os.Getpagesize()) + } + // Read out all available messages - n, _, _, _, err := c.s.Recvmsg(context.Background(), b, nil, 0) + n, oobn, _, _, err := c.s.Recvmsg(context.Background(), b, oob, 0) if err != nil { return nil, err } + var rawOob []byte + if c.enableControlMessages { + rawOob = oob[:cmsgAlign(oobn)] + } + raw, err := syscall.ParseNetlinkMessage(b[:nlmsgAlign(n)]) if err != nil { return nil, err @@ -155,10 +164,10 @@ func (c *conn) Receive() ([]Message, error) { msgs := make([]Message, 0, len(raw)) for _, r := range raw { m := Message{ - Header: sysToHeader(r.Header), - Data: r.Data, + Header: sysToHeader(r.Header), + Data: r.Data, + OobData: rawOob, } - msgs = append(msgs, m) } diff --git a/example_test.go b/example_test.go index c1d641c..4d1b321 100644 --- a/example_test.go +++ b/example_test.go @@ -6,6 +6,7 @@ import ( "github.com/mdlayher/netlink" "github.com/mdlayher/netlink/nlenc" "github.com/mdlayher/netlink/nltest" + "golang.org/x/sys/unix" ) // This example demonstrates using a netlink.Conn to execute requests against @@ -108,3 +109,59 @@ func exampleAttributes() []byte { }, }) } + +func ExampleConn_listenMulticastAllNSID() { + const ( + // Speak to route netlink using netlink + familyRoute = 0 + + // Listen for events triggered by addition or deletion of + // network interfaces + rtmGroupLink = 0x1 + ) + + c, err := netlink.Dial(familyRoute, &netlink.Config{ + // Groups is a bitmask; more than one group can be specified + // by OR'ing multiple group values together + Groups: rtmGroupLink, + // Enable control messages to receive the netnsid + // since we're going to set NETLINK_LISTEN_ALL_NSID + EnableControlMessages: true, + }) + if err != nil { + log.Fatalf("failed to dial netlink: %v", err) + } + defer c.Close() + c.SetOption(netlink.ListenAllNSID, true) + + for { + // Listen for netlink messages triggered by multicast groups + msgs, err := c.Receive() + if err != nil { + log.Fatalf("failed to receive messages: %v", err) + } + + // Iterate over recieved messages and print them + for _, msg := range msgs { + // If the message contains oob data, parse it and print the netnsid + if msg.OobData != nil { + // ParseSocketControlMessage returns a slice of ControlMessages + cmsg, err := unix.ParseSocketControlMessage(msg.OobData) + if err != nil { + log.Printf("Error parsing oob data: %v", err) + continue + } + // Iterate over the control messages, find the + // one with level SOL_NETLINK and type NETLINK_LISTEN_ALL_NSID + for _, oob := range cmsg { + if oob.Header.Level == unix.SOL_NETLINK && oob.Header.Type == unix.NETLINK_LISTEN_ALL_NSID { + netnsid := int(oob.Data[0]) + log.Printf("netnsid: %d", netnsid) + break + } + } + } + log.Printf("msg: %+v", msg) + } + } +} diff --git a/message.go b/message.go index 5727716..edfdceb 100644 --- a/message.go +++ b/message.go @@ -194,12 +194,14 @@ type Header struct { // A Message is a netlink message. It contains a Header and an arbitrary // byte payload, which may be decoded using information from the Header. +// It may also contain out-of-band data, which was sent along with the message. // // Data is often populated with netlink attributes. For easy encoding and // decoding of attributes, see the AttributeDecoder and AttributeEncoder types. type Message struct { - Header Header - Data []byte + Header Header + Data []byte + OobData []byte } // MarshalBinary marshals a Message into a byte slice.