diff --git a/docs/manual/kinds/bridge.md b/docs/manual/kinds/bridge.md index 85450259b..41d9c619c 100644 --- a/docs/manual/kinds/bridge.md +++ b/docs/manual/kinds/bridge.md @@ -15,7 +15,7 @@ For example, by connecting a lab node to a bridge we can: 3. scale out containerlab labs by running separate labs in different hosts and get network reachability between them 4. wiring nodes' data interfaces via a broadcast domain (linux bridge) and use vlans to making dynamic connections -
+-{{diagram(url='srl-labs/containerlab/diagrams/containerlab.drawio', page='8', title='Using bridges')}}- ## Using bridge kind @@ -53,9 +53,11 @@ In the example above, node `br-clab` of kind `bridge` tells containerlab to iden When connecting other nodes to a bridge, the bridge endpoint must be present in the `links` section. -!!!note - When choosing names of the interfaces that need to be connected to the bridge make sure that these names are not clashing with existing interfaces. - In the example above we named interfaces `eth1`, `eth2`, `eth3` accordingly and ensured that none of these interfaces existed before in the root netns. +/// admonition + type: subtle-note +When choosing names of the interfaces that need to be connected to the bridge make sure that these names are not clashing with existing interfaces. +In the example above we named interfaces `eth1`, `eth2`, `eth3` accordingly and ensured that none of these interfaces existed before in the root netns. +/// As a result of such topology definition, you will see bridge `br-clab` with three interfaces attached to it: @@ -66,12 +68,12 @@ br-clab 8000.6281eb7133d2 no eth1 eth3 ``` -Containerlab automatically adds an iptables rule for the referenced bridges to allow forwarding over them. Namely, for a given bridge named `br-clab` containerlab will attempt to call the following iptables command during the lab deployment: +Containerlab automatically adds iptables rules for the referenced bridges (v4 and v6) to allow traffic ingressing to the bridges. Namely, for a given bridge named `br-clab` containerlab will attempt to create the allowing rule in the filter table, FORWARD chain like this: ``` iptables -I FORWARD -i br-clab -j ACCEPT ``` -This will ensure that traffic is forwarded when passing this particular bridge. Note, that once you destroy the lab, the rule will stay. +This will ensure that traffic is forwarded when passing this particular bridge. Note, that once you destroy the lab, the rule will stay, if you wish to remove it, you will have to do it manually. Check out ["External bridge"](../../lab-examples/ext-bridge.md) lab for a ready-made example on how to use bridges. diff --git a/nodes/bridge/bridge.go b/nodes/bridge/bridge.go index 5b9fbb1ee..274310a08 100644 --- a/nodes/bridge/bridge.go +++ b/nodes/bridge/bridge.go @@ -6,10 +6,6 @@ package bridge import ( "context" - "fmt" - "os/exec" - "regexp" - "strings" "github.com/containernetworking/plugins/pkg/ns" log "github.com/sirupsen/logrus" @@ -18,6 +14,8 @@ import ( "github.com/srl-labs/containerlab/nodes" "github.com/srl-labs/containerlab/nodes/state" "github.com/srl-labs/containerlab/runtime" + "github.com/srl-labs/containerlab/runtime/docker/firewall" + "github.com/srl-labs/containerlab/runtime/docker/firewall/definitions" "github.com/srl-labs/containerlab/types" "github.com/srl-labs/containerlab/utils" "github.com/vishvananda/netlink" @@ -26,9 +24,6 @@ import ( var kindNames = []string{"bridge"} const ( - iptCheckCmd = "-vL FORWARD -w 5" - iptAllowCmd = "-I FORWARD -i %s -j ACCEPT -w 5" - generateable = true generateIfFormat = "eth%d" ) @@ -64,7 +59,12 @@ func (n *bridge) Deploy(_ context.Context, _ *nodes.DeployParams) error { return nil } -func (*bridge) Delete(_ context.Context) error { return nil } +func (*bridge) Delete(_ context.Context) error { + // we are not deleting iptables rules set up in the post deploy stage + // because we can't guarantee that the bridge is not used by another topology. + return nil +} + func (*bridge) GetImages(_ context.Context) map[string]string { return map[string]string{} } // DeleteNetnsSymlink is a noop for bridge nodes. @@ -87,38 +87,6 @@ func (b *bridge) CheckDeploymentConditions(_ context.Context) error { return nil } -// installIPTablesBridgeFwdRule calls iptables to install `allow` rule for traffic passing through the bridge -// otherwise, communication over the bridge is not permitted. -func (b *bridge) installIPTablesBridgeFwdRule() (err error) { - // first check if a rule already exists for this bridge to not create duplicates - res, err := exec.Command("iptables", strings.Split(iptCheckCmd, " ")...).Output() - - re, _ := regexp.Compile(fmt.Sprintf("ACCEPT[^\n]+%s", b.Cfg.ShortName)) - - if re.Match(res) { - log.Debugf("found iptables forwarding rule targeting the bridge %q. Skipping creation of the forwarding rule.", b.Cfg.ShortName) - return err - } - if err != nil { - return fmt.Errorf("failed to add iptables forwarding rule for bridge %q: %w", b.Cfg.ShortName, err) - } - - cmd := fmt.Sprintf(iptAllowCmd, b.Cfg.ShortName) - - log.Debugf("Installing iptables rules for bridge %q", b.Cfg.ShortName) - - stdOutErr, err := exec.Command("iptables", strings.Split(cmd, " ")...).CombinedOutput() - - log.Debugf("iptables install stdout for bridge %s:%s", b.Cfg.ShortName, stdOutErr) - - if err != nil { - log.Warnf("iptables install stdout/stderr result is: %s", stdOutErr) - return fmt.Errorf("unable to create iptables rules: %w", err) - } - - return nil -} - func (*bridge) PullImage(_ context.Context) error { return nil } // UpdateConfigWithRuntimeInfo is a noop for bridges. @@ -160,3 +128,15 @@ func (b *bridge) AddLinkToContainer(ctx context.Context, link netlink.Link, f fu func (b *bridge) GetLinkEndpointType() links.LinkEndpointType { return links.LinkEndpointTypeBridge } + +// installIPTablesBridgeFwdRule installs `allow` rule for the traffic routed to ingress the bridge +// otherwise, communication over the bridge is not permitted on most systems. +func (b *bridge) installIPTablesBridgeFwdRule() (err error) { + f, err := firewall.NewFirewallClient() + if err != nil { + return err + } + log.Debugf("setting up bridge firewall rules using %s as the firewall interface", f.Name()) + + return f.InstallForwardingRules(b.Cfg.ShortName, "", definitions.ForwardChain) +} diff --git a/runtime/docker/docker.go b/runtime/docker/docker.go index 3e450b2b4..a986feade 100644 --- a/runtime/docker/docker.go +++ b/runtime/docker/docker.go @@ -354,7 +354,7 @@ func (d *DockerRuntime) postCreateNetActions() (err error) { if err != nil { log.Warnf("failed to disable TX checksum offloading for the %s bridge interface: %v", d.mgmt.Bridge, err) } - err = d.installFwdRule() + err = d.installMgmtNetworkFwdRule() if err != nil { log.Warnf("errors during iptables rules install: %v", err) } @@ -391,7 +391,7 @@ func (d *DockerRuntime) DeleteNet(ctx context.Context) (err error) { return err } - err = d.deleteFwdRule() + err = d.deleteMgmtNetworkFwdRule() if err != nil { log.Warnf("errors during iptables rules removal: %v", err) } diff --git a/runtime/docker/firewall.go b/runtime/docker/firewall.go index 0905ea1ea..bb24ebe6e 100644 --- a/runtime/docker/firewall.go +++ b/runtime/docker/firewall.go @@ -3,27 +3,30 @@ package docker import ( log "github.com/sirupsen/logrus" "github.com/srl-labs/containerlab/runtime/docker/firewall" + "github.com/srl-labs/containerlab/runtime/docker/firewall/definitions" ) -// deleteFwdRule deletes `allow` rule installed with installFwdRule when the bridge interface doesn't exist anymore. -func (d *DockerRuntime) deleteFwdRule() (err error) { +// deleteMgmtNetworkFwdRule deletes `allow` rule installed with installFwdRule +// when the containerlab management network (bridge) interface doesn't exist anymore. +func (d *DockerRuntime) deleteMgmtNetworkFwdRule() (err error) { if !*d.mgmt.ExternalAccess { return } - f, err := firewall.NewFirewallClient(d.mgmt.Bridge) + f, err := firewall.NewFirewallClient() if err != nil { return err } - return f.DeleteForwardingRules() + return f.DeleteForwardingRules("", d.mgmt.Bridge, definitions.DockerUserChain) } -// installFwdRule installs the `allow` rule for traffic destined to the nodes +// installMgmtNetworkFwdRule installs the `allow` rule for traffic destined to the nodes // on the clab management network for v4 and v6. // This rule is required for external access to the nodes. -func (d *DockerRuntime) installFwdRule() (err error) { +func (d *DockerRuntime) installMgmtNetworkFwdRule() (err error) { if !*d.mgmt.ExternalAccess { + log.Debug("skipping setup of forwarding rules for the management network since External Access is disabled by a user") return } @@ -32,11 +35,13 @@ func (d *DockerRuntime) installFwdRule() (err error) { return } - f, err := firewall.NewFirewallClient(d.mgmt.Bridge) + f, err := firewall.NewFirewallClient() if err != nil { return err } log.Debugf("using %s as the firewall interface", f.Name()) - return f.InstallForwardingRules() + // install the rules with the management bridge listed as the outgoing interface with the allow action + // in the DOCKER-USER chain + return f.InstallForwardingRules("", d.mgmt.Bridge, definitions.DockerUserChain) } diff --git a/runtime/docker/firewall/definitions/definitions.go b/runtime/docker/firewall/definitions/definitions.go index 0e99ba480..2f6ff7e02 100644 --- a/runtime/docker/firewall/definitions/definitions.go +++ b/runtime/docker/firewall/definitions/definitions.go @@ -5,8 +5,9 @@ import "errors" var ErrNotAvailable = errors.New("not available") const ( - DockerFWUserChain = "DOCKER-USER" - DockerFWTable = "filter" + DockerUserChain = "DOCKER-USER" + ForwardChain = "FORWARD" + FilterTable = "filter" IPTablesRuleComment = "set by containerlab" @@ -15,7 +16,7 @@ const ( // ClabFirewall is the interface that all firewall clients must implement. type ClabFirewall interface { - DeleteForwardingRules() error - InstallForwardingRules() error + DeleteForwardingRules(inInterface, outInterface, chain string) error + InstallForwardingRules(inInterface, outInterface, chain string) error Name() string } diff --git a/runtime/docker/firewall/firewall.go b/runtime/docker/firewall/firewall.go index be77d1c13..909c683ba 100644 --- a/runtime/docker/firewall/firewall.go +++ b/runtime/docker/firewall/firewall.go @@ -7,15 +7,15 @@ import ( ) // NewFirewallClient returns a firewall client based on the availability of nftables or iptables. -func NewFirewallClient(bridgeName string) (definitions.ClabFirewall, error) { +func NewFirewallClient() (definitions.ClabFirewall, error) { var clf definitions.ClabFirewall - clf, err := nftables.NewNftablesClient(bridgeName) + clf, err := nftables.NewNftablesClient() if err == nil { return clf, nil } - clf, err = iptables.NewIpTablesClient(bridgeName) + clf, err = iptables.NewIpTablesClient() if err == nil { return clf, nil } diff --git a/runtime/docker/firewall/iptables/client.go b/runtime/docker/firewall/iptables/client.go index 977017525..defe9ef85 100644 --- a/runtime/docker/firewall/iptables/client.go +++ b/runtime/docker/firewall/iptables/client.go @@ -13,9 +13,9 @@ import ( ) const ( - iptCheckArgs = "-vL DOCKER-USER" - iptAllowArgs = "-I DOCKER-USER -o %s -j ACCEPT -m comment --comment \"" + definitions.IPTablesRuleComment + "\"" - iptDelArgs = "-D DOCKER-USER -o %s -j ACCEPT -m comment --comment \"" + definitions.IPTablesRuleComment + "\"" + iptCheckArgs = "-vL DOCKER-USER -w 5" + iptAllowArgs = "-I DOCKER-USER %s %s -j ACCEPT -w 5 -m comment --comment \"" + definitions.IPTablesRuleComment + "\"" + iptDelArgs = "-D DOCKER-USER %s %s -j ACCEPT -w 5 -m comment --comment \"" + definitions.IPTablesRuleComment + "\"" ipTables = "ip_tables" v4AF = "v4" @@ -26,12 +26,11 @@ const ( // IpTablesClient is a client for iptables. type IpTablesClient struct { - bridgeName string ip6_tables bool } // NewIpTablesClient returns a new IpTablesClient. -func NewIpTablesClient(bridgeName string) (*IpTablesClient, error) { +func NewIpTablesClient() (*IpTablesClient, error) { v4ModLoaded, err := utils.IsKernelModuleLoaded("ip_tables") if err != nil { return nil, err @@ -46,7 +45,6 @@ func NewIpTablesClient(bridgeName string) (*IpTablesClient, error) { } return &IpTablesClient{ - bridgeName: bridgeName, ip6_tables: v6ModLoaded, }, nil } @@ -56,38 +54,46 @@ func (*IpTablesClient) Name() string { return ipTables } -// InstallForwardingRules installs the forwarding rules for v4 and v6 address families. -func (c *IpTablesClient) InstallForwardingRules() error { - err := c.InstallForwardingRulesForAF(v4AF) +// InstallForwardingRules installs the forwarding rules for v4 and v6 address families for the provided +// input or output interface and chain. +func (c *IpTablesClient) InstallForwardingRules(inInterface, outInterface, chain string) error { + err := c.InstallForwardingRulesForAF(v4AF, inInterface, outInterface, chain) if err != nil { return err } if c.ip6_tables { - err = c.InstallForwardingRulesForAF(v6AF) + err = c.InstallForwardingRulesForAF(v6AF, inInterface, outInterface, chain) } return err } // InstallForwardingRulesForAF installs the forwarding rules for the specified address family. -func (c *IpTablesClient) InstallForwardingRulesForAF(af string) error { +func (c *IpTablesClient) InstallForwardingRulesForAF(af, inInterface, outInterface, chain string) error { iptCmd := ip4tablesCmd if af == v6AF { iptCmd = ip6tablesCmd } + iface := inInterface + direction := "i" + if outInterface != "" { + iface = outInterface + direction = "o" + } + // first check if a rule already exists to not create duplicates - if c.allowRuleForMgmtBrExists(af) { + if c.allowRuleExistsForInterface(af, iface) { return nil } - cmd, err := shlex.Split(fmt.Sprintf(iptAllowArgs, c.bridgeName)) + cmd, err := shlex.Split(fmt.Sprintf(iptAllowArgs, direction, iface)) if err != nil { return err } - log.Debugf("Installing iptables (%s) rules for bridge %q", af, c.bridgeName) + log.Debugf("Installing iptables (%s) rules for bridge %q", af, iface) stdOutErr, err := exec.Command(iptCmd, cmd...).CombinedOutput() if err != nil { @@ -99,26 +105,33 @@ func (c *IpTablesClient) InstallForwardingRulesForAF(af string) error { } // DeleteForwardingRules deletes the forwarding rules for v4 and v6 address families. -func (c *IpTablesClient) DeleteForwardingRules() error { - err := c.DeleteForwardingRulesForAF(v4AF) +func (c *IpTablesClient) DeleteForwardingRules(inInterface, outInterface, chain string) error { + err := c.DeleteForwardingRulesForAF(v4AF, inInterface, outInterface, chain) if err != nil { return err } if c.ip6_tables { - err = c.InstallForwardingRulesForAF(v6AF) + err = c.DeleteForwardingRulesForAF(v6AF, inInterface, outInterface, chain) } return err } // DeleteForwardingRulesForAF deletes the forwarding rules for a specified AF. -func (c *IpTablesClient) DeleteForwardingRulesForAF(af string) error { +func (c *IpTablesClient) DeleteForwardingRulesForAF(af, inInterface, outInterface, chain string) error { iptCmd := ip4tablesCmd if af == v6AF { iptCmd = ip6tablesCmd } + iface := inInterface + direction := "i" + if outInterface != "" { + iface = outInterface + direction = "o" + } + // first check if a rule exists before trying to delete it res, err := exec.Command(iptCmd, strings.Split(iptCheckArgs, " ")...).Output() if err != nil { @@ -127,7 +140,7 @@ func (c *IpTablesClient) DeleteForwardingRulesForAF(af string) error { return fmt.Errorf("missing DOCKER-USER iptables chain. See http://containerlab.dev/manual/network/#external-access") } - if !bytes.Contains(res, []byte(c.bridgeName)) { + if !bytes.Contains(res, []byte(iface)) { log.Debug("external access iptables rule doesn't exist. Skipping deletion") return nil } @@ -135,18 +148,18 @@ func (c *IpTablesClient) DeleteForwardingRulesForAF(af string) error { // we are not deleting the rule if the bridge still exists // it happens when bridge is either still in use by docker network // or it is managed externally (created manually) - _, err = utils.BridgeByName(c.bridgeName) + _, err = utils.BridgeByName(iface) if err == nil { - log.Debugf("bridge %s is still in use, not removing the forwarding rule", c.bridgeName) + log.Debugf("bridge %s is still in use, not removing the forwarding rule", iface) return nil } - cmd, err := shlex.Split(fmt.Sprintf(iptDelArgs, c.bridgeName)) + cmd, err := shlex.Split(fmt.Sprintf(iptDelArgs, direction, iface)) if err != nil { return err } - log.Debugf("removing clab iptables rules for bridge %q", c.bridgeName) + log.Debugf("removing clab iptables rules for bridge %q", iface) log.Debugf("trying to delete the forwarding rule with cmd: iptables %s", cmd) stdOutErr, err := exec.Command(iptCmd, cmd...).CombinedOutput() @@ -158,10 +171,10 @@ func (c *IpTablesClient) DeleteForwardingRulesForAF(af string) error { return nil } -// allowRuleForMgmtBrExists checks if an allow rule for the provided bridge name exists. +// allowRuleExistsForInterface checks if an allow rule for the provided bridge name exists. // The actual check doesn't verify that `allow` is set, it just checks if the rule // has the provided bridge name in the output interface. -func (c *IpTablesClient) allowRuleForMgmtBrExists(af string) bool { +func (c *IpTablesClient) allowRuleExistsForInterface(af, iface string) bool { iptCmd := ip4tablesCmd if af == v6AF { iptCmd = ip6tablesCmd @@ -173,8 +186,8 @@ func (c *IpTablesClient) allowRuleForMgmtBrExists(af string) bool { // if we errored on check we don't want to try setting up the rule return true } - if bytes.Contains(res, []byte(c.bridgeName)) { - log.Debugf("found iptables forwarding rule targeting the bridge %q. Skipping creation of the forwarding rule.", c.bridgeName) + if bytes.Contains(res, []byte(iface)) { + log.Debugf("found iptables forwarding rule targeting the bridge %q. Skipping creation of the forwarding rule.", iface) return true } diff --git a/runtime/docker/firewall/nftables/client.go b/runtime/docker/firewall/nftables/client.go index 6e91de159..3fda2783f 100644 --- a/runtime/docker/firewall/nftables/client.go +++ b/runtime/docker/firewall/nftables/client.go @@ -17,14 +17,13 @@ const nfTables = "nf_tables" // NftablesClient is a client for nftables. type NftablesClient struct { - nftConn *nftables.Conn - bridgeName string + nftConn *nftables.Conn // is ip6_tables supported ip6_tables bool } // NewNftablesClient returns a new NftablesClient. -func NewNftablesClient(bridgeName string) (*NftablesClient, error) { +func NewNftablesClient() (*NftablesClient, error) { // setup netlink connection with nftables nftConn, err := nftables.New(nftables.AsLasting()) if err != nil { @@ -33,15 +32,15 @@ func NewNftablesClient(bridgeName string) (*NftablesClient, error) { nftC := &NftablesClient{ nftConn: nftConn, - bridgeName: bridgeName, + ip6_tables: true, } - chains, err := nftC.getChains(definitions.DockerFWUserChain) + chains, err := nftC.getChains(definitions.DockerUserChain) if err != nil { return nil, err } if len(chains) == 0 { - log.Debugf("nftables does not seem to be in use, no %s chain found.", definitions.DockerFWUserChain) + log.Debugf("nftables does not seem to be in use, no %s chain found.", definitions.DockerUserChain) return nil, definitions.ErrNotAvailable } @@ -59,23 +58,28 @@ func (*NftablesClient) Name() string { return nfTables } -// DeleteForwardingRules deletes the forwarding rules. -func (c *NftablesClient) DeleteForwardingRules() error { - if c.bridgeName == "docker0" { +// DeleteForwardingRules deletes the forwarding rules for in or out interface in a given chain. +func (c *NftablesClient) DeleteForwardingRules(inInterface, outInterface, chain string) error { + iface := inInterface + if outInterface != "" { + iface = outInterface + } + + if iface == "docker0" { log.Debug("skipping deletion of iptables forwarding rule for non-bridged or default management network") return nil } defer c.close() - allRules, err := c.getRules(definitions.DockerFWUserChain, definitions.DockerFWTable, nftables.TableFamilyIPv4) + allRules, err := c.getRules(chain, definitions.FilterTable, nftables.TableFamilyIPv4) if err != nil { return fmt.Errorf("%w. See http://containerlab.dev/manual/network/#external-access", err) } var v6rules []*nftables.Rule if c.ip6_tables { - v6rules, err = c.getRules(definitions.DockerFWUserChain, definitions.DockerFWTable, nftables.TableFamilyIPv6) + v6rules, err = c.getRules(chain, definitions.FilterTable, nftables.TableFamilyIPv6) if err != nil { return fmt.Errorf("%w. See http://containerlab.dev/manual/network/#external-access", err) } @@ -83,8 +87,8 @@ func (c *NftablesClient) DeleteForwardingRules() error { allRules = append(allRules, v6rules...) - mgmtBrRules := c.getRulesForMgmtBr(c.bridgeName, allRules) - if len(mgmtBrRules) == 0 { + clabRules := c.getClabRulesForInterface(iface, allRules) + if len(clabRules) == 0 { log.Debug("external access iptables rule doesn't exist. Skipping deletion") return nil } @@ -92,14 +96,14 @@ func (c *NftablesClient) DeleteForwardingRules() error { // we are not deleting the rule if the bridge still exists // it happens when bridge is either still in use by docker network // or it is managed externally (created manually) - _, err = utils.BridgeByName(c.bridgeName) + _, err = utils.BridgeByName(iface) if err == nil { - log.Debugf("bridge %s is still in use, not removing the forwarding rule", c.bridgeName) + log.Debugf("bridge %s is still in use, not removing the forwarding rule", iface) return nil } - log.Debugf("removing clab iptables rules for bridge %q", c.bridgeName) - for _, r := range mgmtBrRules { + log.Debugf("removing clab iptables rules for bridge %q", iface) + for _, r := range clabRules { c.deleteRule(r) } @@ -107,44 +111,56 @@ func (c *NftablesClient) DeleteForwardingRules() error { return nil } -// InstallForwardingRules installs the forwarding rules for v4 and v6 address families. -func (c *NftablesClient) InstallForwardingRules() error { +// InstallForwardingRules installs the forwarding rules for v4 and v6 address families for the provided +// input or output interface and chain. +func (c *NftablesClient) InstallForwardingRules(inInterface, outInterface, chain string) error { defer c.close() - err := c.InstallForwardingRulesForAF(nftables.TableFamilyIPv4) + err := c.InstallForwardingRulesForAF(nftables.TableFamilyIPv4, inInterface, outInterface, chain) if err != nil { return err } if c.ip6_tables { - err = c.InstallForwardingRulesForAF(nftables.TableFamilyIPv6) + err = c.InstallForwardingRulesForAF(nftables.TableFamilyIPv6, inInterface, outInterface, chain) } return err } -// InstallForwardingRulesForAF installs the forwarding rules for the specified address family. -func (c *NftablesClient) InstallForwardingRulesForAF(af nftables.TableFamily) error { +// InstallForwardingRulesForAF installs the forwarding rules for the specified address family +// input interface and chain. +func (c *NftablesClient) InstallForwardingRulesForAF(af nftables.TableFamily, inInterface, outInterface, chain string) error { + iface := inInterface + if outInterface != "" { + iface = outInterface + } - rules, err := c.getRules(definitions.DockerFWUserChain, definitions.DockerFWTable, af) + rules, err := c.getRules(chain, definitions.FilterTable, af) if err != nil { return fmt.Errorf("%w. See http://containerlab.dev/manual/network/#external-access", err) } - if c.allowRuleForMgmtBrExists(c.bridgeName, rules) { - log.Debugf("found iptables forwarding rule targeting the bridge %q. Skipping creation of the forwarding rule.", c.bridgeName) + if c.allowRuleExistsForInterface(iface, rules) { + log.Debugf("found allowing iptables forwarding rule targeting the bridge %q. Skipping creation of the forwarding rule.", inInterface) return nil } - log.Debugf("Installing iptables rules for bridge %q", c.bridgeName) + log.Debugf("Installing iptables rules for bridge %q", iface) // create a new rule - rule, err := c.newClabNftablesRule(definitions.DockerFWUserChain, definitions.DockerFWTable, af, 0) + rule, err := c.newClabNftablesRule(chain, definitions.FilterTable, af, 0) if err != nil { return err } - // set Output interface match - rule.AddOutputInterfaceFilter(c.bridgeName) + // set interface match + if inInterface != "" { + rule.AddInterfaceFilter(iface, false) + } + + if outInterface != "" { + rule.AddInterfaceFilter(iface, true) + } // add a comment err = rule.AddComment(definitions.IPTablesRuleComment) @@ -246,17 +262,17 @@ func (nftC *NftablesClient) close() { nftC.nftConn.CloseLasting() } -// allowRuleForMgmtBrExists checks if an allow rule for the provided bridge name exists. +// allowRuleExistsForInterface checks if an allow rule for the provided interface name exists. // The actual check doesn't verify that `allow` is set, it just checks if the rule -// has the provided bridge name in the output interface match and has a comment that is setup +// has the provided interface name and has a comment that is setup // by containerlab. -func (nftC *NftablesClient) allowRuleForMgmtBrExists(brName string, rules []*nftables.Rule) bool { - return len(nftC.getRulesForMgmtBr(brName, rules)) > 0 +func (nftC *NftablesClient) allowRuleExistsForInterface(iface string, rules []*nftables.Rule) bool { + return len(nftC.getClabRulesForInterface(iface, rules)) > 0 } -// getRulesForMgmtBr returns all rules that have the provided bridge name in the output interface match -// and have a comment that is setup by containerlab. -func (*NftablesClient) getRulesForMgmtBr(brName string, rules []*nftables.Rule) []*nftables.Rule { +// getClabRulesForInterface returns rules that have the provided interface name in the output interface match +// and have a comment that is setup by containerlab from the list of `rules`. +func (*NftablesClient) getClabRulesForInterface(brName string, rules []*nftables.Rule) []*nftables.Rule { var result []*nftables.Rule for _, r := range rules { diff --git a/runtime/docker/firewall/nftables/rule.go b/runtime/docker/firewall/nftables/rule.go index 99c01de3f..c287b54d3 100644 --- a/runtime/docker/firewall/nftables/rule.go +++ b/runtime/docker/firewall/nftables/rule.go @@ -13,22 +13,29 @@ type clabNftablesRule struct { rule *nftables.Rule } -func (cnr *clabNftablesRule) AddOutputInterfaceFilter(oif string) { +// AddInterfaceFilter adds an interface filter to the rule. +func (cnr *clabNftablesRule) AddInterfaceFilter(ifName string, isOutput bool) { // define the metadata to evaluate - metaOifName := &expr.Meta{ - Key: expr.MetaKeyOIFNAME, + metaKey := expr.MetaKeyIIFNAME + if isOutput { + metaKey = expr.MetaKeyOIFNAME + } + + metaIfName := &expr.Meta{ + Key: metaKey, SourceRegister: false, Register: 1, } + // define the comparison comp := &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, - Data: []byte(oif + "\x00"), + Data: []byte(ifName + "\x00"), } // add expr to rule - cnr.rule.Exprs = append(cnr.rule.Exprs, metaOifName, comp) + cnr.rule.Exprs = append(cnr.rule.Exprs, metaIfName, comp) } func (cnr *clabNftablesRule) AddCounter() error {