github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/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 }