Skip to content

Commit a0ec4a9

Browse files
committed
feat: add ConntrackDelete command
Add a new ConntrackDelete() function that operates directly on flows, same as the ConntrackCreate() and ConntrackUpdate() functions. We already have ConntrackDeleteFilters() that is very useful to batch operations and to express the intent based on filter matches, but having the function that operate on flows allow to create much more complex filtering. Signed-off-by: Antonio Ojea <[email protected]>
1 parent 19840db commit a0ec4a9

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

conntrack_linux.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ func ConntrackUpdate(table ConntrackTableType, family InetFamily, flow *Conntrac
7171
return pkgHandle.ConntrackUpdate(table, family, flow)
7272
}
7373

74+
// ConntrackDelete deletes an existing conntrack flow in the desired table using the handle
75+
// conntrack -D [table] Delete conntrack flow
76+
func ConntrackDelete(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error {
77+
return pkgHandle.ConntrackDelete(table, family, flow)
78+
}
79+
7480
// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter
7581
// conntrack -D [table] parameters Delete conntrack or expectation
7682
//
@@ -148,6 +154,23 @@ func (h *Handle) ConntrackUpdate(table ConntrackTableType, family InetFamily, fl
148154
return err
149155
}
150156

157+
// ConntrackDelete deletes an existing conntrack flow in the desired table using the handle
158+
// conntrack -D [table] Delete a conntrack
159+
func (h *Handle) ConntrackDelete(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error {
160+
req := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_DELETE, unix.NLM_F_ACK)
161+
attr, err := flow.toNlData()
162+
if err != nil {
163+
return err
164+
}
165+
166+
for _, a := range attr {
167+
req.AddData(a)
168+
}
169+
170+
_, err = req.Execute(unix.NETLINK_NETFILTER, 0)
171+
return err
172+
}
173+
151174
// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed
152175
// conntrack -D [table] parameters Delete conntrack or expectation
153176
//

conntrack_test.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,226 @@ func TestConntrackCreateV6(t *testing.T) {
14981498
checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo)
14991499
}
15001500

1501+
// TestConntrackDeleteV4 creates an IPv4 conntrack entry, verifies it exists,
1502+
// deletes it via the package-level wrapper ConntrackDelete (which uses pkgHandle),
1503+
// and verifies it was removed.
1504+
func TestConntrackDeleteV4(t *testing.T) {
1505+
// Print timestamps in UTC
1506+
os.Setenv("TZ", "")
1507+
1508+
requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"}
1509+
k, m, err := KernelVersion()
1510+
if err != nil {
1511+
t.Fatal(err)
1512+
}
1513+
// Conntrack l3proto was unified since 4.19
1514+
// https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
1515+
if k < 4 || k == 4 && m < 19 {
1516+
requiredModules = append(requiredModules, "nf_conntrack_ipv4")
1517+
}
1518+
// Implicitly skips test if not root:
1519+
nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...)
1520+
t.Cleanup(teardown)
1521+
1522+
ns, err := netns.GetFromName(nsStr)
1523+
if err != nil {
1524+
t.Fatalf("couldn't get handle to generated namespace: %s", err)
1525+
}
1526+
1527+
h, err := NewHandleAt(ns, nl.FAMILY_V4)
1528+
if err != nil {
1529+
t.Fatalf("failed to create netlink handle: %s", err)
1530+
}
1531+
1532+
// Point pkgHandle to the namespaced handle so the package-level wrapper acts in this ns.
1533+
orig := pkgHandle
1534+
pkgHandle = h
1535+
defer func() { pkgHandle = orig }()
1536+
1537+
flow := ConntrackFlow{
1538+
FamilyType: FAMILY_V4,
1539+
Forward: IPTuple{
1540+
SrcIP: net.IP{234, 234, 234, 234},
1541+
DstIP: net.IP{123, 123, 123, 123},
1542+
SrcPort: 48385,
1543+
DstPort: 53,
1544+
Protocol: unix.IPPROTO_TCP,
1545+
},
1546+
Reverse: IPTuple{
1547+
SrcIP: net.IP{123, 123, 123, 123},
1548+
DstIP: net.IP{234, 234, 234, 234},
1549+
SrcPort: 53,
1550+
DstPort: 48385,
1551+
Protocol: unix.IPPROTO_TCP,
1552+
},
1553+
TimeOut: 100,
1554+
Mark: 12,
1555+
ProtoInfo: &ProtoInfoTCP{
1556+
State: nl.TCP_CONNTRACK_ESTABLISHED,
1557+
},
1558+
}
1559+
1560+
// Create the entry using the handle
1561+
if err := h.ConntrackCreate(ConntrackTable, nl.FAMILY_V4, &flow); err != nil {
1562+
t.Fatalf("failed to insert conntrack: %s", err)
1563+
}
1564+
1565+
// Verify it exists
1566+
flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V4)
1567+
if err != nil {
1568+
t.Fatalf("failed to list conntracks following successful insert: %s", err)
1569+
}
1570+
filter := ConntrackFilter{
1571+
ipNetFilter: map[ConntrackFilterType]*net.IPNet{
1572+
ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP),
1573+
ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP),
1574+
ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP),
1575+
ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP),
1576+
},
1577+
portFilter: map[ConntrackFilterType]uint16{
1578+
ConntrackOrigSrcPort: flow.Forward.SrcPort,
1579+
ConntrackOrigDstPort: flow.Forward.DstPort,
1580+
},
1581+
protoFilter: unix.IPPROTO_TCP,
1582+
}
1583+
var match *ConntrackFlow
1584+
for _, f := range flows {
1585+
if filter.MatchConntrackFlow(f) {
1586+
match = f
1587+
break
1588+
}
1589+
}
1590+
if match == nil {
1591+
t.Fatalf("didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter)
1592+
}
1593+
1594+
// Delete using the handler
1595+
if err := h.ConntrackDelete(ConntrackTable, InetFamily(nl.FAMILY_V4), &flow); err != nil {
1596+
t.Fatalf("failed to delete conntrack via handler: %s", err)
1597+
}
1598+
1599+
// Verify it's gone
1600+
flows, err = h.ConntrackTableList(ConntrackTable, nl.FAMILY_V4)
1601+
if err != nil {
1602+
t.Fatalf("failed to list conntracks following delete: %s", err)
1603+
}
1604+
for _, f := range flows {
1605+
if filter.MatchConntrackFlow(f) {
1606+
t.Fatalf("found flow after delete: %+v", f)
1607+
}
1608+
}
1609+
}
1610+
1611+
// TestConntrackDeleteV6 creates an IPv6 conntrack entry, verifies it exists,
1612+
// deletes it via the package-level wrapper ConntrackDelete (which uses pkgHandle),
1613+
// and verifies it was removed.
1614+
func TestConntrackDeleteV6(t *testing.T) {
1615+
// Print timestamps in UTC
1616+
os.Setenv("TZ", "")
1617+
1618+
requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"}
1619+
k, m, err := KernelVersion()
1620+
if err != nil {
1621+
t.Fatal(err)
1622+
}
1623+
// Conntrack l3proto was unified since 4.19
1624+
// https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
1625+
if k < 4 || k == 4 && m < 19 {
1626+
requiredModules = append(requiredModules, "nf_conntrack_ipv4")
1627+
}
1628+
// Implicitly skips test if not root:
1629+
nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...)
1630+
t.Cleanup(teardown)
1631+
1632+
ns, err := netns.GetFromName(nsStr)
1633+
if err != nil {
1634+
t.Fatalf("couldn't get handle to generated namespace: %s", err)
1635+
}
1636+
1637+
h, err := NewHandleAt(ns, nl.FAMILY_V6)
1638+
if err != nil {
1639+
t.Fatalf("failed to create netlink handle: %s", err)
1640+
}
1641+
1642+
// Point pkgHandle to the namespaced handle so the package-level wrapper acts in this ns.
1643+
orig := pkgHandle
1644+
pkgHandle = h
1645+
defer func() { pkgHandle = orig }()
1646+
1647+
flow := ConntrackFlow{
1648+
FamilyType: FAMILY_V6,
1649+
Forward: IPTuple{
1650+
SrcIP: net.ParseIP("2001:db8::68"),
1651+
DstIP: net.ParseIP("2001:db9::32"),
1652+
SrcPort: 48385,
1653+
DstPort: 53,
1654+
Protocol: unix.IPPROTO_TCP,
1655+
},
1656+
Reverse: IPTuple{
1657+
SrcIP: net.ParseIP("2001:db9::32"),
1658+
DstIP: net.ParseIP("2001:db8::68"),
1659+
SrcPort: 53,
1660+
DstPort: 48385,
1661+
Protocol: unix.IPPROTO_TCP,
1662+
},
1663+
TimeOut: 100,
1664+
Mark: 12,
1665+
ProtoInfo: &ProtoInfoTCP{
1666+
State: nl.TCP_CONNTRACK_ESTABLISHED,
1667+
},
1668+
}
1669+
1670+
// Create the entry using the handle
1671+
if err := h.ConntrackCreate(ConntrackTable, nl.FAMILY_V6, &flow); err != nil {
1672+
t.Fatalf("failed to insert conntrack: %s", err)
1673+
}
1674+
1675+
// Verify it exists
1676+
flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V6)
1677+
if err != nil {
1678+
t.Fatalf("failed to list conntracks following successful insert: %s", err)
1679+
}
1680+
filter := ConntrackFilter{
1681+
ipNetFilter: map[ConntrackFilterType]*net.IPNet{
1682+
ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP),
1683+
ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP),
1684+
ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP),
1685+
ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP),
1686+
},
1687+
portFilter: map[ConntrackFilterType]uint16{
1688+
ConntrackOrigSrcPort: flow.Forward.SrcPort,
1689+
ConntrackOrigDstPort: flow.Forward.DstPort,
1690+
},
1691+
protoFilter: unix.IPPROTO_TCP,
1692+
}
1693+
var match *ConntrackFlow
1694+
for _, f := range flows {
1695+
if filter.MatchConntrackFlow(f) {
1696+
match = f
1697+
break
1698+
}
1699+
}
1700+
if match == nil {
1701+
t.Fatalf("didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter)
1702+
}
1703+
1704+
// Delete using the handler
1705+
if err := h.ConntrackDelete(ConntrackTable, InetFamily(nl.FAMILY_V6), &flow); err != nil {
1706+
t.Fatalf("failed to delete conntrack via handler: %s", err)
1707+
}
1708+
1709+
// Verify it's gone
1710+
flows, err = h.ConntrackTableList(ConntrackTable, nl.FAMILY_V6)
1711+
if err != nil {
1712+
t.Fatalf("failed to list conntracks following delete: %s", err)
1713+
}
1714+
for _, f := range flows {
1715+
if filter.MatchConntrackFlow(f) {
1716+
t.Fatalf("found flow after delete: %+v", f)
1717+
}
1718+
}
1719+
}
1720+
15011721
// TestConntrackFlowToNlData generates a serialized representation of a
15021722
// ConntrackFlow and runs the resulting bytes back through `parseRawData` to validate.
15031723
func TestConntrackFlowToNlData(t *testing.T) {

0 commit comments

Comments
 (0)