diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 8f240ef598..f1397dc326 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -226,13 +226,18 @@ func InitDriver(config *Config) error { bridgeIPv6Addr = networkv6.IP } + if config.EnableIptables { + iptables.FirewalldInit() + } + // Configure iptables for link support if config.EnableIptables { if err := setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq); err != nil { logrus.Errorf("Error configuing iptables: %s", err) return err } - + // call this on Firewalld reload + iptables.OnReloaded(func() { setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq) }) } if config.EnableIpForward { @@ -262,10 +267,16 @@ func InitDriver(config *Config) error { if err != nil { return err } + // call this on Firewalld reload + iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) }) + chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) if err != nil { return err } + // call this on Firewalld reload + iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) }) + portMapper.SetIptablesChain(chain) } @@ -310,6 +321,10 @@ func InitDriver(config *Config) error { // Block BridgeIP in IP allocator ipAllocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP) + if config.EnableIptables { + iptables.OnReloaded(portMapper.ReMapAll) // call this on Firewalld reload + } + return nil } diff --git a/daemon/networkdriver/portmapper/mapper.go b/daemon/networkdriver/portmapper/mapper.go index 8f79bae3f2..09952ba35b 100644 --- a/daemon/networkdriver/portmapper/mapper.go +++ b/daemon/networkdriver/portmapper/mapper.go @@ -132,6 +132,18 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host return m.host, nil } +// re-apply all port mappings +func (pm *PortMapper) ReMapAll() { + logrus.Debugln("Re-applying all port mappings.") + for _, data := range pm.currentMappings { + containerIP, containerPort := getIPAndPort(data.container) + hostIP, hostPort := getIPAndPort(data.host) + if err := pm.forward(iptables.Append, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { + logrus.Errorf("Error on iptables add: %s", err) + } + } +} + func (pm *PortMapper) Unmap(host net.Addr) error { pm.lock.Lock() defer pm.lock.Unlock() diff --git a/links/links.go b/links/links.go index 1ae8f23aea..8bbacdd3d7 100644 --- a/links/links.go +++ b/links/links.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/nat" + "github.com/docker/docker/pkg/iptables" ) type Link struct { @@ -143,6 +144,8 @@ func (l *Link) Enable() error { if err := l.toggle("-A", false); err != nil { return err } + // call this on Firewalld reload + iptables.OnReloaded(func() { l.toggle("-I", false) }) l.IsEnabled = true return nil } @@ -152,7 +155,8 @@ func (l *Link) Disable() { // exist in iptables // -D == iptables delete flag l.toggle("-D", true) - + // call this on Firewalld reload + iptables.OnReloaded(func() { l.toggle("-D", true) }) l.IsEnabled = false } diff --git a/pkg/iptables/firewalld.go b/pkg/iptables/firewalld.go new file mode 100644 index 0000000000..3087794131 --- /dev/null +++ b/pkg/iptables/firewalld.go @@ -0,0 +1,163 @@ +package iptables + +import ( + "fmt" + "github.com/Sirupsen/logrus" + "github.com/godbus/dbus" + "strings" +) + +type IPV string + +const ( + Iptables IPV = "ipv4" + Ip6tables IPV = "ipv6" + Ebtables IPV = "eb" +) +const ( + dbusInterface = "org.fedoraproject.FirewallD1" + dbusPath = "/org/fedoraproject/FirewallD1" +) + +// Conn is a connection to firewalld dbus endpoint. +type Conn struct { + sysconn *dbus.Conn + sysobj *dbus.Object + signal chan *dbus.Signal +} + +var ( + connection *Conn + firewalldRunning bool // is Firewalld service running + onReloaded []*func() // callbacks when Firewalld has been reloaded +) + +func FirewalldInit() { + var err error + + connection, err = newConnection() + + if err != nil { + logrus.Errorf("Failed to connect to D-Bus system bus: %s", err) + } + + firewalldRunning = checkRunning() +} + +// New() establishes a connection to the system bus. +func newConnection() (*Conn, error) { + c := new(Conn) + if err := c.initConnection(); err != nil { + return nil, err + } + + return c, nil +} + +// Innitialize D-Bus connection. +func (c *Conn) initConnection() error { + var err error + + c.sysconn, err = dbus.SystemBus() + if err != nil { + return err + } + + // This never fails, even if the service is not running atm. + c.sysobj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath)) + + rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'", + dbusPath, dbusInterface, dbusInterface) + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) + + rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", + dbusInterface) + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) + + c.signal = make(chan *dbus.Signal, 10) + c.sysconn.Signal(c.signal) + go signalHandler() + + return nil +} + +func signalHandler() { + if connection != nil { + for signal := range connection.signal { + if strings.Contains(signal.Name, "NameOwnerChanged") { + firewalldRunning = checkRunning() + dbusConnectionChanged(signal.Body) + } else if strings.Contains(signal.Name, "Reloaded") { + reloaded() + } + } + } +} + +func dbusConnectionChanged(args []interface{}) { + name := args[0].(string) + old_owner := args[1].(string) + new_owner := args[2].(string) + + if name != dbusInterface { + return + } + + if len(new_owner) > 0 { + connectionEstablished() + } else if len(old_owner) > 0 { + connectionLost() + } +} + +func connectionEstablished() { + reloaded() +} + +func connectionLost() { + // Doesn't do anything for now. Libvirt also doesn't react to this. +} + +// call all callbacks +func reloaded() { + for _, pf := range onReloaded { + (*pf)() + } +} + +// add callback +func OnReloaded(callback func()) { + for _, pf := range onReloaded { + if pf == &callback { + return + } + } + onReloaded = append(onReloaded, &callback) +} + +// Call some remote method to see whether the service is actually running. +func checkRunning() bool { + var zone string + var err error + + if connection != nil { + err = connection.sysobj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone) + logrus.Infof("Firewalld running: %t", err == nil) + return err == nil + } + logrus.Info("Firewalld not running") + return false +} + +// Firewalld's passthrough method simply passes args through to iptables/ip6tables +func Passthrough(ipv IPV, args ...string) ([]byte, error) { + var output string + + logrus.Debugf("Firewalld passthrough: %s, %s", ipv, args) + err := connection.sysobj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output) + if output != "" { + logrus.Debugf("passthrough output: %s", output) + } + + return []byte(output), err +} diff --git a/pkg/iptables/firewalld_test.go b/pkg/iptables/firewalld_test.go new file mode 100644 index 0000000000..3896007d64 --- /dev/null +++ b/pkg/iptables/firewalld_test.go @@ -0,0 +1,78 @@ +package iptables + +import ( + "net" + "strconv" + "testing" +) + +func TestFirewalldInit(t *testing.T) { + FirewalldInit() +} + +func TestReloaded(t *testing.T) { + var err error + var fwdChain *Chain + + fwdChain, err = NewChain("FWD", "lo", Filter) + if err != nil { + t.Fatal(err) + } + defer fwdChain.Remove() + + // copy-pasted from iptables_test:TestLink + ip1 := net.ParseIP("192.168.1.1") + ip2 := net.ParseIP("192.168.1.2") + port := 1234 + proto := "tcp" + + err = fwdChain.Link(Append, ip1, ip2, port, proto) + if err != nil { + t.Fatal(err) + } else { + // to be re-called again later + OnReloaded(func() { fwdChain.Link(Append, ip1, ip2, port, proto) }) + } + + rule1 := []string{ + "-i", fwdChain.Bridge, + "-o", fwdChain.Bridge, + "-p", proto, + "-s", ip1.String(), + "-d", ip2.String(), + "--dport", strconv.Itoa(port), + "-j", "ACCEPT"} + + if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { + t.Fatalf("rule1 does not exist") + } + + // flush all rules + fwdChain.Remove() + + reloaded() + + // make sure the rules have been recreated + if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { + t.Fatalf("rule1 hasn't been recreated") + } +} + +func TestPassthrough(t *testing.T) { + rule1 := []string{ + "-i", "lo", + "-p", "udp", + "--dport", "123", + "-j", "ACCEPT"} + + if firewalldRunning { + _, err := Passthrough(Iptables, append([]string{"-A"}, rule1...)...) + if err != nil { + t.Fatal(err) + } + if !Exists(Filter, "INPUT", rule1...) { + t.Fatalf("rule1 does not exist") + } + } + +} diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 204d703a61..0cfcca7502 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -275,6 +275,13 @@ func Exists(table Table, chain string, rule ...string) bool { // 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