blob: 6e05b9c191396553f5fc728c9aeab0a6d45a7ea4 [file] [log] [blame]
Mateusz Zalega3ccf6962023-01-23 17:01:40 +00001// Package psample provides a receiver for sampled network packets using the
2// Netlink psample interface.
3package psample
4
5import (
6 "fmt"
7
8 "github.com/mdlayher/genetlink"
9 "github.com/mdlayher/netlink"
10)
11
12// attrId identifies psample netlink message attributes.
13// Identifier numbers are based on psample kernel module sources:
14// https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/uapi/linux/psample.h?h=v5.15.89#n5
15type attrId uint16
16
17const (
18 aIIfIndex attrId = iota // u16
19 aOIfIndex // u16
20 aOrigSize // u32
21 aSampleGroup // u32
22 aGroupSeq // u32
23 aSampleRate // u32
24 aData // []byte
25 aGroupRefcount // u32
26 aTunnel
27
28 aPad
29 aOutTC // u16
30 aOutTCOCC // u64
31 aLatency // u64, nanoseconds
32 aTimestamp // u64, nanoseconds
33 aProto // u16
34)
35
36// Packet contains the sampled packet in its raw form, along with its
37// 'psample' metadata.
38type Packet struct {
39 // IncomingInterfaceIndex is the incoming interface index of the packet or 0
40 // if not applicable.
41 IncomingInterfaceIndex uint16
42 // OutgoingInterfaceIndex is the outgoing interface index of the packet or 0
43 // if not applicable.
44 OutgoingInterfaceIndex uint16
45 // OriginalSize is the packet's original size in bytes without any
46 // truncation.
47 OriginalSize uint32
48 // SampleGroup is the sample group to which this packet belongs. This is set
49 // by the sampling action and can be used to differentiate different
50 // sampling streams.
51 SampleGroup uint32
52 // GroupSequence is a monotonically-increasing counter of packets sampled
53 // for each sample group.
54 GroupSequence uint32
55 // SampleRate is the sampling rate (1 in SampleRate packets) used to capture
56 // this packet.
57 SampleRate uint32
58 // Data contains the packet data up to the specified size for truncation.
59 Data []byte
60
61 // The following attributes are only available on kernel versions 5.13+
62
63 // Latency is the sampled packet's latency as indicated by psample. It's
64 // expressed in nanoseconds.
65 Latency uint64
66 // Timestamp marks time of the packet's sampling. It's set by the kernel, and
67 // expressed in Unix nanoseconds.
68 Timestamp uint64
69}
70
71// decode converts raw generic netlink message attributes into a Packet. In
72// cases where some of the known psample attributes were left unspecified in
73// the message, appropriate Packet member variables will be left with their
74// zero values.
75func decode(b []byte) (*Packet, error) {
76 ad, err := netlink.NewAttributeDecoder(b)
77 if err != nil {
78 return nil, err
79 }
80
81 var p Packet
82 for ad.Next() {
83 switch attrId(ad.Type()) {
84 case aIIfIndex:
85 p.IncomingInterfaceIndex = ad.Uint16()
86 case aOIfIndex:
87 p.OutgoingInterfaceIndex = ad.Uint16()
88 case aOrigSize:
89 p.OriginalSize = ad.Uint32()
90 case aSampleGroup:
91 p.SampleGroup = ad.Uint32()
92 case aGroupSeq:
93 p.GroupSequence = ad.Uint32()
94 case aSampleRate:
95 p.SampleRate = ad.Uint32()
96 case aData:
97 p.Data = ad.Bytes()
98 case aLatency:
99 p.Latency = ad.Uint64()
100 case aTimestamp:
101 p.Timestamp = ad.Uint64()
Tim Windelschmidt9b2c1562024-04-11 01:39:25 +0200102 default:
Mateusz Zalega3ccf6962023-01-23 17:01:40 +0000103 }
104 }
105 return &p, nil
106}
107
108// Subscribe returns a NetlinkSocket that's already subscribed to "packets"
109// psample multicast group, which makes it ready to receive packet samples.
110// Close should be called on the returned socket.
111func Subscribe() (*genetlink.Conn, error) {
112 // Create a netlink socket.
113 c, err := genetlink.Dial(nil)
114 if err != nil {
115 return nil, fmt.Errorf("while dialing netlink socket: %w", err)
116 }
117
118 // Lookup the netlink family id associated with psample kernel module.
119 f, err := c.GetFamily("psample")
120 if err != nil {
121 c.Close()
122 return nil, fmt.Errorf("couldn't lookup \"psample\" netlink family: %w", err)
123 }
124
125 // Lookup psample's packet sampling netlink multicast group.
126 var pktGrpId uint32
127 for _, mgrp := range f.Groups {
128 if mgrp.Name == "packets" {
129 pktGrpId = mgrp.ID
130 break
131 }
132 }
133 if pktGrpId == 0 {
134 c.Close()
135 return nil, fmt.Errorf("packets multicast group not found")
136 }
137
138 // Subscribe to 'packets' multicast group in order to receive packet
139 // samples.
140 if err := c.JoinGroup(pktGrpId); err != nil {
141 c.Close()
142 return nil, fmt.Errorf("couldn't join multicast group: %w", err)
143 }
144 return c, nil
145}
146
147// Receive returns one or more of the sampled packets as soon as they're
148// available. It may return a syscall.ENOBUFS error which indicates that the
149// kernel-side buffer of the netlink connection has overflowed and lost
150// packets. This is a transient error, calling Receive again will retrieve
151// future packet samples.
152func Receive(c *genetlink.Conn) ([]Packet, error) {
153 // Wait for the samples to arrive over generic netlink connection c.
154 gnms, nms, err := c.Receive()
155 if err != nil {
156 return nil, fmt.Errorf("while receiving netlink notifications: %w", err)
157 }
158
159 var pkts []Packet
160 for i := 0; i < len(nms); i++ {
161 // Only process multicast notifications.
162 if nms[i].Header.PID != 0 {
163 continue
164 }
165
166 // PSAMPLE_CMD_SAMPLE should be zero in multicast notifications.
167 if gnms[i].Header.Command != 0 {
168 continue
169 }
170
171 // Iterate over the Generic Netlink attributes present in the message,
172 // extracting any relating to the sampled packet.
173 pkt, err := decode(gnms[i].Data)
174 if err != nil {
175 return nil, fmt.Errorf("while decoding netlink notification: %w", err)
176 }
177 pkts = append(pkts, *pkt)
178 }
179 return pkts, nil
180}