diff --git a/align.go b/align.go index 20892c70..2aaa4eff 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 7138665b..c814eb93 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 4af18c99..d380c0b9 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 c1d641c1..4d1b321e 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 57277165..edfdcebf 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.