Merge pull request #20 from devplayer0/fix-bridge-use-detection

Fix bridge conflict detection
master v0.1.3
Jack O'Sullivan 3 years ago committed by GitHub
commit 4b61eb0db7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -87,7 +87,7 @@ $
# With IPv6 enabled
# Although `docker network create` has a `--ipv6` flag, it doesn't work with the null IPAM driver
$ docker network create -d ghcr.io/devplayer0/docker-net-dhcp:release-linux-amd64 --ipam-driver null -o bridge=test -o ipv6=true my-dhcp-net
$ docker network create -d ghcr.io/devplayer0/docker-net-dhcp:release-linux-amd64 --ipam-driver null -o bridge=my-bridge -o ipv6=true my-dhcp-net
<some network id>
$
```
@ -148,9 +148,11 @@ services:
- dhcp
networks:
dhcp:
driver: ghcr.io/devplayer0/docker-net-dhcp:golang
driver: ghcr.io/devplayer0/docker-net-dhcp:release-linux-amd64
driver_opts:
bridge: my-bridge
ipv6: 'true'
ignore_conflicts: 'false'
ipam:
driver: 'null'
```
@ -164,11 +166,10 @@ Note:
- Add `--hostname my-host` to have the DHCP transmit this name as the host for the container. This is useful if your
DHCP server is configured to update DNS records from DHCP leases.
- If the `docker run` command times out waiting for a lease, you can try increasing the initial timeout value by
passing `-o lease_timeout=60s` (e.g. to increase to 60 seconds)
## Docker Compose
Sample Docker Compose file:
passing `-o lease_timeout=60s` when creating the network (e.g. to increase to 60 seconds)
- By default, a bridge can only be used for a single DHCP network. There is additionally a check to see if a bridge is
is used by any other Docker networks. To disable this check (it's also possible this check might mistakenly detect a
conflict), pass `-o ignore_conflicts=true` when creating the network.
## Debugging

@ -10,14 +10,14 @@ require (
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0 // indirect
github.com/mitchellh/mapstructure v1.4.1
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0 // indirect
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect
golang.org/x/sys v0.0.0-20210603125802-9665404d3644
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
google.golang.org/grpc v1.38.0 // indirect
)

@ -25,6 +25,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
@ -431,6 +433,8 @@ github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGq
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@ -697,6 +701,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -777,6 +783,8 @@ golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b h1:qh4f65QIVFjq9eBURLEYWqaEX
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644 h1:CA1DEQ4NdKphKeL70tvsWNdT5oFh1lOjihRcEDROi0I=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -793,6 +801,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

@ -1,6 +1,7 @@
package plugin
import (
"bytes"
"context"
"fmt"
"net"
@ -41,53 +42,65 @@ 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)
}
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)
if link.Type() != "bridge" {
return util.ErrNotBridge
}
found := false
for _, l := range links {
attrs := l.Attrs()
if l.Type() != "bridge" || attrs.Name != opts.Bridge {
continue
if !opts.IgnoreConflicts {
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)
}
v4Addrs, err := netlink.AddrList(l, unix.AF_INET)
v6Addrs, err := netlink.AddrList(link, unix.AF_INET6)
if err != nil {
return fmt.Errorf("failed to retrieve IPv4 addresses for %v: %w", attrs.Name, err)
return fmt.Errorf("failed to retrieve IPv6 addresses for %v: %w", opts.Bridge, err)
}
v6Addrs, err := netlink.AddrList(l, unix.AF_INET6)
bridgeAddrs := append(v4Addrs, v6Addrs...)
nets, err := p.docker.NetworkList(context.Background(), dTypes.NetworkListOptions{})
if err != nil {
return fmt.Errorf("failed to retrieve IPv6 addresses for %v: %w", attrs.Name, err)
return fmt.Errorf("failed to retrieve list of networks from Docker: %w", err)
}
addrs := append(v4Addrs, v6Addrs...)
// 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
}
for _, c := range n.IPAM.Config {
_, cidr, err := net.ParseCIDR(c.Subnet)
_, 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) {
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{

@ -4,6 +4,7 @@ import (
"fmt"
"net"
"net/http"
"regexp"
"time"
docker "github.com/docker/docker/client"
@ -19,11 +20,19 @@ 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
IPv6 bool
LeaseTimeout time.Duration `mapstructure:"lease_timeout"`
Bridge string
IPv6 bool
LeaseTimeout time.Duration `mapstructure:"lease_timeout"`
IgnoreConflicts bool `mapstructure:"ignore_conflicts"`
}
func decodeOpts(input interface{}) (DHCPNetworkOptions, error) {

@ -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:

Loading…
Cancel
Save