Skip to content

Commit 4b61eb0

Browse files
authored
Merge pull request #20 from devplayer0/fix-bridge-use-detection
Fix bridge conflict detection
2 parents b4790cd + 813aa20 commit 4b61eb0

File tree

7 files changed

+77
-42
lines changed

7 files changed

+77
-42
lines changed

.dockerignore

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
/bin/
12
/plugin/
3+
/multiarch/

README.md

+8-7
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ $
8787
8888
# With IPv6 enabled
8989
# Although `docker network create` has a `--ipv6` flag, it doesn't work with the null IPAM driver
90-
$ 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
90+
$ 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
9191
<some network id>
9292
$
9393
```
@@ -148,9 +148,11 @@ services:
148148
- dhcp
149149
networks:
150150
dhcp:
151-
driver: ghcr.io/devplayer0/docker-net-dhcp:golang
151+
driver: ghcr.io/devplayer0/docker-net-dhcp:release-linux-amd64
152152
driver_opts:
153153
bridge: my-bridge
154+
ipv6: 'true'
155+
ignore_conflicts: 'false'
154156
ipam:
155157
driver: 'null'
156158
```
@@ -164,11 +166,10 @@ Note:
164166
- Add `--hostname my-host` to have the DHCP transmit this name as the host for the container. This is useful if your
165167
DHCP server is configured to update DNS records from DHCP leases.
166168
- If the `docker run` command times out waiting for a lease, you can try increasing the initial timeout value by
167-
passing `-o lease_timeout=60s` (e.g. to increase to 60 seconds)
168-
169-
## Docker Compose
170-
171-
Sample Docker Compose file:
169+
passing `-o lease_timeout=60s` when creating the network (e.g. to increase to 60 seconds)
170+
- By default, a bridge can only be used for a single DHCP network. There is additionally a check to see if a bridge is
171+
is used by any other Docker networks. To disable this check (it's also possible this check might mistakenly detect a
172+
conflict), pass `-o ignore_conflicts=true` when creating the network.
172173

173174
## Debugging
174175

go.mod

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ require (
1010
github.com/gorilla/handlers v1.5.1
1111
github.com/gorilla/mux v1.8.0 // indirect
1212
github.com/mitchellh/mapstructure v1.4.1
13-
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
13+
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
1414
github.com/morikuni/aec v1.0.0 // indirect
1515
github.com/sirupsen/logrus v1.8.1
1616
github.com/stretchr/testify v1.7.0 // indirect
1717
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
1818
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f
19-
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect
20-
golang.org/x/sys v0.0.0-20210603125802-9665404d3644
21-
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
19+
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
20+
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
21+
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
2222
google.golang.org/grpc v1.38.0 // indirect
2323
)

go.sum

+10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
2525
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
2626
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
2727
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
28+
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
29+
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
2830
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
2931
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
3032
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
431433
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
432434
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
433435
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
436+
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
437+
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
434438
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
435439
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
436440
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
697701
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
698702
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
699703
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
704+
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
705+
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
700706
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
701707
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
702708
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
777783
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
778784
golang.org/x/sys v0.0.0-20210603125802-9665404d3644 h1:CA1DEQ4NdKphKeL70tvsWNdT5oFh1lOjihRcEDROi0I=
779785
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
786+
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
787+
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
780788
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
781789
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
782790
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
793801
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
794802
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
795803
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
804+
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
805+
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
796806
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
797807
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
798808
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

pkg/plugin/network.go

+38-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package plugin
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"net"
@@ -41,53 +42,65 @@ func (p *Plugin) CreateNetwork(r CreateNetworkRequest) error {
4142
}
4243
}
4344

44-
links, err := netlink.LinkList()
45+
link, err := netlink.LinkByName(opts.Bridge)
4546
if err != nil {
46-
return fmt.Errorf("failed to retrieve list of network interfaces: %w", err)
47+
return fmt.Errorf("failed to lookup interface %v: %w", opts.Bridge, err)
4748
}
48-
49-
nets, err := p.docker.NetworkList(context.Background(), dTypes.NetworkListOptions{})
50-
if err != nil {
51-
return fmt.Errorf("failed to retrieve list of networks from Docker: %w", err)
49+
if link.Type() != "bridge" {
50+
return util.ErrNotBridge
5251
}
5352

54-
found := false
55-
for _, l := range links {
56-
attrs := l.Attrs()
57-
if l.Type() != "bridge" || attrs.Name != opts.Bridge {
58-
continue
53+
if !opts.IgnoreConflicts {
54+
v4Addrs, err := netlink.AddrList(link, unix.AF_INET)
55+
if err != nil {
56+
return fmt.Errorf("failed to retrieve IPv4 addresses for %v: %w", opts.Bridge, err)
5957
}
60-
61-
v4Addrs, err := netlink.AddrList(l, unix.AF_INET)
58+
v6Addrs, err := netlink.AddrList(link, unix.AF_INET6)
6259
if err != nil {
63-
return fmt.Errorf("failed to retrieve IPv4 addresses for %v: %w", attrs.Name, err)
60+
return fmt.Errorf("failed to retrieve IPv6 addresses for %v: %w", opts.Bridge, err)
6461
}
65-
v6Addrs, err := netlink.AddrList(l, unix.AF_INET6)
62+
bridgeAddrs := append(v4Addrs, v6Addrs...)
63+
64+
nets, err := p.docker.NetworkList(context.Background(), dTypes.NetworkListOptions{})
6665
if err != nil {
67-
return fmt.Errorf("failed to retrieve IPv6 addresses for %v: %w", attrs.Name, err)
66+
return fmt.Errorf("failed to retrieve list of networks from Docker: %w", err)
6867
}
69-
addrs := append(v4Addrs, v6Addrs...)
7068

7169
// Make sure the addresses on this bridge aren't used by another network
7270
for _, n := range nets {
71+
if IsDHCPPlugin(n.Driver) {
72+
otherOpts, err := decodeOpts(n.Options)
73+
if err != nil {
74+
log.
75+
WithField("network", n.Name).
76+
WithError(err).
77+
Warn("Failed to parse other DHCP network's options")
78+
} else if otherOpts.Bridge == opts.Bridge {
79+
return util.ErrBridgeUsed
80+
}
81+
}
82+
if n.IPAM.Driver == "null" {
83+
// Null driver networks will have 0.0.0.0/0 which covers any address range!
84+
continue
85+
}
86+
7387
for _, c := range n.IPAM.Config {
74-
_, cidr, err := net.ParseCIDR(c.Subnet)
88+
_, dockerCIDR, err := net.ParseCIDR(c.Subnet)
7589
if err != nil {
7690
return fmt.Errorf("failed to parse subnet %v on Docker network %v: %w", c.Subnet, n.ID, err)
7791
}
92+
if bytes.Equal(dockerCIDR.Mask, net.CIDRMask(0, 32)) || bytes.Equal(dockerCIDR.Mask, net.CIDRMask(0, 128)) {
93+
// Last check to make sure the network isn't 0.0.0.0/0 or ::/0 (which would always pass the check below)
94+
continue
95+
}
7896

79-
for _, linkAddr := range addrs {
80-
if linkAddr.IPNet.Contains(cidr.IP) || cidr.Contains(linkAddr.IP) {
97+
for _, bridgeAddr := range bridgeAddrs {
98+
if bridgeAddr.IPNet.Contains(dockerCIDR.IP) || dockerCIDR.Contains(bridgeAddr.IP) {
8199
return util.ErrBridgeUsed
82100
}
83101
}
84102
}
85103
}
86-
found = true
87-
break
88-
}
89-
if !found {
90-
return util.ErrBridgeNotFound
91104
}
92105

93106
log.WithFields(log.Fields{

pkg/plugin/plugin.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"net"
66
"net/http"
7+
"regexp"
78
"time"
89

910
docker "github.com/docker/docker/client"
@@ -19,11 +20,19 @@ const DriverName string = "net-dhcp"
1920

2021
const defaultLeaseTimeout = 10 * time.Second
2122

23+
var driverRegexp = regexp.MustCompile(`^ghcr\.io/devplayer0/docker-net-dhcp:.+$`)
24+
25+
// IsDHCPPlugin checks if a Docker network driver is an instance of this plugin
26+
func IsDHCPPlugin(driver string) bool {
27+
return driverRegexp.MatchString(driver)
28+
}
29+
2230
// DHCPNetworkOptions contains options for the DHCP network driver
2331
type DHCPNetworkOptions struct {
24-
Bridge string
25-
IPv6 bool
26-
LeaseTimeout time.Duration `mapstructure:"lease_timeout"`
32+
Bridge string
33+
IPv6 bool
34+
LeaseTimeout time.Duration `mapstructure:"lease_timeout"`
35+
IgnoreConflicts bool `mapstructure:"ignore_conflicts"`
2736
}
2837

2938
func decodeOpts(input interface{}) (DHCPNetworkOptions, error) {

pkg/util/errors.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ var (
1010
ErrIPAM = errors.New("only the null IPAM driver is supported")
1111
// ErrBridgeRequired indicates a network bridge was not provided for network creation
1212
ErrBridgeRequired = errors.New("bridge required")
13-
// ErrBridgeNotFound indicates that a bridge could not be found
14-
ErrBridgeNotFound = errors.New("bridge not found")
13+
// ErrNotBridge indicates that the provided network interface is not a bridge
14+
ErrNotBridge = errors.New("network interface is not a bridge")
1515
// ErrBridgeUsed indicates that a bridge is already in use
1616
ErrBridgeUsed = errors.New("bridge already in use by Docker")
1717
// ErrMACAddress indicates an invalid MAC address
@@ -30,7 +30,7 @@ var (
3030

3131
func ErrToStatus(err error) int {
3232
switch {
33-
case errors.Is(err, ErrIPAM), errors.Is(err, ErrBridgeRequired), errors.Is(err, ErrBridgeNotFound),
33+
case errors.Is(err, ErrIPAM), errors.Is(err, ErrBridgeRequired), errors.Is(err, ErrNotBridge),
3434
errors.Is(err, ErrBridgeUsed), errors.Is(err, ErrMACAddress):
3535
return http.StatusBadRequest
3636
default:

0 commit comments

Comments
 (0)