Firewalld [1] is a firewall managing daemon with D-Bus interface. What sort of problem are we trying to solve with this ? Firewalld internally also executes iptables/ip6tables to change firewall settings. It might happen on systems where both docker and firewalld are running concurrently, that both of them try to call iptables at the same time. The result is that the second one fails because the first one is holding a xtables lock. One workaround is to use --wait/-w option in both docker & firewalld when calling iptables. It's already been done in both upstreams:b315c380f4
b3b451d6f8
But it'd still be better if docker used firewalld when it's running. Other problem the firewalld support would solve is that iptables/firewalld service's restart flushes all firewall rules previously added by docker. See next patch for possible solution. This patch utilizes firewalld's D-Bus interface. If firewalld is running, we call direct.passthrough() [2] method instead of executing iptables directly. direct.passthrough() takes the same arguments as iptables tool itself and passes them through to iptables tool. It might be better to use other methods, like direct.addChain and direct.addRule [3] so it'd be more intergrated with firewalld, but that'd make the patch much bigger. If firewalld is not running, everything works as before. [1] http://www.firewalld.org/ [2] https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.direct.Methods.passthrough [3] https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.direct.Methods.addChain https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.direct.Methods.addRule Signed-off-by: Jiri Popelka <jpopelka@redhat.com>
306 lines
7.8 KiB
Go
306 lines
7.8 KiB
Go
package iptables
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
)
|
|
|
|
type Action string
|
|
type Table string
|
|
|
|
const (
|
|
Append Action = "-A"
|
|
Delete Action = "-D"
|
|
Insert Action = "-I"
|
|
Nat Table = "nat"
|
|
Filter Table = "filter"
|
|
Mangle Table = "mangle"
|
|
)
|
|
|
|
var (
|
|
iptablesPath string
|
|
supportsXlock = false
|
|
ErrIptablesNotFound = errors.New("Iptables not found")
|
|
)
|
|
|
|
type Chain struct {
|
|
Name string
|
|
Bridge string
|
|
Table Table
|
|
}
|
|
|
|
type ChainError struct {
|
|
Chain string
|
|
Output []byte
|
|
}
|
|
|
|
func (e *ChainError) Error() string {
|
|
return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
|
|
}
|
|
|
|
func initCheck() error {
|
|
|
|
if iptablesPath == "" {
|
|
path, err := exec.LookPath("iptables")
|
|
if err != nil {
|
|
return ErrIptablesNotFound
|
|
}
|
|
iptablesPath = path
|
|
supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewChain(name, bridge string, table Table) (*Chain, error) {
|
|
c := &Chain{
|
|
Name: name,
|
|
Bridge: bridge,
|
|
Table: table,
|
|
}
|
|
|
|
if string(c.Table) == "" {
|
|
c.Table = Filter
|
|
}
|
|
|
|
// Add chain if it doesn't exist
|
|
if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
|
|
if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil {
|
|
return nil, err
|
|
} else if len(output) != 0 {
|
|
return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
|
|
}
|
|
}
|
|
|
|
switch table {
|
|
case Nat:
|
|
preroute := []string{
|
|
"-m", "addrtype",
|
|
"--dst-type", "LOCAL"}
|
|
if !Exists(Nat, "PREROUTING", preroute...) {
|
|
if err := c.Prerouting(Append, preroute...); err != nil {
|
|
return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
|
|
}
|
|
}
|
|
output := []string{
|
|
"-m", "addrtype",
|
|
"--dst-type", "LOCAL",
|
|
"!", "--dst", "127.0.0.0/8"}
|
|
if !Exists(Nat, "OUTPUT", output...) {
|
|
if err := c.Output(Append, output...); err != nil {
|
|
return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
|
|
}
|
|
}
|
|
case Filter:
|
|
link := []string{
|
|
"-o", c.Bridge,
|
|
"-j", c.Name}
|
|
if !Exists(Filter, "FORWARD", link...) {
|
|
insert := append([]string{string(Insert), "FORWARD"}, link...)
|
|
if output, err := Raw(insert...); err != nil {
|
|
return nil, err
|
|
} else if len(output) != 0 {
|
|
return nil, fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
|
|
}
|
|
}
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func RemoveExistingChain(name string, table Table) error {
|
|
c := &Chain{
|
|
Name: name,
|
|
Table: table,
|
|
}
|
|
if string(c.Table) == "" {
|
|
c.Table = Filter
|
|
}
|
|
return c.Remove()
|
|
}
|
|
|
|
// Add forwarding rule to 'filter' table and corresponding nat rule to 'nat' table
|
|
func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int) error {
|
|
daddr := ip.String()
|
|
if ip.IsUnspecified() {
|
|
// iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
|
|
// want "0.0.0.0/0". "0/0" is correctly interpreted as "any
|
|
// value" by both iptables and ip6tables.
|
|
daddr = "0/0"
|
|
}
|
|
if output, err := Raw("-t", string(Nat), string(action), c.Name,
|
|
"-p", proto,
|
|
"-d", daddr,
|
|
"--dport", strconv.Itoa(port),
|
|
"!", "-i", c.Bridge,
|
|
"-j", "DNAT",
|
|
"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil {
|
|
return err
|
|
} else if len(output) != 0 {
|
|
return &ChainError{Chain: "FORWARD", Output: output}
|
|
}
|
|
|
|
if output, err := Raw("-t", string(Filter), string(action), c.Name,
|
|
"!", "-i", c.Bridge,
|
|
"-o", c.Bridge,
|
|
"-p", proto,
|
|
"-d", destAddr,
|
|
"--dport", strconv.Itoa(destPort),
|
|
"-j", "ACCEPT"); err != nil {
|
|
return err
|
|
} else if len(output) != 0 {
|
|
return &ChainError{Chain: "FORWARD", Output: output}
|
|
}
|
|
|
|
if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING",
|
|
"-p", proto,
|
|
"-s", destAddr,
|
|
"-d", destAddr,
|
|
"--dport", strconv.Itoa(destPort),
|
|
"-j", "MASQUERADE"); err != nil {
|
|
return err
|
|
} else if len(output) != 0 {
|
|
return &ChainError{Chain: "FORWARD", Output: output}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Add reciprocal ACCEPT rule for two supplied IP addresses.
|
|
// Traffic is allowed from ip1 to ip2 and vice-versa
|
|
func (c *Chain) Link(action Action, ip1, ip2 net.IP, port int, proto string) error {
|
|
if output, err := Raw("-t", string(Filter), string(action), c.Name,
|
|
"-i", c.Bridge, "-o", c.Bridge,
|
|
"-p", proto,
|
|
"-s", ip1.String(),
|
|
"-d", ip2.String(),
|
|
"--dport", strconv.Itoa(port),
|
|
"-j", "ACCEPT"); err != nil {
|
|
return err
|
|
} else if len(output) != 0 {
|
|
return fmt.Errorf("Error iptables forward: %s", output)
|
|
}
|
|
if output, err := Raw("-t", string(Filter), string(action), c.Name,
|
|
"-i", c.Bridge, "-o", c.Bridge,
|
|
"-p", proto,
|
|
"-s", ip2.String(),
|
|
"-d", ip1.String(),
|
|
"--sport", strconv.Itoa(port),
|
|
"-j", "ACCEPT"); err != nil {
|
|
return err
|
|
} else if len(output) != 0 {
|
|
return fmt.Errorf("Error iptables forward: %s", output)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Add linking rule to nat/PREROUTING chain.
|
|
func (c *Chain) Prerouting(action Action, args ...string) error {
|
|
a := []string{"-t", string(Nat), string(action), "PREROUTING"}
|
|
if len(args) > 0 {
|
|
a = append(a, args...)
|
|
}
|
|
if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
|
|
return err
|
|
} else if len(output) != 0 {
|
|
return &ChainError{Chain: "PREROUTING", Output: output}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Add linking rule to an OUTPUT chain
|
|
func (c *Chain) Output(action Action, args ...string) error {
|
|
a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
|
|
if len(args) > 0 {
|
|
a = append(a, args...)
|
|
}
|
|
if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
|
|
return err
|
|
} else if len(output) != 0 {
|
|
return &ChainError{Chain: "OUTPUT", Output: output}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Chain) Remove() error {
|
|
// Ignore errors - This could mean the chains were never set up
|
|
if c.Table == Nat {
|
|
c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL")
|
|
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8")
|
|
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6
|
|
|
|
c.Prerouting(Delete)
|
|
c.Output(Delete)
|
|
}
|
|
Raw("-t", string(c.Table), "-F", c.Name)
|
|
Raw("-t", string(c.Table), "-X", c.Name)
|
|
return nil
|
|
}
|
|
|
|
// Check if a rule exists
|
|
func Exists(table Table, chain string, rule ...string) bool {
|
|
if string(table) == "" {
|
|
table = Filter
|
|
}
|
|
|
|
// iptables -C, --check option was added in v.1.4.11
|
|
// http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
|
|
|
|
// try -C
|
|
// if exit status is 0 then return true, the rule exists
|
|
if _, err := Raw(append([]string{
|
|
"-t", string(table), "-C", chain}, rule...)...); err == nil {
|
|
return true
|
|
}
|
|
|
|
// parse "iptables -S" for the rule (this checks rules in a specific chain
|
|
// in a specific table)
|
|
ruleString := strings.Join(rule, " ")
|
|
existingRules, _ := exec.Command("iptables", "-t", string(table), "-S", chain).Output()
|
|
|
|
// regex to replace ips in rule
|
|
// because MASQUERADE rule will not be exactly what was passed
|
|
re := regexp.MustCompile(`[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}`)
|
|
|
|
return strings.Contains(
|
|
re.ReplaceAllString(string(existingRules), "?"),
|
|
re.ReplaceAllString(ruleString, "?"),
|
|
)
|
|
}
|
|
|
|
// Call 'iptables' system command, passing supplied arguments
|
|
func Raw(args ...string) ([]byte, error) {
|
|
if firewalldRunning {
|
|
output, err := Passthrough(Iptables, args...)
|
|
if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
|
|
return output, err
|
|
}
|
|
|
|
}
|
|
|
|
if err := initCheck(); err != nil {
|
|
return nil, err
|
|
}
|
|
if supportsXlock {
|
|
args = append([]string{"--wait"}, args...)
|
|
}
|
|
|
|
logrus.Debugf("%s, %v", iptablesPath, args)
|
|
|
|
output, err := exec.Command(iptablesPath, args...).CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
|
|
}
|
|
|
|
// ignore iptables' message about xtables lock
|
|
if strings.Contains(string(output), "waiting for it to exit") {
|
|
output = []byte("")
|
|
}
|
|
|
|
return output, err
|
|
}
|