Rework network creation sanity checks

Fixes #18.
pull/20/head
Jack O'Sullivan 3 years ago
parent e9f925ba4c
commit 4f9e4aa925

@ -1 +1,3 @@
/bin/
/plugin/ /plugin/
/multiarch/

@ -1,6 +1,7 @@
package plugin package plugin
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"net" "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 { 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{}) nets, err := p.docker.NetworkList(context.Background(), dTypes.NetworkListOptions{})
if err != nil { if err != nil {
return fmt.Errorf("failed to retrieve list of networks from Docker: %w", err) return fmt.Errorf("failed to retrieve list of networks from Docker: %w", err)
} }
found := false // Make sure the addresses on this bridge aren't used by another network
for _, l := range links { for _, n := range nets {
attrs := l.Attrs() if IsDHCPPlugin(n.Driver) {
if l.Type() != "bridge" || attrs.Name != opts.Bridge { 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 continue
} }
v4Addrs, err := netlink.AddrList(l, unix.AF_INET) for _, c := range n.IPAM.Config {
if err != nil { _, dockerCIDR, err := net.ParseCIDR(c.Subnet)
return fmt.Errorf("failed to retrieve IPv4 addresses for %v: %w", attrs.Name, err) if err != nil {
} return fmt.Errorf("failed to parse subnet %v on Docker network %v: %w", c.Subnet, n.ID, err)
v6Addrs, err := netlink.AddrList(l, unix.AF_INET6) }
if err != nil { if bytes.Equal(dockerCIDR.Mask, net.CIDRMask(0, 32)) || bytes.Equal(dockerCIDR.Mask, net.CIDRMask(0, 128)) {
return fmt.Errorf("failed to retrieve IPv6 addresses for %v: %w", attrs.Name, err) // Last check to make sure the network isn't 0.0.0.0/0 or ::/0 (which would always pass the check below)
} continue
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 _, linkAddr := range addrs { for _, bridgeAddr := range bridgeAddrs {
if linkAddr.IPNet.Contains(cidr.IP) || cidr.Contains(linkAddr.IP) { if bridgeAddr.IPNet.Contains(dockerCIDR.IP) || dockerCIDR.Contains(bridgeAddr.IP) {
return util.ErrBridgeUsed return util.ErrBridgeUsed
}
} }
} }
} }
found = true
break
}
if !found {
return util.ErrBridgeNotFound
} }
log.WithFields(log.Fields{ log.WithFields(log.Fields{

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"regexp"
"time" "time"
docker "github.com/docker/docker/client" docker "github.com/docker/docker/client"
@ -19,6 +20,13 @@ const DriverName string = "net-dhcp"
const defaultLeaseTimeout = 10 * time.Second 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 // DHCPNetworkOptions contains options for the DHCP network driver
type DHCPNetworkOptions struct { type DHCPNetworkOptions struct {
Bridge string Bridge string

@ -10,8 +10,8 @@ var (
ErrIPAM = errors.New("only the null IPAM driver is supported") ErrIPAM = errors.New("only the null IPAM driver is supported")
// ErrBridgeRequired indicates a network bridge was not provided for network creation // ErrBridgeRequired indicates a network bridge was not provided for network creation
ErrBridgeRequired = errors.New("bridge required") ErrBridgeRequired = errors.New("bridge required")
// ErrBridgeNotFound indicates that a bridge could not be found // ErrNotBridge indicates that the provided network interface is not a bridge
ErrBridgeNotFound = errors.New("bridge not found") ErrNotBridge = errors.New("network interface is not a bridge")
// ErrBridgeUsed indicates that a bridge is already in use // ErrBridgeUsed indicates that a bridge is already in use
ErrBridgeUsed = errors.New("bridge already in use by Docker") ErrBridgeUsed = errors.New("bridge already in use by Docker")
// ErrMACAddress indicates an invalid MAC address // ErrMACAddress indicates an invalid MAC address
@ -30,7 +30,7 @@ var (
func ErrToStatus(err error) int { func ErrToStatus(err error) int {
switch { 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): errors.Is(err, ErrBridgeUsed), errors.Is(err, ErrMACAddress):
return http.StatusBadRequest return http.StatusBadRequest
default: default:

Loading…
Cancel
Save