diff --git a/.dockerignore b/.dockerignore index 87553b2..b660be0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,3 @@ +/bin/ /plugin/ +/multiarch/ diff --git a/pkg/plugin/network.go b/pkg/plugin/network.go index 0d7dac2..24d8118 100644 --- a/pkg/plugin/network.go +++ b/pkg/plugin/network.go @@ -1,6 +1,7 @@ package plugin import ( + "bytes" "context" "fmt" "net" @@ -41,53 +42,63 @@ func (p *Plugin) CreateNetwork(r CreateNetworkRequest) error { } } - links, err := netlink.LinkList() + link, err := netlink.LinkByName(opts.Bridge) if err != nil { - return fmt.Errorf("failed to retrieve list of network interfaces: %w", err) + return fmt.Errorf("failed to lookup interface %v: %w", opts.Bridge, err) + } + if link.Type() != "bridge" { + return util.ErrNotBridge } + v4Addrs, err := netlink.AddrList(link, unix.AF_INET) + if err != nil { + return fmt.Errorf("failed to retrieve IPv4 addresses for %v: %w", opts.Bridge, err) + } + v6Addrs, err := netlink.AddrList(link, unix.AF_INET6) + if err != nil { + return fmt.Errorf("failed to retrieve IPv6 addresses for %v: %w", opts.Bridge, err) + } + bridgeAddrs := append(v4Addrs, v6Addrs...) + nets, err := p.docker.NetworkList(context.Background(), dTypes.NetworkListOptions{}) if err != nil { return fmt.Errorf("failed to retrieve list of networks from Docker: %w", err) } - found := false - for _, l := range links { - attrs := l.Attrs() - if l.Type() != "bridge" || attrs.Name != opts.Bridge { + // Make sure the addresses on this bridge aren't used by another network + for _, n := range nets { + if IsDHCPPlugin(n.Driver) { + otherOpts, err := decodeOpts(n.Options) + if err != nil { + log. + WithField("network", n.Name). + WithError(err). + Warn("Failed to parse other DHCP network's options") + } else if otherOpts.Bridge == opts.Bridge { + return util.ErrBridgeUsed + } + } + if n.IPAM.Driver == "null" { + // Null driver networks will have 0.0.0.0/0 which covers any address range! continue } - v4Addrs, err := netlink.AddrList(l, unix.AF_INET) - if err != nil { - return fmt.Errorf("failed to retrieve IPv4 addresses for %v: %w", attrs.Name, err) - } - v6Addrs, err := netlink.AddrList(l, unix.AF_INET6) - if err != nil { - return fmt.Errorf("failed to retrieve IPv6 addresses for %v: %w", attrs.Name, err) - } - addrs := append(v4Addrs, v6Addrs...) - - // Make sure the addresses on this bridge aren't used by another network - for _, n := range nets { - for _, c := range n.IPAM.Config { - _, cidr, err := net.ParseCIDR(c.Subnet) - if err != nil { - return fmt.Errorf("failed to parse subnet %v on Docker network %v: %w", c.Subnet, n.ID, err) - } + for _, c := range n.IPAM.Config { + _, dockerCIDR, err := net.ParseCIDR(c.Subnet) + if err != nil { + return fmt.Errorf("failed to parse subnet %v on Docker network %v: %w", c.Subnet, n.ID, err) + } + if bytes.Equal(dockerCIDR.Mask, net.CIDRMask(0, 32)) || bytes.Equal(dockerCIDR.Mask, net.CIDRMask(0, 128)) { + // Last check to make sure the network isn't 0.0.0.0/0 or ::/0 (which would always pass the check below) + continue + } - for _, linkAddr := range addrs { - if linkAddr.IPNet.Contains(cidr.IP) || cidr.Contains(linkAddr.IP) { - return util.ErrBridgeUsed - } + for _, bridgeAddr := range bridgeAddrs { + if bridgeAddr.IPNet.Contains(dockerCIDR.IP) || dockerCIDR.Contains(bridgeAddr.IP) { + return util.ErrBridgeUsed } } } - found = true - break - } - if !found { - return util.ErrBridgeNotFound } log.WithFields(log.Fields{ diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 3e17c8c..9b6e15a 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "net/http" + "regexp" "time" docker "github.com/docker/docker/client" @@ -19,6 +20,13 @@ const DriverName string = "net-dhcp" const defaultLeaseTimeout = 10 * time.Second +var driverRegexp = regexp.MustCompile(`^ghcr\.io/devplayer0/docker-net-dhcp:.+$`) + +// IsDHCPPlugin checks if a Docker network driver is an instance of this plugin +func IsDHCPPlugin(driver string) bool { + return driverRegexp.MatchString(driver) +} + // DHCPNetworkOptions contains options for the DHCP network driver type DHCPNetworkOptions struct { Bridge string diff --git a/pkg/util/errors.go b/pkg/util/errors.go index 43df8c3..e6069a5 100644 --- a/pkg/util/errors.go +++ b/pkg/util/errors.go @@ -10,8 +10,8 @@ var ( ErrIPAM = errors.New("only the null IPAM driver is supported") // ErrBridgeRequired indicates a network bridge was not provided for network creation ErrBridgeRequired = errors.New("bridge required") - // ErrBridgeNotFound indicates that a bridge could not be found - ErrBridgeNotFound = errors.New("bridge not found") + // ErrNotBridge indicates that the provided network interface is not a bridge + ErrNotBridge = errors.New("network interface is not a bridge") // ErrBridgeUsed indicates that a bridge is already in use ErrBridgeUsed = errors.New("bridge already in use by Docker") // ErrMACAddress indicates an invalid MAC address @@ -30,7 +30,7 @@ var ( func ErrToStatus(err error) int { switch { - case errors.Is(err, ErrIPAM), errors.Is(err, ErrBridgeRequired), errors.Is(err, ErrBridgeNotFound), + case errors.Is(err, ErrIPAM), errors.Is(err, ErrBridgeRequired), errors.Is(err, ErrNotBridge), errors.Is(err, ErrBridgeUsed), errors.Is(err, ErrMACAddress): return http.StatusBadRequest default: