-
Notifications
You must be signed in to change notification settings - Fork 48
/
ematch.go
271 lines (249 loc) · 7.99 KB
/
ematch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// The userspace part transforms the logic expressions into an array
// consisting of multiple sequences of interconnected ematches separated
// by markers. Precedence is implemented by a special ematch kind
// referencing a sequence beyond the marker of the current sequence
// causing the current position in the sequence to be pushed onto a stack
// to allow the current position to be overwritten by the position referenced
// in the special ematch. Matching continues in the new sequence until a
// marker is reached causing the position to be restored from the stack.
//
// Example:
// A AND (B1 OR B2) AND C AND D
//
// ------->-PUSH-------
// -->-- / -->-- \ -->--
// / \ / / \ \ / \
// +-------+-------+-------+-------+-------+--------+
// | A AND | B AND | C AND | D END | B1 OR | B2 END |
// +-------+-------+-------+-------+-------+--------+
// \ /
// --------<-POP---------
//
// where B is a virtual ematch referencing to sequence starting with B1. and B
// implemented with Container.
//
// When the skb input ematch module is used, the ematch match logic operates on
// the entire array. If the kernel finds the kind to be a container, it goes back
// to B until it reaches the end. This is implemented using recursive functions.
//
// The above is kernel logic.
//
// In userspace, the ematch array needs to be encapsulated. Logical combinations
// need to update flags and use containers. The updated ematch array would look like this:
// -------------------------------------------------------------------------------------------------
// index | 0 | 1 | 2 | 3 | 4 | 5 |
// ematch| A | B | C | D | B1 | B2 |
// kind | EmatchIPSet | EmatchContainer | EmatchIPT | EmatchNByte | EmatchCmp | EmatchU32 |
// flags | EmatchRelAnd | EmatchRelAnd | EmatchRelAnd | EmatchRelEND | EmatchRelOr |EmatchRelEnd|
// extend| ... | Pos=4 | ... | ... | ... | ... |
// --------------------------------------------------------------------------------------------------
//
// last match order is:
// index: 0 --> 1 --> 4 --> 5 --> 2 --> 3
package tc
import (
"fmt"
"github.com/mdlayher/netlink"
)
// EmatchLayer defines the layer the match will be applied upon.
type EmatchLayer uint8
// Various Ematch network layers.
const (
EmatchLayerLink = EmatchLayer(0)
EmatchLayerNetwork = EmatchLayer(1)
EmatchLayerTransport = EmatchLayer(2)
)
// EmatchOpnd defines how matches are concatenated.
type EmatchOpnd uint8
// Various Ematch operands
const (
EmatchOpndEq = EmatchOpnd(0)
EmatchOpndGt = EmatchOpnd(1)
EmatchOpndLt = EmatchOpnd(2)
)
const (
tcaEmatchTreeUnspec = iota
tcaEmatchTreeHdr
tcaEmatchTreeList
)
// EmatchKind defines the matching module.
type EmatchKind uint16
// Various Ematch kinds
const (
EmatchContainer = EmatchKind(iota)
EmatchCmp
EmatchNByte
EmatchU32
EmatchMeta
EmatchText
EmatchVLan
EmatchCanID
EmatchIPSet
EmatchIPT
ematchInvalid
)
// Various Ematch flags
const (
EmatchRelEnd uint16 = 0
EmatchRelAnd uint16 = 1 << (iota - 1)
EmatchRelOr
EmatchInvert
EmatchSimple
)
// Ematch contains attributes of the ematch discipline
// https://man7.org/linux/man-pages/man8/tc-ematch.8.html
type Ematch struct {
Hdr *EmatchTreeHdr
Matches *[]EmatchMatch
}
// EmatchTreeHdr from tcf_ematch_tree_hdr in include/uapi/linux/pkt_cls.h
type EmatchTreeHdr struct {
NMatches uint16
ProgID uint16
}
// EmatchHdr from tcf_ematch_hdr in include/uapi/linux/pkt_cls.h
type EmatchHdr struct {
MatchID uint16
Kind EmatchKind
Flags uint16
Pad uint16
}
// EmatchMatch contains attributes of the ematch discipline
type EmatchMatch struct {
Hdr EmatchHdr
U32Match *U32Match
CmpMatch *CmpMatch
IPSetMatch *IPSetMatch
IptMatch *IptMatch
ContainerMatch *ContainerMatch
NByteMatch *NByteMatch
}
// unmarshalEmatch parses the Ematch-encoded data and stores the result in the value pointed to by info.
func unmarshalEmatch(data []byte, info *Ematch) error {
ad, err := netlink.NewAttributeDecoder(data)
if err != nil {
return err
}
var multiError error
for ad.Next() {
switch ad.Type() {
case tcaEmatchTreeHdr:
hdr := &EmatchTreeHdr{}
err := unmarshalStruct(ad.Bytes(), hdr)
multiError = concatError(multiError, err)
info.Hdr = hdr
case tcaEmatchTreeList:
list := []EmatchMatch{}
err := unmarshalEmatchTreeList(ad.Bytes(), &list)
multiError = concatError(multiError, err)
info.Matches = &list
default:
return fmt.Errorf("UnmarshalEmatch()\t%d\n\t%v", ad.Type(), ad.Bytes())
}
}
return concatError(multiError, ad.Err())
}
// marshalEmatch returns the binary encoding of Ematch
func marshalEmatch(info *Ematch) ([]byte, error) {
options := []tcOption{}
if info == nil {
return []byte{}, fmt.Errorf("Ematch: %w", ErrNoArg)
}
var multiError error
if info.Hdr != nil {
data, err := marshalStruct(info.Hdr)
multiError = concatError(multiError, err)
options = append(options, tcOption{Interpretation: vtBytes, Type: tcaEmatchTreeHdr, Data: data})
}
if info.Matches != nil {
data, err := marshalEmatchTreeList(info.Matches)
multiError = concatError(multiError, err)
options = append(options, tcOption{Interpretation: vtBytes, Type: tcaEmatchTreeList | nlaFNnested, Data: data})
}
if multiError != nil {
return []byte{}, multiError
}
return marshalAttributes(options)
}
func unmarshalEmatchTreeList(data []byte, info *[]EmatchMatch) error {
ad, err := netlink.NewAttributeDecoder(data)
if err != nil {
return err
}
var multiError error
for ad.Next() {
match := EmatchMatch{}
tmp := ad.Bytes()
if err := unmarshalStruct(tmp[:8], &match.Hdr); err != nil {
return err
}
switch match.Hdr.Kind {
case EmatchU32:
expr := &U32Match{}
err := unmarshalU32Match(tmp[8:], expr)
multiError = concatError(multiError, err)
match.U32Match = expr
case EmatchCmp:
expr := &CmpMatch{}
err := unmarshalCmpMatch(tmp[8:], expr)
multiError = concatError(multiError, err)
match.CmpMatch = expr
case EmatchIPSet:
expr := &IPSetMatch{}
err := unmarshalIPSetMatch(tmp[8:], expr)
multiError = concatError(multiError, err)
match.IPSetMatch = expr
case EmatchIPT:
expr := &IptMatch{}
err := unmarshalIptMatch(tmp[8:], expr)
multiError = concatError(multiError, err)
match.IptMatch = expr
case EmatchContainer:
expr := &ContainerMatch{}
err := unmarshalContainerMatch(tmp[8:], expr)
multiError = concatError(multiError, err)
match.ContainerMatch = expr
case EmatchNByte:
expr := &NByteMatch{}
err := unmarshalNByteMatch(tmp[8:], expr)
multiError = concatError(multiError, err)
match.NByteMatch = expr
default:
return fmt.Errorf("unmarshalEmatchTreeList() kind %d is not yet implemented", match.Hdr.Kind)
}
*info = append(*info, match)
}
return concatError(multiError, ad.Err())
}
func marshalEmatchTreeList(info *[]EmatchMatch) ([]byte, error) {
options := []tcOption{}
for i, m := range *info {
payload, err := marshalStruct(m.Hdr)
if err != nil {
return []byte{}, err
}
var expr []byte
switch m.Hdr.Kind {
case EmatchU32:
expr, err = marshalU32Match(m.U32Match)
case EmatchCmp:
expr, err = marshalCmpMatch(m.CmpMatch)
case EmatchIPSet:
expr, err = marshalIPSetMatch(m.IPSetMatch)
case EmatchIPT:
expr, err = marshalIptMatch(m.IptMatch)
case EmatchContainer:
expr, err = marshalContainerMatch(m.ContainerMatch)
case EmatchNByte:
expr, err = marshalNByteMatch(m.NByteMatch)
default:
return []byte{}, fmt.Errorf("marshalEmatchTreeList() kind %d is not yet implemented", m.Hdr.Kind)
}
if err != nil {
return []byte{}, fmt.Errorf("marshalEmatchTreeList(): %v", err)
}
payload = append(payload, expr...)
options = append(options, tcOption{Interpretation: vtBytes, Type: uint16(i + 1), Data: payload})
}
return marshalAttributes(options)
}