21fc078476
This re-applies commit b39d02b with additional iptables rules to solve the issue with containers routing back into themselves. The previous issue with this attempt was that the DNAT rule would send traffic back into the container it came from. When this happens you have 2 issues. 1) reverse path filtering. The container is going to see the traffic coming in from the outside and it's going to have a source address of itself. So reverse path filtering will kick in and drop the packet. 2) direct return mismatch. Assuming you turned reverse path filtering off, when the packet comes back in, it's goign to have a source address of itself, thus when the reply traffic is sent, it's going to have a source address of itself. But the original packet was sent to the host IP address, so the traffic will be dropped because it's coming from an address which the original traffic was not sent to (and likely with an incorrect port as well). The solution to this is to masquerade the traffic when it gets routed back into the origin container. However for this to work you need to enable hairpin mode on the bridge port, otherwise the kernel will just drop the traffic. The hairpin mode set is part of libcontainer, while the MASQ change is part of docker. This reverts commit 63c303eecdbaf4dc7967fd51b82cd447c778cecc. Docker-DCO-1.1-Signed-off-by: Patrick Hemmer <patrick.hemmer@gmail.com> (github: phemmer)
202 lines
5.1 KiB
Go
202 lines
5.1 KiB
Go
package iptables
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
)
|
|
|
|
type Action string
|
|
|
|
const (
|
|
Add Action = "-A"
|
|
Delete Action = "-D"
|
|
)
|
|
|
|
var (
|
|
ErrIptablesNotFound = errors.New("Iptables not found")
|
|
nat = []string{"-t", "nat"}
|
|
supportsXlock = false
|
|
)
|
|
|
|
type Chain struct {
|
|
Name string
|
|
Bridge string
|
|
}
|
|
|
|
func init() {
|
|
supportsXlock = exec.Command("iptables", "--wait", "-L", "-n").Run() == nil
|
|
}
|
|
|
|
func NewChain(name, bridge string) (*Chain, error) {
|
|
if output, err := Raw("-t", "nat", "-N", name); err != nil {
|
|
return nil, err
|
|
} else if len(output) != 0 {
|
|
return nil, fmt.Errorf("Error creating new iptables chain: %s", output)
|
|
}
|
|
chain := &Chain{
|
|
Name: name,
|
|
Bridge: bridge,
|
|
}
|
|
|
|
if err := chain.Prerouting(Add, "-m", "addrtype", "--dst-type", "LOCAL"); err != nil {
|
|
return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
|
|
}
|
|
if err := chain.Output(Add, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8"); err != nil {
|
|
return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
|
|
}
|
|
return chain, nil
|
|
}
|
|
|
|
func RemoveExistingChain(name string) error {
|
|
chain := &Chain{
|
|
Name: name,
|
|
}
|
|
return chain.Remove()
|
|
}
|
|
|
|
func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port 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", "nat", fmt.Sprint(action), c.Name,
|
|
"-p", proto,
|
|
"-d", daddr,
|
|
"--dport", strconv.Itoa(port),
|
|
"-j", "DNAT",
|
|
"--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))); err != nil {
|
|
return err
|
|
} else if len(output) != 0 {
|
|
return fmt.Errorf("Error iptables forward: %s", output)
|
|
}
|
|
|
|
fAction := action
|
|
if fAction == Add {
|
|
fAction = "-I"
|
|
}
|
|
if output, err := Raw(string(fAction), "FORWARD",
|
|
"!", "-i", c.Bridge,
|
|
"-o", c.Bridge,
|
|
"-p", proto,
|
|
"-d", dest_addr,
|
|
"--dport", strconv.Itoa(dest_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", "nat", string(fAction), "POSTROUTING",
|
|
"-p", proto,
|
|
"-s", dest_addr,
|
|
"-d", dest_addr,
|
|
"--dport", strconv.Itoa(dest_port),
|
|
"-j", "MASQUERADE"); err != nil {
|
|
return err
|
|
} else if len(output) != 0 {
|
|
return fmt.Errorf("Error iptables forward: %s", output)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Chain) Prerouting(action Action, args ...string) error {
|
|
a := append(nat, fmt.Sprint(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 fmt.Errorf("Error iptables prerouting: %s", output)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Chain) Output(action Action, args ...string) error {
|
|
a := append(nat, fmt.Sprint(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 fmt.Errorf("Error iptables output: %s", output)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Chain) Remove() error {
|
|
// Ignore errors - This could mean the chains were never set up
|
|
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", "nat", "-F", c.Name)
|
|
Raw("-t", "nat", "-X", c.Name)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Check if an existing rule exists
|
|
func Exists(args ...string) bool {
|
|
// 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{"-C"}, args...)...); err == nil {
|
|
return true
|
|
}
|
|
|
|
// parse iptables-save for the rule
|
|
rule := strings.Replace(strings.Join(args, " "), "-t nat ", "", -1)
|
|
existingRules, _ := exec.Command("iptables-save").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(rule, "?"),
|
|
)
|
|
}
|
|
|
|
func Raw(args ...string) ([]byte, error) {
|
|
path, err := exec.LookPath("iptables")
|
|
if err != nil {
|
|
return nil, ErrIptablesNotFound
|
|
}
|
|
|
|
if supportsXlock {
|
|
args = append([]string{"--wait"}, args...)
|
|
}
|
|
|
|
log.Debugf("%s, %v", path, args)
|
|
|
|
output, err := exec.Command(path, 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
|
|
}
|