Repository: moby/moby
File: daemon/libnetwork/drivers/bridge/port_mapping_linux.go (Go)

Code before:
		if err := pm.UnmapPorts(ctx, []portmapperapi.PortBinding{b}, n.firewallerNetwork); err != nil {
			errs = append(errs, fmt.Errorf("unmapping port binding %s: %w", b.PortBinding, err))
		}
		if b.StopProxy != nil {
			if err := b.StopProxy(); err != nil && !errors.Is(err, os.ErrProcessDone) {
				errs = append(errs, fmt.Errorf("unmapping port binding %s: failed to stop userland proxy: %w", b.PortBinding, err))
			}
		}
	}

	if err := n.firewallerNetwork.DelPorts(ctx, collectFirewallPorts(bindings)); err != nil {
		return err
	}

	return errors.Join(errs...)
}

func (n *bridgeNetwork) reapplyPerPortIptables() {
	n.Lock()
	var allPBs []portmapperapi.PortBinding
	var allEPs []*bridgeEndpoint
	for _, ep := range n.endpoints {
		allPBs = append(allPBs, ep.portMapping...)
		allEPs = append(allEPs, ep)
	}
	n.Unlock()

	for _, ep := range allEPs {
		netip4, netip6 := ep.netipAddrs()
		if err := n.firewallerNetwork.AddEndpoint(context.TODO(), netip4, netip6); err != nil {
			log.G(context.TODO()).Warnf("Failed to reconfigure Endpoint: %s", err)
		}
	}

	if err := n.firewallerNetwork.AddPorts(context.Background(), collectFirewallPorts(allPBs)); err != nil {
		log.G(context.TODO()).Warnf("Failed to reconfigure NAT: %s", err)
	}
}

// collectFirewallPorts collects all the types.PortBinding needed to reconfigure
// the host firewall for a given list of port bindings.
func collectFirewallPorts(pbs []portmapperapi.PortBinding) []types.PortBinding {
	var fwdPorts, natPorts []types.PortBinding
	for _, pb := range pbs {
		if pb.Forwarding {
			fwdPorts = append(fwdPorts, toFwdBinding(pb))
		}
		if pb.NAT.IsValid() {
			natPorts = append(natPorts, toNATBinding(pb))
		}
	}

Review comment (refactor):
NAT currently implies forwarding (the `nat` portmapper needs forwarding enabled if the proxy is disabled, but it doesn't set `Forwarding: true`, and I think giving the Firewaller two separate `PortBinding` objects for the same port would lead to duplicate-rule errors). So ...

```suggestion
		if pb.NAT.IsValid() {
			natPorts = append(natPorts, toNATBinding(pb))
		} else if pb.Forwarding {
			fwdPorts = append(fwdPorts, toFwdBinding(pb))
		}
```

If this is right, the godoc strings for the new `NAT`/`Forwarding` fields in the port binding will need an update.

(And could just use a single list?)

Code after:
		if err := n.firewallerNetwork.AddEndpoint(context.TODO(), netip4, netip6); err != nil {
			log.G(context.TODO()).Warnf("Failed to reconfigure Endpoint: %s", err)
		}
	}

	if err := n.firewallerNetwork.AddPorts(context.Background(), collectFirewallPorts(allPBs)); err != nil {
		log.G(context.TODO()).Warnf("Failed to reconfigure NAT: %s", err)
	}
}

// collectFirewallPorts collects all the types.PortBinding needed to
// reconfigure the host firewall for a given list of port bindings. If one of
// the pbs is NATed, but has an invalid NAT field (i.e. multicast address, or a
// port 0), an error is returned.
func collectFirewallPorts(pbs []portmapperapi.PortBinding) []types.PortBinding {
	var fwPBs []types.PortBinding
	for _, pb := range pbs {
		if pb.NAT.IsValid() {
			if pb.NAT.Addr().IsMulticast() || pb.NAT.Port() == 0 {
				log.G(context.Background()).WithFields(log.Fields{"pb": pb}).Error("invalid NAT address")
				continue
			}
			fwPBs = append(fwPBs, toNATBinding(pb))
		} else if pb.Forwarding {
			fwPBs = append(fwPBs, toFwdBinding(pb))
		}
	}
	return fwPBs
}

// toNATBinding converts a portmapperapi.PortBinding to a types.PortBinding
// that can be passed to firewaller.Network for setting up a NAT rule.
func toNATBinding(pb portmapperapi.PortBinding) types.PortBinding {
	return types.PortBinding{
		IP:          pb.IP,
		Port:        pb.Port,
		Proto:       pb.Proto,
		HostIP:      pb.NAT.Addr().AsSlice(),
		HostPort:    pb.NAT.Port(),
		HostPortEnd: pb.NAT.Port(),
	}
}

// toFwdBinding converts a portmapperapi.PortBinding to a types.PortBinding
// that can be passed to firewaller.Network for setting up forwarding.
func toFwdBinding(pb portmapperapi.PortBinding) types.PortBinding {
	unspecAddr := netip.IPv4Unspecified()
	if pb.IP.To4() == nil {
		unspecAddr = netip.IPv6Unspecified()
	}
	return types.PortBinding{