Skip to content

Commit

Permalink
conntrack filter by port and protocol
Browse files Browse the repository at this point in the history
Add a new method to the ConntrackFilter to be able to
filter conntrack entries by Layer 4 protocol and source
and destination port.

Signed-off-by: Antonio Ojea <aojea@redhat.com>
  • Loading branch information
aojea authored and aboch committed Jun 25, 2020
1 parent cf66001 commit bca67df
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 60 deletions.
116 changes: 88 additions & 28 deletions conntrack_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,18 +318,25 @@ func parseRawData(data []byte) *ConntrackFlow {
// --mask-src ip Source mask address
// --mask-dst ip Destination mask address

// Layer 4 Protocol common parameters and options:
// TCP, UDP, SCTP, UDPLite and DCCP
// --sport, --orig-port-src port Source port in original direction
// --dport, --orig-port-dst port Destination port in original direction

// Filter types
type ConntrackFilterType uint8

const (
ConntrackOrigSrcIP = iota // -orig-src ip Source address from original direction
ConntrackOrigDstIP // -orig-dst ip Destination address from original direction
ConntrackReplySrcIP // --reply-src ip Reply Source IP
ConntrackReplyDstIP // --reply-dst ip Reply Destination IP
ConntrackReplyAnyIP // Match source or destination reply IP
ConntrackNatSrcIP = ConntrackReplySrcIP // deprecated use instead ConntrackReplySrcIP
ConntrackNatDstIP = ConntrackReplyDstIP // deprecated use instead ConntrackReplyDstIP
ConntrackNatAnyIP = ConntrackReplyAnyIP // deprecated use instaed ConntrackReplyAnyIP
ConntrackOrigSrcIP = iota // -orig-src ip Source address from original direction
ConntrackOrigDstIP // -orig-dst ip Destination address from original direction
ConntrackReplySrcIP // --reply-src ip Reply Source IP
ConntrackReplyDstIP // --reply-dst ip Reply Destination IP
ConntrackReplyAnyIP // Match source or destination reply IP
ConntrackOrigSrcPort // --orig-port-src port Source port in original direction
ConntrackOrigDstPort // --orig-port-dst port Destination port in original direction
ConntrackNatSrcIP = ConntrackReplySrcIP // deprecated use instead ConntrackReplySrcIP
ConntrackNatDstIP = ConntrackReplyDstIP // deprecated use instead ConntrackReplyDstIP
ConntrackNatAnyIP = ConntrackReplyAnyIP // deprecated use instead ConntrackReplyAnyIP
)

type CustomConntrackFilter interface {
Expand All @@ -339,7 +346,9 @@ type CustomConntrackFilter interface {
}

type ConntrackFilter struct {
ipFilter map[ConntrackFilterType]net.IP
ipFilter map[ConntrackFilterType]net.IP
portFilter map[ConntrackFilterType]uint16
protoFilter uint8
}

// AddIP adds an IP to the conntrack filter
Expand All @@ -354,38 +363,89 @@ func (f *ConntrackFilter) AddIP(tp ConntrackFilterType, ip net.IP) error {
return nil
}

// AddPort adds a Port to the conntrack filter if the Layer 4 protocol allows it
func (f *ConntrackFilter) AddPort(tp ConntrackFilterType, port uint16) error {
switch f.protoFilter {
// TCP, UDP, DCCP, SCTP, UDPLite
case 6, 17, 33, 132, 136:
default:
return fmt.Errorf("Filter attribute not available without a valid Layer 4 protocol: %d", f.protoFilter)
}

if f.portFilter == nil {
f.portFilter = make(map[ConntrackFilterType]uint16)
}
if _, ok := f.portFilter[tp]; ok {
return errors.New("Filter attribute already present")
}
f.portFilter[tp] = port
return nil
}

// AddProtocol adds the Layer 4 protocol to the conntrack filter
func (f *ConntrackFilter) AddProtocol(proto uint8) error {
if f.protoFilter != 0 {
return errors.New("Filter attribute already present")
}
f.protoFilter = proto
return nil
}

// MatchConntrackFlow applies the filter to the flow and returns true if the flow matches the filter
// false otherwise
func (f *ConntrackFilter) MatchConntrackFlow(flow *ConntrackFlow) bool {
if len(f.ipFilter) == 0 {
if len(f.ipFilter) == 0 && len(f.portFilter) == 0 && f.protoFilter == 0 {
// empty filter always not match
return false
}

match := true
// -orig-src ip Source address from original direction
if elem, found := f.ipFilter[ConntrackOrigSrcIP]; found {
match = match && elem.Equal(flow.Forward.SrcIP)
// -p, --protonum proto Layer 4 Protocol, eg. 'tcp'
if f.protoFilter != 0 && flow.Forward.Protocol != f.protoFilter {
// different Layer 4 protocol always not match
return false
}

// -orig-dst ip Destination address from original direction
if elem, found := f.ipFilter[ConntrackOrigDstIP]; match && found {
match = match && elem.Equal(flow.Forward.DstIP)
}
match := true

// -src-nat ip Source NAT ip
if elem, found := f.ipFilter[ConntrackReplySrcIP]; match && found {
match = match && elem.Equal(flow.Reverse.SrcIP)
}
// IP conntrack filter
if len(f.ipFilter) > 0 {
// -orig-src ip Source address from original direction
if elem, found := f.ipFilter[ConntrackOrigSrcIP]; found {
match = match && elem.Equal(flow.Forward.SrcIP)
}

// -orig-dst ip Destination address from original direction
if elem, found := f.ipFilter[ConntrackOrigDstIP]; match && found {
match = match && elem.Equal(flow.Forward.DstIP)
}

// -dst-nat ip Destination NAT ip
if elem, found := f.ipFilter[ConntrackReplyDstIP]; match && found {
match = match && elem.Equal(flow.Reverse.DstIP)
// -src-nat ip Source NAT ip
if elem, found := f.ipFilter[ConntrackReplySrcIP]; match && found {
match = match && elem.Equal(flow.Reverse.SrcIP)
}

// -dst-nat ip Destination NAT ip
if elem, found := f.ipFilter[ConntrackReplyDstIP]; match && found {
match = match && elem.Equal(flow.Reverse.DstIP)
}

// Match source or destination reply IP
if elem, found := f.ipFilter[ConntrackReplyAnyIP]; match && found {
match = match && (elem.Equal(flow.Reverse.SrcIP) || elem.Equal(flow.Reverse.DstIP))
}
}

// Match source or destination reply IP
if elem, found := f.ipFilter[ConntrackReplyAnyIP]; match && found {
match = match && (elem.Equal(flow.Reverse.SrcIP) || elem.Equal(flow.Reverse.DstIP))
// Layer 4 Port filter
if len(f.portFilter) > 0 {
// -orig-port-src port Source port from original direction
if elem, found := f.portFilter[ConntrackOrigSrcPort]; match && found {
match = match && elem == flow.Forward.SrcPort
}

// -orig-port-dst port Destination port from original direction
if elem, found := f.portFilter[ConntrackOrigDstPort]; match && found {
match = match && elem == flow.Forward.DstPort
}
}

return match
Expand Down
166 changes: 134 additions & 32 deletions conntrack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,18 @@ func TestConntrackSocket(t *testing.T) {
// Creates some flows and checks that they are correctly fetched from the conntrack table
func TestConntrackTableList(t *testing.T) {
skipUnlessRoot(t)
k, m, err := KernelVersion()
if err != nil {
t.Fatal(err)
}
// conntrack l3proto was unified since 4.19
// https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
if k < 4 || k == 4 && m < 19 {
setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4")
setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv6")
}
setUpNetlinkTestWithKModule(t, "nf_conntrack")
setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink")
setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4")
setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv6")

// Creates a new namespace and bring up the loopback interface
origns, ns, h := nsCreateAndEnter(t)
Expand All @@ -107,7 +115,7 @@ func TestConntrackTableList(t *testing.T) {
setUpF(t, "/proc/sys/net/netfilter/nf_conntrack_acct", "1")

// Flush the table to start fresh
err := h.ConntrackTableFlush(ConntrackTable)
err = h.ConntrackTableFlush(ConntrackTable)
CheckErrorFail(t, err)

// Create 5 udp
Expand Down Expand Up @@ -149,8 +157,16 @@ func TestConntrackTableFlush(t *testing.T) {
skipUnlessRoot(t)
setUpNetlinkTestWithKModule(t, "nf_conntrack")
setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink")
setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4")

k, m, err := KernelVersion()
if err != nil {
t.Fatal(err)
}
// conntrack l3proto was unified since 4.19
// https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
if k < 4 || k == 4 && m < 19 {
setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4")
}
setUpNetlinkTestWithKModule(t, "nf_conntrack")
// Creates a new namespace and bring up the loopback interface
origns, ns, h := nsCreateAndEnter(t)
defer netns.Set(*origns)
Expand Down Expand Up @@ -211,7 +227,15 @@ func TestConntrackTableDelete(t *testing.T) {
skipUnlessRoot(t)
setUpNetlinkTestWithKModule(t, "nf_conntrack")
setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink")
setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4")
k, m, err := KernelVersion()
if err != nil {
t.Fatal(err)
}
// conntrack l3proto was unified since 4.19
// https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
if k < 4 || k == 4 && m < 19 {
setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4")
}

// Creates a new namespace and bring up the loopback interface
origns, ns, h := nsCreateAndEnter(t)
Expand Down Expand Up @@ -252,6 +276,8 @@ func TestConntrackTableDelete(t *testing.T) {
// Create a filter to erase groupB flows
filter := &ConntrackFilter{}
filter.AddIP(ConntrackOrigDstIP, net.ParseIP("127.0.0.20"))
filter.AddProtocol(17)
filter.AddPort(ConntrackOrigDstPort, 8000)

// Flush entries of groupB
var deleted uint
Expand Down Expand Up @@ -296,46 +322,52 @@ func TestConntrackFilter(t *testing.T) {
flowList = append(flowList, ConntrackFlow{
FamilyType: unix.AF_INET,
Forward: ipTuple{
SrcIP: net.ParseIP("10.0.0.1"),
DstIP: net.ParseIP("20.0.0.1"),
SrcPort: 1000,
DstPort: 2000,
SrcIP: net.ParseIP("10.0.0.1"),
DstIP: net.ParseIP("20.0.0.1"),
SrcPort: 1000,
DstPort: 2000,
Protocol: 17,
},
Reverse: ipTuple{
SrcIP: net.ParseIP("20.0.0.1"),
DstIP: net.ParseIP("192.168.1.1"),
SrcPort: 2000,
DstPort: 1000,
SrcIP: net.ParseIP("20.0.0.1"),
DstIP: net.ParseIP("192.168.1.1"),
SrcPort: 2000,
DstPort: 1000,
Protocol: 17,
},
},
ConntrackFlow{
FamilyType: unix.AF_INET,
Forward: ipTuple{
SrcIP: net.ParseIP("10.0.0.2"),
DstIP: net.ParseIP("20.0.0.2"),
SrcPort: 5000,
DstPort: 6000,
SrcIP: net.ParseIP("10.0.0.2"),
DstIP: net.ParseIP("20.0.0.2"),
SrcPort: 5000,
DstPort: 6000,
Protocol: 6,
},
Reverse: ipTuple{
SrcIP: net.ParseIP("20.0.0.2"),
DstIP: net.ParseIP("192.168.1.1"),
SrcPort: 6000,
DstPort: 5000,
SrcIP: net.ParseIP("20.0.0.2"),
DstIP: net.ParseIP("192.168.1.1"),
SrcPort: 6000,
DstPort: 5000,
Protocol: 6,
},
},
ConntrackFlow{
FamilyType: unix.AF_INET6,
Forward: ipTuple{
SrcIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"),
DstIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"),
SrcPort: 1000,
DstPort: 2000,
SrcIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"),
DstIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"),
SrcPort: 1000,
DstPort: 2000,
Protocol: 132,
},
Reverse: ipTuple{
SrcIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"),
DstIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"),
SrcPort: 2000,
DstPort: 1000,
SrcIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"),
DstIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"),
SrcPort: 2000,
DstPort: 1000,
Protocol: 132,
},
})

Expand All @@ -345,11 +377,53 @@ func TestConntrackFilter(t *testing.T) {
t.Fatalf("Error, empty filter cannot match, v4:%d, v6:%d", v4Match, v6Match)
}

// SrcIP filter
// Filter errors

// Adding same attribute should fail
filter := &ConntrackFilter{}
filter.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1"))
if err := filter.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1")); err == nil {
t.Fatalf("Error, it should fail adding same attribute to the filter")
}
filter.AddProtocol(6)
if err := filter.AddProtocol(17); err == nil {
t.Fatalf("Error, it should fail adding same attribute to the filter")
}
filter.AddPort(ConntrackOrigSrcPort, 80)
if err := filter.AddPort(ConntrackOrigSrcPort, 80); err == nil {
t.Fatalf("Error, it should fail adding same attribute to the filter")
}

// Can not add a Port filter without Layer 4 protocol
filter = &ConntrackFilter{}
if err := filter.AddPort(ConntrackOrigSrcPort, 80); err == nil {
t.Fatalf("Error, it should fail adding a port filter without a protocol")
}

// Can not add a Port filter if the Layer 4 protocol does not support it
filter = &ConntrackFilter{}
filter.AddProtocol(47)
if err := filter.AddPort(ConntrackOrigSrcPort, 80); err == nil {
t.Fatalf("Error, it should fail adding a port filter with a wrong protocol")
}

// Proto filter
filterV4 := &ConntrackFilter{}
filterV4.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1"))
filterV4.AddProtocol(6)

filterV6 := &ConntrackFilter{}
filterV6.AddProtocol(132)

v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
if v4Match != 1 || v6Match != 1 {
t.Fatalf("Error, there should be only 1 match for TCP:%d, UDP:%d", v4Match, v6Match)
}

// SrcIP filter
filterV4 = &ConntrackFilter{}
filterV4.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1"))

filterV6 = &ConntrackFilter{}
filterV6.AddIP(ConntrackOrigSrcIP, net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"))

v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
Expand Down Expand Up @@ -404,4 +478,32 @@ func TestConntrackFilter(t *testing.T) {
if v4Match != 2 || v6Match != 1 {
t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match)
}

// SrcPort filter
filterV4 = &ConntrackFilter{}
filterV4.AddProtocol(6)
filterV4.AddPort(ConntrackOrigSrcPort, 5000)

filterV6 = &ConntrackFilter{}
filterV6.AddProtocol(132)
filterV6.AddPort(ConntrackOrigSrcPort, 1000)

v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
if v4Match != 1 || v6Match != 1 {
t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match)
}

// DstPort filter
filterV4 = &ConntrackFilter{}
filterV4.AddProtocol(6)
filterV4.AddPort(ConntrackOrigDstPort, 6000)

filterV6 = &ConntrackFilter{}
filterV6.AddProtocol(132)
filterV6.AddPort(ConntrackOrigDstPort, 2000)

v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
if v4Match != 1 || v6Match != 1 {
t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match)
}
}

0 comments on commit bca67df

Please sign in to comment.