Skip to content

Commit

Permalink
added iptables based add/delete for v6
Browse files Browse the repository at this point in the history
  • Loading branch information
hellt committed Jan 16, 2025
1 parent a2bbf00 commit 99e6ce8
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 22 deletions.
108 changes: 86 additions & 22 deletions runtime/docker/firewall/iptables/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,41 @@ import (
)

const (
iptCheckCmd = "-vL DOCKER-USER"
iptAllowCmd = "-I DOCKER-USER -o %s -j ACCEPT -m comment --comment \"" + definitions.IPTablesRuleComment + "\""
iptDelCmd = "-D DOCKER-USER -o %s -j ACCEPT -m comment --comment \"" + definitions.IPTablesRuleComment + "\""
ipTables = "ip_tables"
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 + "\""
ipTables = "ip_tables"

v4AF = "v4"
ip4tablesCmd = "iptables"
v6AF = "v6"
ip6tablesCmd = "ip6tables"
)

// 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) {
loaded, err := utils.IsKernelModuleLoaded("ip_tables")
v4ModLoaded, err := utils.IsKernelModuleLoaded("ip_tables")
if err != nil {
return nil, err
}

if !loaded {
v6ModLoaded, _ := utils.IsKernelModuleLoaded("ip6_tables")

if !v4ModLoaded {
log.Debug("ip_tables kernel module not available")
// module is not loaded
return nil, definitions.ErrNotAvailable
}

return &IpTablesClient{
bridgeName: bridgeName,
ip6_tables: v6ModLoaded,
}, nil
}

Expand All @@ -47,28 +56,40 @@ func (*IpTablesClient) Name() string {
return ipTables
}

// InstallForwardingRules installs the forwarding rules.
// InstallForwardingRules installs the forwarding rules for v4 and v6 address families.
func (c *IpTablesClient) InstallForwardingRules() error {
// first check if a rule already exists to not create duplicates
res, err := exec.Command("iptables", strings.Split(iptCheckCmd, " ")...).Output()
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)
err := c.InstallForwardingRulesForAF(v4AF)
if err != nil {
return err
}
if err != nil {
// non nil error typically means that DOCKER-USER chain doesn't exist
// this happens with old docker installations (centos7 hello) from default repos
return fmt.Errorf("missing DOCKER-USER iptables chain. See http://containerlab.dev/manual/network/#external-access")

if c.ip6_tables {
err = c.InstallForwardingRulesForAF(v6AF)
}

cmd, err := shlex.Split(fmt.Sprintf(iptAllowCmd, c.bridgeName))
return err
}

// InstallForwardingRulesForAF installs the forwarding rules for the specified address family.
func (c *IpTablesClient) InstallForwardingRulesForAF(af string) error {
iptCmd := ip4tablesCmd
if af == v6AF {
iptCmd = ip6tablesCmd
}

// first check if a rule already exists to not create duplicates
if c.allowRuleForMgmtBrExists(af) {
return nil
}

cmd, err := shlex.Split(fmt.Sprintf(iptAllowArgs, c.bridgeName))
if err != nil {
return err
}

log.Debugf("Installing iptables rules for bridge %q", c.bridgeName)
log.Debugf("Installing iptables (%s) rules for bridge %q", af, c.bridgeName)

stdOutErr, err := exec.Command("iptables", cmd...).CombinedOutput()
stdOutErr, err := exec.Command(iptCmd, cmd...).CombinedOutput()
if err != nil {
log.Warnf("Iptables install stdout/stderr result is: %s", stdOutErr)
return fmt.Errorf("unable to install iptables rule using '%s' command: %w", cmd, err)
Expand All @@ -77,10 +98,29 @@ func (c *IpTablesClient) InstallForwardingRules() error {
return nil
}

// DeleteForwardingRules deletes the forwarding rules.
// DeleteForwardingRules deletes the forwarding rules for v4 and v6 address families.
func (c *IpTablesClient) DeleteForwardingRules() error {
err := c.DeleteForwardingRulesForAF(v4AF)
if err != nil {
return err
}

if c.ip6_tables {
err = c.InstallForwardingRulesForAF(v6AF)
}

return err
}

// DeleteForwardingRulesForAF deletes the forwarding rules for a specified AF.
func (c *IpTablesClient) DeleteForwardingRulesForAF(af string) error {
iptCmd := ip4tablesCmd
if af == v6AF {
iptCmd = ip6tablesCmd
}

// first check if a rule exists before trying to delete it
res, err := exec.Command("iptables", strings.Split(iptCheckCmd, " ")...).Output()
res, err := exec.Command(iptCmd, strings.Split(iptCheckArgs, " ")...).Output()
if err != nil {
// non nil error typically means that DOCKER-USER chain doesn't exist
// this happens with old docker installations (centos7 hello) from default repos
Expand All @@ -101,19 +141,43 @@ func (c *IpTablesClient) DeleteForwardingRules() error {
return nil
}

cmd, err := shlex.Split(fmt.Sprintf(iptDelCmd, c.bridgeName))
cmd, err := shlex.Split(fmt.Sprintf(iptDelArgs, c.bridgeName))
if err != nil {
return err
}

log.Debugf("removing clab iptables rules for bridge %q", c.bridgeName)
log.Debugf("trying to delete the forwarding rule with cmd: iptables %s", cmd)

stdOutErr, err := exec.Command("iptables", cmd...).CombinedOutput()
stdOutErr, err := exec.Command(iptCmd, cmd...).CombinedOutput()
if err != nil {
log.Warnf("Iptables delete stdout/stderr result is: %s", stdOutErr)
return fmt.Errorf("unable to delete iptables rules: %w", err)
}

return nil
}

// allowRuleForMgmtBrExists 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 {
iptCmd := ip4tablesCmd
if af == v6AF {
iptCmd = ip6tablesCmd
}

res, err := exec.Command(iptCmd, strings.Split(iptCheckArgs, " ")...).CombinedOutput()
if err != nil {
log.Warnf("iptables check error: %s. Output: %s", err, string(res))
// 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)

return true
}

return false
}
31 changes: 31 additions & 0 deletions tests/01-smoke/01-basic-flow.robot
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,25 @@ Verify iptables allow rule is set
... ignore_case=True
... collapse_spaces=True

Verify ip6tables allow rule is set
[Documentation] Checking if ip6tables allow rule is set so that external traffic can reach containerlab management network
Skip If '${runtime}' != 'docker'

# Add check for ip6tables availability
${rc} ${output} = Run And Return Rc And Output which ip6tables
Skip If ${rc} != 0 ip6tables command not found


${ipt} = Run
... sudo ip6tables -vnL DOCKER-USER
Log ${ipt}
# debian 12 uses `0` for protocol, while previous versions use `all`
Should Contain Any ${ipt}
... ACCEPT all -- * ${MgmtBr}
... ACCEPT 0 -- * ${MgmtBr}
... ignore_case=True
... collapse_spaces=True

Verify DNS-Server Config
[Documentation] Check if the DNS config did take effect
Skip If '${runtime}' != 'docker'
Expand Down Expand Up @@ -403,6 +422,18 @@ Verify iptables allow rule are gone
Log ${ipt}
Should Not Contain ${ipt} ${MgmtBr}

Verify ip6tables allow rule are gone
[Documentation] Checking if ip6tables allow rule is removed once the lab is destroyed
Skip If '${runtime}' != 'docker'

# Add check for ip6tables availability
${rc} ${output} = Run And Return Rc And Output which ip6tables
Skip If ${rc} != 0 ip6tables command not found

${ipt} = Run
... sudo ip6tables -vnL DOCKER-USER
Log ${ipt}
Should Not Contain ${ipt} ${MgmtBr}

*** Keywords ***
Match IPv6 Address
Expand Down

0 comments on commit 99e6ce8

Please sign in to comment.