libnetwork/drivers/bridge: assorted cleanups
### libnetwork/drivers/bridge: link.Enable, link.Disable use iptables types

The iptables package has types defined for these actions; use them directly
instead of creating a string only to convert it to a known value.

### libnetwork/drivers/bridge: link.Enable: don't register reload on error

Only register a reload function if we actually managed to enable the link.

### libnetwork/drivers/bridge: don't convert IP to string and back again

### libnetwork/drivers/bridge: driver.link: name return var for defer handling

Name the return variable to prevent accidental shadowing of the error,
which is used in defers.


### libnetwork/drivers/bridge: driver.link: don't defer in a loop

Collect a list of all the links we successfully enabled (if any), and
use a single defer to disable them.


### libnetwork/drivers/bridge: bridge.NetworkgetEndpoint(): move lock

Don't lock if there's no need to.

### ibnetwork/drivers/bridge: newLink: validate before creating


**- A picture of a cute animal (not mandatory but encouraged)**


libnetwork/drivers/bridge/link.go
//go:build linux

package bridge

import (
	"context"
	"fmt"
	"net"

	"github.com/containerd/containerd/log"
	"github.com/docker/docker/libnetwork/iptables"
	"github.com/docker/docker/libnetwork/types"
)

type link struct {
	parentIP string
	childIP  string
	ports    []types.TransportPort
	bridge   string
}

func (l *link) String() string {
	return fmt.Sprintf("%s <-> %s [%v] on %s", l.parentIP, l.childIP, l.ports, l.bridge)
}

func newLink(parentIP, childIP string, ports []types.TransportPort, bridge string) *link {
	return &link{
		childIP:  childIP,
		parentIP: parentIP,
		ports:    ports,
		bridge:   bridge,
	}
}

func (l *link) Enable() error {
	// -A == iptables append flag
	linkFunction := func() error {
		return linkContainers("-A", l.parentIP, l.childIP, l.ports, l.bridge, false)
	}

	iptables.OnReloaded(func() { linkFunction() })
	return linkFunction()
}

func (l *link) Disable() {
	// -D == iptables delete flag
	err := linkContainers("-D", l.parentIP, l.childIP, l.ports, l.bridge, true)
	if err != nil {
		log.G(context.TODO()).Errorf("Error removing IPTables rules for a link %s due to %s", l.String(), err.Error())
	}
	// Return proper error once we move to use a proper iptables package
	// that returns typed errors
}

func linkContainers(action, parentIP, childIP string, ports []types.TransportPort, bridge string, ignoreErrors bool) error {
	var nfAction iptables.Action

	switch action {
	case "-A":
		nfAction = iptables.Append
	case "-I":
		nfAction = iptables.Insert
	case "-D":
		nfAction = iptables.Delete
	default:
		return fmt.Errorf("invalid iptables action: %s", action)
	}

	ip1 := net.ParseIP(parentIP)
	if ip1 == nil {
		return fmt.Errorf("cannot link to a container with an invalid parent IP address %q", parentIP)
	}
	ip2 := net.ParseIP(childIP)
	if ip2 == nil {
		return fmt.Errorf("cannot link to a container with an invalid child IP address %q", childIP)
	}

	chain := iptables.ChainInfo{Name: DockerChain}
	for _, port := range ports {
		err := chain.Link(nfAction, ip1, ip2, int(port.Port), port.Proto.String(), bridge)
		if !ignoreErrors && err != nil {
			return err
		}
	}
	return nil
}

//go:build linux

package bridge

import (
	"context"
	"fmt"
	"net"

	"github.com/containerd/containerd/log"
	"github.com/docker/docker/libnetwork/iptables"
	"github.com/docker/docker/libnetwork/types"
)

type link struct {
	parentIP net.IP
	childIP  net.IP
	ports    []types.TransportPort
	bridge   string
}

func (l *link) String() string {
	return fmt.Sprintf("%s <-> %s [%v] on %s", l.parentIP, l.childIP, l.ports, l.bridge)
}

func newLink(parentIP, childIP net.IP, ports []types.TransportPort, bridge string) (*link, error) {
	if parentIP == nil {
		return nil, fmt.Errorf("cannot link to a container with an empty parent IP address")
	}
	if childIP == nil {
		return nil, fmt.Errorf("cannot link to a container with an empty child IP address")
	}

	return &link{
		childIP:  childIP,
		parentIP: parentIP,
		ports:    ports,
		bridge:   bridge,
	}, nil
}

func (l *link) Enable() error {
	linkFunction := func() error {
		return linkContainers(iptables.Append, l.parentIP, l.childIP, l.ports, l.bridge, false)
	}
	if err := linkFunction(); err != nil {
		return err
	}

	iptables.OnReloaded(func() { _ = linkFunction() })
	return nil
}

func (l *link) Disable() {
	if err := linkContainers(iptables.Delete, l.parentIP, l.childIP, l.ports, l.bridge, true); err != nil {
		// @TODO: Return error once we have the iptables package return typed errors.
		log.G(context.TODO()).WithError(err).Errorf("Error removing IPTables rules for link: %s", l.String())
	}
}

func linkContainers(action iptables.Action, parentIP, childIP net.IP, ports []types.TransportPort, bridge string, ignoreErrors bool) error {
	if parentIP == nil {
		return fmt.Errorf("cannot link to a container with an empty parent IP address")
	}
	if childIP == nil {
		return fmt.Errorf("cannot link to a container with an empty child IP address")
	}

	chain := iptables.ChainInfo{Name: DockerChain}
	for _, port := range ports {
		err := chain.Link(action, parentIP, childIP, int(port.Port), port.Proto.String(), bridge)
		if !ignoreErrors && err != nil {
			return err
		}
	}
	return nil
}

done 👍 