File: expr.go

package info (click to toggle)
golang-github-google-nftables 0.2.0-3
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid, trixie
  • size: 756 kB
  • sloc: makefile: 8
file content (431 lines) | stat: -rw-r--r-- 12,131 bytes parent folder | download
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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
// Copyright 2018 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package expr provides nftables rule expressions.
package expr

import (
	"encoding/binary"

	"github.com/google/nftables/binaryutil"
	"github.com/google/nftables/internal/parseexprfunc"
	"github.com/mdlayher/netlink"
	"golang.org/x/sys/unix"
)

func init() {
	parseexprfunc.ParseExprBytesFunc = func(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]interface{}, error) {
		exprs, err := exprsFromBytes(fam, ad, b)
		if err != nil {
			return nil, err
		}
		result := make([]interface{}, len(exprs))
		for idx, expr := range exprs {
			result[idx] = expr
		}
		return result, nil
	}
	parseexprfunc.ParseExprMsgFunc = func(fam byte, b []byte) ([]interface{}, error) {
		ad, err := netlink.NewAttributeDecoder(b)
		if err != nil {
			return nil, err
		}
		ad.ByteOrder = binary.BigEndian
		var exprs []interface{}
		for ad.Next() {
			e, err := parseexprfunc.ParseExprBytesFunc(fam, ad, b)
			if err != nil {
				return e, err
			}
			exprs = append(exprs, e...)
		}
		return exprs, ad.Err()
	}
}

// Marshal serializes the specified expression into a byte slice.
func Marshal(fam byte, e Any) ([]byte, error) {
	return e.marshal(fam)
}

// Unmarshal fills an expression from the specified byte slice.
func Unmarshal(fam byte, data []byte, e Any) error {
	return e.unmarshal(fam, data)
}

// exprsFromBytes parses nested raw expressions bytes
// to construct nftables expressions
func exprsFromBytes(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]Any, error) {
	var exprs []Any
	ad.Do(func(b []byte) error {
		ad, err := netlink.NewAttributeDecoder(b)
		if err != nil {
			return err
		}
		ad.ByteOrder = binary.BigEndian
		var name string
		for ad.Next() {
			switch ad.Type() {
			case unix.NFTA_EXPR_NAME:
				name = ad.String()
				if name == "notrack" {
					e := &Notrack{}
					exprs = append(exprs, e)
				}
			case unix.NFTA_EXPR_DATA:
				var e Any
				switch name {
				case "ct":
					e = &Ct{}
				case "range":
					e = &Range{}
				case "meta":
					e = &Meta{}
				case "cmp":
					e = &Cmp{}
				case "counter":
					e = &Counter{}
				case "objref":
					e = &Objref{}
				case "payload":
					e = &Payload{}
				case "lookup":
					e = &Lookup{}
				case "immediate":
					e = &Immediate{}
				case "bitwise":
					e = &Bitwise{}
				case "redir":
					e = &Redir{}
				case "nat":
					e = &NAT{}
				case "limit":
					e = &Limit{}
				case "quota":
					e = &Quota{}
				case "dynset":
					e = &Dynset{}
				case "log":
					e = &Log{}
				case "exthdr":
					e = &Exthdr{}
				case "match":
					e = &Match{}
				case "target":
					e = &Target{}
				case "connlimit":
					e = &Connlimit{}
				case "queue":
					e = &Queue{}
				case "flow_offload":
					e = &FlowOffload{}
				case "reject":
					e = &Reject{}
				case "masq":
					e = &Masq{}
				case "hash":
					e = &Hash{}
				}
				if e == nil {
					// TODO: introduce an opaque expression type so that users know
					// something is here.
					continue // unsupported expression type
				}

				ad.Do(func(b []byte) error {
					if err := Unmarshal(fam, b, e); err != nil {
						return err
					}
					// Verdict expressions are a special-case of immediate expressions, so
					// if the expression is an immediate writing nothing into the verdict
					// register (invalid), re-parse it as a verdict expression.
					if imm, isImmediate := e.(*Immediate); isImmediate && imm.Register == unix.NFT_REG_VERDICT && len(imm.Data) == 0 {
						e = &Verdict{}
						if err := Unmarshal(fam, b, e); err != nil {
							return err
						}
					}
					exprs = append(exprs, e)
					return nil
				})
			}
		}
		return ad.Err()
	})
	return exprs, ad.Err()
}

// Any is an interface implemented by any expression type.
type Any interface {
	marshal(fam byte) ([]byte, error)
	unmarshal(fam byte, data []byte) error
}

// MetaKey specifies which piece of meta information should be loaded. See also
// https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation
type MetaKey uint32

// Possible MetaKey values.
const (
	MetaKeyLEN        MetaKey = unix.NFT_META_LEN
	MetaKeyPROTOCOL   MetaKey = unix.NFT_META_PROTOCOL
	MetaKeyPRIORITY   MetaKey = unix.NFT_META_PRIORITY
	MetaKeyMARK       MetaKey = unix.NFT_META_MARK
	MetaKeyIIF        MetaKey = unix.NFT_META_IIF
	MetaKeyOIF        MetaKey = unix.NFT_META_OIF
	MetaKeyIIFNAME    MetaKey = unix.NFT_META_IIFNAME
	MetaKeyOIFNAME    MetaKey = unix.NFT_META_OIFNAME
	MetaKeyIIFTYPE    MetaKey = unix.NFT_META_IIFTYPE
	MetaKeyOIFTYPE    MetaKey = unix.NFT_META_OIFTYPE
	MetaKeySKUID      MetaKey = unix.NFT_META_SKUID
	MetaKeySKGID      MetaKey = unix.NFT_META_SKGID
	MetaKeyNFTRACE    MetaKey = unix.NFT_META_NFTRACE
	MetaKeyRTCLASSID  MetaKey = unix.NFT_META_RTCLASSID
	MetaKeySECMARK    MetaKey = unix.NFT_META_SECMARK
	MetaKeyNFPROTO    MetaKey = unix.NFT_META_NFPROTO
	MetaKeyL4PROTO    MetaKey = unix.NFT_META_L4PROTO
	MetaKeyBRIIIFNAME MetaKey = unix.NFT_META_BRI_IIFNAME
	MetaKeyBRIOIFNAME MetaKey = unix.NFT_META_BRI_OIFNAME
	MetaKeyPKTTYPE    MetaKey = unix.NFT_META_PKTTYPE
	MetaKeyCPU        MetaKey = unix.NFT_META_CPU
	MetaKeyIIFGROUP   MetaKey = unix.NFT_META_IIFGROUP
	MetaKeyOIFGROUP   MetaKey = unix.NFT_META_OIFGROUP
	MetaKeyCGROUP     MetaKey = unix.NFT_META_CGROUP
	MetaKeyPRANDOM    MetaKey = unix.NFT_META_PRANDOM
)

// Meta loads packet meta information for later comparisons. See also
// https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation
type Meta struct {
	Key            MetaKey
	SourceRegister bool
	Register       uint32
}

func (e *Meta) marshal(fam byte) ([]byte, error) {
	regData := []byte{}
	exprData, err := netlink.MarshalAttributes(
		[]netlink.Attribute{
			{Type: unix.NFTA_META_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))},
		},
	)
	if err != nil {
		return nil, err
	}
	if e.SourceRegister {
		regData, err = netlink.MarshalAttributes(
			[]netlink.Attribute{
				{Type: unix.NFTA_META_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
			},
		)
	} else {
		regData, err = netlink.MarshalAttributes(
			[]netlink.Attribute{
				{Type: unix.NFTA_META_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
			},
		)
	}
	if err != nil {
		return nil, err
	}
	exprData = append(exprData, regData...)

	return netlink.MarshalAttributes([]netlink.Attribute{
		{Type: unix.NFTA_EXPR_NAME, Data: []byte("meta\x00")},
		{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
	})
}

func (e *Meta) unmarshal(fam byte, data []byte) error {
	ad, err := netlink.NewAttributeDecoder(data)
	if err != nil {
		return err
	}
	ad.ByteOrder = binary.BigEndian
	for ad.Next() {
		switch ad.Type() {
		case unix.NFTA_META_SREG:
			e.Register = ad.Uint32()
			e.SourceRegister = true
		case unix.NFTA_META_DREG:
			e.Register = ad.Uint32()
		case unix.NFTA_META_KEY:
			e.Key = MetaKey(ad.Uint32())
		}
	}
	return ad.Err()
}

// Masq (Masquerade) is a special case of SNAT, where the source address is
// automagically set to the address of the output interface. See also
// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)#Masquerading
type Masq struct {
	Random      bool
	FullyRandom bool
	Persistent  bool
	ToPorts     bool
	RegProtoMin uint32
	RegProtoMax uint32
}

// TODO, Once the constants below are available in golang.org/x/sys/unix, switch to use those.
const (
	// NF_NAT_RANGE_PROTO_RANDOM defines flag for a random masquerade
	NF_NAT_RANGE_PROTO_RANDOM = 0x4
	// NF_NAT_RANGE_PROTO_RANDOM_FULLY defines flag for a fully random masquerade
	NF_NAT_RANGE_PROTO_RANDOM_FULLY = 0x10
	// NF_NAT_RANGE_PERSISTENT defines flag for a persistent masquerade
	NF_NAT_RANGE_PERSISTENT = 0x8
	// NF_NAT_RANGE_PREFIX defines flag for a prefix masquerade
	NF_NAT_RANGE_PREFIX = 0x40
)

func (e *Masq) marshal(fam byte) ([]byte, error) {
	msgData := []byte{}
	if !e.ToPorts {
		flags := uint32(0)
		if e.Random {
			flags |= NF_NAT_RANGE_PROTO_RANDOM
		}
		if e.FullyRandom {
			flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY
		}
		if e.Persistent {
			flags |= NF_NAT_RANGE_PERSISTENT
		}
		if flags != 0 {
			flagsData, err := netlink.MarshalAttributes([]netlink.Attribute{
				{Type: unix.NFTA_MASQ_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}})
			if err != nil {
				return nil, err
			}
			msgData = append(msgData, flagsData...)
		}
	} else {
		regsData, err := netlink.MarshalAttributes([]netlink.Attribute{
			{Type: unix.NFTA_MASQ_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMin)}})
		if err != nil {
			return nil, err
		}
		msgData = append(msgData, regsData...)
		if e.RegProtoMax != 0 {
			regsData, err := netlink.MarshalAttributes([]netlink.Attribute{
				{Type: unix.NFTA_MASQ_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMax)}})
			if err != nil {
				return nil, err
			}
			msgData = append(msgData, regsData...)
		}
	}
	return netlink.MarshalAttributes([]netlink.Attribute{
		{Type: unix.NFTA_EXPR_NAME, Data: []byte("masq\x00")},
		{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: msgData},
	})
}

func (e *Masq) unmarshal(fam byte, data []byte) error {
	ad, err := netlink.NewAttributeDecoder(data)
	if err != nil {
		return err
	}
	ad.ByteOrder = binary.BigEndian
	for ad.Next() {
		switch ad.Type() {
		case unix.NFTA_MASQ_REG_PROTO_MIN:
			e.ToPorts = true
			e.RegProtoMin = ad.Uint32()
		case unix.NFTA_MASQ_REG_PROTO_MAX:
			e.RegProtoMax = ad.Uint32()
		case unix.NFTA_MASQ_FLAGS:
			flags := ad.Uint32()
			e.Persistent = (flags & NF_NAT_RANGE_PERSISTENT) != 0
			e.Random = (flags & NF_NAT_RANGE_PROTO_RANDOM) != 0
			e.FullyRandom = (flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) != 0
		}
	}
	return ad.Err()
}

// CmpOp specifies which type of comparison should be performed.
type CmpOp uint32

// Possible CmpOp values.
const (
	CmpOpEq  CmpOp = unix.NFT_CMP_EQ
	CmpOpNeq CmpOp = unix.NFT_CMP_NEQ
	CmpOpLt  CmpOp = unix.NFT_CMP_LT
	CmpOpLte CmpOp = unix.NFT_CMP_LTE
	CmpOpGt  CmpOp = unix.NFT_CMP_GT
	CmpOpGte CmpOp = unix.NFT_CMP_GTE
)

// Cmp compares a register with the specified data.
type Cmp struct {
	Op       CmpOp
	Register uint32
	Data     []byte
}

func (e *Cmp) marshal(fam byte) ([]byte, error) {
	cmpData, err := netlink.MarshalAttributes([]netlink.Attribute{
		{Type: unix.NFTA_DATA_VALUE, Data: e.Data},
	})
	if err != nil {
		return nil, err
	}
	exprData, err := netlink.MarshalAttributes([]netlink.Attribute{
		{Type: unix.NFTA_CMP_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
		{Type: unix.NFTA_CMP_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))},
		{Type: unix.NLA_F_NESTED | unix.NFTA_CMP_DATA, Data: cmpData},
	})
	if err != nil {
		return nil, err
	}
	return netlink.MarshalAttributes([]netlink.Attribute{
		{Type: unix.NFTA_EXPR_NAME, Data: []byte("cmp\x00")},
		{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
	})
}

func (e *Cmp) unmarshal(fam byte, data []byte) error {
	ad, err := netlink.NewAttributeDecoder(data)
	if err != nil {
		return err
	}
	ad.ByteOrder = binary.BigEndian
	for ad.Next() {
		switch ad.Type() {
		case unix.NFTA_CMP_SREG:
			e.Register = ad.Uint32()
		case unix.NFTA_CMP_OP:
			e.Op = CmpOp(ad.Uint32())
		case unix.NFTA_CMP_DATA:
			ad.Do(func(b []byte) error {
				ad, err := netlink.NewAttributeDecoder(b)
				if err != nil {
					return err
				}
				ad.ByteOrder = binary.BigEndian
				if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE {
					ad.Do(func(b []byte) error {
						e.Data = b
						return nil
					})
				}
				return ad.Err()
			})
		}
	}
	return ad.Err()
}