Skip to content

Commit 4222321

Browse files
committed
Rework the interface to libnet/internal/nftables
Add nftables.Modifier, to hold a queue of commands that can be applied using Modifier.Apply. No updates are made to the underlying Table until Apply is called, errors in the queue if commands are deferred until Apply. This has the advantages that: - less error handling is needed in code that generates update commands - it's transactional, without needing explicit transactions Minor disadvantages are that t's slightly more difficult to debug updates, as it's no longer possible to step through the call making an update to the Table manipulation in a debugger - and errors in the command, and errors like trying to update a nonexistent chain/set/vmap, deleting an object that doesn't exist or creating a duplicate are not reported until the updates are applied (so, it's a little less clear where the update came from). Signed-off-by: Rob Murray <rob.murray@docker.com>
1 parent 07ed20f commit 4222321

26 files changed

+1559
-860
lines changed

libnetwork/drivers/bridge/internal/nftabler/endpoint.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,24 @@ func (n *network) DelEndpoint(ctx context.Context, epIPv4, epIPv6 netip.Addr) er
2525

2626
func (n *network) modEndpoint(ctx context.Context, epIPv4, epIPv6 netip.Addr, enable bool) error {
2727
if n.fw.config.IPv4 && epIPv4.IsValid() {
28-
if err := n.filterDirectAccess(ctx, n.fw.table4, n.config.Config4, epIPv4, enable); err != nil {
29-
return err
28+
tm := n.fw.table4.Modifier()
29+
updater := tm.Create
30+
if !enable {
31+
updater = tm.Delete
3032
}
31-
if err := nftApply(ctx, n.fw.table4); err != nil {
33+
n.filterDirectAccess(updater, tm.Family(), n.config.Config4, epIPv4)
34+
if err := tm.Apply(ctx); err != nil {
3235
return fmt.Errorf("adding rules for bridge %s: %w", n.config.IfName, err)
3336
}
3437
}
3538
if n.fw.config.IPv6 && epIPv6.IsValid() {
36-
if err := n.filterDirectAccess(ctx, n.fw.table6, n.config.Config6, epIPv6, enable); err != nil {
37-
return err
39+
tm := n.fw.table6.Modifier()
40+
updater := tm.Create
41+
if !enable {
42+
updater = tm.Delete
3843
}
39-
if err := nftApply(ctx, n.fw.table6); err != nil {
44+
n.filterDirectAccess(updater, tm.Family(), n.config.Config6, epIPv6)
45+
if err := tm.Apply(ctx); err != nil {
4046
return fmt.Errorf("adding rules for bridge %s: %w", n.config.IfName, err)
4147
}
4248
}
@@ -53,17 +59,21 @@ func (n *network) modEndpoint(ctx context.Context, epIPv4, epIPv6 netip.Addr, en
5359
// kernel support).
5460
//
5561
// Packets originating on the bridge's own interface and addressed directly to the
56-
// container are allowed - the host always has direct access to its own containers
57-
// (it doesn't need to use the port mapped to its own addresses, although it can).
62+
// container are allowed - the host always has direct access to its own containers.
63+
// (It doesn't need to use the port mapped to its own addresses, although it can.)
5864
//
5965
// "Trusted interfaces" are treated in the same way as the bridge itself.
60-
func (n *network) filterDirectAccess(ctx context.Context, table nftables.TableRef, conf firewaller.NetworkConfigFam, epIP netip.Addr, enable bool) error {
66+
func (n *network) filterDirectAccess(updater func(nftables.Obj), fam nftables.Family, conf firewaller.NetworkConfigFam, epIP netip.Addr) {
6167
if n.config.Internal || conf.Unprotected || conf.Routed || n.fw.config.AllowDirectRouting {
62-
return nil
68+
return
6369
}
64-
updater := table.ChainUpdateFunc(ctx, rawPreroutingChain, enable)
6570
ifNames := strings.Join(n.config.TrustedHostInterfaces, ", ")
66-
return updater(ctx, rawPreroutingPortsRuleGroup,
67-
`%s daddr %s iifname != { %s, %s } counter drop comment "DROP DIRECT ACCESS"`,
68-
table.Family(), epIP, n.config.IfName, ifNames)
71+
updater(nftables.Rule{
72+
Chain: rawPreroutingChain,
73+
Group: rawPreroutingPortsRuleGroup,
74+
Rule: []string{
75+
string(fam), "daddr", epIP.String(),
76+
"iifname != {", n.config.IfName, ",", ifNames, `} counter drop comment "DROP DIRECT ACCESS"`,
77+
},
78+
})
6979
}

libnetwork/drivers/bridge/internal/nftabler/link.go

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"errors"
88
"fmt"
99
"net/netip"
10+
"strconv"
1011

1112
"github.com/containerd/log"
13+
"github.com/docker/docker/libnetwork/internal/nftables"
1214
"github.com/docker/docker/libnetwork/types"
1315
)
1416

@@ -24,44 +26,47 @@ func (n *network) AddLink(ctx context.Context, parentIP, childIP netip.Addr, por
2426
n.fw.cleaner.DelLink(ctx, n.config, parentIP, childIP, ports)
2527
}
2628

27-
chain := n.fw.table4.Chain(ctx, chainFilterFwdIn(n.config.IfName))
29+
tm := n.fw.table4.Modifier()
2830
for _, port := range ports {
29-
for _, rule := range legacyLinkRules(parentIP, childIP, port) {
30-
if err := chain.AppendRule(ctx, fwdInLegacyLinksRuleGroup, rule); err != nil {
31-
return err
32-
}
33-
}
31+
updateLegacyLinkRules(tm.Create, chainFilterFwdIn(n.config.IfName), parentIP, childIP, port)
3432
}
35-
if err := nftApply(ctx, n.fw.table4); err != nil {
33+
if err := tm.Apply(ctx); err != nil {
3634
return fmt.Errorf("adding rules for bridge %s: %w", n.config.IfName, err)
3735
}
3836
return nil
3937
}
4038

4139
func (n *network) DelLink(ctx context.Context, parentIP, childIP netip.Addr, ports []types.TransportPort) {
42-
chain := n.fw.table4.Chain(ctx, chainFilterFwdIn(n.config.IfName))
40+
tm := n.fw.table4.Modifier()
4341
for _, port := range ports {
44-
for _, rule := range legacyLinkRules(parentIP, childIP, port) {
45-
if err := chain.DeleteRule(ctx, fwdInLegacyLinksRuleGroup, rule); err != nil {
46-
log.G(ctx).WithFields(log.Fields{
47-
"rule": rule,
48-
"error": err,
49-
}).Warn("Failed to remove link between containers")
50-
}
51-
}
42+
updateLegacyLinkRules(tm.Delete, chainFilterFwdIn(n.config.IfName), parentIP, childIP, port)
5243
}
53-
if err := nftApply(ctx, n.fw.table4); err != nil {
44+
if err := tm.Apply(ctx); err != nil {
5445
log.G(ctx).WithError(err).Warn("Removing link, failed to update nftables")
5546
}
5647
}
5748

58-
func legacyLinkRules(parentIP, childIP netip.Addr, port types.TransportPort) []string {
49+
func updateLegacyLinkRules(updater func(command nftables.Obj), chainName string, parentIP, childIP netip.Addr, port types.TransportPort) {
5950
// TODO(robmry) - could combine rules for each proto by using an anonymous set.
60-
return []string{
61-
// Match the iptables implementation, but without checking iifname/oifname (not needed
62-
// because the addresses belong to the bridge).
63-
fmt.Sprintf("ip saddr %s ip daddr %s %s dport %d counter accept", parentIP.Unmap(), childIP.Unmap(), port.Proto, port.Port),
64-
// Conntrack will allow responses. So, this must be to allow unsolicited packets from an exposed port.
65-
fmt.Sprintf("ip daddr %s ip saddr %s %s sport %d counter accept", parentIP.Unmap(), childIP.Unmap(), port.Proto, port.Port),
66-
}
51+
// Match the iptables implementation, but without checking iifname/oifname (not needed
52+
// because the addresses belong to the bridge).
53+
updater(nftables.Rule{
54+
Chain: chainName,
55+
Group: fwdInLegacyLinksRuleGroup,
56+
Rule: []string{
57+
"ip saddr", parentIP.Unmap().String(),
58+
"ip daddr", childIP.Unmap().String(), port.Proto.String(), "dport", strconv.Itoa(int(port.Port)),
59+
"counter accept",
60+
},
61+
})
62+
// Conntrack will allow responses. So, this must be to allow unsolicited packets from an exposed port.
63+
updater(nftables.Rule{
64+
Chain: chainName,
65+
Group: fwdInLegacyLinksRuleGroup,
66+
Rule: []string{
67+
"ip daddr", parentIP.Unmap().String(),
68+
"ip saddr", childIP.Unmap().String(), port.Proto.String(), "sport", strconv.Itoa(int(port.Port)),
69+
"counter accept",
70+
},
71+
})
6772
}

0 commit comments

Comments
 (0)