Skip to content

Commit 8e1ce96

Browse files
tssuryaaboch
authored andcommitted
Add labelFilter for conntrack
This PR adds support for filtering flows based on conntrack labels. It adds two filters `ConntrackMatchLabels` && `ConntackUnmatchLabels` through which user can provide a list of labels as type "bytes" which will then be compared to flow.Labels to see if any matches were found. ConntrackMatchLabels: Every label passed should be contained in flow.Labels for a match to be true ConntrackUmmatchLabels: Every label passed should not be contained in the flow.Labels for a match to be true Signed-off-by: Surya Seetharaman <[email protected]>
1 parent eab52ee commit 8e1ce96

File tree

3 files changed

+97
-6
lines changed

3 files changed

+97
-6
lines changed

conntrack_linux.go

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,20 +149,26 @@ type ConntrackFlow struct {
149149
TimeStart uint64
150150
TimeStop uint64
151151
TimeOut uint32
152+
Labels []byte
152153
}
153154

154155
func (s *ConntrackFlow) String() string {
155156
// conntrack cmd output:
156-
// udp 17 src=127.0.0.1 dst=127.0.0.1 sport=4001 dport=1234 packets=5 bytes=532 [UNREPLIED] src=127.0.0.1 dst=127.0.0.1 sport=1234 dport=4001 packets=10 bytes=1078 mark=0
157+
// udp 17 src=127.0.0.1 dst=127.0.0.1 sport=4001 dport=1234 packets=5 bytes=532 [UNREPLIED] src=127.0.0.1 dst=127.0.0.1 sport=1234 dport=4001 packets=10 bytes=1078 mark=0 labels=0x00000000050012ac4202010000000000
157158
// start=2019-07-26 01:26:21.557800506 +0000 UTC stop=1970-01-01 00:00:00 +0000 UTC timeout=30(sec)
158159
start := time.Unix(0, int64(s.TimeStart))
159160
stop := time.Unix(0, int64(s.TimeStop))
160161
timeout := int32(s.TimeOut)
161-
return fmt.Sprintf("%s\t%d src=%s dst=%s sport=%d dport=%d packets=%d bytes=%d\tsrc=%s dst=%s sport=%d dport=%d packets=%d bytes=%d mark=0x%x start=%v stop=%v timeout=%d(sec)",
162+
res := fmt.Sprintf("%s\t%d src=%s dst=%s sport=%d dport=%d packets=%d bytes=%d\tsrc=%s dst=%s sport=%d dport=%d packets=%d bytes=%d mark=0x%x ",
162163
nl.L4ProtoMap[s.Forward.Protocol], s.Forward.Protocol,
163164
s.Forward.SrcIP.String(), s.Forward.DstIP.String(), s.Forward.SrcPort, s.Forward.DstPort, s.Forward.Packets, s.Forward.Bytes,
164165
s.Reverse.SrcIP.String(), s.Reverse.DstIP.String(), s.Reverse.SrcPort, s.Reverse.DstPort, s.Reverse.Packets, s.Reverse.Bytes,
165-
s.Mark, start, stop, timeout)
166+
s.Mark)
167+
if len(s.Labels) > 0 {
168+
res += fmt.Sprintf("labels=0x%x ", s.Labels)
169+
}
170+
res += fmt.Sprintf("start=%v stop=%v timeout=%d(sec)", start, stop, timeout)
171+
return res
166172
}
167173

168174
// This method parse the ip tuple structure
@@ -306,6 +312,12 @@ func parseConnectionMark(r *bytes.Reader) (mark uint32) {
306312
return
307313
}
308314

315+
func parseConnectionLabels(r *bytes.Reader) (label []byte) {
316+
label = make([]byte, 16) // netfilter defines 128 bit labels value
317+
binary.Read(r, nl.NativeEndian(), &label)
318+
return
319+
}
320+
309321
func parseRawData(data []byte) *ConntrackFlow {
310322
s := &ConntrackFlow{}
311323
// First there is the Nfgenmsg header
@@ -351,6 +363,8 @@ func parseRawData(data []byte) *ConntrackFlow {
351363
switch t {
352364
case nl.CTA_MARK:
353365
s.Mark = parseConnectionMark(reader)
366+
case nl.CTA_LABELS:
367+
s.Labels = parseConnectionLabels(reader)
354368
case nl.CTA_TIMEOUT:
355369
s.TimeOut = parseTimeOut(reader)
356370
case nl.CTA_STATUS, nl.CTA_USE, nl.CTA_ID:
@@ -406,6 +420,8 @@ const (
406420
ConntrackReplyAnyIP // Match source or destination reply IP
407421
ConntrackOrigSrcPort // --orig-port-src port Source port in original direction
408422
ConntrackOrigDstPort // --orig-port-dst port Destination port in original direction
423+
ConntrackMatchLabels // --label label1,label2 Labels used in entry
424+
ConntrackUnmatchLabels // --label label1,label2 Labels not used in entry
409425
ConntrackNatSrcIP = ConntrackReplySrcIP // deprecated use instead ConntrackReplySrcIP
410426
ConntrackNatDstIP = ConntrackReplyDstIP // deprecated use instead ConntrackReplyDstIP
411427
ConntrackNatAnyIP = ConntrackReplyAnyIP // deprecated use instead ConntrackReplyAnyIP
@@ -421,6 +437,7 @@ type ConntrackFilter struct {
421437
ipNetFilter map[ConntrackFilterType]*net.IPNet
422438
portFilter map[ConntrackFilterType]uint16
423439
protoFilter uint8
440+
labelFilter map[ConntrackFilterType][][]byte
424441
}
425442

426443
// AddIPNet adds a IP subnet to the conntrack filter
@@ -474,10 +491,34 @@ func (f *ConntrackFilter) AddProtocol(proto uint8) error {
474491
return nil
475492
}
476493

494+
// AddLabels adds the provided list (zero or more) of labels to the conntrack filter
495+
// ConntrackFilterType here can be either:
496+
// 1) ConntrackMatchLabels: This matches every flow that has a label value (len(flow.Labels) > 0)
497+
// against the list of provided labels. If `flow.Labels` contains ALL the provided labels
498+
// it is considered a match. This can be used when you want to match flows that contain
499+
// one or more labels.
500+
// 2) ConntrackUnmatchLabels: This matches every flow that has a label value (len(flow.Labels) > 0)
501+
// against the list of provided labels. If `flow.Labels` does NOT contain ALL the provided labels
502+
// it is considered a match. This can be used when you want to match flows that don't contain
503+
// one or more labels.
504+
func (f *ConntrackFilter) AddLabels(tp ConntrackFilterType, labels [][]byte) error {
505+
if len(labels) == 0 {
506+
return errors.New("Invalid length for provided labels")
507+
}
508+
if f.labelFilter == nil {
509+
f.labelFilter = make(map[ConntrackFilterType][][]byte)
510+
}
511+
if _, ok := f.labelFilter[tp]; ok {
512+
return errors.New("Filter attribute already present")
513+
}
514+
f.labelFilter[tp] = labels
515+
return nil
516+
}
517+
477518
// MatchConntrackFlow applies the filter to the flow and returns true if the flow matches the filter
478519
// false otherwise
479520
func (f *ConntrackFilter) MatchConntrackFlow(flow *ConntrackFlow) bool {
480-
if len(f.ipNetFilter) == 0 && len(f.portFilter) == 0 && f.protoFilter == 0 {
521+
if len(f.ipNetFilter) == 0 && len(f.portFilter) == 0 && f.protoFilter == 0 && len(f.labelFilter) == 0 {
481522
// empty filter always not match
482523
return false
483524
}
@@ -531,6 +572,29 @@ func (f *ConntrackFilter) MatchConntrackFlow(flow *ConntrackFlow) bool {
531572
}
532573
}
533574

575+
// Label filter
576+
if len(f.labelFilter) > 0 {
577+
if len(flow.Labels) > 0 {
578+
// --label label1,label2 in conn entry;
579+
// every label passed should be contained in flow.Labels for a match to be true
580+
if elem, found := f.labelFilter[ConntrackMatchLabels]; match && found {
581+
for _, label := range elem {
582+
match = match && (bytes.Contains(flow.Labels, label))
583+
}
584+
}
585+
// --label label1,label2 in conn entry;
586+
// every label passed should be not contained in flow.Labels for a match to be true
587+
if elem, found := f.labelFilter[ConntrackUnmatchLabels]; match && found {
588+
for _, label := range elem {
589+
match = match && !(bytes.Contains(flow.Labels, label))
590+
}
591+
}
592+
} else {
593+
// flow doesn't contain labels, so it doesn't contain or notContain any provided matches
594+
match = false
595+
}
596+
}
597+
534598
return match
535599
}
536600

conntrack_test.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ func TestConntrackFilter(t *testing.T) {
380380
DstPort: 5000,
381381
Protocol: 6,
382382
},
383+
Labels: []byte{0, 0, 0, 0, 3, 4, 61, 141, 207, 170, 2, 0, 0, 0, 0, 0},
383384
},
384385
ConntrackFlow{
385386
FamilyType: unix.AF_INET6,
@@ -732,6 +733,28 @@ func TestConntrackFilter(t *testing.T) {
732733
if v4Match != 1 || v6Match != 1 {
733734
t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match)
734735
}
736+
737+
// Labels filter
738+
filterV4 = &ConntrackFilter{}
739+
var labels [][]byte
740+
labels = append(labels, []byte{3, 4, 61, 141, 207, 170})
741+
labels = append(labels, []byte{0x2})
742+
err = filterV4.AddLabels(ConntrackMatchLabels, labels)
743+
if err != nil {
744+
t.Fatalf("Unexpected error: %v", err)
745+
}
746+
747+
filterV6 = &ConntrackFilter{}
748+
err = filterV6.AddLabels(ConntrackUnmatchLabels, labels)
749+
if err != nil {
750+
t.Fatalf("Unexpected error: %v", err)
751+
}
752+
753+
v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
754+
if v4Match != 1 || v6Match != 0 {
755+
t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match)
756+
}
757+
735758
}
736759

737760
func TestParseRawData(t *testing.T) {
@@ -826,9 +849,12 @@ func TestParseRawData(t *testing.T) {
826849
16, 0, 20, 128,
827850
/* >>>> CTA_TIMESTAMP_START */
828851
12, 0, 1, 0,
829-
22, 134, 80, 142, 230, 127, 74, 166},
852+
22, 134, 80, 142, 230, 127, 74, 166,
853+
/* >> CTA_LABELS */
854+
20, 0, 22, 0,
855+
0, 0, 0, 0, 5, 0, 18, 172, 66, 2, 1, 0, 0, 0, 0, 0},
830856
expConntrackFlow: "udp\t17 src=192.168.0.10 dst=192.168.0.3 sport=48385 dport=53 packets=1 bytes=55\t" +
831-
"src=192.168.0.3 dst=192.168.0.10 sport=53 dport=48385 packets=1 bytes=71 mark=0x5 " +
857+
"src=192.168.0.3 dst=192.168.0.10 sport=53 dport=48385 packets=1 bytes=71 mark=0x5 labels=0x00000000050012ac4202010000000000 " +
832858
"start=2021-06-07 13:41:30.39632247 +0000 UTC stop=1970-01-01 00:00:00 +0000 UTC timeout=32(sec)",
833859
},
834860
{

nl/conntrack_linux.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ const (
8989
CTA_USE = 11
9090
CTA_ID = 12
9191
CTA_TIMESTAMP = 20
92+
CTA_LABELS = 22
9293
)
9394

9495
// enum ctattr_tuple {

0 commit comments

Comments
 (0)