blob: 210e353ecd22623b20fefc88dd71bedfd89eb16a [file] [log] [blame]
Lorenz Brun153c9c12025-01-07 17:44:45 +01001From 5aceb9e681cd6c82a2eccc25e1452d72d991c613 Mon Sep 17 00:00:00 2001
2From: Mateusz Zalega <mateusz@monogon.tech>
3Date: Wed, 25 Jan 2023 11:20:06 +0000
4Subject: [PATCH] Support "sample" filter action
5
6This change adds support for packet sampling using "psample" kernel
7module.
8---
9 filter.go | 23 +++++++++
10 filter_linux.go | 25 +++++++++
11 filter_test.go | 132 ++++++++++++++++++++++++++++++++++++++++++++++++
12 nl/tc_linux.go | 11 ++++
13 4 files changed, 191 insertions(+)
14
15diff --git a/filter.go b/filter.go
16index 84e1ca7..e4f3167 100644
17--- a/filter.go
18+++ b/filter.go
19@@ -369,6 +369,29 @@ func NewPoliceAction() *PoliceAction {
20 }
21 }
22
23+type SampleAction struct {
24+ ActionAttrs
25+ Group uint32
26+ Rate uint32
27+ TruncSize uint32
28+}
29+
30+func (action *SampleAction) Type() string {
31+ return "sample"
32+}
33+
34+func (action *SampleAction) Attrs() *ActionAttrs {
35+ return &action.ActionAttrs
36+}
37+
38+func NewSampleAction() *SampleAction {
39+ return &SampleAction{
40+ ActionAttrs: ActionAttrs{
41+ Action: TC_ACT_PIPE,
42+ },
43+ }
44+}
45+
46 // MatchAll filters match all packets
47 type MatchAll struct {
48 FilterAttrs
49diff --git a/filter_linux.go b/filter_linux.go
50index 1930661..d61e357 100644
51--- a/filter_linux.go
52+++ b/filter_linux.go
53@@ -705,6 +705,17 @@ func EncodeActions(attr *nl.RtAttr, actions []Action) error {
54 aopts.AddRtAttr(nl.TCA_ACT_BPF_PARMS, gen.Serialize())
55 aopts.AddRtAttr(nl.TCA_ACT_BPF_FD, nl.Uint32Attr(uint32(action.Fd)))
56 aopts.AddRtAttr(nl.TCA_ACT_BPF_NAME, nl.ZeroTerminated(action.Name))
57+ case *SampleAction:
58+ table := attr.AddRtAttr(tabIndex, nil)
59+ tabIndex++
60+ table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("sample"))
61+ aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil)
62+ gen := nl.TcGen{}
63+ toTcGen(action.Attrs(), &gen)
64+ aopts.AddRtAttr(nl.TCA_ACT_SAMPLE_PARMS, gen.Serialize())
65+ aopts.AddRtAttr(nl.TCA_ACT_SAMPLE_RATE, nl.Uint32Attr(action.Rate))
66+ aopts.AddRtAttr(nl.TCA_ACT_SAMPLE_PSAMPLE_GROUP, nl.Uint32Attr(action.Group))
67+ aopts.AddRtAttr(nl.TCA_ACT_SAMPLE_TRUNC_SIZE, nl.Uint32Attr(action.TruncSize))
68 case *GenericAction:
69 table := attr.AddRtAttr(tabIndex, nil)
70 tabIndex++
71@@ -790,6 +801,8 @@ func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) {
72 action = &ConnmarkAction{}
73 case "csum":
74 action = &CsumAction{}
75+ case "sample":
76+ action = &SampleAction{}
77 case "gact":
78 action = &GenericAction{}
79 case "tunnel_key":
80@@ -902,6 +915,18 @@ func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) {
81 tcTs := nl.DeserializeTcf(adatum.Value)
82 actionTimestamp = toTimeStamp(tcTs)
83 }
84+ case "sample":
85+ switch adatum.Attr.Type {
86+ case nl.TCA_ACT_SAMPLE_PARMS:
87+ gen := *nl.DeserializeTcGen(adatum.Value)
88+ toAttrs(&gen, action.Attrs())
89+ case nl.TCA_ACT_SAMPLE_RATE:
90+ action.(*SampleAction).Rate = native.Uint32(adatum.Value[0:4])
91+ case nl.TCA_ACT_SAMPLE_PSAMPLE_GROUP:
92+ action.(*SampleAction).Group = native.Uint32(adatum.Value[0:4])
93+ case nl.TCA_ACT_SAMPLE_TRUNC_SIZE:
94+ action.(*SampleAction).TruncSize = native.Uint32(adatum.Value[0:4])
95+ }
96 case "gact":
97 switch adatum.Attr.Type {
98 case nl.TCA_GACT_PARMS:
99diff --git a/filter_test.go b/filter_test.go
100index 3a49f1b..774e7d6 100644
101--- a/filter_test.go
102+++ b/filter_test.go
103@@ -2471,3 +2471,135 @@ func TestFilterChainAddDel(t *testing.T) {
104 t.Fatal("Failed to remove qdisc")
105 }
106 }
107+
108+func TestFilterSampleAddDel(t *testing.T) {
109+ minKernelRequired(t, 4, 11)
110+ if _, err := GenlFamilyGet("psample"); err != nil {
111+ t.Skip("psample genetlink family unavailable - is CONFIG_PSAMPLE enabled?")
112+ }
113+
114+ tearDown := setUpNetlinkTest(t)
115+ defer tearDown()
116+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
117+ t.Fatal(err)
118+ }
119+ link, err := LinkByName("foo")
120+ if err != nil {
121+ t.Fatal(err)
122+ }
123+ if err := LinkSetUp(link); err != nil {
124+ t.Fatal(err)
125+ }
126+
127+ qdisc := &Ingress{
128+ QdiscAttrs: QdiscAttrs{
129+ LinkIndex: link.Attrs().Index,
130+ Handle: MakeHandle(0xffff, 0),
131+ Parent: HANDLE_INGRESS,
132+ },
133+ }
134+ if err := QdiscAdd(qdisc); err != nil {
135+ t.Fatal(err)
136+ }
137+ qdiscs, err := SafeQdiscList(link)
138+ if err != nil {
139+ t.Fatal(err)
140+ }
141+
142+ found := false
143+ for _, v := range qdiscs {
144+ if _, ok := v.(*Ingress); ok {
145+ found = true
146+ break
147+ }
148+ }
149+ if !found {
150+ t.Fatal("Qdisc is the wrong type")
151+ }
152+
153+ sample := NewSampleAction()
154+ sample.Group = 7
155+ sample.Rate = 12
156+ sample.TruncSize = 200
157+
158+ classId := MakeHandle(1, 1)
159+ filter := &MatchAll{
160+ FilterAttrs: FilterAttrs{
161+ LinkIndex: link.Attrs().Index,
162+ Parent: MakeHandle(0xffff, 0),
163+ Priority: 1,
164+ Protocol: unix.ETH_P_ALL,
165+ },
166+ ClassId: classId,
167+ Actions: []Action{
168+ sample,
169+ },
170+ }
171+
172+ if err := FilterAdd(filter); err != nil {
173+ t.Fatal(err)
174+ }
175+
176+ filters, err := FilterList(link, MakeHandle(0xffff, 0))
177+ if err != nil {
178+ t.Fatal(err)
179+ }
180+ if len(filters) != 1 {
181+ t.Fatal("Failed to add filter")
182+ }
183+ mf, ok := filters[0].(*MatchAll)
184+ if !ok {
185+ t.Fatal("Filter is the wrong type")
186+ }
187+
188+ if len(mf.Actions) < 1 {
189+ t.Fatalf("Too few Actions in filter")
190+ }
191+ if mf.ClassId != classId {
192+ t.Fatalf("ClassId of the filter is the wrong value")
193+ }
194+
195+ lsample, ok := mf.Actions[0].(*SampleAction)
196+ if !ok {
197+ t.Fatal("Unable to find sample action")
198+ }
199+ if lsample.Group != sample.Group {
200+ t.Fatalf("Inconsistent sample action group")
201+ }
202+ if lsample.Rate != sample.Rate {
203+ t.Fatalf("Inconsistent sample action rate")
204+ }
205+ if lsample.TruncSize != sample.TruncSize {
206+ t.Fatalf("Inconsistent sample truncation size")
207+ }
208+
209+ if err := FilterDel(filter); err != nil {
210+ t.Fatal(err)
211+ }
212+ filters, err = FilterList(link, MakeHandle(0xffff, 0))
213+ if err != nil {
214+ t.Fatal(err)
215+ }
216+ if len(filters) != 0 {
217+ t.Fatal("Failed to remove filter")
218+ }
219+
220+ if err := QdiscDel(qdisc); err != nil {
221+ t.Fatal(err)
222+ }
223+ qdiscs, err = SafeQdiscList(link)
224+ if err != nil {
225+ t.Fatal(err)
226+ }
227+
228+ found = false
229+ for _, v := range qdiscs {
230+ if _, ok := v.(*Ingress); ok {
231+ found = true
232+ break
233+ }
234+ }
235+ if found {
236+ t.Fatal("Failed to remove qdisc")
237+ }
238+}
239diff --git a/nl/tc_linux.go b/nl/tc_linux.go
240index 0720729..db3ca1c 100644
241--- a/nl/tc_linux.go
242+++ b/nl/tc_linux.go
243@@ -77,6 +77,17 @@ const (
244 TCA_ACT_MAX
245 )
246
247+const (
248+ TCA_ACT_SAMPLE_UNSPEC = iota
249+ TCA_ACT_SAMPLE_TM
250+ TCA_ACT_SAMPLE_PARMS
251+ TCA_ACT_SAMPLE_RATE
252+ TCA_ACT_SAMPLE_TRUNC_SIZE
253+ TCA_ACT_SAMPLE_PSAMPLE_GROUP
254+ TCA_ACT_SAMPLE_PAD
255+ TCA_ACT_SAMPLE_MAX
256+)
257+
258 const (
259 TCA_PRIO_UNSPEC = iota
260 TCA_PRIO_MQ
261--
2622.47.0
263