blob: 065c6b661b1edcc2ca2891e5d8eb3853f4e88296 [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()
102 }
103 }
104 return &p, nil
105}
106
107// Subscribe returns a NetlinkSocket that's already subscribed to "packets"
108// psample multicast group, which makes it ready to receive packet samples.
109// Close should be called on the returned socket.
110func Subscribe() (*genetlink.Conn, error) {
111 // Create a netlink socket.
112 c, err := genetlink.Dial(nil)
113 if err != nil {
114 return nil, fmt.Errorf("while dialing netlink socket: %w", err)
115 }
116
117 // Lookup the netlink family id associated with psample kernel module.
118 f, err := c.GetFamily("psample")
119 if err != nil {
120 c.Close()
121 return nil, fmt.Errorf("couldn't lookup \"psample\" netlink family: %w", err)
122 }
123
124 // Lookup psample's packet sampling netlink multicast group.
125 var pktGrpId uint32
126 for _, mgrp := range f.Groups {
127 if mgrp.Name == "packets" {
128 pktGrpId = mgrp.ID
129 break
130 }
131 }
132 if pktGrpId == 0 {
133 c.Close()
134 return nil, fmt.Errorf("packets multicast group not found")
135 }
136
137 // Subscribe to 'packets' multicast group in order to receive packet
138 // samples.
139 if err := c.JoinGroup(pktGrpId); err != nil {
140 c.Close()
141 return nil, fmt.Errorf("couldn't join multicast group: %w", err)
142 }
143 return c, nil
144}
145
146// Receive returns one or more of the sampled packets as soon as they're
147// available. It may return a syscall.ENOBUFS error which indicates that the
148// kernel-side buffer of the netlink connection has overflowed and lost
149// packets. This is a transient error, calling Receive again will retrieve
150// future packet samples.
151func Receive(c *genetlink.Conn) ([]Packet, error) {
152 // Wait for the samples to arrive over generic netlink connection c.
153 gnms, nms, err := c.Receive()
154 if err != nil {
155 return nil, fmt.Errorf("while receiving netlink notifications: %w", err)
156 }
157
158 var pkts []Packet
159 for i := 0; i < len(nms); i++ {
160 // Only process multicast notifications.
161 if nms[i].Header.PID != 0 {
162 continue
163 }
164
165 // PSAMPLE_CMD_SAMPLE should be zero in multicast notifications.
166 if gnms[i].Header.Command != 0 {
167 continue
168 }
169
170 // Iterate over the Generic Netlink attributes present in the message,
171 // extracting any relating to the sampled packet.
172 pkt, err := decode(gnms[i].Data)
173 if err != nil {
174 return nil, fmt.Errorf("while decoding netlink notification: %w", err)
175 }
176 pkts = append(pkts, *pkt)
177 }
178 return pkts, nil
179}