diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +* diff --git a/.gitignore b/.gitignore index 0e0d489204..08e11d78e2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.o *.a *.so +bin/ # Folders integration-tmp/ @@ -33,4 +34,4 @@ cmd/dnet/dnet .project .settings/ -libnetwork-build.created +libnetworkbuild.created diff --git a/Dockerfile.build b/Dockerfile.build new file mode 100644 index 0000000000..f43d4805cc --- /dev/null +++ b/Dockerfile.build @@ -0,0 +1,8 @@ +FROM golang:1.4-cross +RUN apt-get update && apt-get -y install iptables +RUN go get github.com/tools/godep \ + github.com/golang/lint/golint \ + golang.org/x/tools/cmd/vet \ + golang.org/x/tools/cmd/goimports \ + golang.org/x/tools/cmd/cover\ + github.com/mattn/goveralls diff --git a/Makefile b/Makefile index b1eabf5223..73c5887320 100644 --- a/Makefile +++ b/Makefile @@ -1,42 +1,46 @@ -.PHONY: all all-local build build-local check check-code check-format run-tests check-local integration-tests install-deps coveralls circle-ci start-services clean +.PHONY: all all-local build build-local clean cross cross-local check check-code check-format run-tests integration-tests check-local coveralls circle-ci-cross circle-ci-build circle-ci-check circle-ci SHELL=/bin/bash build_image=libnetworkbuild dockerargs = --privileged -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork container_env = -e "INSIDECONTAINER=-incontainer=true" -docker = docker run --rm -it ${dockerargs} ${container_env} ${build_image} +docker = docker run --rm -it ${dockerargs} $$EXTRA_ARGS ${container_env} ${build_image} ciargs = -e "COVERALLS_TOKEN=$$COVERALLS_TOKEN" -e "INSIDECONTAINER=-incontainer=true" -cidocker = docker run ${ciargs} ${dockerargs} golang:1.4 - -all: ${build_image}.created build check integration-tests clean - -integration-tests: ./cmd/dnet/dnet - @./test/integration/dnet/run-integration-tests.sh - -./cmd/dnet/dnet: - make build - -clean: - @if [ -e ./cmd/dnet/dnet ]; then \ - echo "Removing dnet binary"; \ - rm -rf ./cmd/dnet/dnet; \ - fi - -all-local: check-local build-local +cidocker = docker run ${dockerargs} ${ciargs} ${container_env} ${build_image} +CROSS_PLATFORMS = linux/amd64 linux/386 linux/arm windows/amd64 windows/386 ${build_image}.created: - docker run --name=libnetworkbuild -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork golang:1.4 make install-deps - docker commit libnetworkbuild ${build_image} - docker rm libnetworkbuild + docker build -f Dockerfile.build -t ${build_image} . touch ${build_image}.created +all: ${build_image}.created build check integration-tests clean + +all-local: build-local check-local integration-tests-local clean + build: ${build_image}.created @echo "Building code... " @${docker} ./wrapmake.sh build-local @echo "Done building code" build-local: - @$(shell which godep) go build ./... - @$(shell which godep) go build -o ./cmd/dnet/dnet ./cmd/dnet + @mkdir -p "bin" + $(shell which godep) go build -o "bin/dnet" ./cmd/dnet + +clean: + @if [ -d bin ]; then \ + echo "Removing dnet binaries"; \ + rm -rf bin; \ + fi + +cross: ${build_image}.created + @mkdir -p "bin" + @for platform in ${CROSS_PLATFORMS}; do \ + EXTRA_ARGS="-e GOOS=$${platform%/*} -e GOARCH=$${platform##*/}" ; \ + echo "$${platform}..." ; \ + ${docker} make cross-local ; \ + done + +cross-local: + $(shell which godep) go build -o "bin/dnet-$$GOOS-$$GOARCH" ./cmd/dnet check: ${build_image}.created @${docker} ./wrapmake.sh check-local @@ -71,27 +75,31 @@ run-tests: done @echo "Done running tests" -check-local: check-format check-code start-services run-tests +check-local: check-format check-code run-tests + +integration-tests: ./bin/dnet + @./test/integration/dnet/run-integration-tests.sh -install-deps: - apt-get update && apt-get -y install iptables zookeeperd - git clone https://github.com/golang/tools /go/src/golang.org/x/tools - go install golang.org/x/tools/cmd/vet - go install golang.org/x/tools/cmd/goimports - go install golang.org/x/tools/cmd/cover - go get github.com/tools/godep - go get github.com/golang/lint/golint - go get github.com/mattn/goveralls +./bin/dnet: + make build coveralls: -@goveralls -service circleci -coverprofile=coverage.coverprofile -repotoken $$COVERALLS_TOKEN # CircleCI's Docker fails when cleaning up using the --rm flag -# The following target is a workaround for this +# The following targets are a workaround for this +circle-ci-cross: ${build_image}.created + @mkdir -p "bin" + @for platform in ${CROSS_PLATFORMS}; do \ + EXTRA_ARGS="-e GOOS=$${platform%/*} -e GOARCH=$${platform##*/}" ; \ + echo "$${platform}..." ; \ + ${cidocker} make cross-local ; \ + done + +circle-ci-check: ${build_image}.created + @${cidocker} make check-local coveralls -circle-ci: - @${cidocker} make install-deps build-local check-local coveralls - make integration-tests +circle-ci-build: ${build_image}.created + @${cidocker} make build-local -start-services: - service zookeeper start +circle-ci: circle-ci-check circle-ci-build integration-tests diff --git a/bitseq/sequence.go b/bitseq/sequence.go index 14304e3400..553e150c26 100644 --- a/bitseq/sequence.go +++ b/bitseq/sequence.go @@ -552,7 +552,7 @@ func pushReservation(bytePos, bitPos uint64, head *sequence, release bool) *sequ } removeCurrentIfEmpty(&newHead, newSequence, current) mergeSequences(previous) - } else if precBlocks == current.count-2 { // Last in sequence (B) + } else if precBlocks == current.count { // Last in sequence (B) newSequence.next = current.next current.next = newSequence mergeSequences(current) diff --git a/bitseq/sequence_test.go b/bitseq/sequence_test.go index f650778f04..87c3641d40 100644 --- a/bitseq/sequence_test.go +++ b/bitseq/sequence_test.go @@ -352,6 +352,62 @@ func TestPushReservation(t *testing.T) { {&sequence{block: 0xffff0000, count: 7}, 25, 7, &sequence{block: 0xffff0000, count: 7}}, {&sequence{block: 0xffffffff, count: 7, next: &sequence{block: 0xfffffffe, count: 1, next: &sequence{block: 0xffffffff, count: 1}}}, 7, 7, &sequence{block: 0xffffffff, count: 7, next: &sequence{block: 0xfffffffe, count: 1, next: &sequence{block: 0xffffffff, count: 1}}}}, + + // Set last bit + {&sequence{block: 0x0, count: 8}, 31, 7, &sequence{block: 0x0, count: 7, next: &sequence{block: 0x1, count: 1}}}, + + // Set bit in a middle sequence in the first block, first bit + {&sequence{block: 0x40000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 4, 0, + &sequence{block: 0x40000000, count: 1, next: &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5, + next: &sequence{block: 0x1, count: 1}}}}}, + + // Set bit in a middle sequence in the first block, first bit (merge involved) + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 4, 0, + &sequence{block: 0x80000000, count: 2, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x1, count: 1}}}}, + + // Set bit in a middle sequence in the first block, last bit + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 4, 31, + &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x1, count: 1, next: &sequence{block: 0x0, count: 5, + next: &sequence{block: 0x1, count: 1}}}}}, + + // Set bit in a middle sequence in the first block, middle bit + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 4, 16, + &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x8000, count: 1, next: &sequence{block: 0x0, count: 5, + next: &sequence{block: 0x1, count: 1}}}}}, + + // Set bit in a middle sequence in a middle block, first bit + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 16, 0, + &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 3, next: &sequence{block: 0x80000000, count: 1, + next: &sequence{block: 0x0, count: 2, next: &sequence{block: 0x1, count: 1}}}}}}, + + // Set bit in a middle sequence in a middle block, last bit + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 16, 31, + &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 3, next: &sequence{block: 0x1, count: 1, + next: &sequence{block: 0x0, count: 2, next: &sequence{block: 0x1, count: 1}}}}}}, + + // Set bit in a middle sequence in a middle block, middle bit + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 16, 15, + &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 3, next: &sequence{block: 0x10000, count: 1, + next: &sequence{block: 0x0, count: 2, next: &sequence{block: 0x1, count: 1}}}}}}, + + // Set bit in a middle sequence in the last block, first bit + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 24, 0, + &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x80000000, count: 1, + next: &sequence{block: 0x1, count: 1}}}}}, + + // Set bit in a middle sequence in the last block, last bit + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x4, count: 1}}}, 24, 31, + &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x1, count: 1, + next: &sequence{block: 0x4, count: 1}}}}}, + + // Set bit in a middle sequence in the last block, last bit (merge involved) + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 24, 31, + &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x1, count: 2}}}}, + + // Set bit in a middle sequence in the last block, middle bit + {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 24, 16, + &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x8000, count: 1, + next: &sequence{block: 0x1, count: 1}}}}}, } for n, i := range input { @@ -635,3 +691,58 @@ func TestSetInRange(t *testing.T) { t.Fatalf("Unexpected error: %v", err) } } + +// This one tests an allocation pattern which unveiled an issue in pushReservation +// Specifically a failure in detecting when we are in the (B) case (the bit to set +// belongs to the last block of the current sequence). Because of a bug, code +// was assuming the bit belonged to a block in the middle of the current sequence. +// Which in turn caused an incorrect allocation when requesting a bit which is not +// in the first or last sequence block. +func TestSetAnyInRange(t *testing.T) { + numBits := uint64(8 * blockLen) + hnd, err := NewHandle("", nil, "", numBits) + if err != nil { + t.Fatal(err) + } + + if err := hnd.Set(0); err != nil { + t.Fatal(err) + } + + if err := hnd.Set(255); err != nil { + t.Fatal(err) + } + + o, err := hnd.SetAnyInRange(128, 255) + if err != nil { + t.Fatal(err) + } + if o != 128 { + t.Fatalf("Unexpected ordinal: %d", o) + } + + o, err = hnd.SetAnyInRange(128, 255) + if err != nil { + t.Fatal(err) + } + + if o != 129 { + t.Fatalf("Unexpected ordinal: %d", o) + } + + o, err = hnd.SetAnyInRange(246, 255) + if err != nil { + t.Fatal(err) + } + if o != 246 { + t.Fatalf("Unexpected ordinal: %d", o) + } + + o, err = hnd.SetAnyInRange(246, 255) + if err != nil { + t.Fatal(err) + } + if o != 247 { + t.Fatalf("Unexpected ordinal: %d", o) + } +} diff --git a/circle.yml b/circle.yml index d02f6a92ed..a454d21201 100644 --- a/circle.yml +++ b/circle.yml @@ -1,12 +1,18 @@ machine: - services: - - docker + services: + - docker dependencies: - override: - - echo "Nothing to install" + override: + - sudo apt-get update; sudo apt-get install -y iptables zookeeperd + - go get golang.org/x/tools/cmd/vet + - go get golang.org/x/tools/cmd/goimports + - go get golang.org/x/tools/cmd/cover + - go get github.com/tools/godep + - go get github.com/golang/lint/golint + - go get github.com/mattn/goveralls test: - override: - - make circle-ci + override: + - make circle-ci diff --git a/drivers/bridge/bridge.go b/drivers/bridge/bridge.go index 8cc26f951f..7991c6f67c 100644 --- a/drivers/bridge/bridge.go +++ b/drivers/bridge/bridge.go @@ -41,6 +41,9 @@ const ( DefaultGatewayV6AuxKey = "DefaultGatewayIPv6" ) +type iptableCleanFunc func() error +type iptablesCleanFuncs []iptableCleanFunc + // configuration info for the "bridge" driver. type configuration struct { EnableIPForwarding bool @@ -92,12 +95,13 @@ type bridgeEndpoint struct { } type bridgeNetwork struct { - id string - bridge *bridgeInterface // The bridge's L3 interface - config *networkConfiguration - endpoints map[string]*bridgeEndpoint // key: endpoint id - portMapper *portmapper.PortMapper - driver *driver // The network's driver + id string + bridge *bridgeInterface // The bridge's L3 interface + config *networkConfiguration + endpoints map[string]*bridgeEndpoint // key: endpoint id + portMapper *portmapper.PortMapper + driver *driver // The network's driver + iptCleanFuncs iptablesCleanFuncs sync.Mutex } @@ -236,6 +240,10 @@ func parseErr(label, value, errString string) error { return types.BadRequestErrorf("failed to parse %s value: %v (%s)", label, value, errString) } +func (n *bridgeNetwork) registerIptCleanFunc(clean iptableCleanFunc) { + n.iptCleanFuncs = append(n.iptCleanFuncs, clean) +} + func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, error) { n.Lock() defer n.Unlock() @@ -604,6 +612,10 @@ func (d *driver) createNetwork(config *networkConfiguration) error { } return err } + network.registerIptCleanFunc(func() error { + nwList := d.getNetworks() + return network.isolateNetwork(nwList, false) + }) return nil } @@ -722,22 +734,6 @@ func (d *driver) DeleteNetwork(nid string) error { return err } - // In case of failures after this point, restore the network isolation rules - nwList := d.getNetworks() - defer func() { - if err != nil { - if err := n.isolateNetwork(nwList, true); err != nil { - logrus.Warnf("Failed on restoring the inter-network iptables rules on cleanup: %v", err) - } - } - }() - - // Remove inter-network communication rules. - err = n.isolateNetwork(nwList, false) - if err != nil { - return err - } - // We only delete the bridge when it's not the default bridge. This is keep the backward compatible behavior. if !config.DefaultBridge { if err := netlink.LinkDel(n.bridge.Link); err != nil { @@ -745,6 +741,12 @@ func (d *driver) DeleteNetwork(nid string) error { } } + // clean all relevant iptables rules + for _, cleanFunc := range n.iptCleanFuncs { + if errClean := cleanFunc(); errClean != nil { + logrus.Warnf("Failed to clean iptables rules for bridge network: %v", errClean) + } + } return d.storeDelete(config) } diff --git a/drivers/bridge/setup_ip_tables.go b/drivers/bridge/setup_ip_tables.go index f597f67b7f..6b1c5dcb11 100644 --- a/drivers/bridge/setup_ip_tables.go +++ b/drivers/bridge/setup_ip_tables.go @@ -68,21 +68,27 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt if err = setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil { return fmt.Errorf("Failed to Setup IP tables: %s", err.Error()) } + n.registerIptCleanFunc(func() error { + return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false) + }) natChain, filterChain, err := n.getDriverChains() if err != nil { return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error()) } - err = iptables.ProgramChain(natChain, config.BridgeName, hairpinMode) + err = iptables.ProgramChain(natChain, config.BridgeName, hairpinMode, true) if err != nil { return fmt.Errorf("Failed to program NAT chain: %s", err.Error()) } - err = iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode) + err = iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, true) if err != nil { return fmt.Errorf("Failed to program FILTER chain: %s", err.Error()) } + n.registerIptCleanFunc(func() error { + return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false) + }) n.portMapper.SetIptablesChain(filterChain, n.getNetworkBridgeName()) diff --git a/endpoint_info.go b/endpoint_info.go index db23eb738f..1028308557 100644 --- a/endpoint_info.go +++ b/endpoint_info.go @@ -159,7 +159,11 @@ func (ep *endpoint) Info() EndpointInfo { return ep } - return sb.getEndpoint(ep.ID()) + if epi := sb.getEndpoint(ep.ID()); epi != nil { + return epi + } + + return nil } func (ep *endpoint) DriverInfo() (map[string]interface{}, error) { diff --git a/ipamutils/utils_freebsd.go b/ipamutils/utils_freebsd.go new file mode 100644 index 0000000000..09eced12d1 --- /dev/null +++ b/ipamutils/utils_freebsd.go @@ -0,0 +1,22 @@ +// Package ipamutils provides utililty functions for ipam management +package ipamutils + +import ( + "net" + + "github.com/docker/libnetwork/types" +) + +// ElectInterfaceAddresses looks for an interface on the OS with the specified name +// and returns its IPv4 and IPv6 addresses in CIDR form. If the interface does not exist, +// it chooses from a predifined list the first IPv4 address which does not conflict +// with other interfaces on the system. +func ElectInterfaceAddresses(name string) (*net.IPNet, []*net.IPNet, error) { + return nil, nil, types.NotImplementedErrorf("not supported on freebsd") +} + +// FindAvailableNetwork returns a network from the passed list which does not +// overlap with existing interfaces in the system +func FindAvailableNetwork(list []*net.IPNet) (*net.IPNet, error) { + return nil, types.NotImplementedErrorf("not supported on freebsd") +} diff --git a/iptables/firewalld_test.go b/iptables/firewalld_test.go index 6607307564..1ac11a9adf 100644 --- a/iptables/firewalld_test.go +++ b/iptables/firewalld_test.go @@ -22,7 +22,7 @@ func TestReloaded(t *testing.T) { fwdChain, err = NewChain("FWD", Filter, false) bridgeName := "lo" - err = ProgramChain(fwdChain, bridgeName, false) + err = ProgramChain(fwdChain, bridgeName, false, true) if err != nil { t.Fatal(err) } diff --git a/iptables/iptables.go b/iptables/iptables.go index 4e24c3ac52..be7725f85d 100644 --- a/iptables/iptables.go +++ b/iptables/iptables.go @@ -95,7 +95,7 @@ func NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) { } // ProgramChain is used to add rules to a chain -func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode bool) error { +func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error { if c.Name == "" { return fmt.Errorf("Could not program chain, missing chain name.") } @@ -106,10 +106,14 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode bool) error { "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name} - if !Exists(Nat, "PREROUTING", preroute...) { + if !Exists(Nat, "PREROUTING", preroute...) && enable { if err := c.Prerouting(Append, preroute...); err != nil { return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) } + } else if Exists(Nat, "PREROUTING", preroute...) && !enable { + if err := c.Prerouting(Delete, preroute...); err != nil { + return fmt.Errorf("Failed to remove docker in PREROUTING chain: %s", err) + } } output := []string{ "-m", "addrtype", @@ -118,10 +122,14 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode bool) error { if !hairpinMode { output = append(output, "!", "--dst", "127.0.0.0/8") } - if !Exists(Nat, "OUTPUT", output...) { + if !Exists(Nat, "OUTPUT", output...) && enable { if err := c.Output(Append, output...); err != nil { return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) } + } else if Exists(Nat, "OUTPUT", output...) && !enable { + if err := c.Output(Delete, output...); err != nil { + return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) + } } case Filter: if bridgeName == "" { @@ -131,13 +139,21 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode bool) error { link := []string{ "-o", bridgeName, "-j", c.Name} - if !Exists(Filter, "FORWARD", link...) { + if !Exists(Filter, "FORWARD", link...) && enable { insert := append([]string{string(Insert), "FORWARD"}, link...) if output, err := Raw(insert...); err != nil { return err } else if len(output) != 0 { return fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output) } + } else if Exists(Filter, "FORWARD", link...) && !enable { + del := append([]string{string(Delete), "FORWARD"}, link...) + if output, err := Raw(del...); err != nil { + return err + } else if len(output) != 0 { + return fmt.Errorf("Could not delete linking rule from %s/%s: %s", c.Table, c.Name, output) + } + } } return nil diff --git a/iptables/iptables_test.go b/iptables/iptables_test.go index 262ba18a72..2e5a2b5d25 100644 --- a/iptables/iptables_test.go +++ b/iptables/iptables_test.go @@ -22,13 +22,13 @@ func TestNewChain(t *testing.T) { bridgeName = "lo" natChain, err = NewChain(chainName, Nat, false) - err = ProgramChain(natChain, bridgeName, false) + err = ProgramChain(natChain, bridgeName, false, true) if err != nil { t.Fatal(err) } filterChain, err = NewChain(chainName, Filter, false) - err = ProgramChain(filterChain, bridgeName, false) + err = ProgramChain(filterChain, bridgeName, false, true) if err != nil { t.Fatal(err) } diff --git a/netutils/utils.go b/netutils/utils.go index a1ead3618c..19ea2fddfb 100644 --- a/netutils/utils.go +++ b/netutils/utils.go @@ -9,10 +9,8 @@ import ( "fmt" "io" "net" - "strings" "github.com/docker/libnetwork/types" - "github.com/vishvananda/netlink" ) var ( @@ -22,8 +20,6 @@ var ( ErrNetworkOverlaps = errors.New("requested network overlaps with existing network") // ErrNoDefaultRoute preformatted error ErrNoDefaultRoute = errors.New("no default route") - - networkGetRoutesFct = netlink.RouteList ) // CheckNameserverOverlaps checks whether the passed network overlaps with any of the nameservers @@ -42,21 +38,6 @@ func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { return nil } -// CheckRouteOverlaps checks whether the passed network overlaps with any existing routes -func CheckRouteOverlaps(toCheck *net.IPNet) error { - networks, err := networkGetRoutesFct(nil, netlink.FAMILY_V4) - if err != nil { - return err - } - - for _, network := range networks { - if network.Dst != nil && NetworkOverlaps(toCheck, network.Dst) { - return ErrNetworkOverlaps - } - } - return nil -} - // NetworkOverlaps detects overlap between one IPNet and another func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { return netX.Contains(netY.IP) || netY.Contains(netX.IP) @@ -151,22 +132,3 @@ func GenerateRandomName(prefix string, size int) (string, error) { } return prefix + hex.EncodeToString(id)[:size], nil } - -// GenerateIfaceName returns an interface name using the passed in -// prefix and the length of random bytes. The api ensures that the -// there are is no interface which exists with that name. -func GenerateIfaceName(prefix string, len int) (string, error) { - for i := 0; i < 3; i++ { - name, err := GenerateRandomName(prefix, len) - if err != nil { - continue - } - if _, err := netlink.LinkByName(name); err != nil { - if strings.Contains(err.Error(), "not found") { - return name, nil - } - return "", err - } - } - return "", types.InternalErrorf("could not generate interface name") -} diff --git a/netutils/utils_linux.go b/netutils/utils_linux.go new file mode 100644 index 0000000000..782e542a52 --- /dev/null +++ b/netutils/utils_linux.go @@ -0,0 +1,50 @@ +// +build linux +// Network utility functions. + +package netutils + +import ( + "net" + "strings" + + "github.com/docker/libnetwork/types" + "github.com/vishvananda/netlink" +) + +var ( + networkGetRoutesFct = netlink.RouteList +) + +// CheckRouteOverlaps checks whether the passed network overlaps with any existing routes +func CheckRouteOverlaps(toCheck *net.IPNet) error { + networks, err := networkGetRoutesFct(nil, netlink.FAMILY_V4) + if err != nil { + return err + } + + for _, network := range networks { + if network.Dst != nil && NetworkOverlaps(toCheck, network.Dst) { + return ErrNetworkOverlaps + } + } + return nil +} + +// GenerateIfaceName returns an interface name using the passed in +// prefix and the length of random bytes. The api ensures that the +// there are is no interface which exists with that name. +func GenerateIfaceName(prefix string, len int) (string, error) { + for i := 0; i < 3; i++ { + name, err := GenerateRandomName(prefix, len) + if err != nil { + continue + } + if _, err := netlink.LinkByName(name); err != nil { + if strings.Contains(err.Error(), "not found") { + return name, nil + } + return "", err + } + } + return "", types.InternalErrorf("could not generate interface name") +} diff --git a/network.go b/network.go index 00061ea016..378cd80f20 100644 --- a/network.go +++ b/network.go @@ -671,6 +671,12 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi ep.processOptions(options...) + if opt, ok := ep.generic[netlabel.MacAddress]; ok { + if mac, ok := opt.(net.HardwareAddr); ok { + ep.iface.mac = mac + } + } + if err = ep.assignAddress(true, !n.postIPv6); err != nil { return nil, err } diff --git a/osl/sandbox_freebsd.go b/osl/sandbox_freebsd.go index 36bd6c8002..7c6dcacead 100644 --- a/osl/sandbox_freebsd.go +++ b/osl/sandbox_freebsd.go @@ -19,6 +19,11 @@ func NewSandbox(key string, osCreate bool) (Sandbox, error) { return nil, nil } +// GetSandboxForExternalKey returns sandbox object for the supplied path +func GetSandboxForExternalKey(path string, key string) (Sandbox, error) { + return nil, nil +} + // GC triggers garbage collection of namespace path right away // and waits for it. func GC() { diff --git a/sandbox_externalkey.go b/sandbox_externalkey.go index 0b2385b3bf..3c362f30d6 100644 --- a/sandbox_externalkey.go +++ b/sandbox_externalkey.go @@ -1,19 +1,6 @@ package libnetwork -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net" - "os" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/pkg/reexec" - "github.com/docker/libnetwork/types" - "github.com/opencontainers/runc/libcontainer" - "github.com/opencontainers/runc/libcontainer/configs" -) +import "github.com/docker/docker/pkg/reexec" type setKeyData struct { ContainerID string @@ -23,163 +10,3 @@ type setKeyData struct { func init() { reexec.Register("libnetwork-setkey", processSetKeyReexec) } - -const udsBase = "/var/lib/docker/network/files/" -const success = "success" - -// processSetKeyReexec is a private function that must be called only on an reexec path -// It expects 3 args { [0] = "libnetwork-setkey", [1] = , [2] = } -// It also expects libcontainer.State as a json string in -// Refer to https://github.com/opencontainers/runc/pull/160/ for more information -func processSetKeyReexec() { - var err error - - // Return a failure to the calling process via ExitCode - defer func() { - if err != nil { - logrus.Fatalf("%v", err) - } - }() - - // expecting 3 args {[0]="libnetwork-setkey", [1]=, [2]= } - if len(os.Args) < 3 { - err = fmt.Errorf("Re-exec expects 3 args, received : %d", len(os.Args)) - return - } - containerID := os.Args[1] - - // We expect libcontainer.State as a json string in - stateBuf, err := ioutil.ReadAll(os.Stdin) - if err != nil { - return - } - var state libcontainer.State - if err = json.Unmarshal(stateBuf, &state); err != nil { - return - } - - controllerID := os.Args[2] - key := state.NamespacePaths[configs.NamespaceType("NEWNET")] - - err = SetExternalKey(controllerID, containerID, key) - return -} - -// SetExternalKey provides a convenient way to set an External key to a sandbox -func SetExternalKey(controllerID string, containerID string, key string) error { - keyData := setKeyData{ - ContainerID: containerID, - Key: key} - - c, err := net.Dial("unix", udsBase+controllerID+".sock") - if err != nil { - return err - } - defer c.Close() - - if err = sendKey(c, keyData); err != nil { - return fmt.Errorf("sendKey failed with : %v", err) - } - return processReturn(c) -} - -func sendKey(c net.Conn, data setKeyData) error { - var err error - defer func() { - if err != nil { - c.Close() - } - }() - - var b []byte - if b, err = json.Marshal(data); err != nil { - return err - } - - _, err = c.Write(b) - return err -} - -func processReturn(r io.Reader) error { - buf := make([]byte, 1024) - n, err := r.Read(buf[:]) - if err != nil { - return fmt.Errorf("failed to read buf in processReturn : %v", err) - } - if string(buf[0:n]) != success { - return fmt.Errorf(string(buf[0:n])) - } - return nil -} - -func (c *controller) startExternalKeyListener() error { - if err := os.MkdirAll(udsBase, 0600); err != nil { - return err - } - uds := udsBase + c.id + ".sock" - l, err := net.Listen("unix", uds) - if err != nil { - return err - } - if err := os.Chmod(uds, 0600); err != nil { - l.Close() - return err - } - c.Lock() - c.extKeyListener = l - c.Unlock() - - go c.acceptClientConnections(uds, l) - return nil -} - -func (c *controller) acceptClientConnections(sock string, l net.Listener) { - for { - conn, err := l.Accept() - if err != nil { - if _, err1 := os.Stat(sock); os.IsNotExist(err1) { - logrus.Debugf("Unix socket %s doesnt exist. cannot accept client connections", sock) - return - } - logrus.Errorf("Error accepting connection %v", err) - continue - } - go func() { - err := c.processExternalKey(conn) - ret := success - if err != nil { - ret = err.Error() - } - - _, err = conn.Write([]byte(ret)) - if err != nil { - logrus.Errorf("Error returning to the client %v", err) - } - }() - } -} - -func (c *controller) processExternalKey(conn net.Conn) error { - buf := make([]byte, 1280) - nr, err := conn.Read(buf) - if err != nil { - return err - } - var s setKeyData - if err = json.Unmarshal(buf[0:nr], &s); err != nil { - return err - } - - var sandbox Sandbox - search := SandboxContainerWalker(&sandbox, s.ContainerID) - c.WalkSandboxes(search) - if sandbox == nil { - return types.BadRequestErrorf("no sandbox present for %s", s.ContainerID) - } - - return sandbox.SetKey(s.Key) -} - -func (c *controller) stopExternalKeyListener() { - c.extKeyListener.Close() -} diff --git a/sandbox_externalkey_unix.go b/sandbox_externalkey_unix.go new file mode 100644 index 0000000000..74ae2af78e --- /dev/null +++ b/sandbox_externalkey_unix.go @@ -0,0 +1,177 @@ +// +build !windows + +package libnetwork + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net" + "os" + + "github.com/Sirupsen/logrus" + "github.com/docker/libnetwork/types" + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/configs" +) + +const udsBase = "/var/lib/docker/network/files/" +const success = "success" + +// processSetKeyReexec is a private function that must be called only on an reexec path +// It expects 3 args { [0] = "libnetwork-setkey", [1] = , [2] = } +// It also expects libcontainer.State as a json string in +// Refer to https://github.com/opencontainers/runc/pull/160/ for more information +func processSetKeyReexec() { + var err error + + // Return a failure to the calling process via ExitCode + defer func() { + if err != nil { + logrus.Fatalf("%v", err) + } + }() + + // expecting 3 args {[0]="libnetwork-setkey", [1]=, [2]= } + if len(os.Args) < 3 { + err = fmt.Errorf("Re-exec expects 3 args, received : %d", len(os.Args)) + return + } + containerID := os.Args[1] + + // We expect libcontainer.State as a json string in + stateBuf, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return + } + var state libcontainer.State + if err = json.Unmarshal(stateBuf, &state); err != nil { + return + } + + controllerID := os.Args[2] + key := state.NamespacePaths[configs.NamespaceType("NEWNET")] + + err = SetExternalKey(controllerID, containerID, key) + return +} + +// SetExternalKey provides a convenient way to set an External key to a sandbox +func SetExternalKey(controllerID string, containerID string, key string) error { + keyData := setKeyData{ + ContainerID: containerID, + Key: key} + + c, err := net.Dial("unix", udsBase+controllerID+".sock") + if err != nil { + return err + } + defer c.Close() + + if err = sendKey(c, keyData); err != nil { + return fmt.Errorf("sendKey failed with : %v", err) + } + return processReturn(c) +} + +func sendKey(c net.Conn, data setKeyData) error { + var err error + defer func() { + if err != nil { + c.Close() + } + }() + + var b []byte + if b, err = json.Marshal(data); err != nil { + return err + } + + _, err = c.Write(b) + return err +} + +func processReturn(r io.Reader) error { + buf := make([]byte, 1024) + n, err := r.Read(buf[:]) + if err != nil { + return fmt.Errorf("failed to read buf in processReturn : %v", err) + } + if string(buf[0:n]) != success { + return fmt.Errorf(string(buf[0:n])) + } + return nil +} + +func (c *controller) startExternalKeyListener() error { + if err := os.MkdirAll(udsBase, 0600); err != nil { + return err + } + uds := udsBase + c.id + ".sock" + l, err := net.Listen("unix", uds) + if err != nil { + return err + } + if err := os.Chmod(uds, 0600); err != nil { + l.Close() + return err + } + c.Lock() + c.extKeyListener = l + c.Unlock() + + go c.acceptClientConnections(uds, l) + return nil +} + +func (c *controller) acceptClientConnections(sock string, l net.Listener) { + for { + conn, err := l.Accept() + if err != nil { + if _, err1 := os.Stat(sock); os.IsNotExist(err1) { + logrus.Debugf("Unix socket %s doesnt exist. cannot accept client connections", sock) + return + } + logrus.Errorf("Error accepting connection %v", err) + continue + } + go func() { + err := c.processExternalKey(conn) + ret := success + if err != nil { + ret = err.Error() + } + + _, err = conn.Write([]byte(ret)) + if err != nil { + logrus.Errorf("Error returning to the client %v", err) + } + }() + } +} + +func (c *controller) processExternalKey(conn net.Conn) error { + buf := make([]byte, 1280) + nr, err := conn.Read(buf) + if err != nil { + return err + } + var s setKeyData + if err = json.Unmarshal(buf[0:nr], &s); err != nil { + return err + } + + var sandbox Sandbox + search := SandboxContainerWalker(&sandbox, s.ContainerID) + c.WalkSandboxes(search) + if sandbox == nil { + return types.BadRequestErrorf("no sandbox present for %s", s.ContainerID) + } + + return sandbox.SetKey(s.Key) +} + +func (c *controller) stopExternalKeyListener() { + c.extKeyListener.Close() +} diff --git a/sandbox_externalkey_windows.go b/sandbox_externalkey_windows.go new file mode 100644 index 0000000000..3c4113e252 --- /dev/null +++ b/sandbox_externalkey_windows.go @@ -0,0 +1,45 @@ +// +build windows + +package libnetwork + +import ( + "io" + "net" + + "github.com/docker/libnetwork/types" +) + +// processSetKeyReexec is a private function that must be called only on an reexec path +// It expects 3 args { [0] = "libnetwork-setkey", [1] = , [2] = } +// It also expects libcontainer.State as a json string in +// Refer to https://github.com/opencontainers/runc/pull/160/ for more information +func processSetKeyReexec() { +} + +// SetExternalKey provides a convenient way to set an External key to a sandbox +func SetExternalKey(controllerID string, containerID string, key string) error { + return types.NotImplementedErrorf("SetExternalKey isn't supported on non linux systems") +} + +func sendKey(c net.Conn, data setKeyData) error { + return types.NotImplementedErrorf("sendKey isn't supported on non linux systems") +} + +func processReturn(r io.Reader) error { + return types.NotImplementedErrorf("processReturn isn't supported on non linux systems") +} + +// no-op on non linux systems +func (c *controller) startExternalKeyListener() error { + return nil +} + +func (c *controller) acceptClientConnections(sock string, l net.Listener) { +} + +func (c *controller) processExternalKey(conn net.Conn) error { + return types.NotImplementedErrorf("processExternalKey isn't supported on non linux systems") +} + +func (c *controller) stopExternalKeyListener() { +} diff --git a/test/integration/dnet/dnet.bats b/test/integration/dnet/dnet.bats index e5c60dc055..cf73eab5bb 100644 --- a/test/integration/dnet/dnet.bats +++ b/test/integration/dnet/dnet.bats @@ -21,10 +21,10 @@ load helpers run dnet_cmd 8080 network ls echo ${output} [ "$status" -ne 0 ] - run ./cmd/dnet/dnet -H=unix://var/run/dnet.sock network ls + run ./bin/dnet -H=unix://var/run/dnet.sock network ls echo ${output} [ "$status" -ne 0 ] - run ./cmd/dnet/dnet -H= -l=invalid network ls + run ./bin/dnet -H= -l=invalid network ls echo ${output} [ "$status" -ne 0 ] stop_dnet 1 c diff --git a/test/integration/dnet/helpers.bash b/test/integration/dnet/helpers.bash index 7b507abf79..5f85bd20a5 100644 --- a/test/integration/dnet/helpers.bash +++ b/test/integration/dnet/helpers.bash @@ -73,7 +73,7 @@ function wait_for_dnet() { echo "waiting on dnet to come up ..." for i in `seq 1 10`; do - hrun ./cmd/dnet/dnet -H tcp://127.0.0.1:${hport} network ls + hrun ./bin/dnet -H tcp://127.0.0.1:${hport} network ls echo ${output} if [ "$status" -eq 0 ]; then return @@ -168,7 +168,7 @@ EOF -v $(pwd)/${TMPC_ROOT}:/scratch \ -v /usr/local/bin/runc:/usr/local/bin/runc \ -w /go/src/github.com/docker/libnetwork \ - mrjana/golang ./cmd/dnet/dnet -d -D ${hopt} -c ${tomlfile} + mrjana/golang ./bin/dnet -d -D ${hopt} -c ${tomlfile} wait_for_dnet $(inst_id2port ${inst}) ${name} } @@ -196,7 +196,7 @@ function dnet_cmd() { hport=$1 shift - ./cmd/dnet/dnet -H tcp://127.0.0.1:${hport} $* + ./bin/dnet -H tcp://127.0.0.1:${hport} $* } function dnet_exec() {