github.com/moby/docker@v26.1.3+incompatible/libnetwork/iptables/firewalld.go (about)

     1  //go:build linux
     2  
     3  package iptables
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/containerd/log"
    11  	dbus "github.com/godbus/dbus/v5"
    12  )
    13  
    14  // IPV defines the table string
    15  type IPV string
    16  
    17  const (
    18  	// Iptables point ipv4 table
    19  	Iptables IPV = "ipv4"
    20  	// IP6Tables point to ipv6 table
    21  	IP6Tables IPV = "ipv6"
    22  )
    23  
    24  const (
    25  	dbusInterface  = "org.fedoraproject.FirewallD1"
    26  	dbusPath       = "/org/fedoraproject/FirewallD1"
    27  	dbusConfigPath = "/org/fedoraproject/FirewallD1/config"
    28  	dockerZone     = "docker"
    29  )
    30  
    31  // Conn is a connection to firewalld dbus endpoint.
    32  type Conn struct {
    33  	sysconn    *dbus.Conn
    34  	sysObj     dbus.BusObject
    35  	sysConfObj dbus.BusObject
    36  	signal     chan *dbus.Signal
    37  }
    38  
    39  var (
    40  	connection *Conn
    41  
    42  	firewalldRunning bool      // is Firewalld service running
    43  	onReloaded       []*func() // callbacks when Firewalld has been reloaded
    44  )
    45  
    46  // firewalldInit initializes firewalld management code.
    47  func firewalldInit() error {
    48  	var err error
    49  
    50  	if connection, err = newConnection(); err != nil {
    51  		return fmt.Errorf("Failed to connect to D-Bus system bus: %v", err)
    52  	}
    53  	firewalldRunning = checkRunning()
    54  	if !firewalldRunning {
    55  		connection.sysconn.Close()
    56  		connection = nil
    57  	}
    58  	if connection != nil {
    59  		go signalHandler()
    60  		if err := setupDockerZone(); err != nil {
    61  			return err
    62  		}
    63  	}
    64  
    65  	return nil
    66  }
    67  
    68  // newConnection establishes a connection to the system bus.
    69  func newConnection() (*Conn, error) {
    70  	c := &Conn{}
    71  
    72  	var err error
    73  	c.sysconn, err = dbus.SystemBus()
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	// This never fails, even if the service is not running atm.
    79  	c.sysObj = c.sysconn.Object(dbusInterface, dbusPath)
    80  	c.sysConfObj = c.sysconn.Object(dbusInterface, dbusConfigPath)
    81  
    82  	rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'", dbusPath, dbusInterface, dbusInterface)
    83  	c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
    84  
    85  	rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", dbusInterface)
    86  	c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
    87  
    88  	c.signal = make(chan *dbus.Signal, 10)
    89  	c.sysconn.Signal(c.signal)
    90  	return c, nil
    91  }
    92  
    93  func signalHandler() {
    94  	for signal := range connection.signal {
    95  		switch {
    96  		case strings.Contains(signal.Name, "NameOwnerChanged"):
    97  			firewalldRunning = checkRunning()
    98  			dbusConnectionChanged(signal.Body)
    99  
   100  		case strings.Contains(signal.Name, "Reloaded"):
   101  			reloaded()
   102  		}
   103  	}
   104  }
   105  
   106  func dbusConnectionChanged(args []interface{}) {
   107  	name := args[0].(string)
   108  	oldOwner := args[1].(string)
   109  	newOwner := args[2].(string)
   110  
   111  	if name != dbusInterface {
   112  		return
   113  	}
   114  
   115  	if len(newOwner) > 0 {
   116  		connectionEstablished()
   117  	} else if len(oldOwner) > 0 {
   118  		connectionLost()
   119  	}
   120  }
   121  
   122  func connectionEstablished() {
   123  	reloaded()
   124  }
   125  
   126  func connectionLost() {
   127  	// Doesn't do anything for now. Libvirt also doesn't react to this.
   128  }
   129  
   130  // call all callbacks
   131  func reloaded() {
   132  	for _, pf := range onReloaded {
   133  		(*pf)()
   134  	}
   135  }
   136  
   137  // OnReloaded add callback
   138  func OnReloaded(callback func()) {
   139  	for _, pf := range onReloaded {
   140  		if pf == &callback {
   141  			return
   142  		}
   143  	}
   144  	onReloaded = append(onReloaded, &callback)
   145  }
   146  
   147  // Call some remote method to see whether the service is actually running.
   148  func checkRunning() bool {
   149  	if connection == nil {
   150  		return false
   151  	}
   152  	var zone string
   153  	err := connection.sysObj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone)
   154  	return err == nil
   155  }
   156  
   157  // Passthrough method simply passes args through to iptables/ip6tables
   158  func Passthrough(ipv IPV, args ...string) ([]byte, error) {
   159  	var output string
   160  	log.G(context.TODO()).Debugf("Firewalld passthrough: %s, %s", ipv, args)
   161  	if err := connection.sysObj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output); err != nil {
   162  		return nil, err
   163  	}
   164  	return []byte(output), nil
   165  }
   166  
   167  // firewalldZone holds the firewalld zone settings.
   168  //
   169  // Documented in https://firewalld.org/documentation/man-pages/firewalld.dbus.html#FirewallD1.zone
   170  type firewalldZone struct {
   171  	version            string
   172  	name               string
   173  	description        string
   174  	unused             bool
   175  	target             string
   176  	services           []string
   177  	ports              [][]interface{}
   178  	icmpBlocks         []string
   179  	masquerade         bool
   180  	forwardPorts       [][]interface{}
   181  	interfaces         []string
   182  	sourceAddresses    []string
   183  	richRules          []string
   184  	protocols          []string
   185  	sourcePorts        [][]interface{}
   186  	icmpBlockInversion bool
   187  }
   188  
   189  // settings returns the firewalldZone struct as an interface slice,
   190  // which can be passed to "org.fedoraproject.FirewallD1.config.addZone".
   191  func (z firewalldZone) settings() []interface{} {
   192  	// TODO(thaJeztah): does D-Bus require optional fields to be passed as well?
   193  	return []interface{}{
   194  		z.version,
   195  		z.name,
   196  		z.description,
   197  		z.unused,
   198  		z.target,
   199  		z.services,
   200  		z.ports,
   201  		z.icmpBlocks,
   202  		z.masquerade,
   203  		z.forwardPorts,
   204  		z.interfaces,
   205  		z.sourceAddresses,
   206  		z.richRules,
   207  		z.protocols,
   208  		z.sourcePorts,
   209  		z.icmpBlockInversion,
   210  	}
   211  }
   212  
   213  // setupDockerZone creates a zone called docker in firewalld which includes docker interfaces to allow
   214  // container networking
   215  func setupDockerZone() error {
   216  	var zones []string
   217  	// Check if zone exists
   218  	if err := connection.sysObj.Call(dbusInterface+".zone.getZones", 0).Store(&zones); err != nil {
   219  		return err
   220  	}
   221  	if contains(zones, dockerZone) {
   222  		log.G(context.TODO()).Infof("Firewalld: %s zone already exists, returning", dockerZone)
   223  		return nil
   224  	}
   225  	log.G(context.TODO()).Debugf("Firewalld: creating %s zone", dockerZone)
   226  
   227  	// Permanent
   228  	dz := firewalldZone{
   229  		version:     "1.0",
   230  		name:        dockerZone,
   231  		description: "zone for docker bridge network interfaces",
   232  		target:      "ACCEPT",
   233  	}
   234  	if err := connection.sysConfObj.Call(dbusInterface+".config.addZone", 0, dockerZone, dz.settings()).Err; err != nil {
   235  		return err
   236  	}
   237  	// Reload for change to take effect
   238  	if err := connection.sysObj.Call(dbusInterface+".reload", 0).Err; err != nil {
   239  		return err
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  // AddInterfaceFirewalld adds the interface to the trusted zone. It is a
   246  // no-op if firewalld is not running.
   247  func AddInterfaceFirewalld(intf string) error {
   248  	if !firewalldRunning {
   249  		return nil
   250  	}
   251  
   252  	var intfs []string
   253  	// Check if interface is already added to the zone
   254  	if err := connection.sysObj.Call(dbusInterface+".zone.getInterfaces", 0, dockerZone).Store(&intfs); err != nil {
   255  		return err
   256  	}
   257  	// Return if interface is already part of the zone
   258  	if contains(intfs, intf) {
   259  		log.G(context.TODO()).Infof("Firewalld: interface %s already part of %s zone, returning", intf, dockerZone)
   260  		return nil
   261  	}
   262  
   263  	log.G(context.TODO()).Debugf("Firewalld: adding %s interface to %s zone", intf, dockerZone)
   264  	// Runtime
   265  	if err := connection.sysObj.Call(dbusInterface+".zone.addInterface", 0, dockerZone, intf).Err; err != nil {
   266  		return err
   267  	}
   268  	return nil
   269  }
   270  
   271  // DelInterfaceFirewalld removes the interface from the trusted zone It is a
   272  // no-op if firewalld is not running.
   273  func DelInterfaceFirewalld(intf string) error {
   274  	if !firewalldRunning {
   275  		return nil
   276  	}
   277  
   278  	var intfs []string
   279  	// Check if interface is part of the zone
   280  	if err := connection.sysObj.Call(dbusInterface+".zone.getInterfaces", 0, dockerZone).Store(&intfs); err != nil {
   281  		return err
   282  	}
   283  	// Remove interface if it exists
   284  	if !contains(intfs, intf) {
   285  		return &interfaceNotFound{fmt.Errorf("firewalld: interface %q not found in %s zone", intf, dockerZone)}
   286  	}
   287  
   288  	log.G(context.TODO()).Debugf("Firewalld: removing %s interface from %s zone", intf, dockerZone)
   289  	// Runtime
   290  	if err := connection.sysObj.Call(dbusInterface+".zone.removeInterface", 0, dockerZone, intf).Err; err != nil {
   291  		return err
   292  	}
   293  	return nil
   294  }
   295  
   296  type interfaceNotFound struct{ error }
   297  
   298  func (interfaceNotFound) NotFound() {}
   299  
   300  func contains(list []string, val string) bool {
   301  	for _, v := range list {
   302  		if v == val {
   303  			return true
   304  		}
   305  	}
   306  	return false
   307  }