Skip to content

Commit 95ea400

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 03b8f90 commit 95ea400

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
@@ -1511,6 +1511,226 @@ func TestConntrackCreateV6(t *testing.T) {
15111511
checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo)
15121512
}
15131513

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

0 commit comments

Comments
 (0)