| // Package psample provides a receiver for sampled network packets using the | 
 | // Netlink psample interface. | 
 | package psample | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 |  | 
 | 	"github.com/mdlayher/genetlink" | 
 | 	"github.com/mdlayher/netlink" | 
 | ) | 
 |  | 
 | // attrId identifies psample netlink message attributes. | 
 | // Identifier numbers are based on psample kernel module sources: | 
 | // https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/uapi/linux/psample.h?h=v5.15.89#n5 | 
 | type attrId uint16 | 
 |  | 
 | const ( | 
 | 	aIIfIndex      attrId = iota // u16 | 
 | 	aOIfIndex                    // u16 | 
 | 	aOrigSize                    // u32 | 
 | 	aSampleGroup                 // u32 | 
 | 	aGroupSeq                    // u32 | 
 | 	aSampleRate                  // u32 | 
 | 	aData                        // []byte | 
 | 	aGroupRefcount               // u32 | 
 | 	aTunnel | 
 |  | 
 | 	aPad | 
 | 	aOutTC     // u16 | 
 | 	aOutTCOCC  // u64 | 
 | 	aLatency   // u64, nanoseconds | 
 | 	aTimestamp // u64, nanoseconds | 
 | 	aProto     // u16 | 
 | ) | 
 |  | 
 | // Packet contains the sampled packet in its raw form, along with its | 
 | // 'psample' metadata. | 
 | type Packet struct { | 
 | 	// IncomingInterfaceIndex is the incoming interface index of the packet or 0 | 
 | 	// if not applicable. | 
 | 	IncomingInterfaceIndex uint16 | 
 | 	// OutgoingInterfaceIndex is the outgoing interface index of the packet or 0 | 
 | 	// if not applicable. | 
 | 	OutgoingInterfaceIndex uint16 | 
 | 	// OriginalSize is the packet's original size in bytes without any | 
 | 	// truncation. | 
 | 	OriginalSize uint32 | 
 | 	// SampleGroup is the sample group to which this packet belongs. This is set | 
 | 	// by the sampling action and can be used to differentiate different | 
 | 	// sampling streams. | 
 | 	SampleGroup uint32 | 
 | 	// GroupSequence is a monotonically-increasing counter of packets sampled | 
 | 	// for each sample group. | 
 | 	GroupSequence uint32 | 
 | 	// SampleRate is the sampling rate (1 in SampleRate packets) used to capture | 
 | 	// this packet. | 
 | 	SampleRate uint32 | 
 | 	// Data contains the packet data up to the specified size for truncation. | 
 | 	Data []byte | 
 |  | 
 | 	// The following attributes are only available on kernel versions 5.13+ | 
 |  | 
 | 	// Latency is the sampled packet's latency as indicated by psample. It's | 
 | 	// expressed in nanoseconds. | 
 | 	Latency uint64 | 
 | 	// Timestamp marks time of the packet's sampling. It's set by the kernel, and | 
 | 	// expressed in Unix nanoseconds. | 
 | 	Timestamp uint64 | 
 | } | 
 |  | 
 | // decode converts raw generic netlink message attributes into a Packet. In | 
 | // cases where some of the known psample attributes were left unspecified in | 
 | // the message, appropriate Packet member variables will be left with their | 
 | // zero values. | 
 | func decode(b []byte) (*Packet, error) { | 
 | 	ad, err := netlink.NewAttributeDecoder(b) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	var p Packet | 
 | 	for ad.Next() { | 
 | 		switch attrId(ad.Type()) { | 
 | 		case aIIfIndex: | 
 | 			p.IncomingInterfaceIndex = ad.Uint16() | 
 | 		case aOIfIndex: | 
 | 			p.OutgoingInterfaceIndex = ad.Uint16() | 
 | 		case aOrigSize: | 
 | 			p.OriginalSize = ad.Uint32() | 
 | 		case aSampleGroup: | 
 | 			p.SampleGroup = ad.Uint32() | 
 | 		case aGroupSeq: | 
 | 			p.GroupSequence = ad.Uint32() | 
 | 		case aSampleRate: | 
 | 			p.SampleRate = ad.Uint32() | 
 | 		case aData: | 
 | 			p.Data = ad.Bytes() | 
 | 		case aLatency: | 
 | 			p.Latency = ad.Uint64() | 
 | 		case aTimestamp: | 
 | 			p.Timestamp = ad.Uint64() | 
 | 		} | 
 | 	} | 
 | 	return &p, nil | 
 | } | 
 |  | 
 | // Subscribe returns a NetlinkSocket that's already subscribed to "packets" | 
 | // psample multicast group, which makes it ready to receive packet samples. | 
 | // Close should be called on the returned socket. | 
 | func Subscribe() (*genetlink.Conn, error) { | 
 | 	// Create a netlink socket. | 
 | 	c, err := genetlink.Dial(nil) | 
 | 	if err != nil { | 
 | 		return nil, fmt.Errorf("while dialing netlink socket: %w", err) | 
 | 	} | 
 |  | 
 | 	// Lookup the netlink family id associated with psample kernel module. | 
 | 	f, err := c.GetFamily("psample") | 
 | 	if err != nil { | 
 | 		c.Close() | 
 | 		return nil, fmt.Errorf("couldn't lookup \"psample\" netlink family: %w", err) | 
 | 	} | 
 |  | 
 | 	// Lookup psample's packet sampling netlink multicast group. | 
 | 	var pktGrpId uint32 | 
 | 	for _, mgrp := range f.Groups { | 
 | 		if mgrp.Name == "packets" { | 
 | 			pktGrpId = mgrp.ID | 
 | 			break | 
 | 		} | 
 | 	} | 
 | 	if pktGrpId == 0 { | 
 | 		c.Close() | 
 | 		return nil, fmt.Errorf("packets multicast group not found") | 
 | 	} | 
 |  | 
 | 	// Subscribe to 'packets' multicast group in order to receive packet | 
 | 	// samples. | 
 | 	if err := c.JoinGroup(pktGrpId); err != nil { | 
 | 		c.Close() | 
 | 		return nil, fmt.Errorf("couldn't join multicast group: %w", err) | 
 | 	} | 
 | 	return c, nil | 
 | } | 
 |  | 
 | // Receive returns one or more of the sampled packets as soon as they're | 
 | // available. It may return a syscall.ENOBUFS error which indicates that the | 
 | // kernel-side buffer of the netlink connection has overflowed and lost | 
 | // packets. This is a transient error, calling Receive again will retrieve | 
 | // future packet samples. | 
 | func Receive(c *genetlink.Conn) ([]Packet, error) { | 
 | 	// Wait for the samples to arrive over generic netlink connection c. | 
 | 	gnms, nms, err := c.Receive() | 
 | 	if err != nil { | 
 | 		return nil, fmt.Errorf("while receiving netlink notifications: %w", err) | 
 | 	} | 
 |  | 
 | 	var pkts []Packet | 
 | 	for i := 0; i < len(nms); i++ { | 
 | 		// Only process multicast notifications. | 
 | 		if nms[i].Header.PID != 0 { | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		// PSAMPLE_CMD_SAMPLE should be zero in multicast notifications. | 
 | 		if gnms[i].Header.Command != 0 { | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		// Iterate over the Generic Netlink attributes present in the message, | 
 | 		// extracting any relating to the sampled packet. | 
 | 		pkt, err := decode(gnms[i].Data) | 
 | 		if err != nil { | 
 | 			return nil, fmt.Errorf("while decoding netlink notification: %w", err) | 
 | 		} | 
 | 		pkts = append(pkts, *pkt) | 
 | 	} | 
 | 	return pkts, nil | 
 | } |