-
Notifications
You must be signed in to change notification settings - Fork 11
/
nfrules.go
1247 lines (1121 loc) · 31.4 KB
/
nfrules.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
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
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package nftableslib
import (
"encoding/json"
"fmt"
"net"
"sync"
"time"
"github.com/google/nftables"
"github.com/google/nftables/binaryutil"
"github.com/google/nftables/expr"
"github.com/google/uuid"
"golang.org/x/sys/unix"
)
// By some reason github.com/golang/unix does not define these constants but
// they can by used in a Verdict.
const (
// NFT_DROP defines Drop action for a verdict
NFT_DROP = 0x0
// NFT_ACCEPT defines Accept action for a verdict
NFT_ACCEPT = 0x1
)
type ruleOperation uint32
const (
operationAdd ruleOperation = iota
operationInsert
operationReplace
)
// RulesInterface defines third level interface operating with nf Rules
type RulesInterface interface {
Rules() RuleFuncs
}
// RuleFuncs defines funcations to operate with Rules
type RuleFuncs interface {
Create(*Rule) (uint32, error)
CreateImm(*Rule) (uint64, error)
Delete(uint32) error
DeleteImm(uint64) error
Insert(*Rule) (uint32, error)
InsertImm(*Rule) (uint64, error)
Update(*Rule, uint64) error
Dump() ([]byte, error)
Sync() error
UpdateRulesHandle() error
GetRuleHandle(id uint32) (uint64, error)
GetRulesUserData() (map[uint64][]byte, error)
}
type nfRules struct {
conn NetNS
table *nftables.Table
chain *nftables.Chain
sync.Mutex
currentID uint32
rules *nfRule
}
type nfSet struct {
set *nftables.Set
elements []nftables.SetElement
}
type nfRule struct {
id uint32
rule *nftables.Rule
sets []*nfSet
sync.Mutex
next *nfRule
prev *nfRule
}
type nfUserData struct {
RuleID uint32
AppData []byte
}
func (nfr *nfRules) Rules() RuleFuncs {
return nfr
}
func (nfr *nfRules) buildRule(rule *Rule) (*nfRule, error) {
r := &nftables.Rule{}
var err error
var sets []*nfSet
var set []*nfSet
e := []expr.Any{}
// Some Rule elements can request to skip processing of certain blocks
var skipL3, skipL4, skipAction bool
if rule.Concat != nil {
if rule.Concat.VMap {
skipL3, skipL4, skipAction = true, true, true
}
}
// Dynamic rules has its own matching criterions, no need to process global L3 and L4 selectors.
if rule.Dynamic != nil {
skipL3, skipL4 = true, true
}
// MatchAct rule has its own matching criterias and corresponding action, hence skipping regular rule processing.
if rule.MatchAct != nil {
skipL3, skipL4, skipAction = true, true, true
}
// Counter could be used a standalone key word, in this case it will cound number of
// packets and bytes which hit the chain where it is defined.
// Counter can also be used before and within any rules.
if rule.Counter != nil {
e := getExprForCounter()
r.Exprs = append(r.Exprs, e...)
}
if rule.Fib != nil {
e := getExprForFib(rule.Fib)
r.Exprs = append(r.Exprs, e...)
}
if rule.L3 != nil && !skipL3 {
if e, set, err = createL3(nfr.table.Family, rule); err != nil {
return nil, err
}
sets = append(sets, set...)
r.Exprs = append(r.Exprs, e...)
}
if rule.L4 != nil && !skipL4 {
if e, set, err = createL4(nfr.table.Family, rule); err != nil {
return nil, err
}
sets = append(sets, set...)
r.Exprs = append(r.Exprs, e...)
}
// If L3Rule or L4Rule did not produce a rule, initialize one to carry
// Rule's Action expression
if len(r.Exprs) == 0 {
r.Exprs = []expr.Any{}
}
// Check if Meta is specified appending to rule's list of expressions
if rule.Meta != nil {
switch {
case rule.Meta.Mark != nil:
r.Exprs = append(r.Exprs, getExprForMetaMark(rule.Meta.Mark)...)
case len(rule.Meta.Expr) != 0:
r.Exprs = append(r.Exprs, getExprForMetaExpr(rule.Meta.Expr)...)
}
}
// Check if Meta is specified appending to rule's list of expressions
if rule.Log != nil {
r.Exprs = append(r.Exprs, getExprForLog(rule.Log)...)
}
if len(rule.Conntracks) > 0 {
r.Exprs = append(r.Exprs, getExprForConntracks(rule.Conntracks)...)
}
if rule.Action != nil && !skipAction {
switch {
case rule.Action.redirect != nil:
if rule.Action.redirect.tproxy {
r.Exprs = append(r.Exprs, getExprForTProxyRedirect(rule.Action.redirect.port, nfr.table.Family)...)
} else {
r.Exprs = append(r.Exprs, getExprForRedirect(rule.Action.redirect.port, nfr.table.Family)...)
}
case rule.Action.verdict != nil:
r.Exprs = append(r.Exprs, rule.Action.verdict)
case rule.Action.masq != nil:
r.Exprs = append(r.Exprs, getExprForMasq(rule.Action.masq)...)
case rule.Action.reject != nil:
r.Exprs = append(r.Exprs, getExprForReject(rule.Action.reject)...)
case rule.Action.loadbalance != nil:
e, err := getExprForLoadbalance(nfr, rule.Action.loadbalance)
if err != nil {
return nil, err
}
// Adding generated loadbalancing expressions and anonymous set
r.Exprs = append(r.Exprs, e...)
case rule.Action.nat != nil:
e, err = getExprForNAT(nfr.table.Family, rule.Action.nat)
if err != nil {
return nil, err
}
r.Exprs = append(r.Exprs, e...)
}
}
if rule.Concat != nil {
e, err = getExprForConcat(nfr.table.Family, rule.Concat)
if err != nil {
return nil, err
}
r.Exprs = append(r.Exprs, e...)
}
if rule.Dynamic != nil {
e, err = getExprForDynamic(nfr.table.Family, rule.Dynamic)
if err != nil {
return nil, err
}
r.Exprs = append(r.Exprs, e...)
}
if rule.MatchAct != nil {
e, err = getExprForMatchAct(nfr, nfr.table.Family, rule.MatchAct)
if err != nil {
return nil, err
}
r.Exprs = append(r.Exprs, e...)
}
r.Table = nfr.table
r.Chain = nfr.chain
rr := &nfRule{}
rr.rule = r
for _, s := range sets {
s.set.Table = nfr.table
if err := nfr.conn.AddSet(s.set, s.elements); err != nil {
return nil, err
}
// s.set.DataLen = len(s.elements)
rr.sets = append(rr.sets, s)
}
return rr, nil
}
func (nfr *nfRules) Create(rule *Rule) (uint32, error) {
nfr.Lock()
defer nfr.Unlock()
return nfr.create(rule, operationAdd)
}
func (nfr *nfRules) create(rule *Rule, ruleOp ruleOperation) (uint32, error) {
// Process all user specified expressions and return nfRule
rr, err := nfr.buildRule(rule)
if err != nil {
return 0, err
}
// Adding nfRule to the list
nfr.addRule(rr)
if rule.Position != 0 {
// Used by Insert call
rr.rule.Position = uint64(rule.Position)
}
ul := len(rule.UserData)
// Extra 4 bytes to keep rule ID in userdata during the rule programming interactions.
rr.rule.UserData = make([]byte, ul+4)
if rule.UserData != nil {
copy(rr.rule.UserData, rule.UserData)
}
// Adding rule ID into the last 4 bytes
rr.rule.UserData[ul] = 0x2
rr.rule.UserData[ul+1] = 2
copy(rr.rule.UserData[ul+2:], binaryutil.BigEndian.PutUint16(uint16(rr.id)))
// Pushing rule to netlink library to be programmed by Flush()
switch ruleOp {
case operationAdd:
nfr.conn.AddRule(rr.rule)
case operationInsert:
nfr.conn.InsertRule(rr.rule)
}
return rr.id, nil
}
func (nfr *nfRules) CreateImm(rule *Rule) (uint64, error) {
nfr.Lock()
defer nfr.Unlock()
id, err := nfr.create(rule, operationAdd)
if err != nil {
return 0, err
}
// Programming rule
if err := nfr.conn.Flush(); err != nil {
return 0, err
}
// Getting rule's handle allocated by the kernel
handle, err := nfr.GetRuleHandle(id)
if err != nil {
return 0, err
}
if err := nfr.UpdateRuleHandleByID(id, handle); err != nil {
return 0, err
}
return handle, nil
}
func (nfr *nfRules) delete(id uint32) error {
r, err := getRuleByID(nfr.rules, id)
if err != nil {
return err
}
// If rule's handle is 0, it means it has not been already programmed
// then no reason to call netfilter module
if r.rule.Handle != 0 {
if err := nfr.conn.DelRule(r.rule); err != nil {
return err
}
}
return nfr.removeRule(r.id)
}
func (nfr *nfRules) Delete(id uint32) error {
nfr.Lock()
defer nfr.Unlock()
return nfr.delete(id)
}
func (nfr *nfRules) DeleteImm(rh uint64) error {
nfr.Lock()
defer nfr.Unlock()
r, err := getRuleByHandle(nfr.rules, rh)
if err != nil {
return err
}
if err := nfr.delete(r.id); err != nil {
return err
}
// Programming rule's deleteion
if err := nfr.conn.Flush(); err != nil {
return err
}
return nil
}
// Insert inserts a rule passed as a parameter before the rule which handle value matches
// the value of position passed in Rule.Position.
// Example: rule1 has handle of 5, you want to insert rule2 before rule1, then position for rule2 will be 5
func (nfr *nfRules) Insert(rule *Rule) (uint32, error) {
return nfr.create(rule, operationInsert)
}
func (nfr *nfRules) InsertImm(rule *Rule) (uint64, error) {
id, err := nfr.Insert(rule)
if err != nil {
return 0, err
}
// Programming rule
if err := nfr.conn.Flush(); err != nil {
return 0, err
}
// Getting rule's handle allocated by the kernel
handle, err := nfr.GetRuleHandle(id)
if err != nil {
return 0, err
}
if err := nfr.UpdateRuleHandleByID(id, handle); err != nil {
return 0, err
}
return handle, nil
}
func (nfr *nfRules) Update(rule *Rule, handle uint64) error {
nfrule, err := getRuleByHandle(nfr.rules, handle)
if err != nil {
return err
}
r, err := nfr.buildRule(rule)
if err != nil {
return err
}
r.rule.Handle = handle
ul := len(rule.UserData)
// Extra 4 bytes to keep rule ID in userdata during the rule programming interactions.
r.rule.UserData = make([]byte, ul+4)
if rule.UserData != nil {
copy(r.rule.UserData, rule.UserData)
}
// Adding rule ID into the last 4 bytes
r.rule.UserData[ul] = 0x2
r.rule.UserData[ul+1] = 2
copy(r.rule.UserData[ul+2:], binaryutil.BigEndian.PutUint16(uint16(r.id)))
// Updating rule expressions and sets but preserving pointers to prev and next
nfrule.rule = r.rule
nfrule.sets = r.sets
// Pushing rule to netlink library to be programmed by Flush()
nfr.conn.AddRule(nfrule.rule)
// Programming Update rule
if err := nfr.conn.Flush(); err != nil {
return err
}
return nil
}
func (nfr *nfRules) Dump() ([]byte, error) {
nfr.Lock()
defer nfr.Unlock()
var data []byte
for _, r := range nfr.dumpRules() {
b, err := json.Marshal(&r)
if err != nil {
return nil, err
}
data = append(data, b...)
}
return data, nil
}
func (nfr *nfRules) Sync() error {
rules, err := nfr.conn.GetRule(nfr.table, nfr.chain)
if err != nil {
return err
}
for _, rule := range rules {
sets := make([]*nfSet, 0)
for _, e := range rule.Exprs {
exp, ok := e.(*expr.Lookup)
if !ok {
continue
}
set, err := nfr.getSet(exp.SetName)
if err != nil {
return err
}
elements, err := nfr.getSetElements(set)
if err != nil {
return err
}
// set.DataLen = len(elements)
sets = append(sets, &nfSet{set: set, elements: elements})
}
rr := &nfRule{}
rr.rule = rule
if len(sets) != 0 {
rr.sets = sets
}
nfr.addRule(rr)
}
return nil
}
func (nfr *nfRules) getSet(name string) (*nftables.Set, error) {
sets, err := nfr.conn.GetSets(nfr.table)
if err != nil {
return nil, err
}
for _, set := range sets {
if set.Name == name {
set.Table = nfr.table
return set, nil
}
}
return &nftables.Set{}, nil
}
func (nfr *nfRules) getSetElements(set *nftables.Set) ([]nftables.SetElement, error) {
elements, err := nfr.conn.GetSetElements(set)
if err != nil {
return nil, err
}
return elements, nil
}
// UpdateRulesHandle populates rule's handle information with handle value allocated by the kernel.
// Handle information can be used for further rule's management.
func (nfr *nfRules) UpdateRulesHandle() error {
r := nfr.rules
for ; r != nil; r = r.next {
handle, err := nfr.GetRuleHandle(r.id)
if err != nil {
return err
}
r.rule.Handle = handle
}
return nil
}
func (nfr *nfRules) UpdateRuleHandleByID(id uint32, handle uint64) error {
r := nfr.rules
for ; r != nil; r = r.next {
if r.id == id {
nfr.rules.Lock()
defer nfr.rules.Unlock()
r.rule.Handle = handle
return nil
}
}
return fmt.Errorf("rule id %d is not found", id)
}
// GetRuleHandle gets a handle of rule specified by its id
func (nfr *nfRules) GetRuleHandle(id uint32) (uint64, error) {
rules, err := nfr.conn.GetRule(nfr.table, nfr.chain)
if err != nil {
return 0, err
}
for _, rule := range rules {
if rule.UserData != nil {
// Rule ID TLV is stored in last 4 bytes of User data
n := make([]byte, 4)
// Rule ID TLV 4 bytes:
// [0] - TLV type , must be 0x2
// [1] - Value length, must be 2
// [2:] - 2 bytes carrying Rule ID
if rule.UserData[len(rule.UserData)-4] != 0x2 || rule.UserData[len(rule.UserData)-3] != 0x2 {
return 0, fmt.Errorf("did not find Rule ID TLV in user data")
}
// Copy last 2 bytes of user data which carry rule id
copy(n[2:], rule.UserData[len(rule.UserData)-2:])
ruleID := binaryutil.BigEndian.Uint32(n)
if ruleID == id {
return rule.Handle, nil
}
}
}
return 0, fmt.Errorf("rule with id %d is not found", id)
}
func (nfr *nfRules) GetRulesUserData() (map[uint64][]byte, error) {
rules, err := nfr.conn.GetRule(nfr.table, nfr.chain)
if err != nil {
return nil, err
}
ud := make(map[uint64][]byte, 0)
for _, rule := range rules {
if rule.UserData != nil {
// TODO, needs to be tested
ud[rule.Handle] = rule.UserData[:len(rule.UserData)-4]
}
}
return ud, nil
}
func newRules(conn NetNS, t *nftables.Table, c *nftables.Chain) RulesInterface {
return &nfRules{
conn: conn,
table: t,
chain: c,
currentID: 10,
rules: nil,
}
}
// IPAddr defines a type of ip address, if it is host address with mask of 32 for ipv4 and mask of 128 for ipv6
// then CIDR should be false, if it is a network address, then CIDR should be true and Mask set to a number of bits
// in the address' mask. Mask value is from 0 to 32 for ipv4 and from 0 to 128 for ipv6 addresses.
type IPAddr struct {
*net.IPAddr
CIDR bool
Mask *uint8
}
// IsIPv6 is a helper function, it returns true if IPAddr struct holds IPv6 address, otherwise it returns false
func (ip *IPAddr) IsIPv6() bool {
if ip.IP.To4() == nil {
return true
}
return false
}
// Validate checks validity of ip address and its parameters
func (ip *IPAddr) Validate() error {
// If CIDR is not specified, there is nothing to validate
if !ip.CIDR {
return nil
}
if ip.CIDR && ip.Mask == nil {
return fmt.Errorf("mask length must be specified when CIDR is true")
}
return nil
}
// Operator defines type used for relational operations in the rule
type Operator byte
// List of supported relational operations, starts with 0. if not specified, default 0 inidcates eq operator
const (
EQ Operator = iota
NEQ
)
// IPAddrSpec lists possible flavours if specifying ip address, either List or Range can be specified
type IPAddrSpec struct {
List []*IPAddr
Range [2]*IPAddr
SetRef *SetRef
RelOp Operator
}
// NewIPAddr is a helper function which converts ip address into IPAddr format
// required by IPAddrSpec. If CIDR format is specified, Mask will be set to address'
// subnet mask and CIDR will e set to true
func NewIPAddr(addr string) (*IPAddr, error) {
if _, ipnet, err := net.ParseCIDR(addr); err == nil {
// Found a valid CIDR address
ones, _ := ipnet.Mask.Size()
mask := uint8(ones)
return &IPAddr{
&net.IPAddr{
IP: ipnet.IP,
},
true,
&mask,
}, nil
}
// Check if addr is just ip address in a non CIDR format
ip := net.ParseIP(addr)
if ip == nil {
return nil, fmt.Errorf("%s is invalid ip address", addr)
}
mask := uint8(32)
if ip.To4() == nil {
mask = uint8(128)
}
_, ipnet, err := net.ParseCIDR(addr + "/" + fmt.Sprintf("%d", mask))
if err != nil {
return nil, err
}
return &IPAddr{
&net.IPAddr{
IP: ipnet.IP,
},
true,
&mask,
}, nil
}
// Validate checks IPAddrSpec struct
func (ip *IPAddrSpec) Validate() error {
if len(ip.List) != 0 && (ip.Range[0] != nil || ip.Range[1] != nil) {
return fmt.Errorf("either List or Range but not both can be specified")
}
if len(ip.List) == 0 && (ip.Range[0] == nil || ip.Range[1] == nil) {
return fmt.Errorf("neither List nor Range is specified")
}
if len(ip.List) != 0 {
for i := 0; i < len(ip.List); i++ {
if err := ip.List[i].Validate(); err != nil {
return err
}
}
}
if ip.Range[0] != nil && ip.Range[1] != nil {
for i := 0; i < len(ip.Range); i++ {
if err := ip.Range[i].Validate(); err != nil {
return err
}
}
}
return nil
}
// L3Rule contains parameters for L3 based rule, either Source or Destination can be specified
type L3Rule struct {
Src *IPAddrSpec
Dst *IPAddrSpec
Version *byte
Protocol *uint32
RelOp Operator
Counter *Counter
}
// L3Protocol is a helper function to convert a value of L3 protocol
// to the type required by L3Rule *uint32
func L3Protocol(proto int) *uint32 {
p := uint32(proto)
return &p
}
// Validate checks parameters of L3Rule struct
func (l3 *L3Rule) Validate() error {
switch {
case l3.Src != nil:
if err := l3.Src.Validate(); err != nil {
return err
}
case l3.Dst != nil:
if err := l3.Dst.Validate(); err != nil {
return err
}
case l3.Version != nil:
case l3.Protocol != nil:
default:
return fmt.Errorf("invalid L3 rule as none of L3 parameters are provided")
}
return nil
}
// SetRef defines a reference to a Set/Map/Vmap
type SetRef struct {
Name string
ID uint32
IsMap bool
}
// Port lists possible flavours of specifying port information
type Port struct {
List []*uint16
Range [2]*uint16
RelOp Operator
SetRef *SetRef
}
// SetPortList is a helper function which transforms a slice of int into
// a format required by Port struct
func SetPortList(ports []int) []*uint16 {
p := make([]*uint16, len(ports))
for i, port := range ports {
pp := uint16(port)
p[i] = &pp
}
return p
}
// SetPortRange is a helper function which transforms an 2 element array of int into
// a format required by Port struct
func SetPortRange(ports [2]int) [2]*uint16 {
p := [2]*uint16{}
for i, port := range ports {
pp := uint16(port)
p[i] = &pp
}
return p
}
// Validate check parameters of Port struct
func (p *Port) Validate() error {
set := 0
switch {
case len(p.List) != 0:
set++
case p.Range[0] != nil || p.Range[1] != nil:
if p.Range[0] == nil || p.Range[1] == nil {
return fmt.Errorf("port range requires both ports of the range to be non nil")
}
set++
case p.SetRef != nil:
set++
}
if set > 1 {
return fmt.Errorf("either List or Range or SetRef but not the combination of them can be specified")
}
if set == 0 {
return fmt.Errorf("neither List nor Range nor SetRef is specified")
}
return nil
}
// L4Rule contains parameters for L4 based rule
type L4Rule struct {
L4Proto uint8
Src *Port
Dst *Port
RelOp Operator
Counter *Counter
}
// Validate checks parameters of L4Rule struct
func (l4 *L4Rule) Validate() error {
if l4.L4Proto == 0 {
return fmt.Errorf("L4Proto cannot be 0")
}
if l4.Src != nil {
if err := l4.Src.Validate(); err != nil {
return err
}
}
if l4.Dst != nil {
if err := l4.Dst.Validate(); err != nil {
return err
}
}
return nil
}
// redirect defines struct describing Redirection action, if Transparent Proxy is required
// TProxy should be set
type redirect struct {
port uint16
tproxy bool
}
// masquarade defines a struct describing Masquerade action, flags cannot be combined with
// toPort
type masquerade struct {
random *bool
fullyRandom *bool
persistent *bool
toPort [2]*uint16
}
// nat defines a struct describing nat action
type nat struct {
nattype expr.NATType
random *bool
fullyRandom *bool
persistent *bool
address *IPAddrSpec
port *Port
}
// reject defines reject action
type reject struct {
rejectType uint32
rejectCode uint8
}
// loadbalance defines action to loadbalance between 1 or more chains
type loadbalance struct {
chains []string
action int
mode int
}
// MetaMark defines Mark keyword of Meta key
// Mark can be used either to Set or Match a mark.
// If Set is true, then the Value will be used to mark a packet,
// and if Set is false, then the Value will be used to match packet's mark against it.
// Mask can be used to test for or to set only particular bits in mark.
// If mask is 0, than it is not used at all.
type MetaMark struct {
Set bool
Value uint32
Mask uint32
}
// MetaExpr allows specifing Meta expressions by meta key and its value,
// example Key: unix.NFT_META_SKGID and Value: 1024
type MetaExpr struct {
Key uint32
Value []byte
RelOp Operator
}
// Meta defines parameters used to build nft meta expression
type Meta struct {
Mark *MetaMark
Expr []MetaExpr
}
// RuleAction defines what action needs to be executed on the rule match
type RuleAction struct {
verdict *expr.Verdict
redirect *redirect
masq *masquerade
nat *nat
reject *reject
loadbalance *loadbalance
}
// SetLoadbalance builds RuleAction struct for Verdict based actions,
// action parameter defines whether unix.NFT_JUMP (default) or unix.NFT_GOTO will be used to reach one of
// load balanced chains
// mode parameters defines the mode of load balancing between the chains; unix.NFT_NG_RANDOM (default)
// or unix.NFT_NG_INCREMENTAL.
func SetLoadbalance(chains []string, action int, mode int) (*RuleAction, error) {
if len(chains) == 0 {
return nil, fmt.Errorf("number of chains for loadbalancing cannot be 0")
}
ra := &RuleAction{
loadbalance: &loadbalance{
chains: chains,
action: action,
},
}
return ra, nil
}
// SetVerdict builds RuleAction struct for Verdict based actions
func SetVerdict(key int, chain ...string) (*RuleAction, error) {
ra := &RuleAction{}
if err := ra.setVerdict(key, chain...); err != nil {
return nil, err
}
return ra, nil
}
// SetRedirect builds RuleAction struct for Redirect action
func SetRedirect(port int, tproxy bool) (*RuleAction, error) {
ra := &RuleAction{}
if err := ra.setRedirect(port, tproxy); err != nil {
return nil, err
}
return ra, nil
}
// SetMasq builds RuleAction struct for Masquerade action
func SetMasq(random, fullyRandom, persistent bool) (*RuleAction, error) {
ra := &RuleAction{}
ra.masq = &masquerade{}
ra.masq.random = &random
ra.masq.fullyRandom = &fullyRandom
ra.masq.persistent = &persistent
return ra, nil
}
// SetMasqToPort builds RuleAction struct for Masquerade action
func SetMasqToPort(port ...int) (*RuleAction, error) {
ra := &RuleAction{}
ra.masq = &masquerade{}
if len(port) == 0 {
return nil, fmt.Errorf("no port provided")
}
if len(port) > 2 {
return nil, fmt.Errorf("more than maximum of 2 ports provided")
}
ports := [2]*uint16{}
p := uint16(port[0])
ports[0] = &p
if len(port) == 2 {
p := uint16(port[1])
ports[1] = &p
}
ra.masq.toPort = ports
return ra, nil
}
// NATAttributes defines parameters used to generate nftables nat rule
// it is used as input parameter to two helper functions SetSNAT and SetDNAT
// Either L3Addr or Port must be defined.
// When 2 elements of array are specified, then the range of either ip addresses
// or ports will be specified in NAT rule.
type NATAttributes struct {
L3Addr [2]*IPAddr
Port [2]uint16
FullyRandom bool
Random bool
Persistent bool
}
func setNat(nattype expr.NATType, natAttrs *NATAttributes) (*RuleAction, error) {
if len(natAttrs.L3Addr) == 0 && len(natAttrs.Port) == 0 {
return nil, fmt.Errorf("either ip address or port must be specified")
}
ra := &RuleAction{}
ra.nat = &nat{
nattype: nattype,
fullyRandom: &natAttrs.FullyRandom,
random: &natAttrs.Random,
persistent: &natAttrs.Persistent,
}
addr := &IPAddrSpec{}
switch {
case natAttrs.L3Addr[0] != nil && natAttrs.L3Addr[1] != nil:
// Both IP addresses are not nil, then pass them as Range
addr.Range = [2]*IPAddr{}
addr.Range[0] = natAttrs.L3Addr[0]
addr.Range[1] = natAttrs.L3Addr[1]
case natAttrs.L3Addr[0] == nil && natAttrs.L3Addr[1] != nil:
return nil, fmt.Errorf("first element of a range cannot be nil")
case natAttrs.L3Addr[0] != nil:
// Single IP is specified, then pass it as a single element of the list
addr.List = make([]*IPAddr, 1)
addr.List[0] = natAttrs.L3Addr[0]
}
ra.nat.address = addr
// Process port if it is specified
port := Port{}
switch {
case natAttrs.Port[0] != 0 && natAttrs.Port[1] != 0:
// Both Ports are not 0, then pass them as Range
port.Range = [2]*uint16{}
port.Range[0] = &natAttrs.Port[0]
port.Range[1] = &natAttrs.Port[1]
case natAttrs.Port[0] == 0 && natAttrs.Port[1] != 0:
return nil, fmt.Errorf("first element of a port range cannot be 0")
case natAttrs.Port[0] != 0:
// Single Port is specified, then pass it as a single element of the list
port.List = make([]*uint16, 1)
port.List[0] = &natAttrs.Port[0]
}
ra.nat.port = &port
// Add NAT flags is any specified
ra.nat.random = &natAttrs.Random
ra.nat.fullyRandom = &natAttrs.FullyRandom
ra.nat.persistent = &natAttrs.Persistent
return ra, nil
}
// SetSNAT builds RuleAction struct for SNAT action
func SetSNAT(natAttrs *NATAttributes) (*RuleAction, error) {
return setNat(expr.NATTypeSourceNAT, natAttrs)