--- /dev/null
+version: 2
+
+defaults: &defaults
+ working_directory: ~/go/src/github.com/docker/libnetwork
+ docker:
+ # the following image is irrelevant for the build, everything is built inside a container, check the Makefile
+ - image: 'circleci/golang:latest'
+ environment:
+ dockerbuildargs: .
+ dockerargs: --privileged -e CIRCLECI
+
+jobs:
+ builder:
+ <<: *defaults
+ steps:
+ - checkout
+ - setup_remote_docker:
+ version: 18.05.0-ce
+ reusable: true
+ exclusive: false
+ - run: make builder
+
+ build:
+ <<: *defaults
+ steps:
+ - checkout
+ - setup_remote_docker:
+ version: 18.05.0-ce
+ reusable: true
+ exclusive: false
+ - run: make build
+
+ check:
+ <<: *defaults
+ steps:
+ - checkout
+ - setup_remote_docker:
+ version: 18.05.0-ce
+ reusable: true
+ exclusive: false
+ - run: make check
+
+ cross:
+ <<: *defaults
+ steps:
+ - checkout
+ - setup_remote_docker:
+ version: 18.05.0-ce
+ reusable: true
+ exclusive: false
+ - run: make cross
+
+ unit-tests:
+ <<: *defaults
+ steps:
+ - checkout
+ - setup_remote_docker:
+ version: 18.05.0-ce
+ reusable: true
+ exclusive: false
+ - run: make unit-tests
+
+workflows:
+ version: 2
+ ci:
+ jobs:
+ - builder
+ - build:
+ requires:
+ - builder
+ - check:
+ requires:
+ - builder
+ - cross:
+ requires:
+ - builder
+ - unit-tests:
+ requires:
+ - builder
--- /dev/null
+.git
+.dockerignore
+Dockerfile
+bin
+tags
--- /dev/null
+# Changelog
+
+## 0.8.0-dev.2 (2016-05-07)
+- Fix an issue which may arise during sandbox cleanup (https://github.com/docker/libnetwork/pull/1157)
+- Fix cleanup logic in case of ipv6 allocation failure
+- Don't add /etc/hosts record if container's ip is empty (--net=none)
+- Fix default gw logic for internal networks
+- Error when updating IPv6 gateway (https://github.com/docker/libnetwork/issues/1142)
+- Fixes https://github.com/docker/libnetwork/issues/1113
+- Fixes https://github.com/docker/libnetwork/issues/1069
+- Fxies https://github.com/docker/libnetwork/issues/1117
+- Increase the concurrent query rate-limit count
+- Changes to build libnetwork in Solaris
+
+## 0.8.0-dev.1 (2016-04-16)
+- Fixes docker/docker#16964
+- Added maximum egress bandwidth qos for Windows
+
+## 0.7.0-rc.6 (2016-04-10)
+- Flush cached resolver socket on default gateway change
+
+## 0.7.0-rc.5 (2016-04-08)
+- Persist ipam driver options
+- Fixes https://github.com/docker/libnetwork/issues/1087
+- Use go vet from go tool
+- Godep update to pick up latest docker/docker packages
+- Validate remote driver response using docker plugins package method.
+
+## 0.7.0-rc.4 (2016-04-06)
+- Fix the handling for default gateway Endpoint join/leave.
+
+## 0.7.0-rc.3 (2016-04-05)
+- Revert fix for default gateway endpoint join/leave. Needs to be reworked.
+- Persist the network internal mode for bridge networks
+
+## 0.7.0-rc.2 (2016-04-05)
+- Fixes https://github.com/docker/libnetwork/issues/1070
+- Move IPAM resource initialization out of init()
+- Initialize overlay driver before network delete
+- Fix the handling for default gateway Endpoint join/lean
+
+## 0.7.0-rc.1 (2016-03-30)
+- Fixes https://github.com/docker/libnetwork/issues/985
+- Fixes https://github.com/docker/libnetwork/issues/945
+- Log time taken to set sandbox key
+- Limit number of concurrent DNS queries
+
+## 0.7.0-dev.10 (2016-03-21)
+- Add IPv6 service discovery (AAAA records) in embedded DNS server
+- Honor enableIPv6 flag in network create for the IP allocation
+- Avoid V6 queries in docker domain going to external nameservers
+
+## 0.7.0-dev.9 (2016-03-18)
+- Support labels on networks
+
+## 0.7.0-dev.8 (2016-03-16)
+- Windows driver to respect user set MAC address.
+- Fix possible nil pointer reference in ServeDNS() with concurrent go routines.
+- Fix netns path setting from hook (for containerd integration)
+- Clear cached udp connections on resolver Stop()
+- Avoid network/endpoint count inconsistences and remove stale networks after ungraceful shutdown
+- Fix possible endpoint count inconsistency after ungraceful shutdown
+- Reject a null v4 IPAM slice in exp vlan drivers
+- Removed experimental drivers modprobe check
+
+## 0.7.0-dev.7 (2016-03-11)
+- Bumped up the minimum kernel version for ipvlan to 4.2
+- Removed modprobe from macvlan/ipvlan drivers to resolve docker IT failures
+- Close dbus connection if firewalld is not started
+
+## 0.7.0-dev.6 (2016-03-10)
+- Experimental support for macvlan and ipvlan drivers
+
+## 0.7.0-dev.5 (2016-03-08)
+- Fixes https://github.com/docker/docker/issues/20847
+- Fixes https://github.com/docker/docker/issues/20997
+- Fixes issues unveiled by docker integ test over 0.7.0-dev.4
+
+## 0.7.0-dev.4 (2016-03-07)
+- Changed ownership of exposed ports and port-mapping options from Endpoint to Sandbox
+- Implement DNS RR in the Docker embedded DNS server
+- Fixes https://github.com/docker/libnetwork/issues/984 (multi container overlay veth leak)
+- Libnetwork to program container's interface MAC address
+- Fixed bug in iptables.Exists() logic
+- Fixes https://github.com/docker/docker/issues/20694
+- Source external DNS queries from container namespace
+- Added inbuilt nil IPAM driver
+- Windows drivers integration fixes
+- Extract hostname from (hostname.domainname). Related to https://github.com/docker/docker/issues/14282
+- Fixed race in sandbox statistics read
+- Fixes https://github.com/docker/libnetwork/issues/892 (docker start fails when ipv6.disable=1)
+- Fixed error message on bridge network creation conflict
+
+## 0.7.0-dev.3 (2016-02-17)
+- Fixes https://github.com/docker/docker/issues/20350
+- Fixes https://github.com/docker/docker/issues/20145
+- Initial Windows HNS integration
+- Allow passing global datastore config to libnetwork after boot
+- Set Recursion Available bit in DNS query responses
+- Make sure iptables chains are recreated on firewalld reload
+
+## 0.7.0-dev.2 (2016-02-11)
+- Fixes https://github.com/docker/docker/issues/20140
+
+## 0.7.0-dev.1 (2016-02-10)
+- Expose EnableIPV6 option
+- discoverapi refactoring
+- Fixed a few typos & docs update
+
+## 0.6.1-rc2 (2016-02-09)
+- Fixes https://github.com/docker/docker/issues/20132
+- Fixes https://github.com/docker/docker/issues/20140
+- Fixes https://github.com/docker/docker/issues/20019
+
+## 0.6.1-rc1 (2016-02-05)
+- Fixes https://github.com/docker/docker/issues/20026
+
+## 0.6.0-rc7 (2016-02-01)
+- Allow inter-network connections via exposed ports
+
+## 0.6.0-rc6 (2016-01-30)
+- Properly fixes https://github.com/docker/docker/issues/18814
+
+## 0.6.0-rc5 (2016-01-26)
+- Cleanup stale overlay sandboxes
+
+## 0.6.0-rc4 (2016-01-25)
+- Add Endpoints() API to Sandbox interface
+- Fixed a race-condition in default gateway network creation
+
+## 0.6.0-rc3 (2016-01-25)
+- Fixes docker/docker#19576
+- Fixed embedded DNS to listen in TCP as well
+- Fixed a race-condition in IPAM to choose non-overlapping subnet for concurrent requests
+
+## 0.6.0-rc2 (2016-01-21)
+- Fixes docker/docker#19376
+- Fixes docker/docker#15819
+- Fixes libnetwork/#885, Not filter v6 DNS servers from resolv.conf
+- Fixes docker/docker #19448, also handles the . in service and network names correctly.
+
+## 0.6.0-rc1 (2016-01-14)
+- Fixes docker/docker#19404
+- Fixes the ungraceful daemon restart issue in systemd with remote network plugin
+ (https://github.com/docker/libnetwork/issues/813)
+
+## 0.5.6 (2016-01-14)
+- Setup embedded DNS server correctly on container restart. Fixes docker/docker#19354
+
+## 0.5.5 (2016-01-14)
+- Allow network-scoped alias to be resolved for anonymous endpoint
+- Self repair corrupted IP database that could happen in 1.9.0 & 1.9.1
+- Skip IPTables cleanup if --iptables=false is set. Fixes docker/docker#19063
+
+## 0.5.4 (2016-01-12)
+- Removed the isNodeAlive protection when user forces an endpoint delete
+
+## 0.5.3 (2016-01-12)
+- Bridge driver supporting internal network option
+- Backend implementation to support "force" option to network disconnect
+- Fixing a regex in etchosts package to fix docker/docker#19080
+
+## 0.5.2 (2016-01-08)
+- Embedded DNS replacing /etc/hosts based Service Discovery
+- Container local alias and Network-scoped alias support
+- Backend support for internal network mode
+- Support for IPAM driver options
+- Fixes overlay veth cleanup issue : docker/docker#18814
+- fixes docker/docker#19139
+- disable IPv6 Duplicate Address Detection
+
+## 0.5.1 (2015-12-07)
+- Allowing user to assign IP Address for containers
+- Fixes docker/docker#18214
+- Fixes docker/docker#18380
+
+## 0.5.0 (2015-10-30)
+
+- Docker multi-host networking exiting experimental channel
+- Introduced IP Address Management and IPAM drivers
+- DEPRECATE service discovery from default bridge network
+- Introduced new network UX
+- Support for multiple networks in bridge driver
+- Local persistence with boltdb
+
+## 0.4.0 (2015-07-24)
+
+- Introduce experimental version of Overlay driver
+- Introduce experimental version of network plugins
+- Introduce experimental version of network & service UX
+- Introduced experimental /etc/hosts based service discovery
+- Integrated with libkv
+- Improving test coverage
+- Fixed a bunch of issues with osl namespace mgmt
+
+## 0.3.0 (2015-05-27)
+
+- Introduce CNM (Container Networking Model)
+- Replace docker networking with CNM & Bridge driver
--- /dev/null
+FROM golang:1.12.6 as dev
+RUN apt-get update && apt-get -y install iptables \
+ protobuf-compiler
+
+RUN go get -d github.com/gogo/protobuf/protoc-gen-gogo && \
+ cd /go/src/github.com/gogo/protobuf/protoc-gen-gogo && \
+ git reset --hard 30cf7ac33676b5786e78c746683f0d4cd64fa75b && \
+ go install
+
+RUN go get golang.org/x/lint/golint \
+ golang.org/x/tools/cmd/cover \
+ github.com/mattn/goveralls \
+ github.com/gordonklaus/ineffassign \
+ github.com/client9/misspell/cmd/misspell
+
+WORKDIR /go/src/github.com/docker/libnetwork
+
+FROM dev
+
+COPY . .
--- /dev/null
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
--- /dev/null
+# Libnetwork maintainers file
+#
+# This file describes who runs the docker/libnetwork project and how.
+# This is a living document - if you see something out of date or missing, speak up!
+#
+# It is structured to be consumable by both humans and programs.
+# To extract its contents programmatically, use any TOML-compliant parser.
+#
+# This file is compiled into the MAINTAINERS file in docker/opensource.
+#
+[Org]
+ [Org."Core maintainers"]
+ people = [
+ "abhi",
+ "aboch",
+ "ctelfer",
+ "chenchun",
+ "fcrisciani",
+ "mavenugo",
+ ]
+
+[people]
+
+# A reference list of all people associated with the project.
+# All other sections should refer to people by their canonical key
+# in the people section.
+
+ # ADD YOURSELF HERE IN ALPHABETICAL ORDER
+
+ [people.abhi]
+ Name = "Abhinandan Prativadi Bayankaram"
+ Email = "abhi@docker.com"
+ GitHub = "abhi"
+
+ [people.aboch]
+ Name = "Alessandro Boch"
+ Email = "aboch@docker.com"
+ GitHub = "aboch"
+
+ [people.ctelfer]
+ Name = "Christopher Telfer"
+ Email = "chris.telfer@docker.com"
+ GitHub = "ctelfer"
+
+ [people.chenchun]
+ Name = "Chun Chen"
+ Email = "ramichen@tencent.com"
+ GitHub = "chenchun"
+
+ [people.fcrisciani]
+ Name = "Flavio Crisciani"
+ Email = "flavio.crisciani@docker.com"
+ GitHub = "fcrisciani"
+
+ [people.mavenugo]
+ Name = "Madhu Venugopal"
+ Email = "madhu@docker.com"
+ GitHub = "mavenugo"
+
--- /dev/null
+.PHONY: all all-local build build-local clean cross cross-local vet lint misspell check check-local check-code check-format unit-tests protobuf protobuf-local check-protobuf
+SHELL=/bin/bash
+
+dockerbuildargs ?= --target dev - < Dockerfile
+dockerargs ?= --privileged -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork
+build_image=libnetworkbuild
+container_env = -e "INSIDECONTAINER=-incontainer=true"
+docker = docker run --rm -it --init ${dockerargs} $$EXTRA_ARGS ${container_env} ${build_image}
+
+CROSS_PLATFORMS = linux/amd64 linux/386 linux/arm windows/amd64
+PACKAGES=$(shell go list ./... | grep -v /vendor/)
+PROTOC_FLAGS=-I=. -I=/go/src -I=/go/src/github.com/gogo/protobuf -I=/go/src/github.com/gogo/protobuf/protobuf
+
+export PATH := $(CURDIR)/bin:$(PATH)
+
+
+# Several targets in this Makefile expect to run within the
+# libnetworkbuild container. In general, a target named '<target>-local'
+# relies on utilities inside the build container. Usually there is also
+# a wrapper called '<target>' which starts a container and runs
+# 'make <target>-local' inside it.
+
+###########################################################################
+# Top level targets
+###########################################################################
+
+all: build check clean
+
+all-local: build-local check-local clean
+
+
+###########################################################################
+# Build targets
+###########################################################################
+
+# builder builds the libnetworkbuild container. All wrapper targets
+# must depend on this to ensure that the container exists.
+builder:
+ docker build -t ${build_image} ${dockerbuildargs}
+
+build: builder
+ @echo "🐳 $@"
+ @${docker} make build-local
+
+build-local:
+ @echo "🐳 $@"
+ @mkdir -p "bin"
+ go build -tags experimental -o "bin/dnet" ./cmd/dnet
+ go build -o "bin/docker-proxy" ./cmd/proxy
+ CGO_ENABLED=0 go build -o "bin/diagnosticClient" ./cmd/diagnostic
+ CGO_ENABLED=0 go build -o "bin/testMain" ./cmd/networkdb-test/testMain.go
+
+build-images:
+ @echo "🐳 $@"
+ cp cmd/diagnostic/daemon.json ./bin
+ docker build -f cmd/diagnostic/Dockerfile.client -t dockereng/network-diagnostic:onlyclient bin/
+ docker build -f cmd/diagnostic/Dockerfile.dind -t dockereng/network-diagnostic:17.12-dind bin/
+ docker build -f cmd/networkdb-test/Dockerfile -t dockereng/e2e-networkdb:master bin/
+ docker build -t dockereng/network-diagnostic:support.sh support/
+
+push-images: build-images
+ @echo "🐳 $@"
+ docker push dockereng/network-diagnostic:onlyclient
+ docker push dockereng/network-diagnostic:17.12-dind
+ docker push dockereng/e2e-networkdb:master
+ docker push dockereng/network-diagnostic:support.sh
+
+clean:
+ @echo "🐳 $@"
+ @if [ -d bin ]; then \
+ echo "Removing binaries"; \
+ rm -rf bin; \
+ fi
+
+cross: builder
+ @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:
+ @echo "🐳 $@"
+ go build -o "bin/dnet-$$GOOS-$$GOARCH" ./cmd/dnet
+ go build -o "bin/docker-proxy-$$GOOS-$$GOARCH" ./cmd/proxy
+
+# Rebuild protocol buffers.
+# These may need to be rebuilt after vendoring updates, so .proto files are declared .PHONY so they are always rebuilt.
+PROTO_FILES=$(shell find . -path ./vendor -prune -o -name \*.proto -print)
+PB_FILES=$(PROTO_FILES:.proto=.pb.go)
+
+# Pattern rule for protoc. If PROTOC_CHECK is defined, it checks
+# whether the generated files are up to date and fails if they are not
+%.pb.go: %.proto
+ @if [ ${PROTOC_CHECK} ]; then \
+ protoc ${PROTOC_FLAGS} --gogo_out=/tmp $< ; \
+ diff -q $@ /tmp/$@ >/dev/null || (echo "👹 $@ is out of date; please run 'make protobuf' and check in updates" && exit 1) ; \
+ else \
+ protoc ${PROTOC_FLAGS} --gogo_out=./ $< ; \
+ fi
+
+.PHONY: $(PROTO_FILES)
+protobuf: builder
+ @${docker} make protobuf-local
+protobuf-local: $(PB_FILES)
+
+
+###########################################################################
+# Test targets
+###########################################################################
+
+check: builder
+ @${docker} make check-local
+
+check-local: check-code check-format
+
+check-code: check-protobuf lint vet ineffassign
+
+check-format: fmt misspell
+
+unit-tests: builder
+ ${docker} make unit-tests-local
+
+unit-tests-local:
+ @echo "🐳 Running tests... "
+ @echo "mode: count" > coverage.coverprofile
+ @go build -o "bin/docker-proxy" ./cmd/proxy
+ @for dir in $$( find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -not -path './vendor/*' -type d); do \
+ if ls $$dir/*.go &> /dev/null; then \
+ pushd . &> /dev/null ; \
+ cd $$dir ; \
+ go test ${INSIDECONTAINER} -test.parallel 5 -test.v -covermode=count -coverprofile=./profile.tmp ; \
+ ret=$$? ;\
+ if [ $$ret -ne 0 ]; then exit $$ret; fi ;\
+ popd &> /dev/null; \
+ if [ -f $$dir/profile.tmp ]; then \
+ cat $$dir/profile.tmp | tail -n +2 >> coverage.coverprofile ; \
+ rm $$dir/profile.tmp ; \
+ fi ; \
+ fi ; \
+ done
+ @echo "Done running tests"
+
+# Depends on binaries because vet will silently fail if it can not load compiled imports
+vet: ## run go vet
+ @echo "🐳 $@"
+ @test -z "$$(go vet ${PACKAGES} 2>&1 | grep -v 'constant [0-9]* not a string in call to Errorf' | egrep -v '(timestamp_test.go|duration_test.go|exit status 1)' | tee /dev/stderr)"
+
+misspell:
+ @echo "🐳 $@"
+ @test -z "$$(find . -type f | grep -v vendor/ | grep "\.go\|\.md" | xargs misspell -error | tee /dev/stderr)"
+
+fmt: ## run go fmt
+ @echo "🐳 $@"
+ @test -z "$$(gofmt -s -l . | grep -v vendor/ | grep -v ".pb.go$$" | tee /dev/stderr)" || \
+ (echo "👹 please format Go code with 'gofmt -s -w'" && false)
+
+lint: ## run go lint
+ @echo "🐳 $@"
+ @test -z "$$(golint ./... | grep -v vendor/ | grep -v ".pb.go:" | grep -v ".mock.go" | tee /dev/stderr)"
+
+ineffassign: ## run ineffassign
+ @echo "🐳 $@"
+ @test -z "$$(ineffassign . | grep -v vendor/ | grep -v ".pb.go:" | grep -v ".mock.go" | tee /dev/stderr)"
+
+# check-protobuf rebuilds .pb.go files and fails if they have changed
+check-protobuf: PROTOC_CHECK=1
+check-protobuf: $(PB_FILES)
+ @echo "🐳 $@"
+
+
+###########################################################################
+# Utility targets
+###########################################################################
+
+shell: builder
+ @${docker} ${SHELL}
--- /dev/null
+# libnetwork - networking for containers
+
+[](https://circleci.com/gh/docker/libnetwork/tree/master) [](https://coveralls.io/r/docker/libnetwork) [](https://godoc.org/github.com/docker/libnetwork) [](https://goreportcard.com/report/github.com/docker/libnetwork)
+
+Libnetwork provides a native Go implementation for connecting containers
+
+The goal of libnetwork is to deliver a robust Container Network Model that provides a consistent programming interface and the required network abstractions for applications.
+
+#### Design
+Please refer to the [design](docs/design.md) for more information.
+
+#### Using libnetwork
+
+There are many networking solutions available to suit a broad range of use-cases. libnetwork uses a driver / plugin model to support all of these solutions while abstracting the complexity of the driver implementations by exposing a simple and consistent Network Model to users.
+
+
+```go
+import (
+ "fmt"
+ "log"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork"
+ "github.com/docker/libnetwork/config"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/options"
+)
+
+func main() {
+ if reexec.Init() {
+ return
+ }
+
+ // Select and configure the network driver
+ networkType := "bridge"
+
+ // Create a new controller instance
+ driverOptions := options.Generic{}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = driverOptions
+ controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption))
+ if err != nil {
+ log.Fatalf("libnetwork.New: %s", err)
+ }
+
+ // Create a network for containers to join.
+ // NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can use.
+ network, err := controller.NewNetwork(networkType, "network1", "")
+ if err != nil {
+ log.Fatalf("controller.NewNetwork: %s", err)
+ }
+
+ // For each new container: allocate IP and interfaces. The returned network
+ // settings will be used for container infos (inspect and such), as well as
+ // iptables rules for port publishing. This info is contained or accessible
+ // from the returned endpoint.
+ ep, err := network.CreateEndpoint("Endpoint1")
+ if err != nil {
+ log.Fatalf("network.CreateEndpoint: %s", err)
+ }
+
+ // Create the sandbox for the container.
+ // NewSandbox accepts Variadic optional arguments which libnetwork can use.
+ sbx, err := controller.NewSandbox("container1",
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"))
+ if err != nil {
+ log.Fatalf("controller.NewSandbox: %s", err)
+ }
+
+ // A sandbox can join the endpoint via the join api.
+ err = ep.Join(sbx)
+ if err != nil {
+ log.Fatalf("ep.Join: %s", err)
+ }
+
+ // libnetwork client can check the endpoint's operational data via the Info() API
+ epInfo, err := ep.DriverInfo()
+ if err != nil {
+ log.Fatalf("ep.DriverInfo: %s", err)
+ }
+
+ macAddress, ok := epInfo[netlabel.MacAddress]
+ if !ok {
+ log.Fatalf("failed to get mac address from endpoint info")
+ }
+
+ fmt.Printf("Joined endpoint %s (%s) to sandbox %s (%s)\n", ep.Name(), macAddress, sbx.ContainerID(), sbx.Key())
+}
+```
+
+## Future
+Please refer to [roadmap](ROADMAP.md) for more information.
+
+## Contributing
+
+Want to hack on libnetwork? [Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md) apply.
+
+## Copyright and license
+Code and documentation copyright 2015 Docker, inc. Code released under the Apache 2.0 license. Docs released under Creative commons.
--- /dev/null
+# Roadmap
+
+This document defines the high-level goals of the libnetwork project. See [Project Planning](#project-planning) for information on Releases.
+
+## Long-term Goal
+
+libnetwork project will follow Docker and Linux philosophy of delivering small, highly modular and composable tools that work well independently.
+libnetwork aims to satisfy that composable need for Networking in Containers.
+
+## Short-term Goals
+
+- Modularize the networking logic in Docker Engine and libcontainer in to a single, reusable library
+- Replace the networking subsystem of Docker Engine, with libnetwork
+- Define a flexible model that allows local and remote drivers to provide networking to containers
+- Provide a stand-alone tool "dnet" for managing and testing libnetwork
+
+Project Planning
+================
+
+[Project Pages](https://github.com/docker/libnetwork/wiki) define the goals for each Milestone and identify the release-relationship to the Docker Platform.
--- /dev/null
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
+VAGRANTFILE_API_VERSION = "2"
+
+$consul=<<SCRIPT
+apt-get update
+apt-get -y install wget
+wget -qO- https://experimental.docker.com/ | sh
+gpasswd -a vagrant docker
+service docker restart
+docker run -d -p 8500:8500 -p 8300-8302:8300-8302/tcp -p 8300-8302:8300-8302/udp -h consul progrium/consul -server -bootstrap
+SCRIPT
+
+$bootstrap=<<SCRIPT
+apt-get update
+apt-get -y install wget curl
+apt-get -y install bridge-utils
+wget -qO- https://experimental.docker.com/ | sh
+gpasswd -a vagrant docker
+echo DOCKER_OPTS=\\"--cluster-store=consul://192.168.33.10:8500 --cluster-advertise=${1}:0\\" >> /etc/default/docker
+cp /vagrant/docs/vagrant-systemd/docker.service /etc/systemd/system/
+systemctl daemon-reload
+systemctl restart docker.service
+SCRIPT
+
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+ config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'"
+ num_nodes = 2
+ base_ip = "192.168.33."
+ net_ips = num_nodes.times.collect { |n| base_ip + "#{n+11}" }
+
+ config.vm.define "consul-server" do |consul|
+ consul.vm.box = "ubuntu/trusty64"
+ consul.vm.hostname = "consul-server"
+ consul.vm.network :private_network, ip: "192.168.33.10"
+ consul.vm.provider "virtualbox" do |vb|
+ vb.customize ["modifyvm", :id, "--memory", "512"]
+ end
+ consul.vm.provision :shell, inline: $consul
+ end
+
+ num_nodes.times do |n|
+ config.vm.define "net-#{n+1}" do |net|
+ net.vm.box = "ubuntu/xenial64"
+ net_ip = net_ips[n]
+ net_index = n+1
+ net.vm.hostname = "net-#{net_index}"
+ net.vm.provider "virtualbox" do |vb|
+ vb.customize ["modifyvm", :id, "--memory", "1024"]
+ end
+ net.vm.network :private_network, ip: "#{net_ip}"
+ net.vm.provision :shell, inline: $bootstrap, :args => "#{net_ip}"
+ end
+ end
+
+end
--- /dev/null
+package libnetwork
+
+//go:generate protoc -I.:Godeps/_workspace/src/github.com/gogo/protobuf --gogo_out=import_path=github.com/docker/libnetwork,Mgogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto:. agent.proto
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "sort"
+ "sync"
+
+ "github.com/docker/go-events"
+ "github.com/docker/libnetwork/cluster"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/networkdb"
+ "github.com/docker/libnetwork/types"
+ "github.com/gogo/protobuf/proto"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ subsysGossip = "networking:gossip"
+ subsysIPSec = "networking:ipsec"
+ keyringSize = 3
+)
+
+// ByTime implements sort.Interface for []*types.EncryptionKey based on
+// the LamportTime field.
+type ByTime []*types.EncryptionKey
+
+func (b ByTime) Len() int { return len(b) }
+func (b ByTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
+func (b ByTime) Less(i, j int) bool { return b[i].LamportTime < b[j].LamportTime }
+
+type agent struct {
+ networkDB *networkdb.NetworkDB
+ bindAddr string
+ advertiseAddr string
+ dataPathAddr string
+ coreCancelFuncs []func()
+ driverCancelFuncs map[string][]func()
+ sync.Mutex
+}
+
+func (a *agent) dataPathAddress() string {
+ a.Lock()
+ defer a.Unlock()
+ if a.dataPathAddr != "" {
+ return a.dataPathAddr
+ }
+ return a.advertiseAddr
+}
+
+const libnetworkEPTable = "endpoint_table"
+
+func getBindAddr(ifaceName string) (string, error) {
+ iface, err := net.InterfaceByName(ifaceName)
+ if err != nil {
+ return "", fmt.Errorf("failed to find interface %s: %v", ifaceName, err)
+ }
+
+ addrs, err := iface.Addrs()
+ if err != nil {
+ return "", fmt.Errorf("failed to get interface addresses: %v", err)
+ }
+
+ for _, a := range addrs {
+ addr, ok := a.(*net.IPNet)
+ if !ok {
+ continue
+ }
+ addrIP := addr.IP
+
+ if addrIP.IsLinkLocalUnicast() {
+ continue
+ }
+
+ return addrIP.String(), nil
+ }
+
+ return "", fmt.Errorf("failed to get bind address")
+}
+
+func resolveAddr(addrOrInterface string) (string, error) {
+ // Try and see if this is a valid IP address
+ if net.ParseIP(addrOrInterface) != nil {
+ return addrOrInterface, nil
+ }
+
+ addr, err := net.ResolveIPAddr("ip", addrOrInterface)
+ if err != nil {
+ // If not a valid IP address, it should be a valid interface
+ return getBindAddr(addrOrInterface)
+ }
+ return addr.String(), nil
+}
+
+func (c *controller) handleKeyChange(keys []*types.EncryptionKey) error {
+ drvEnc := discoverapi.DriverEncryptionUpdate{}
+
+ a := c.getAgent()
+ if a == nil {
+ logrus.Debug("Skipping key change as agent is nil")
+ return nil
+ }
+
+ // Find the deleted key. If the deleted key was the primary key,
+ // a new primary key should be set before removing if from keyring.
+ c.Lock()
+ added := []byte{}
+ deleted := []byte{}
+ j := len(c.keys)
+ for i := 0; i < j; {
+ same := false
+ for _, key := range keys {
+ if same = key.LamportTime == c.keys[i].LamportTime; same {
+ break
+ }
+ }
+ if !same {
+ cKey := c.keys[i]
+ if cKey.Subsystem == subsysGossip {
+ deleted = cKey.Key
+ }
+
+ if cKey.Subsystem == subsysIPSec {
+ drvEnc.Prune = cKey.Key
+ drvEnc.PruneTag = cKey.LamportTime
+ }
+ c.keys[i], c.keys[j-1] = c.keys[j-1], c.keys[i]
+ c.keys[j-1] = nil
+ j--
+ }
+ i++
+ }
+ c.keys = c.keys[:j]
+
+ // Find the new key and add it to the key ring
+ for _, key := range keys {
+ same := false
+ for _, cKey := range c.keys {
+ if same = cKey.LamportTime == key.LamportTime; same {
+ break
+ }
+ }
+ if !same {
+ c.keys = append(c.keys, key)
+ if key.Subsystem == subsysGossip {
+ added = key.Key
+ }
+
+ if key.Subsystem == subsysIPSec {
+ drvEnc.Key = key.Key
+ drvEnc.Tag = key.LamportTime
+ }
+ }
+ }
+ c.Unlock()
+
+ if len(added) > 0 {
+ a.networkDB.SetKey(added)
+ }
+
+ key, _, err := c.getPrimaryKeyTag(subsysGossip)
+ if err != nil {
+ return err
+ }
+ a.networkDB.SetPrimaryKey(key)
+
+ key, tag, err := c.getPrimaryKeyTag(subsysIPSec)
+ if err != nil {
+ return err
+ }
+ drvEnc.Primary = key
+ drvEnc.PrimaryTag = tag
+
+ if len(deleted) > 0 {
+ a.networkDB.RemoveKey(deleted)
+ }
+
+ c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
+ err := driver.DiscoverNew(discoverapi.EncryptionKeysUpdate, drvEnc)
+ if err != nil {
+ logrus.Warnf("Failed to update datapath keys in driver %s: %v", name, err)
+ }
+ return false
+ })
+
+ return nil
+}
+
+func (c *controller) agentSetup(clusterProvider cluster.Provider) error {
+ agent := c.getAgent()
+
+ // If the agent is already present there is no need to try to initialize it again
+ if agent != nil {
+ return nil
+ }
+
+ bindAddr := clusterProvider.GetLocalAddress()
+ advAddr := clusterProvider.GetAdvertiseAddress()
+ dataAddr := clusterProvider.GetDataPathAddress()
+ remoteList := clusterProvider.GetRemoteAddressList()
+ remoteAddrList := make([]string, 0, len(remoteList))
+ for _, remote := range remoteList {
+ addr, _, _ := net.SplitHostPort(remote)
+ remoteAddrList = append(remoteAddrList, addr)
+ }
+
+ listen := clusterProvider.GetListenAddress()
+ listenAddr, _, _ := net.SplitHostPort(listen)
+
+ logrus.Infof("Initializing Libnetwork Agent Listen-Addr=%s Local-addr=%s Adv-addr=%s Data-addr=%s Remote-addr-list=%v MTU=%d",
+ listenAddr, bindAddr, advAddr, dataAddr, remoteAddrList, c.Config().Daemon.NetworkControlPlaneMTU)
+ if advAddr != "" && agent == nil {
+ if err := c.agentInit(listenAddr, bindAddr, advAddr, dataAddr); err != nil {
+ logrus.Errorf("error in agentInit: %v", err)
+ return err
+ }
+ c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
+ if capability.ConnectivityScope == datastore.GlobalScope {
+ c.agentDriverNotify(driver)
+ }
+ return false
+ })
+ }
+
+ if len(remoteAddrList) > 0 {
+ if err := c.agentJoin(remoteAddrList); err != nil {
+ logrus.Errorf("Error in joining gossip cluster : %v(join will be retried in background)", err)
+ }
+ }
+
+ return nil
+}
+
+// For a given subsystem getKeys sorts the keys by lamport time and returns
+// slice of keys and lamport time which can used as a unique tag for the keys
+func (c *controller) getKeys(subsys string) ([][]byte, []uint64) {
+ c.Lock()
+ defer c.Unlock()
+
+ sort.Sort(ByTime(c.keys))
+
+ keys := [][]byte{}
+ tags := []uint64{}
+ for _, key := range c.keys {
+ if key.Subsystem == subsys {
+ keys = append(keys, key.Key)
+ tags = append(tags, key.LamportTime)
+ }
+ }
+
+ keys[0], keys[1] = keys[1], keys[0]
+ tags[0], tags[1] = tags[1], tags[0]
+ return keys, tags
+}
+
+// getPrimaryKeyTag returns the primary key for a given subsystem from the
+// list of sorted key and the associated tag
+func (c *controller) getPrimaryKeyTag(subsys string) ([]byte, uint64, error) {
+ c.Lock()
+ defer c.Unlock()
+ sort.Sort(ByTime(c.keys))
+ keys := []*types.EncryptionKey{}
+ for _, key := range c.keys {
+ if key.Subsystem == subsys {
+ keys = append(keys, key)
+ }
+ }
+ return keys[1].Key, keys[1].LamportTime, nil
+}
+
+func (c *controller) agentInit(listenAddr, bindAddrOrInterface, advertiseAddr, dataPathAddr string) error {
+ bindAddr, err := resolveAddr(bindAddrOrInterface)
+ if err != nil {
+ return err
+ }
+
+ keys, _ := c.getKeys(subsysGossip)
+
+ netDBConf := networkdb.DefaultConfig()
+ netDBConf.BindAddr = listenAddr
+ netDBConf.AdvertiseAddr = advertiseAddr
+ netDBConf.Keys = keys
+ if c.Config().Daemon.NetworkControlPlaneMTU != 0 {
+ // Consider the MTU remove the IP hdr (IPv4 or IPv6) and the TCP/UDP hdr.
+ // To be on the safe side let's cut 100 bytes
+ netDBConf.PacketBufferSize = (c.Config().Daemon.NetworkControlPlaneMTU - 100)
+ logrus.Debugf("Control plane MTU: %d will initialize NetworkDB with: %d",
+ c.Config().Daemon.NetworkControlPlaneMTU, netDBConf.PacketBufferSize)
+ }
+ nDB, err := networkdb.New(netDBConf)
+ if err != nil {
+ return err
+ }
+
+ // Register the diagnostic handlers
+ c.DiagnosticServer.RegisterHandler(nDB, networkdb.NetDbPaths2Func)
+
+ var cancelList []func()
+ ch, cancel := nDB.Watch(libnetworkEPTable, "", "")
+ cancelList = append(cancelList, cancel)
+ nodeCh, cancel := nDB.Watch(networkdb.NodeTable, "", "")
+ cancelList = append(cancelList, cancel)
+
+ c.Lock()
+ c.agent = &agent{
+ networkDB: nDB,
+ bindAddr: bindAddr,
+ advertiseAddr: advertiseAddr,
+ dataPathAddr: dataPathAddr,
+ coreCancelFuncs: cancelList,
+ driverCancelFuncs: make(map[string][]func()),
+ }
+ c.Unlock()
+
+ go c.handleTableEvents(ch, c.handleEpTableEvent)
+ go c.handleTableEvents(nodeCh, c.handleNodeTableEvent)
+
+ drvEnc := discoverapi.DriverEncryptionConfig{}
+ keys, tags := c.getKeys(subsysIPSec)
+ drvEnc.Keys = keys
+ drvEnc.Tags = tags
+
+ c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
+ err := driver.DiscoverNew(discoverapi.EncryptionKeysConfig, drvEnc)
+ if err != nil {
+ logrus.Warnf("Failed to set datapath keys in driver %s: %v", name, err)
+ }
+ return false
+ })
+
+ c.WalkNetworks(joinCluster)
+
+ return nil
+}
+
+func (c *controller) agentJoin(remoteAddrList []string) error {
+ agent := c.getAgent()
+ if agent == nil {
+ return nil
+ }
+ return agent.networkDB.Join(remoteAddrList)
+}
+
+func (c *controller) agentDriverNotify(d driverapi.Driver) {
+ agent := c.getAgent()
+ if agent == nil {
+ return
+ }
+
+ if err := d.DiscoverNew(discoverapi.NodeDiscovery, discoverapi.NodeDiscoveryData{
+ Address: agent.dataPathAddress(),
+ BindAddress: agent.bindAddr,
+ Self: true,
+ }); err != nil {
+ logrus.Warnf("Failed the node discovery in driver: %v", err)
+ }
+
+ drvEnc := discoverapi.DriverEncryptionConfig{}
+ keys, tags := c.getKeys(subsysIPSec)
+ drvEnc.Keys = keys
+ drvEnc.Tags = tags
+
+ if err := d.DiscoverNew(discoverapi.EncryptionKeysConfig, drvEnc); err != nil {
+ logrus.Warnf("Failed to set datapath keys in driver: %v", err)
+ }
+}
+
+func (c *controller) agentClose() {
+ // Acquire current agent instance and reset its pointer
+ // then run closing functions
+ c.Lock()
+ agent := c.agent
+ c.agent = nil
+ c.Unlock()
+
+ // when the agent is closed the cluster provider should be cleaned up
+ c.SetClusterProvider(nil)
+
+ if agent == nil {
+ return
+ }
+
+ var cancelList []func()
+
+ agent.Lock()
+ for _, cancelFuncs := range agent.driverCancelFuncs {
+ cancelList = append(cancelList, cancelFuncs...)
+ }
+
+ // Add also the cancel functions for the network db
+ cancelList = append(cancelList, agent.coreCancelFuncs...)
+ agent.Unlock()
+
+ for _, cancel := range cancelList {
+ cancel()
+ }
+
+ agent.networkDB.Close()
+}
+
+// Task has the backend container details
+type Task struct {
+ Name string
+ EndpointID string
+ EndpointIP string
+ Info map[string]string
+}
+
+// ServiceInfo has service specific details along with the list of backend tasks
+type ServiceInfo struct {
+ VIP string
+ LocalLBIndex int
+ Tasks []Task
+ Ports []string
+}
+
+type epRecord struct {
+ ep EndpointRecord
+ info map[string]string
+ lbIndex int
+}
+
+func (n *network) Services() map[string]ServiceInfo {
+ eps := make(map[string]epRecord)
+
+ if !n.isClusterEligible() {
+ return nil
+ }
+ agent := n.getController().getAgent()
+ if agent == nil {
+ return nil
+ }
+
+ // Walk through libnetworkEPTable and fetch the driver agnostic endpoint info
+ entries := agent.networkDB.GetTableByNetwork(libnetworkEPTable, n.id)
+ for eid, value := range entries {
+ var epRec EndpointRecord
+ nid := n.ID()
+ if err := proto.Unmarshal(value.Value, &epRec); err != nil {
+ logrus.Errorf("Unmarshal of libnetworkEPTable failed for endpoint %s in network %s, %v", eid, nid, err)
+ continue
+ }
+ i := n.getController().getLBIndex(epRec.ServiceID, nid, epRec.IngressPorts)
+ eps[eid] = epRecord{
+ ep: epRec,
+ lbIndex: i,
+ }
+ }
+
+ // Walk through the driver's tables, have the driver decode the entries
+ // and return the tuple {ep ID, value}. value is a string that coveys
+ // relevant info about the endpoint.
+ d, err := n.driver(true)
+ if err != nil {
+ logrus.Errorf("Could not resolve driver for network %s/%s while fetching services: %v", n.networkType, n.ID(), err)
+ return nil
+ }
+ for _, table := range n.driverTables {
+ if table.objType != driverapi.EndpointObject {
+ continue
+ }
+ entries := agent.networkDB.GetTableByNetwork(table.name, n.id)
+ for key, value := range entries {
+ epID, info := d.DecodeTableEntry(table.name, key, value.Value)
+ if ep, ok := eps[epID]; !ok {
+ logrus.Errorf("Inconsistent driver and libnetwork state for endpoint %s", epID)
+ } else {
+ ep.info = info
+ eps[epID] = ep
+ }
+ }
+ }
+
+ // group the endpoints into a map keyed by the service name
+ sinfo := make(map[string]ServiceInfo)
+ for ep, epr := range eps {
+ var (
+ s ServiceInfo
+ ok bool
+ )
+ if s, ok = sinfo[epr.ep.ServiceName]; !ok {
+ s = ServiceInfo{
+ VIP: epr.ep.VirtualIP,
+ LocalLBIndex: epr.lbIndex,
+ }
+ }
+ ports := []string{}
+ if s.Ports == nil {
+ for _, port := range epr.ep.IngressPorts {
+ p := fmt.Sprintf("Target: %d, Publish: %d", port.TargetPort, port.PublishedPort)
+ ports = append(ports, p)
+ }
+ s.Ports = ports
+ }
+ s.Tasks = append(s.Tasks, Task{
+ Name: epr.ep.Name,
+ EndpointID: ep,
+ EndpointIP: epr.ep.EndpointIP,
+ Info: epr.info,
+ })
+ sinfo[epr.ep.ServiceName] = s
+ }
+ return sinfo
+}
+
+func (n *network) isClusterEligible() bool {
+ if n.scope != datastore.SwarmScope || !n.driverIsMultihost() {
+ return false
+ }
+ return n.getController().getAgent() != nil
+}
+
+func (n *network) joinCluster() error {
+ if !n.isClusterEligible() {
+ return nil
+ }
+
+ agent := n.getController().getAgent()
+ if agent == nil {
+ return nil
+ }
+
+ return agent.networkDB.JoinNetwork(n.ID())
+}
+
+func (n *network) leaveCluster() error {
+ if !n.isClusterEligible() {
+ return nil
+ }
+
+ agent := n.getController().getAgent()
+ if agent == nil {
+ return nil
+ }
+
+ return agent.networkDB.LeaveNetwork(n.ID())
+}
+
+func (ep *endpoint) addDriverInfoToCluster() error {
+ n := ep.getNetwork()
+ if !n.isClusterEligible() {
+ return nil
+ }
+ if ep.joinInfo == nil {
+ return nil
+ }
+
+ agent := n.getController().getAgent()
+ if agent == nil {
+ return nil
+ }
+
+ for _, te := range ep.joinInfo.driverTableEntries {
+ if err := agent.networkDB.CreateEntry(te.tableName, n.ID(), te.key, te.value); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (ep *endpoint) deleteDriverInfoFromCluster() error {
+ n := ep.getNetwork()
+ if !n.isClusterEligible() {
+ return nil
+ }
+ if ep.joinInfo == nil {
+ return nil
+ }
+
+ agent := n.getController().getAgent()
+ if agent == nil {
+ return nil
+ }
+
+ for _, te := range ep.joinInfo.driverTableEntries {
+ if err := agent.networkDB.DeleteEntry(te.tableName, n.ID(), te.key); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (ep *endpoint) addServiceInfoToCluster(sb *sandbox) error {
+ if ep.isAnonymous() && len(ep.myAliases) == 0 || ep.Iface().Address() == nil {
+ return nil
+ }
+
+ n := ep.getNetwork()
+ if !n.isClusterEligible() {
+ return nil
+ }
+
+ sb.Service.Lock()
+ defer sb.Service.Unlock()
+ logrus.Debugf("addServiceInfoToCluster START for %s %s", ep.svcName, ep.ID())
+
+ // Check that the endpoint is still present on the sandbox before adding it to the service discovery.
+ // This is to handle a race between the EnableService and the sbLeave
+ // It is possible that the EnableService starts, fetches the list of the endpoints and
+ // by the time the addServiceInfoToCluster is called the endpoint got removed from the sandbox
+ // The risk is that the deleteServiceInfoToCluster happens before the addServiceInfoToCluster.
+ // This check under the Service lock of the sandbox ensure the correct behavior.
+ // If the addServiceInfoToCluster arrives first may find or not the endpoint and will proceed or exit
+ // but in any case the deleteServiceInfoToCluster will follow doing the cleanup if needed.
+ // In case the deleteServiceInfoToCluster arrives first, this one is happening after the endpoint is
+ // removed from the list, in this situation the delete will bail out not finding any data to cleanup
+ // and the add will bail out not finding the endpoint on the sandbox.
+ if e := sb.getEndpoint(ep.ID()); e == nil {
+ logrus.Warnf("addServiceInfoToCluster suppressing service resolution ep is not anymore in the sandbox %s", ep.ID())
+ return nil
+ }
+
+ c := n.getController()
+ agent := c.getAgent()
+
+ name := ep.Name()
+ if ep.isAnonymous() {
+ name = ep.MyAliases()[0]
+ }
+
+ var ingressPorts []*PortConfig
+ if ep.svcID != "" {
+ // This is a task part of a service
+ // Gossip ingress ports only in ingress network.
+ if n.ingress {
+ ingressPorts = ep.ingressPorts
+ }
+ if err := c.addServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), name, ep.virtualIP, ingressPorts, ep.svcAliases, ep.myAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil {
+ return err
+ }
+ } else {
+ // This is a container simply attached to an attachable network
+ if err := c.addContainerNameResolution(n.ID(), ep.ID(), name, ep.myAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil {
+ return err
+ }
+ }
+
+ buf, err := proto.Marshal(&EndpointRecord{
+ Name: name,
+ ServiceName: ep.svcName,
+ ServiceID: ep.svcID,
+ VirtualIP: ep.virtualIP.String(),
+ IngressPorts: ingressPorts,
+ Aliases: ep.svcAliases,
+ TaskAliases: ep.myAliases,
+ EndpointIP: ep.Iface().Address().IP.String(),
+ ServiceDisabled: false,
+ })
+ if err != nil {
+ return err
+ }
+
+ if agent != nil {
+ if err := agent.networkDB.CreateEntry(libnetworkEPTable, n.ID(), ep.ID(), buf); err != nil {
+ logrus.Warnf("addServiceInfoToCluster NetworkDB CreateEntry failed for %s %s err:%s", ep.id, n.id, err)
+ return err
+ }
+ }
+
+ logrus.Debugf("addServiceInfoToCluster END for %s %s", ep.svcName, ep.ID())
+
+ return nil
+}
+
+func (ep *endpoint) deleteServiceInfoFromCluster(sb *sandbox, fullRemove bool, method string) error {
+ if ep.isAnonymous() && len(ep.myAliases) == 0 {
+ return nil
+ }
+
+ n := ep.getNetwork()
+ if !n.isClusterEligible() {
+ return nil
+ }
+
+ sb.Service.Lock()
+ defer sb.Service.Unlock()
+ logrus.Debugf("deleteServiceInfoFromCluster from %s START for %s %s", method, ep.svcName, ep.ID())
+
+ // Avoid a race w/ with a container that aborts preemptively. This would
+ // get caught in disableServceInNetworkDB, but we check here to make the
+ // nature of the condition more clear.
+ // See comment in addServiceInfoToCluster()
+ if e := sb.getEndpoint(ep.ID()); e == nil {
+ logrus.Warnf("deleteServiceInfoFromCluster suppressing service resolution ep is not anymore in the sandbox %s", ep.ID())
+ return nil
+ }
+
+ c := n.getController()
+ agent := c.getAgent()
+
+ name := ep.Name()
+ if ep.isAnonymous() {
+ name = ep.MyAliases()[0]
+ }
+
+ if agent != nil {
+ // First update the networkDB then locally
+ if fullRemove {
+ if err := agent.networkDB.DeleteEntry(libnetworkEPTable, n.ID(), ep.ID()); err != nil {
+ logrus.Warnf("deleteServiceInfoFromCluster NetworkDB DeleteEntry failed for %s %s err:%s", ep.id, n.id, err)
+ }
+ } else {
+ disableServiceInNetworkDB(agent, n, ep)
+ }
+ }
+
+ if ep.Iface().Address() != nil {
+ if ep.svcID != "" {
+ // This is a task part of a service
+ var ingressPorts []*PortConfig
+ if n.ingress {
+ ingressPorts = ep.ingressPorts
+ }
+ if err := c.rmServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), name, ep.virtualIP, ingressPorts, ep.svcAliases, ep.myAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster", true, fullRemove); err != nil {
+ return err
+ }
+ } else {
+ // This is a container simply attached to an attachable network
+ if err := c.delContainerNameResolution(n.ID(), ep.ID(), name, ep.myAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster"); err != nil {
+ return err
+ }
+ }
+ }
+
+ logrus.Debugf("deleteServiceInfoFromCluster from %s END for %s %s", method, ep.svcName, ep.ID())
+
+ return nil
+}
+
+func disableServiceInNetworkDB(a *agent, n *network, ep *endpoint) {
+ var epRec EndpointRecord
+
+ logrus.Debugf("disableServiceInNetworkDB for %s %s", ep.svcName, ep.ID())
+
+ // Update existing record to indicate that the service is disabled
+ inBuf, err := a.networkDB.GetEntry(libnetworkEPTable, n.ID(), ep.ID())
+ if err != nil {
+ logrus.Warnf("disableServiceInNetworkDB GetEntry failed for %s %s err:%s", ep.id, n.id, err)
+ return
+ }
+ // Should never fail
+ if err := proto.Unmarshal(inBuf, &epRec); err != nil {
+ logrus.Errorf("disableServiceInNetworkDB unmarshal failed for %s %s err:%s", ep.id, n.id, err)
+ return
+ }
+ epRec.ServiceDisabled = true
+ // Should never fail
+ outBuf, err := proto.Marshal(&epRec)
+ if err != nil {
+ logrus.Errorf("disableServiceInNetworkDB marshalling failed for %s %s err:%s", ep.id, n.id, err)
+ return
+ }
+ // Send update to the whole cluster
+ if err := a.networkDB.UpdateEntry(libnetworkEPTable, n.ID(), ep.ID(), outBuf); err != nil {
+ logrus.Warnf("disableServiceInNetworkDB UpdateEntry failed for %s %s err:%s", ep.id, n.id, err)
+ }
+}
+
+func (n *network) addDriverWatches() {
+ if !n.isClusterEligible() {
+ return
+ }
+
+ c := n.getController()
+ agent := c.getAgent()
+ if agent == nil {
+ return
+ }
+ for _, table := range n.driverTables {
+ ch, cancel := agent.networkDB.Watch(table.name, n.ID(), "")
+ agent.Lock()
+ agent.driverCancelFuncs[n.ID()] = append(agent.driverCancelFuncs[n.ID()], cancel)
+ agent.Unlock()
+ go c.handleTableEvents(ch, n.handleDriverTableEvent)
+ d, err := n.driver(false)
+ if err != nil {
+ logrus.Errorf("Could not resolve driver %s while walking driver tabl: %v", n.networkType, err)
+ return
+ }
+
+ agent.networkDB.WalkTable(table.name, func(nid, key string, value []byte, deleted bool) bool {
+ // skip the entries that are mark for deletion, this is safe because this function is
+ // called at initialization time so there is no state to delete
+ if nid == n.ID() && !deleted {
+ d.EventNotify(driverapi.Create, nid, table.name, key, value)
+ }
+ return false
+ })
+ }
+}
+
+func (n *network) cancelDriverWatches() {
+ if !n.isClusterEligible() {
+ return
+ }
+
+ agent := n.getController().getAgent()
+ if agent == nil {
+ return
+ }
+
+ agent.Lock()
+ cancelFuncs := agent.driverCancelFuncs[n.ID()]
+ delete(agent.driverCancelFuncs, n.ID())
+ agent.Unlock()
+
+ for _, cancel := range cancelFuncs {
+ cancel()
+ }
+}
+
+func (c *controller) handleTableEvents(ch *events.Channel, fn func(events.Event)) {
+ for {
+ select {
+ case ev := <-ch.C:
+ fn(ev)
+ case <-ch.Done():
+ return
+ }
+ }
+}
+
+func (n *network) handleDriverTableEvent(ev events.Event) {
+ d, err := n.driver(false)
+ if err != nil {
+ logrus.Errorf("Could not resolve driver %s while handling driver table event: %v", n.networkType, err)
+ return
+ }
+
+ var (
+ etype driverapi.EventType
+ tname string
+ key string
+ value []byte
+ )
+
+ switch event := ev.(type) {
+ case networkdb.CreateEvent:
+ tname = event.Table
+ key = event.Key
+ value = event.Value
+ etype = driverapi.Create
+ case networkdb.DeleteEvent:
+ tname = event.Table
+ key = event.Key
+ value = event.Value
+ etype = driverapi.Delete
+ case networkdb.UpdateEvent:
+ tname = event.Table
+ key = event.Key
+ value = event.Value
+ etype = driverapi.Delete
+ }
+
+ d.EventNotify(etype, n.ID(), tname, key, value)
+}
+
+func (c *controller) handleNodeTableEvent(ev events.Event) {
+ var (
+ value []byte
+ isAdd bool
+ nodeAddr networkdb.NodeAddr
+ )
+ switch event := ev.(type) {
+ case networkdb.CreateEvent:
+ value = event.Value
+ isAdd = true
+ case networkdb.DeleteEvent:
+ value = event.Value
+ case networkdb.UpdateEvent:
+ logrus.Errorf("Unexpected update node table event = %#v", event)
+ }
+
+ err := json.Unmarshal(value, &nodeAddr)
+ if err != nil {
+ logrus.Errorf("Error unmarshalling node table event %v", err)
+ return
+ }
+ c.processNodeDiscovery([]net.IP{nodeAddr.Addr}, isAdd)
+
+}
+
+func (c *controller) handleEpTableEvent(ev events.Event) {
+ var (
+ nid string
+ eid string
+ value []byte
+ epRec EndpointRecord
+ )
+
+ switch event := ev.(type) {
+ case networkdb.CreateEvent:
+ nid = event.NetworkID
+ eid = event.Key
+ value = event.Value
+ case networkdb.DeleteEvent:
+ nid = event.NetworkID
+ eid = event.Key
+ value = event.Value
+ case networkdb.UpdateEvent:
+ nid = event.NetworkID
+ eid = event.Key
+ value = event.Value
+ default:
+ logrus.Errorf("Unexpected update service table event = %#v", event)
+ return
+ }
+
+ err := proto.Unmarshal(value, &epRec)
+ if err != nil {
+ logrus.Errorf("Failed to unmarshal service table value: %v", err)
+ return
+ }
+
+ containerName := epRec.Name
+ svcName := epRec.ServiceName
+ svcID := epRec.ServiceID
+ vip := net.ParseIP(epRec.VirtualIP)
+ ip := net.ParseIP(epRec.EndpointIP)
+ ingressPorts := epRec.IngressPorts
+ serviceAliases := epRec.Aliases
+ taskAliases := epRec.TaskAliases
+
+ if containerName == "" || ip == nil {
+ logrus.Errorf("Invalid endpoint name/ip received while handling service table event %s", value)
+ return
+ }
+
+ switch ev.(type) {
+ case networkdb.CreateEvent:
+ logrus.Debugf("handleEpTableEvent ADD %s R:%v", eid, epRec)
+ if svcID != "" {
+ // This is a remote task part of a service
+ if err := c.addServiceBinding(svcName, svcID, nid, eid, containerName, vip, ingressPorts, serviceAliases, taskAliases, ip, "handleEpTableEvent"); err != nil {
+ logrus.Errorf("failed adding service binding for %s epRec:%v err:%v", eid, epRec, err)
+ return
+ }
+ } else {
+ // This is a remote container simply attached to an attachable network
+ if err := c.addContainerNameResolution(nid, eid, containerName, taskAliases, ip, "handleEpTableEvent"); err != nil {
+ logrus.Errorf("failed adding container name resolution for %s epRec:%v err:%v", eid, epRec, err)
+ }
+ }
+
+ case networkdb.DeleteEvent:
+ logrus.Debugf("handleEpTableEvent DEL %s R:%v", eid, epRec)
+ if svcID != "" {
+ // This is a remote task part of a service
+ if err := c.rmServiceBinding(svcName, svcID, nid, eid, containerName, vip, ingressPorts, serviceAliases, taskAliases, ip, "handleEpTableEvent", true, true); err != nil {
+ logrus.Errorf("failed removing service binding for %s epRec:%v err:%v", eid, epRec, err)
+ return
+ }
+ } else {
+ // This is a remote container simply attached to an attachable network
+ if err := c.delContainerNameResolution(nid, eid, containerName, taskAliases, ip, "handleEpTableEvent"); err != nil {
+ logrus.Errorf("failed removing container name resolution for %s epRec:%v err:%v", eid, epRec, err)
+ }
+ }
+ case networkdb.UpdateEvent:
+ logrus.Debugf("handleEpTableEvent UPD %s R:%v", eid, epRec)
+ // We currently should only get these to inform us that an endpoint
+ // is disabled. Report if otherwise.
+ if svcID == "" || !epRec.ServiceDisabled {
+ logrus.Errorf("Unexpected update table event for %s epRec:%v", eid, epRec)
+ return
+ }
+ // This is a remote task that is part of a service that is now disabled
+ if err := c.rmServiceBinding(svcName, svcID, nid, eid, containerName, vip, ingressPorts, serviceAliases, taskAliases, ip, "handleEpTableEvent", true, false); err != nil {
+ logrus.Errorf("failed disabling service binding for %s epRec:%v err:%v", eid, epRec, err)
+ return
+ }
+ }
+}
--- /dev/null
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: agent.proto
+
+/*
+ Package libnetwork is a generated protocol buffer package.
+
+ It is generated from these files:
+ agent.proto
+
+ It has these top-level messages:
+ EndpointRecord
+ PortConfig
+*/
+package libnetwork
+
+import proto "github.com/gogo/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import _ "github.com/gogo/protobuf/gogoproto"
+
+import strings "strings"
+import reflect "reflect"
+
+import io "io"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
+
+type PortConfig_Protocol int32
+
+const (
+ ProtocolTCP PortConfig_Protocol = 0
+ ProtocolUDP PortConfig_Protocol = 1
+ ProtocolSCTP PortConfig_Protocol = 2
+)
+
+var PortConfig_Protocol_name = map[int32]string{
+ 0: "TCP",
+ 1: "UDP",
+ 2: "SCTP",
+}
+var PortConfig_Protocol_value = map[string]int32{
+ "TCP": 0,
+ "UDP": 1,
+ "SCTP": 2,
+}
+
+func (x PortConfig_Protocol) String() string {
+ return proto.EnumName(PortConfig_Protocol_name, int32(x))
+}
+func (PortConfig_Protocol) EnumDescriptor() ([]byte, []int) { return fileDescriptorAgent, []int{1, 0} }
+
+// EndpointRecord specifies all the endpoint specific information that
+// needs to gossiped to nodes participating in the network.
+type EndpointRecord struct {
+ // Name of the container
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ // Service name of the service to which this endpoint belongs.
+ ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"`
+ // Service ID of the service to which this endpoint belongs.
+ ServiceID string `protobuf:"bytes,3,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"`
+ // Virtual IP of the service to which this endpoint belongs.
+ VirtualIP string `protobuf:"bytes,4,opt,name=virtual_ip,json=virtualIp,proto3" json:"virtual_ip,omitempty"`
+ // IP assigned to this endpoint.
+ EndpointIP string `protobuf:"bytes,5,opt,name=endpoint_ip,json=endpointIp,proto3" json:"endpoint_ip,omitempty"`
+ // IngressPorts exposed by the service to which this endpoint belongs.
+ IngressPorts []*PortConfig `protobuf:"bytes,6,rep,name=ingress_ports,json=ingressPorts" json:"ingress_ports,omitempty"`
+ // A list of aliases which are alternate names for the service
+ Aliases []string `protobuf:"bytes,7,rep,name=aliases" json:"aliases,omitempty"`
+ // List of aliases task specific aliases
+ TaskAliases []string `protobuf:"bytes,8,rep,name=task_aliases,json=taskAliases" json:"task_aliases,omitempty"`
+ // Whether this enpoint's service has been disabled
+ ServiceDisabled bool `protobuf:"varint,9,opt,name=service_disabled,json=serviceDisabled,proto3" json:"service_disabled,omitempty"`
+}
+
+func (m *EndpointRecord) Reset() { *m = EndpointRecord{} }
+func (*EndpointRecord) ProtoMessage() {}
+func (*EndpointRecord) Descriptor() ([]byte, []int) { return fileDescriptorAgent, []int{0} }
+
+func (m *EndpointRecord) GetName() string {
+ if m != nil {
+ return m.Name
+ }
+ return ""
+}
+
+func (m *EndpointRecord) GetServiceName() string {
+ if m != nil {
+ return m.ServiceName
+ }
+ return ""
+}
+
+func (m *EndpointRecord) GetServiceID() string {
+ if m != nil {
+ return m.ServiceID
+ }
+ return ""
+}
+
+func (m *EndpointRecord) GetVirtualIP() string {
+ if m != nil {
+ return m.VirtualIP
+ }
+ return ""
+}
+
+func (m *EndpointRecord) GetEndpointIP() string {
+ if m != nil {
+ return m.EndpointIP
+ }
+ return ""
+}
+
+func (m *EndpointRecord) GetIngressPorts() []*PortConfig {
+ if m != nil {
+ return m.IngressPorts
+ }
+ return nil
+}
+
+func (m *EndpointRecord) GetAliases() []string {
+ if m != nil {
+ return m.Aliases
+ }
+ return nil
+}
+
+func (m *EndpointRecord) GetTaskAliases() []string {
+ if m != nil {
+ return m.TaskAliases
+ }
+ return nil
+}
+
+func (m *EndpointRecord) GetServiceDisabled() bool {
+ if m != nil {
+ return m.ServiceDisabled
+ }
+ return false
+}
+
+// PortConfig specifies an exposed port which can be
+// addressed using the given name. This can be later queried
+// using a service discovery api or a DNS SRV query. The node
+// port specifies a port that can be used to address this
+// service external to the cluster by sending a connection
+// request to this port to any node on the cluster.
+type PortConfig struct {
+ // Name for the port. If provided the port information can
+ // be queried using the name as in a DNS SRV query.
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ // Protocol for the port which is exposed.
+ Protocol PortConfig_Protocol `protobuf:"varint,2,opt,name=protocol,proto3,enum=libnetwork.PortConfig_Protocol" json:"protocol,omitempty"`
+ // The port which the application is exposing and is bound to.
+ TargetPort uint32 `protobuf:"varint,3,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"`
+ // PublishedPort specifies the port on which the service is
+ // exposed on all nodes on the cluster. If not specified an
+ // arbitrary port in the node port range is allocated by the
+ // system. If specified it should be within the node port
+ // range and it should be available.
+ PublishedPort uint32 `protobuf:"varint,4,opt,name=published_port,json=publishedPort,proto3" json:"published_port,omitempty"`
+}
+
+func (m *PortConfig) Reset() { *m = PortConfig{} }
+func (*PortConfig) ProtoMessage() {}
+func (*PortConfig) Descriptor() ([]byte, []int) { return fileDescriptorAgent, []int{1} }
+
+func (m *PortConfig) GetName() string {
+ if m != nil {
+ return m.Name
+ }
+ return ""
+}
+
+func (m *PortConfig) GetProtocol() PortConfig_Protocol {
+ if m != nil {
+ return m.Protocol
+ }
+ return ProtocolTCP
+}
+
+func (m *PortConfig) GetTargetPort() uint32 {
+ if m != nil {
+ return m.TargetPort
+ }
+ return 0
+}
+
+func (m *PortConfig) GetPublishedPort() uint32 {
+ if m != nil {
+ return m.PublishedPort
+ }
+ return 0
+}
+
+func init() {
+ proto.RegisterType((*EndpointRecord)(nil), "libnetwork.EndpointRecord")
+ proto.RegisterType((*PortConfig)(nil), "libnetwork.PortConfig")
+ proto.RegisterEnum("libnetwork.PortConfig_Protocol", PortConfig_Protocol_name, PortConfig_Protocol_value)
+}
+func (this *EndpointRecord) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 13)
+ s = append(s, "&libnetwork.EndpointRecord{")
+ s = append(s, "Name: "+fmt.Sprintf("%#v", this.Name)+",\n")
+ s = append(s, "ServiceName: "+fmt.Sprintf("%#v", this.ServiceName)+",\n")
+ s = append(s, "ServiceID: "+fmt.Sprintf("%#v", this.ServiceID)+",\n")
+ s = append(s, "VirtualIP: "+fmt.Sprintf("%#v", this.VirtualIP)+",\n")
+ s = append(s, "EndpointIP: "+fmt.Sprintf("%#v", this.EndpointIP)+",\n")
+ if this.IngressPorts != nil {
+ s = append(s, "IngressPorts: "+fmt.Sprintf("%#v", this.IngressPorts)+",\n")
+ }
+ s = append(s, "Aliases: "+fmt.Sprintf("%#v", this.Aliases)+",\n")
+ s = append(s, "TaskAliases: "+fmt.Sprintf("%#v", this.TaskAliases)+",\n")
+ s = append(s, "ServiceDisabled: "+fmt.Sprintf("%#v", this.ServiceDisabled)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func (this *PortConfig) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 8)
+ s = append(s, "&libnetwork.PortConfig{")
+ s = append(s, "Name: "+fmt.Sprintf("%#v", this.Name)+",\n")
+ s = append(s, "Protocol: "+fmt.Sprintf("%#v", this.Protocol)+",\n")
+ s = append(s, "TargetPort: "+fmt.Sprintf("%#v", this.TargetPort)+",\n")
+ s = append(s, "PublishedPort: "+fmt.Sprintf("%#v", this.PublishedPort)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func valueToGoStringAgent(v interface{}, typ string) string {
+ rv := reflect.ValueOf(v)
+ if rv.IsNil() {
+ return "nil"
+ }
+ pv := reflect.Indirect(rv).Interface()
+ return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)
+}
+func (m *EndpointRecord) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *EndpointRecord) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if len(m.Name) > 0 {
+ dAtA[i] = 0xa
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(len(m.Name)))
+ i += copy(dAtA[i:], m.Name)
+ }
+ if len(m.ServiceName) > 0 {
+ dAtA[i] = 0x12
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(len(m.ServiceName)))
+ i += copy(dAtA[i:], m.ServiceName)
+ }
+ if len(m.ServiceID) > 0 {
+ dAtA[i] = 0x1a
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(len(m.ServiceID)))
+ i += copy(dAtA[i:], m.ServiceID)
+ }
+ if len(m.VirtualIP) > 0 {
+ dAtA[i] = 0x22
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(len(m.VirtualIP)))
+ i += copy(dAtA[i:], m.VirtualIP)
+ }
+ if len(m.EndpointIP) > 0 {
+ dAtA[i] = 0x2a
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(len(m.EndpointIP)))
+ i += copy(dAtA[i:], m.EndpointIP)
+ }
+ if len(m.IngressPorts) > 0 {
+ for _, msg := range m.IngressPorts {
+ dAtA[i] = 0x32
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(msg.Size()))
+ n, err := msg.MarshalTo(dAtA[i:])
+ if err != nil {
+ return 0, err
+ }
+ i += n
+ }
+ }
+ if len(m.Aliases) > 0 {
+ for _, s := range m.Aliases {
+ dAtA[i] = 0x3a
+ i++
+ l = len(s)
+ for l >= 1<<7 {
+ dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
+ l >>= 7
+ i++
+ }
+ dAtA[i] = uint8(l)
+ i++
+ i += copy(dAtA[i:], s)
+ }
+ }
+ if len(m.TaskAliases) > 0 {
+ for _, s := range m.TaskAliases {
+ dAtA[i] = 0x42
+ i++
+ l = len(s)
+ for l >= 1<<7 {
+ dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
+ l >>= 7
+ i++
+ }
+ dAtA[i] = uint8(l)
+ i++
+ i += copy(dAtA[i:], s)
+ }
+ }
+ if m.ServiceDisabled {
+ dAtA[i] = 0x48
+ i++
+ if m.ServiceDisabled {
+ dAtA[i] = 1
+ } else {
+ dAtA[i] = 0
+ }
+ i++
+ }
+ return i, nil
+}
+
+func (m *PortConfig) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *PortConfig) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if len(m.Name) > 0 {
+ dAtA[i] = 0xa
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(len(m.Name)))
+ i += copy(dAtA[i:], m.Name)
+ }
+ if m.Protocol != 0 {
+ dAtA[i] = 0x10
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(m.Protocol))
+ }
+ if m.TargetPort != 0 {
+ dAtA[i] = 0x18
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(m.TargetPort))
+ }
+ if m.PublishedPort != 0 {
+ dAtA[i] = 0x20
+ i++
+ i = encodeVarintAgent(dAtA, i, uint64(m.PublishedPort))
+ }
+ return i, nil
+}
+
+func encodeVarintAgent(dAtA []byte, offset int, v uint64) int {
+ for v >= 1<<7 {
+ dAtA[offset] = uint8(v&0x7f | 0x80)
+ v >>= 7
+ offset++
+ }
+ dAtA[offset] = uint8(v)
+ return offset + 1
+}
+func (m *EndpointRecord) Size() (n int) {
+ var l int
+ _ = l
+ l = len(m.Name)
+ if l > 0 {
+ n += 1 + l + sovAgent(uint64(l))
+ }
+ l = len(m.ServiceName)
+ if l > 0 {
+ n += 1 + l + sovAgent(uint64(l))
+ }
+ l = len(m.ServiceID)
+ if l > 0 {
+ n += 1 + l + sovAgent(uint64(l))
+ }
+ l = len(m.VirtualIP)
+ if l > 0 {
+ n += 1 + l + sovAgent(uint64(l))
+ }
+ l = len(m.EndpointIP)
+ if l > 0 {
+ n += 1 + l + sovAgent(uint64(l))
+ }
+ if len(m.IngressPorts) > 0 {
+ for _, e := range m.IngressPorts {
+ l = e.Size()
+ n += 1 + l + sovAgent(uint64(l))
+ }
+ }
+ if len(m.Aliases) > 0 {
+ for _, s := range m.Aliases {
+ l = len(s)
+ n += 1 + l + sovAgent(uint64(l))
+ }
+ }
+ if len(m.TaskAliases) > 0 {
+ for _, s := range m.TaskAliases {
+ l = len(s)
+ n += 1 + l + sovAgent(uint64(l))
+ }
+ }
+ if m.ServiceDisabled {
+ n += 2
+ }
+ return n
+}
+
+func (m *PortConfig) Size() (n int) {
+ var l int
+ _ = l
+ l = len(m.Name)
+ if l > 0 {
+ n += 1 + l + sovAgent(uint64(l))
+ }
+ if m.Protocol != 0 {
+ n += 1 + sovAgent(uint64(m.Protocol))
+ }
+ if m.TargetPort != 0 {
+ n += 1 + sovAgent(uint64(m.TargetPort))
+ }
+ if m.PublishedPort != 0 {
+ n += 1 + sovAgent(uint64(m.PublishedPort))
+ }
+ return n
+}
+
+func sovAgent(x uint64) (n int) {
+ for {
+ n++
+ x >>= 7
+ if x == 0 {
+ break
+ }
+ }
+ return n
+}
+func sozAgent(x uint64) (n int) {
+ return sovAgent(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (this *EndpointRecord) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&EndpointRecord{`,
+ `Name:` + fmt.Sprintf("%v", this.Name) + `,`,
+ `ServiceName:` + fmt.Sprintf("%v", this.ServiceName) + `,`,
+ `ServiceID:` + fmt.Sprintf("%v", this.ServiceID) + `,`,
+ `VirtualIP:` + fmt.Sprintf("%v", this.VirtualIP) + `,`,
+ `EndpointIP:` + fmt.Sprintf("%v", this.EndpointIP) + `,`,
+ `IngressPorts:` + strings.Replace(fmt.Sprintf("%v", this.IngressPorts), "PortConfig", "PortConfig", 1) + `,`,
+ `Aliases:` + fmt.Sprintf("%v", this.Aliases) + `,`,
+ `TaskAliases:` + fmt.Sprintf("%v", this.TaskAliases) + `,`,
+ `ServiceDisabled:` + fmt.Sprintf("%v", this.ServiceDisabled) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func (this *PortConfig) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&PortConfig{`,
+ `Name:` + fmt.Sprintf("%v", this.Name) + `,`,
+ `Protocol:` + fmt.Sprintf("%v", this.Protocol) + `,`,
+ `TargetPort:` + fmt.Sprintf("%v", this.TargetPort) + `,`,
+ `PublishedPort:` + fmt.Sprintf("%v", this.PublishedPort) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func valueToStringAgent(v interface{}) string {
+ rv := reflect.ValueOf(v)
+ if rv.IsNil() {
+ return "nil"
+ }
+ pv := reflect.Indirect(rv).Interface()
+ return fmt.Sprintf("*%v", pv)
+}
+func (m *EndpointRecord) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: EndpointRecord: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: EndpointRecord: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthAgent
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Name = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthAgent
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.ServiceName = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 3:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field ServiceID", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthAgent
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.ServiceID = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 4:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field VirtualIP", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthAgent
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.VirtualIP = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 5:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field EndpointIP", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthAgent
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.EndpointIP = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 6:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field IngressPorts", wireType)
+ }
+ var msglen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ msglen |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if msglen < 0 {
+ return ErrInvalidLengthAgent
+ }
+ postIndex := iNdEx + msglen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.IngressPorts = append(m.IngressPorts, &PortConfig{})
+ if err := m.IngressPorts[len(m.IngressPorts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ case 7:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Aliases", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthAgent
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Aliases = append(m.Aliases, string(dAtA[iNdEx:postIndex]))
+ iNdEx = postIndex
+ case 8:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field TaskAliases", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthAgent
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.TaskAliases = append(m.TaskAliases, string(dAtA[iNdEx:postIndex]))
+ iNdEx = postIndex
+ case 9:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field ServiceDisabled", wireType)
+ }
+ var v int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ v |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ m.ServiceDisabled = bool(v != 0)
+ default:
+ iNdEx = preIndex
+ skippy, err := skipAgent(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthAgent
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *PortConfig) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: PortConfig: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: PortConfig: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthAgent
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Name = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 2:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Protocol", wireType)
+ }
+ m.Protocol = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.Protocol |= (PortConfig_Protocol(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 3:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field TargetPort", wireType)
+ }
+ m.TargetPort = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.TargetPort |= (uint32(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 4:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field PublishedPort", wireType)
+ }
+ m.PublishedPort = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.PublishedPort |= (uint32(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ default:
+ iNdEx = preIndex
+ skippy, err := skipAgent(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthAgent
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func skipAgent(dAtA []byte) (n int, err error) {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ wireType := int(wire & 0x7)
+ switch wireType {
+ case 0:
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ iNdEx++
+ if dAtA[iNdEx-1] < 0x80 {
+ break
+ }
+ }
+ return iNdEx, nil
+ case 1:
+ iNdEx += 8
+ return iNdEx, nil
+ case 2:
+ var length int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ length |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ iNdEx += length
+ if length < 0 {
+ return 0, ErrInvalidLengthAgent
+ }
+ return iNdEx, nil
+ case 3:
+ for {
+ var innerWire uint64
+ var start int = iNdEx
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowAgent
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ innerWire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ innerWireType := int(innerWire & 0x7)
+ if innerWireType == 4 {
+ break
+ }
+ next, err := skipAgent(dAtA[start:])
+ if err != nil {
+ return 0, err
+ }
+ iNdEx = start + next
+ }
+ return iNdEx, nil
+ case 4:
+ return iNdEx, nil
+ case 5:
+ iNdEx += 4
+ return iNdEx, nil
+ default:
+ return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+ }
+ }
+ panic("unreachable")
+}
+
+var (
+ ErrInvalidLengthAgent = fmt.Errorf("proto: negative length found during unmarshaling")
+ ErrIntOverflowAgent = fmt.Errorf("proto: integer overflow")
+)
+
+func init() { proto.RegisterFile("agent.proto", fileDescriptorAgent) }
+
+var fileDescriptorAgent = []byte{
+ // 459 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0x31, 0x6f, 0xd3, 0x4c,
+ 0x18, 0xc7, 0xe3, 0xc4, 0x6f, 0x1b, 0x3f, 0x4e, 0x52, 0xeb, 0xf4, 0x0a, 0x59, 0x1e, 0x1c, 0x13,
+ 0x09, 0x29, 0x48, 0x28, 0x95, 0xca, 0xd8, 0x89, 0x26, 0x0c, 0x5e, 0x90, 0x75, 0x4d, 0x59, 0x83,
+ 0x13, 0x1f, 0xe6, 0x54, 0xe3, 0xb3, 0xee, 0xae, 0x65, 0x65, 0x03, 0xf5, 0x3b, 0x74, 0xe2, 0xcb,
+ 0x30, 0x32, 0x32, 0x55, 0xd4, 0x9f, 0x80, 0x95, 0x0d, 0xdd, 0xf9, 0xae, 0x11, 0x52, 0xb7, 0xf3,
+ 0xef, 0xff, 0x3b, 0xeb, 0xb9, 0xff, 0x03, 0x7e, 0x5e, 0x92, 0x5a, 0x2e, 0x1a, 0xce, 0x24, 0x43,
+ 0x50, 0xd1, 0x6d, 0x4d, 0xe4, 0x27, 0xc6, 0x2f, 0xa3, 0xff, 0x4b, 0x56, 0x32, 0x8d, 0x8f, 0xd5,
+ 0xa9, 0x33, 0x66, 0x7f, 0xfa, 0x30, 0x79, 0x5d, 0x17, 0x0d, 0xa3, 0xb5, 0xc4, 0x64, 0xc7, 0x78,
+ 0x81, 0x10, 0xb8, 0x75, 0xfe, 0x91, 0x84, 0x4e, 0xe2, 0xcc, 0x3d, 0xac, 0xcf, 0xe8, 0x29, 0x8c,
+ 0x04, 0xe1, 0xd7, 0x74, 0x47, 0x36, 0x3a, 0xeb, 0xeb, 0xcc, 0x37, 0xec, 0x8d, 0x52, 0x5e, 0x00,
+ 0x58, 0x85, 0x16, 0xe1, 0x40, 0x09, 0x67, 0xe3, 0xf6, 0x6e, 0xea, 0x9d, 0x77, 0x34, 0x5d, 0x61,
+ 0xcf, 0x08, 0x69, 0xa1, 0xec, 0x6b, 0xca, 0xe5, 0x55, 0x5e, 0x6d, 0x68, 0x13, 0xba, 0x7b, 0xfb,
+ 0x6d, 0x47, 0xd3, 0x0c, 0x7b, 0x46, 0x48, 0x1b, 0x74, 0x0c, 0x3e, 0x31, 0x43, 0x2a, 0xfd, 0x3f,
+ 0xad, 0x4f, 0xda, 0xbb, 0x29, 0xd8, 0xd9, 0xd3, 0x0c, 0x83, 0x55, 0xd2, 0x06, 0x9d, 0xc2, 0x98,
+ 0xd6, 0x25, 0x27, 0x42, 0x6c, 0x1a, 0xc6, 0xa5, 0x08, 0x0f, 0x92, 0xc1, 0xdc, 0x3f, 0x79, 0xb2,
+ 0xd8, 0x17, 0xb2, 0xc8, 0x18, 0x97, 0x4b, 0x56, 0xbf, 0xa7, 0x25, 0x1e, 0x19, 0x59, 0x21, 0x81,
+ 0x42, 0x38, 0xcc, 0x2b, 0x9a, 0x0b, 0x22, 0xc2, 0xc3, 0x64, 0x30, 0xf7, 0xb0, 0xfd, 0x54, 0x35,
+ 0xc8, 0x5c, 0x5c, 0x6e, 0x6c, 0x3c, 0xd4, 0xb1, 0xaf, 0xd8, 0x2b, 0xa3, 0x3c, 0x87, 0xc0, 0xd6,
+ 0x50, 0x50, 0x91, 0x6f, 0x2b, 0x52, 0x84, 0x5e, 0xe2, 0xcc, 0x87, 0xf8, 0xc8, 0xf0, 0x95, 0xc1,
+ 0xb3, 0x2f, 0x7d, 0x80, 0xfd, 0x10, 0x8f, 0xf6, 0x7e, 0x0a, 0x43, 0xbd, 0xa7, 0x1d, 0xab, 0x74,
+ 0xe7, 0x93, 0x93, 0xe9, 0xe3, 0x4f, 0x58, 0x64, 0x46, 0xc3, 0x0f, 0x17, 0xd0, 0x14, 0x7c, 0x99,
+ 0xf3, 0x92, 0x48, 0xdd, 0x81, 0x5e, 0xc9, 0x18, 0x43, 0x87, 0xd4, 0x4d, 0xf4, 0x0c, 0x26, 0xcd,
+ 0xd5, 0xb6, 0xa2, 0xe2, 0x03, 0x29, 0x3a, 0xc7, 0xd5, 0xce, 0xf8, 0x81, 0x2a, 0x6d, 0xf6, 0x0e,
+ 0x86, 0xf6, 0xef, 0x28, 0x84, 0xc1, 0x7a, 0x99, 0x05, 0xbd, 0xe8, 0xe8, 0xe6, 0x36, 0xf1, 0x2d,
+ 0x5e, 0x2f, 0x33, 0x95, 0x5c, 0xac, 0xb2, 0xc0, 0xf9, 0x37, 0xb9, 0x58, 0x65, 0x28, 0x02, 0xf7,
+ 0x7c, 0xb9, 0xce, 0x82, 0x7e, 0x14, 0xdc, 0xdc, 0x26, 0x23, 0x1b, 0x29, 0x16, 0xb9, 0x5f, 0xbf,
+ 0xc5, 0xbd, 0xb3, 0xf0, 0xe7, 0x7d, 0xdc, 0xfb, 0x7d, 0x1f, 0x3b, 0x9f, 0xdb, 0xd8, 0xf9, 0xde,
+ 0xc6, 0xce, 0x8f, 0x36, 0x76, 0x7e, 0xb5, 0xb1, 0xb3, 0x3d, 0xd0, 0xaf, 0x79, 0xf9, 0x37, 0x00,
+ 0x00, 0xff, 0xff, 0x55, 0x29, 0x75, 0x5c, 0xd7, 0x02, 0x00, 0x00,
+}
--- /dev/null
+syntax = "proto3";
+
+import "gogoproto/gogo.proto";
+
+package libnetwork;
+
+option (gogoproto.marshaler_all) = true;
+option (gogoproto.unmarshaler_all) = true;
+option (gogoproto.stringer_all) = true;
+option (gogoproto.gostring_all) = true;
+option (gogoproto.sizer_all) = true;
+option (gogoproto.goproto_stringer_all) = false;
+
+// EndpointRecord specifies all the endpoint specific information that
+// needs to gossiped to nodes participating in the network.
+message EndpointRecord {
+ // Name of the container
+ string name = 1;
+
+ // Service name of the service to which this endpoint belongs.
+ string service_name = 2;
+
+ // Service ID of the service to which this endpoint belongs.
+ string service_id = 3 [(gogoproto.customname) = "ServiceID"];
+
+ // Virtual IP of the service to which this endpoint belongs.
+ string virtual_ip = 4 [(gogoproto.customname) = "VirtualIP"];
+
+ // IP assigned to this endpoint.
+ string endpoint_ip = 5 [(gogoproto.customname) = "EndpointIP"];
+
+ // IngressPorts exposed by the service to which this endpoint belongs.
+ repeated PortConfig ingress_ports = 6;
+
+ // A list of aliases which are alternate names for the service
+ repeated string aliases = 7;
+
+ // List of aliases task specific aliases
+ repeated string task_aliases = 8;
+
+ // Whether this enpoint's service has been disabled
+ bool service_disabled = 9;
+}
+
+// PortConfig specifies an exposed port which can be
+// addressed using the given name. This can be later queried
+// using a service discovery api or a DNS SRV query. The node
+// port specifies a port that can be used to address this
+// service external to the cluster by sending a connection
+// request to this port to any node on the cluster.
+message PortConfig {
+ enum Protocol {
+ option (gogoproto.goproto_enum_prefix) = false;
+
+ TCP = 0 [(gogoproto.enumvalue_customname) = "ProtocolTCP"];
+ UDP = 1 [(gogoproto.enumvalue_customname) = "ProtocolUDP"];
+ SCTP = 2 [(gogoproto.enumvalue_customname) = "ProtocolSCTP"];
+ }
+
+ // Name for the port. If provided the port information can
+ // be queried using the name as in a DNS SRV query.
+ string name = 1;
+
+ // Protocol for the port which is exposed.
+ Protocol protocol = 2;
+
+ // The port which the application is exposing and is bound to.
+ uint32 target_port = 3;
+
+ // PublishedPort specifies the port on which the service is
+ // exposed on all nodes on the cluster. If not specified an
+ // arbitrary port in the node port range is allocated by the
+ // system. If specified it should be within the node port
+ // range and it should be available.
+ uint32 published_port = 4;
+}
--- /dev/null
+package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/docker/libnetwork"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/types"
+ "github.com/gorilla/mux"
+)
+
+var (
+ successResponse = responseStatus{Status: "Success", StatusCode: http.StatusOK}
+ createdResponse = responseStatus{Status: "Created", StatusCode: http.StatusCreated}
+ mismatchResponse = responseStatus{Status: "Body/URI parameter mismatch", StatusCode: http.StatusBadRequest}
+ badQueryResponse = responseStatus{Status: "Unsupported query", StatusCode: http.StatusBadRequest}
+)
+
+const (
+ // Resource name regex
+ // Gorilla mux encloses the passed pattern with '^' and '$'. So we need to do some tricks
+ // to have mux eventually build a query regex which matches empty or word string (`^$|[\w]+`)
+ regex = "[a-zA-Z_0-9-]+"
+ qregx = "$|" + regex
+ // Router URL variable definition
+ nwName = "{" + urlNwName + ":" + regex + "}"
+ nwNameQr = "{" + urlNwName + ":" + qregx + "}"
+ nwID = "{" + urlNwID + ":" + regex + "}"
+ nwPIDQr = "{" + urlNwPID + ":" + qregx + "}"
+ epName = "{" + urlEpName + ":" + regex + "}"
+ epNameQr = "{" + urlEpName + ":" + qregx + "}"
+ epID = "{" + urlEpID + ":" + regex + "}"
+ epPIDQr = "{" + urlEpPID + ":" + qregx + "}"
+ sbID = "{" + urlSbID + ":" + regex + "}"
+ sbPIDQr = "{" + urlSbPID + ":" + qregx + "}"
+ cnIDQr = "{" + urlCnID + ":" + qregx + "}"
+ cnPIDQr = "{" + urlCnPID + ":" + qregx + "}"
+
+ // Internal URL variable name.They can be anything as
+ // long as they do not collide with query fields.
+ urlNwName = "network-name"
+ urlNwID = "network-id"
+ urlNwPID = "network-partial-id"
+ urlEpName = "endpoint-name"
+ urlEpID = "endpoint-id"
+ urlEpPID = "endpoint-partial-id"
+ urlSbID = "sandbox-id"
+ urlSbPID = "sandbox-partial-id"
+ urlCnID = "container-id"
+ urlCnPID = "container-partial-id"
+)
+
+// NewHTTPHandler creates and initialize the HTTP handler to serve the requests for libnetwork
+func NewHTTPHandler(c libnetwork.NetworkController) func(w http.ResponseWriter, req *http.Request) {
+ h := &httpHandler{c: c}
+ h.initRouter()
+ return h.handleRequest
+}
+
+type responseStatus struct {
+ Status string
+ StatusCode int
+}
+
+func (r *responseStatus) isOK() bool {
+ return r.StatusCode == http.StatusOK || r.StatusCode == http.StatusCreated
+}
+
+type processor func(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus)
+
+type httpHandler struct {
+ c libnetwork.NetworkController
+ r *mux.Router
+}
+
+func (h *httpHandler) handleRequest(w http.ResponseWriter, req *http.Request) {
+ // Make sure the service is there
+ if h.c == nil {
+ http.Error(w, "NetworkController is not available", http.StatusServiceUnavailable)
+ return
+ }
+
+ // Get handler from router and execute it
+ h.r.ServeHTTP(w, req)
+}
+
+func (h *httpHandler) initRouter() {
+ m := map[string][]struct {
+ url string
+ qrs []string
+ fct processor
+ }{
+ "GET": {
+ // Order matters
+ {"/networks", []string{"name", nwNameQr}, procGetNetworks},
+ {"/networks", []string{"partial-id", nwPIDQr}, procGetNetworks},
+ {"/networks", nil, procGetNetworks},
+ {"/networks/" + nwID, nil, procGetNetwork},
+ {"/networks/" + nwID + "/endpoints", []string{"name", epNameQr}, procGetEndpoints},
+ {"/networks/" + nwID + "/endpoints", []string{"partial-id", epPIDQr}, procGetEndpoints},
+ {"/networks/" + nwID + "/endpoints", nil, procGetEndpoints},
+ {"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint},
+ {"/services", []string{"network", nwNameQr}, procGetServices},
+ {"/services", []string{"name", epNameQr}, procGetServices},
+ {"/services", []string{"partial-id", epPIDQr}, procGetServices},
+ {"/services", nil, procGetServices},
+ {"/services/" + epID, nil, procGetService},
+ {"/services/" + epID + "/backend", nil, procGetSandbox},
+ {"/sandboxes", []string{"partial-container-id", cnPIDQr}, procGetSandboxes},
+ {"/sandboxes", []string{"container-id", cnIDQr}, procGetSandboxes},
+ {"/sandboxes", []string{"partial-id", sbPIDQr}, procGetSandboxes},
+ {"/sandboxes", nil, procGetSandboxes},
+ {"/sandboxes/" + sbID, nil, procGetSandbox},
+ },
+ "POST": {
+ {"/networks", nil, procCreateNetwork},
+ {"/networks/" + nwID + "/endpoints", nil, procCreateEndpoint},
+ {"/networks/" + nwID + "/endpoints/" + epID + "/sandboxes", nil, procJoinEndpoint},
+ {"/services", nil, procPublishService},
+ {"/services/" + epID + "/backend", nil, procAttachBackend},
+ {"/sandboxes", nil, procCreateSandbox},
+ },
+ "DELETE": {
+ {"/networks/" + nwID, nil, procDeleteNetwork},
+ {"/networks/" + nwID + "/endpoints/" + epID, nil, procDeleteEndpoint},
+ {"/networks/" + nwID + "/endpoints/" + epID + "/sandboxes/" + sbID, nil, procLeaveEndpoint},
+ {"/services/" + epID, nil, procUnpublishService},
+ {"/services/" + epID + "/backend/" + sbID, nil, procDetachBackend},
+ {"/sandboxes/" + sbID, nil, procDeleteSandbox},
+ },
+ }
+
+ h.r = mux.NewRouter()
+ for method, routes := range m {
+ for _, route := range routes {
+ r := h.r.Path("/{.*}" + route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
+ if route.qrs != nil {
+ r.Queries(route.qrs...)
+ }
+
+ r = h.r.Path(route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
+ if route.qrs != nil {
+ r.Queries(route.qrs...)
+ }
+ }
+ }
+}
+
+func makeHandler(ctrl libnetwork.NetworkController, fct processor) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ var (
+ body []byte
+ err error
+ )
+ if req.Body != nil {
+ body, err = ioutil.ReadAll(req.Body)
+ if err != nil {
+ http.Error(w, "Invalid body: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+ }
+
+ res, rsp := fct(ctrl, mux.Vars(req), body)
+ if !rsp.isOK() {
+ http.Error(w, rsp.Status, rsp.StatusCode)
+ return
+ }
+ if res != nil {
+ writeJSON(w, rsp.StatusCode, res)
+ }
+ }
+}
+
+/*****************
+ Resource Builders
+******************/
+
+func buildNetworkResource(nw libnetwork.Network) *networkResource {
+ r := &networkResource{}
+ if nw != nil {
+ r.Name = nw.Name()
+ r.ID = nw.ID()
+ r.Type = nw.Type()
+ epl := nw.Endpoints()
+ r.Endpoints = make([]*endpointResource, 0, len(epl))
+ for _, e := range epl {
+ epr := buildEndpointResource(e)
+ r.Endpoints = append(r.Endpoints, epr)
+ }
+ }
+ return r
+}
+
+func buildEndpointResource(ep libnetwork.Endpoint) *endpointResource {
+ r := &endpointResource{}
+ if ep != nil {
+ r.Name = ep.Name()
+ r.ID = ep.ID()
+ r.Network = ep.Network()
+ }
+ return r
+}
+
+func buildSandboxResource(sb libnetwork.Sandbox) *sandboxResource {
+ r := &sandboxResource{}
+ if sb != nil {
+ r.ID = sb.ID()
+ r.Key = sb.Key()
+ r.ContainerID = sb.ContainerID()
+ }
+ return r
+}
+
+/****************
+ Options Parsers
+*****************/
+
+func (sc *sandboxCreate) parseOptions() []libnetwork.SandboxOption {
+ var setFctList []libnetwork.SandboxOption
+ if sc.HostName != "" {
+ setFctList = append(setFctList, libnetwork.OptionHostname(sc.HostName))
+ }
+ if sc.DomainName != "" {
+ setFctList = append(setFctList, libnetwork.OptionDomainname(sc.DomainName))
+ }
+ if sc.HostsPath != "" {
+ setFctList = append(setFctList, libnetwork.OptionHostsPath(sc.HostsPath))
+ }
+ if sc.ResolvConfPath != "" {
+ setFctList = append(setFctList, libnetwork.OptionResolvConfPath(sc.ResolvConfPath))
+ }
+ if sc.UseDefaultSandbox {
+ setFctList = append(setFctList, libnetwork.OptionUseDefaultSandbox())
+ }
+ if sc.UseExternalKey {
+ setFctList = append(setFctList, libnetwork.OptionUseExternalKey())
+ }
+ if sc.DNS != nil {
+ for _, d := range sc.DNS {
+ setFctList = append(setFctList, libnetwork.OptionDNS(d))
+ }
+ }
+ if sc.ExtraHosts != nil {
+ for _, e := range sc.ExtraHosts {
+ setFctList = append(setFctList, libnetwork.OptionExtraHost(e.Name, e.Address))
+ }
+ }
+ if sc.ExposedPorts != nil {
+ setFctList = append(setFctList, libnetwork.OptionExposedPorts(sc.ExposedPorts))
+ }
+ if sc.PortMapping != nil {
+ setFctList = append(setFctList, libnetwork.OptionPortMapping(sc.PortMapping))
+ }
+ return setFctList
+}
+
+func (ej *endpointJoin) parseOptions() []libnetwork.EndpointOption {
+ // priority will go here
+ return []libnetwork.EndpointOption{}
+}
+
+/******************
+ Process functions
+*******************/
+
+func processCreateDefaults(c libnetwork.NetworkController, nc *networkCreate) {
+ if nc.NetworkType == "" {
+ nc.NetworkType = c.Config().Daemon.DefaultDriver
+ }
+}
+
+/***************************
+ NetworkController interface
+****************************/
+func procCreateNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ var create networkCreate
+
+ err := json.Unmarshal(body, &create)
+ if err != nil {
+ return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+ }
+ processCreateDefaults(c, &create)
+
+ options := []libnetwork.NetworkOption{}
+ if val, ok := create.NetworkOpts[netlabel.Internal]; ok {
+ internal, err := strconv.ParseBool(val)
+ if err != nil {
+ return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
+ }
+ if internal {
+ options = append(options, libnetwork.NetworkOptionInternalNetwork())
+ }
+ }
+ if val, ok := create.NetworkOpts[netlabel.EnableIPv6]; ok {
+ enableIPv6, err := strconv.ParseBool(val)
+ if err != nil {
+ return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
+ }
+ options = append(options, libnetwork.NetworkOptionEnableIPv6(enableIPv6))
+ }
+ if len(create.DriverOpts) > 0 {
+ options = append(options, libnetwork.NetworkOptionDriverOpts(create.DriverOpts))
+ }
+
+ if len(create.IPv4Conf) > 0 {
+ ipamV4Conf := &libnetwork.IpamConf{
+ PreferredPool: create.IPv4Conf[0].PreferredPool,
+ SubPool: create.IPv4Conf[0].SubPool,
+ }
+
+ options = append(options, libnetwork.NetworkOptionIpam("default", "", []*libnetwork.IpamConf{ipamV4Conf}, nil, nil))
+ }
+
+ nw, err := c.NewNetwork(create.NetworkType, create.Name, create.ID, options...)
+ if err != nil {
+ return nil, convertNetworkError(err)
+ }
+
+ return nw.ID(), &createdResponse
+}
+
+func procGetNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ t, by := detectNetworkTarget(vars)
+ nw, errRsp := findNetwork(c, t, by)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+ return buildNetworkResource(nw), &successResponse
+}
+
+func procGetNetworks(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ var list []*networkResource
+
+ // Look for query filters and validate
+ name, queryByName := vars[urlNwName]
+ shortID, queryByPid := vars[urlNwPID]
+ if queryByName && queryByPid {
+ return nil, &badQueryResponse
+ }
+
+ if queryByName {
+ if nw, errRsp := findNetwork(c, name, byName); errRsp.isOK() {
+ list = append(list, buildNetworkResource(nw))
+ }
+ } else if queryByPid {
+ // Return all the prefix-matching networks
+ l := func(nw libnetwork.Network) bool {
+ if strings.HasPrefix(nw.ID(), shortID) {
+ list = append(list, buildNetworkResource(nw))
+ }
+ return false
+ }
+ c.WalkNetworks(l)
+ } else {
+ for _, nw := range c.Networks() {
+ list = append(list, buildNetworkResource(nw))
+ }
+ }
+
+ return list, &successResponse
+}
+
+func procCreateSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ var create sandboxCreate
+
+ err := json.Unmarshal(body, &create)
+ if err != nil {
+ return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+ }
+
+ sb, err := c.NewSandbox(create.ContainerID, create.parseOptions()...)
+ if err != nil {
+ return "", convertNetworkError(err)
+ }
+
+ return sb.ID(), &createdResponse
+}
+
+/******************
+ Network interface
+*******************/
+func procCreateEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ var ec endpointCreate
+
+ err := json.Unmarshal(body, &ec)
+ if err != nil {
+ return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+ }
+
+ nwT, nwBy := detectNetworkTarget(vars)
+ n, errRsp := findNetwork(c, nwT, nwBy)
+ if !errRsp.isOK() {
+ return "", errRsp
+ }
+
+ var setFctList []libnetwork.EndpointOption
+ for _, str := range ec.MyAliases {
+ setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str))
+ }
+
+ ep, err := n.CreateEndpoint(ec.Name, setFctList...)
+ if err != nil {
+ return "", convertNetworkError(err)
+ }
+
+ return ep.ID(), &createdResponse
+}
+
+func procGetEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ nwT, nwBy := detectNetworkTarget(vars)
+ epT, epBy := detectEndpointTarget(vars)
+
+ ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ return buildEndpointResource(ep), &successResponse
+}
+
+func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ // Look for query filters and validate
+ name, queryByName := vars[urlEpName]
+ shortID, queryByPid := vars[urlEpPID]
+ if queryByName && queryByPid {
+ return nil, &badQueryResponse
+ }
+
+ nwT, nwBy := detectNetworkTarget(vars)
+ nw, errRsp := findNetwork(c, nwT, nwBy)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ var list []*endpointResource
+
+ // If query parameter is specified, return a filtered collection
+ if queryByName {
+ if ep, errRsp := findEndpoint(c, nwT, name, nwBy, byName); errRsp.isOK() {
+ list = append(list, buildEndpointResource(ep))
+ }
+ } else if queryByPid {
+ // Return all the prefix-matching endpoints
+ l := func(ep libnetwork.Endpoint) bool {
+ if strings.HasPrefix(ep.ID(), shortID) {
+ list = append(list, buildEndpointResource(ep))
+ }
+ return false
+ }
+ nw.WalkEndpoints(l)
+ } else {
+ for _, ep := range nw.Endpoints() {
+ epr := buildEndpointResource(ep)
+ list = append(list, epr)
+ }
+ }
+
+ return list, &successResponse
+}
+
+func procDeleteNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ target, by := detectNetworkTarget(vars)
+
+ nw, errRsp := findNetwork(c, target, by)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ err := nw.Delete()
+ if err != nil {
+ return nil, convertNetworkError(err)
+ }
+
+ return nil, &successResponse
+}
+
+/******************
+ Endpoint interface
+*******************/
+func procJoinEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ var ej endpointJoin
+ var setFctList []libnetwork.EndpointOption
+ err := json.Unmarshal(body, &ej)
+ if err != nil {
+ return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+ }
+
+ nwT, nwBy := detectNetworkTarget(vars)
+ epT, epBy := detectEndpointTarget(vars)
+
+ ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ sb, errRsp := findSandbox(c, ej.SandboxID, byID)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ for _, str := range ej.Aliases {
+ name, alias, err := netutils.ParseAlias(str)
+ if err != nil {
+ return "", convertNetworkError(err)
+ }
+ setFctList = append(setFctList, libnetwork.CreateOptionAlias(name, alias))
+ }
+
+ err = ep.Join(sb, setFctList...)
+ if err != nil {
+ return nil, convertNetworkError(err)
+ }
+ return sb.Key(), &successResponse
+}
+
+func procLeaveEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ nwT, nwBy := detectNetworkTarget(vars)
+ epT, epBy := detectEndpointTarget(vars)
+
+ ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ sb, errRsp := findSandbox(c, vars[urlSbID], byID)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ err := ep.Leave(sb)
+ if err != nil {
+ return nil, convertNetworkError(err)
+ }
+
+ return nil, &successResponse
+}
+
+func procDeleteEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ nwT, nwBy := detectNetworkTarget(vars)
+ epT, epBy := detectEndpointTarget(vars)
+
+ ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ err := ep.Delete(false)
+ if err != nil {
+ return nil, convertNetworkError(err)
+ }
+
+ return nil, &successResponse
+}
+
+/******************
+ Service interface
+*******************/
+func procGetServices(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ // Look for query filters and validate
+ nwName, filterByNwName := vars[urlNwName]
+ svName, queryBySvName := vars[urlEpName]
+ shortID, queryBySvPID := vars[urlEpPID]
+
+ if filterByNwName && queryBySvName || filterByNwName && queryBySvPID || queryBySvName && queryBySvPID {
+ return nil, &badQueryResponse
+ }
+
+ var list []*endpointResource
+
+ switch {
+ case filterByNwName:
+ // return all service present on the specified network
+ nw, errRsp := findNetwork(c, nwName, byName)
+ if !errRsp.isOK() {
+ return list, &successResponse
+ }
+ for _, ep := range nw.Endpoints() {
+ epr := buildEndpointResource(ep)
+ list = append(list, epr)
+ }
+ case queryBySvName:
+ // Look in each network for the service with the specified name
+ l := func(ep libnetwork.Endpoint) bool {
+ if ep.Name() == svName {
+ list = append(list, buildEndpointResource(ep))
+ return true
+ }
+ return false
+ }
+ for _, nw := range c.Networks() {
+ nw.WalkEndpoints(l)
+ }
+ case queryBySvPID:
+ // Return all the prefix-matching services
+ l := func(ep libnetwork.Endpoint) bool {
+ if strings.HasPrefix(ep.ID(), shortID) {
+ list = append(list, buildEndpointResource(ep))
+ }
+ return false
+ }
+ for _, nw := range c.Networks() {
+ nw.WalkEndpoints(l)
+ }
+ default:
+ for _, nw := range c.Networks() {
+ for _, ep := range nw.Endpoints() {
+ epr := buildEndpointResource(ep)
+ list = append(list, epr)
+ }
+ }
+ }
+
+ return list, &successResponse
+}
+
+func procGetService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ epT, epBy := detectEndpointTarget(vars)
+ sv, errRsp := findService(c, epT, epBy)
+ if !errRsp.isOK() {
+ return nil, endpointToService(errRsp)
+ }
+ return buildEndpointResource(sv), &successResponse
+}
+
+func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ var sp servicePublish
+
+ err := json.Unmarshal(body, &sp)
+ if err != nil {
+ return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+ }
+
+ n, errRsp := findNetwork(c, sp.Network, byName)
+ if !errRsp.isOK() {
+ return "", errRsp
+ }
+
+ var setFctList []libnetwork.EndpointOption
+ for _, str := range sp.MyAliases {
+ setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str))
+ }
+
+ ep, err := n.CreateEndpoint(sp.Name, setFctList...)
+ if err != nil {
+ return "", endpointToService(convertNetworkError(err))
+ }
+
+ return ep.ID(), &createdResponse
+}
+
+func procUnpublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ var sd serviceDelete
+
+ if body != nil {
+ err := json.Unmarshal(body, &sd)
+ if err != nil {
+ return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+ }
+ }
+
+ epT, epBy := detectEndpointTarget(vars)
+ sv, errRsp := findService(c, epT, epBy)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ if err := sv.Delete(sd.Force); err != nil {
+ return nil, endpointToService(convertNetworkError(err))
+ }
+ return nil, &successResponse
+}
+
+func procAttachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ var bk endpointJoin
+ var setFctList []libnetwork.EndpointOption
+ err := json.Unmarshal(body, &bk)
+ if err != nil {
+ return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+ }
+
+ epT, epBy := detectEndpointTarget(vars)
+ sv, errRsp := findService(c, epT, epBy)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ sb, errRsp := findSandbox(c, bk.SandboxID, byID)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ for _, str := range bk.Aliases {
+ name, alias, err := netutils.ParseAlias(str)
+ if err != nil {
+ return "", convertNetworkError(err)
+ }
+ setFctList = append(setFctList, libnetwork.CreateOptionAlias(name, alias))
+ }
+
+ err = sv.Join(sb, setFctList...)
+ if err != nil {
+ return nil, convertNetworkError(err)
+ }
+
+ return sb.Key(), &successResponse
+}
+
+func procDetachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ epT, epBy := detectEndpointTarget(vars)
+ sv, errRsp := findService(c, epT, epBy)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ sb, errRsp := findSandbox(c, vars[urlSbID], byID)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ err := sv.Leave(sb)
+ if err != nil {
+ return nil, convertNetworkError(err)
+ }
+
+ return nil, &successResponse
+}
+
+/******************
+ Sandbox interface
+*******************/
+func procGetSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ if epT, ok := vars[urlEpID]; ok {
+ sv, errRsp := findService(c, epT, byID)
+ if !errRsp.isOK() {
+ return nil, endpointToService(errRsp)
+ }
+ return buildSandboxResource(sv.Info().Sandbox()), &successResponse
+ }
+
+ sbT, by := detectSandboxTarget(vars)
+ sb, errRsp := findSandbox(c, sbT, by)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+ return buildSandboxResource(sb), &successResponse
+}
+
+type cndFnMkr func(string) cndFn
+type cndFn func(libnetwork.Sandbox) bool
+
+// list of (query type, condition function makers) couples
+var cndMkrList = []struct {
+ identifier string
+ maker cndFnMkr
+}{
+ {urlSbPID, func(id string) cndFn {
+ return func(sb libnetwork.Sandbox) bool { return strings.HasPrefix(sb.ID(), id) }
+ }},
+ {urlCnID, func(id string) cndFn {
+ return func(sb libnetwork.Sandbox) bool { return sb.ContainerID() == id }
+ }},
+ {urlCnPID, func(id string) cndFn {
+ return func(sb libnetwork.Sandbox) bool { return strings.HasPrefix(sb.ContainerID(), id) }
+ }},
+}
+
+func getQueryCondition(vars map[string]string) func(libnetwork.Sandbox) bool {
+ for _, im := range cndMkrList {
+ if val, ok := vars[im.identifier]; ok {
+ return im.maker(val)
+ }
+ }
+ return func(sb libnetwork.Sandbox) bool { return true }
+}
+
+func sandboxWalker(condition cndFn, list *[]*sandboxResource) libnetwork.SandboxWalker {
+ return func(sb libnetwork.Sandbox) bool {
+ if condition(sb) {
+ *list = append(*list, buildSandboxResource(sb))
+ }
+ return false
+ }
+}
+
+func procGetSandboxes(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ var list []*sandboxResource
+
+ cnd := getQueryCondition(vars)
+ c.WalkSandboxes(sandboxWalker(cnd, &list))
+
+ return list, &successResponse
+}
+
+func procDeleteSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+ sbT, by := detectSandboxTarget(vars)
+
+ sb, errRsp := findSandbox(c, sbT, by)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+
+ err := sb.Delete()
+ if err != nil {
+ return nil, convertNetworkError(err)
+ }
+
+ return nil, &successResponse
+}
+
+/***********
+ Utilities
+************/
+const (
+ byID = iota
+ byName
+)
+
+func detectNetworkTarget(vars map[string]string) (string, int) {
+ if target, ok := vars[urlNwName]; ok {
+ return target, byName
+ }
+ if target, ok := vars[urlNwID]; ok {
+ return target, byID
+ }
+ // vars are populated from the URL, following cannot happen
+ panic("Missing URL variable parameter for network")
+}
+
+func detectSandboxTarget(vars map[string]string) (string, int) {
+ if target, ok := vars[urlSbID]; ok {
+ return target, byID
+ }
+ // vars are populated from the URL, following cannot happen
+ panic("Missing URL variable parameter for sandbox")
+}
+
+func detectEndpointTarget(vars map[string]string) (string, int) {
+ if target, ok := vars[urlEpName]; ok {
+ return target, byName
+ }
+ if target, ok := vars[urlEpID]; ok {
+ return target, byID
+ }
+ // vars are populated from the URL, following cannot happen
+ panic("Missing URL variable parameter for endpoint")
+}
+
+func findNetwork(c libnetwork.NetworkController, s string, by int) (libnetwork.Network, *responseStatus) {
+ var (
+ nw libnetwork.Network
+ err error
+ )
+ switch by {
+ case byID:
+ nw, err = c.NetworkByID(s)
+ case byName:
+ if s == "" {
+ s = c.Config().Daemon.DefaultNetwork
+ }
+ nw, err = c.NetworkByName(s)
+ default:
+ panic(fmt.Sprintf("unexpected selector for network search: %d", by))
+ }
+ if err != nil {
+ if _, ok := err.(types.NotFoundError); ok {
+ return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound}
+ }
+ return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
+ }
+ return nw, &successResponse
+}
+
+func findSandbox(c libnetwork.NetworkController, s string, by int) (libnetwork.Sandbox, *responseStatus) {
+ var (
+ sb libnetwork.Sandbox
+ err error
+ )
+
+ switch by {
+ case byID:
+ sb, err = c.SandboxByID(s)
+ default:
+ panic(fmt.Sprintf("unexpected selector for sandbox search: %d", by))
+ }
+ if err != nil {
+ if _, ok := err.(types.NotFoundError); ok {
+ return nil, &responseStatus{Status: "Resource not found: Sandbox", StatusCode: http.StatusNotFound}
+ }
+ return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
+ }
+ return sb, &successResponse
+}
+
+func findEndpoint(c libnetwork.NetworkController, ns, es string, nwBy, epBy int) (libnetwork.Endpoint, *responseStatus) {
+ nw, errRsp := findNetwork(c, ns, nwBy)
+ if !errRsp.isOK() {
+ return nil, errRsp
+ }
+ var (
+ err error
+ ep libnetwork.Endpoint
+ )
+ switch epBy {
+ case byID:
+ ep, err = nw.EndpointByID(es)
+ case byName:
+ ep, err = nw.EndpointByName(es)
+ default:
+ panic(fmt.Sprintf("unexpected selector for endpoint search: %d", epBy))
+ }
+ if err != nil {
+ if _, ok := err.(types.NotFoundError); ok {
+ return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound}
+ }
+ return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
+ }
+ return ep, &successResponse
+}
+
+func findService(c libnetwork.NetworkController, svs string, svBy int) (libnetwork.Endpoint, *responseStatus) {
+ for _, nw := range c.Networks() {
+ var (
+ ep libnetwork.Endpoint
+ err error
+ )
+ switch svBy {
+ case byID:
+ ep, err = nw.EndpointByID(svs)
+ case byName:
+ ep, err = nw.EndpointByName(svs)
+ default:
+ panic(fmt.Sprintf("unexpected selector for service search: %d", svBy))
+ }
+ if err == nil {
+ return ep, &successResponse
+ } else if _, ok := err.(types.NotFoundError); !ok {
+ return nil, convertNetworkError(err)
+ }
+ }
+ return nil, &responseStatus{Status: "Service not found", StatusCode: http.StatusNotFound}
+}
+
+func endpointToService(rsp *responseStatus) *responseStatus {
+ rsp.Status = strings.Replace(rsp.Status, "endpoint", "service", -1)
+ return rsp
+}
+
+func convertNetworkError(err error) *responseStatus {
+ var code int
+ switch err.(type) {
+ case types.BadRequestError:
+ code = http.StatusBadRequest
+ case types.ForbiddenError:
+ code = http.StatusForbidden
+ case types.NotFoundError:
+ code = http.StatusNotFound
+ case types.TimeoutError:
+ code = http.StatusRequestTimeout
+ case types.NotImplementedError:
+ code = http.StatusNotImplemented
+ case types.NoServiceError:
+ code = http.StatusServiceUnavailable
+ case types.InternalError:
+ code = http.StatusInternalServerError
+ default:
+ code = http.StatusInternalServerError
+ }
+ return &responseStatus{Status: err.Error(), StatusCode: code}
+}
+
+func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ return json.NewEncoder(w).Encode(v)
+}
--- /dev/null
+package api
+
+import (
+ "github.com/docker/libnetwork/drivers/bridge"
+ "github.com/docker/libnetwork/netlabel"
+)
+
+func GetOpsMap(bridgeName, defaultMTU string) map[string]string {
+ if defaultMTU == "" {
+ return map[string]string{
+ bridge.BridgeName: bridgeName,
+ }
+ }
+ return map[string]string{
+ bridge.BridgeName: bridgeName,
+ netlabel.DriverMTU: defaultMTU,
+ }
+}
--- /dev/null
+package api
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "regexp"
+ "runtime"
+ "testing"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+const (
+ bridgeNetType = "bridge"
+ bridgeName = "docker0"
+)
+
+func i2s(i interface{}) string {
+ s, ok := i.(string)
+ if !ok {
+ panic(fmt.Sprintf("Failed i2s for %v", i))
+ }
+ return s
+}
+
+func i2e(i interface{}) *endpointResource {
+ s, ok := i.(*endpointResource)
+ if !ok {
+ panic(fmt.Sprintf("Failed i2e for %v", i))
+ }
+ return s
+}
+
+func i2eL(i interface{}) []*endpointResource {
+ s, ok := i.([]*endpointResource)
+ if !ok {
+ panic(fmt.Sprintf("Failed i2eL for %v", i))
+ }
+ return s
+}
+
+func i2n(i interface{}) *networkResource {
+ s, ok := i.(*networkResource)
+ if !ok {
+ panic(fmt.Sprintf("Failed i2n for %v", i))
+ }
+ return s
+}
+
+func i2nL(i interface{}) []*networkResource {
+ s, ok := i.([]*networkResource)
+ if !ok {
+ panic(fmt.Sprintf("Failed i2nL for %v", i))
+ }
+ return s
+}
+
+func i2sb(i interface{}) *sandboxResource {
+ s, ok := i.(*sandboxResource)
+ if !ok {
+ panic(fmt.Sprintf("Failed i2sb for %v", i))
+ }
+ return s
+}
+
+func i2sbL(i interface{}) []*sandboxResource {
+ s, ok := i.([]*sandboxResource)
+ if !ok {
+ panic(fmt.Sprintf("Failed i2sbL for %v", i))
+ }
+ return s
+}
+
+func createTestNetwork(t *testing.T, network string) (libnetwork.NetworkController, libnetwork.Network) {
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": network,
+ },
+ }
+ netGeneric := libnetwork.NetworkOptionGeneric(netOption)
+ nw, err := c.NewNetwork(bridgeNetType, network, "", netGeneric)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return c, nw
+}
+
+func getExposedPorts() []types.TransportPort {
+ return []types.TransportPort{
+ {Proto: types.TCP, Port: uint16(5000)},
+ {Proto: types.UDP, Port: uint16(400)},
+ {Proto: types.TCP, Port: uint16(600)},
+ }
+}
+
+func getPortMapping() []types.PortBinding {
+ return []types.PortBinding{
+ {Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
+ {Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
+ {Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
+ }
+}
+
+func TestMain(m *testing.M) {
+ if reexec.Init() {
+ return
+ }
+ os.Exit(m.Run())
+}
+
+func TestSandboxOptionParser(t *testing.T) {
+ hn := "host1"
+ dn := "docker.com"
+ hp := "/etc/hosts"
+ rc := "/etc/resolv.conf"
+ dnss := []string{"8.8.8.8", "172.28.34.5"}
+ ehs := []extraHost{{Name: "extra1", Address: "172.28.9.1"}, {Name: "extra2", Address: "172.28.9.2"}}
+
+ sb := sandboxCreate{
+ HostName: hn,
+ DomainName: dn,
+ HostsPath: hp,
+ ResolvConfPath: rc,
+ DNS: dnss,
+ ExtraHosts: ehs,
+ UseDefaultSandbox: true,
+ }
+
+ if len(sb.parseOptions()) != 9 {
+ t.Fatal("Failed to generate all libnetwork.SandboxOption methods")
+ }
+}
+
+func TestJson(t *testing.T) {
+ nc := networkCreate{NetworkType: bridgeNetType}
+ b, err := json.Marshal(nc)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var ncp networkCreate
+ err = json.Unmarshal(b, &ncp)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if nc.NetworkType != ncp.NetworkType {
+ t.Fatalf("Incorrect networkCreate after json encoding/deconding: %v", ncp)
+ }
+
+ jl := endpointJoin{SandboxID: "abcdef456789"}
+ b, err = json.Marshal(jl)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var jld endpointJoin
+ err = json.Unmarshal(b, &jld)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if jl.SandboxID != jld.SandboxID {
+ t.Fatalf("Incorrect endpointJoin after json encoding/deconding: %v", jld)
+ }
+}
+
+func TestCreateDeleteNetwork(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ badBody, err := json.Marshal("bad body")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars := make(map[string]string)
+ _, errRsp := procCreateNetwork(c, nil, badBody)
+ if errRsp == &createdResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp)
+ }
+
+ incompleteBody, err := json.Marshal(networkCreate{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, errRsp = procCreateNetwork(c, vars, incompleteBody)
+ if errRsp == &createdResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp)
+ }
+
+ dops := GetOpsMap("abc", "")
+ nops := map[string]string{}
+ nc := networkCreate{Name: "network_1", NetworkType: bridgeNetType, DriverOpts: dops, NetworkOpts: nops}
+ goodBody, err := json.Marshal(nc)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, errRsp = procCreateNetwork(c, vars, goodBody)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ vars[urlNwName] = ""
+ _, errRsp = procDeleteNetwork(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+
+ vars[urlNwName] = "abc"
+ _, errRsp = procDeleteNetwork(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+
+ vars[urlNwName] = "network_1"
+ _, errRsp = procDeleteNetwork(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+}
+
+func TestGetNetworksAndEndpoints(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ ops := GetOpsMap("api_test_nw", "")
+ nc := networkCreate{Name: "sh", NetworkType: bridgeNetType, DriverOpts: ops}
+ body, err := json.Marshal(nc)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars := make(map[string]string)
+ inid, errRsp := procCreateNetwork(c, vars, body)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ nid, ok := inid.(string)
+ if !ok {
+ t.FailNow()
+ }
+
+ ec1 := endpointCreate{
+ Name: "ep1",
+ }
+ b1, err := json.Marshal(ec1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ec2 := endpointCreate{Name: "ep2"}
+ b2, err := json.Marshal(ec2)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars[urlNwName] = "sh"
+ vars[urlEpName] = "ep1"
+ ieid1, errRsp := procCreateEndpoint(c, vars, b1)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ eid1 := i2s(ieid1)
+ vars[urlEpName] = "ep2"
+ ieid2, errRsp := procCreateEndpoint(c, vars, b2)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ eid2 := i2s(ieid2)
+
+ vars[urlNwName] = ""
+ vars[urlEpName] = "ep1"
+ _, errRsp = procGetEndpoint(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure but succeeded: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected to fail with http.StatusBadRequest, but got: %d", errRsp.StatusCode)
+ }
+
+ vars = make(map[string]string)
+ vars[urlNwName] = "sh"
+ vars[urlEpID] = ""
+ _, errRsp = procGetEndpoint(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure but succeeded: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected to fail with http.StatusBadRequest, but got: %d", errRsp.StatusCode)
+ }
+
+ vars = make(map[string]string)
+ vars[urlNwID] = ""
+ vars[urlEpID] = eid1
+ _, errRsp = procGetEndpoint(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure but succeeded: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected to fail with http.StatusBadRequest, but got: %d", errRsp.StatusCode)
+ }
+
+ // nw by name and ep by id
+ vars[urlNwName] = "sh"
+ i1, errRsp := procGetEndpoint(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ // nw by name and ep by name
+ delete(vars, urlEpID)
+ vars[urlEpName] = "ep1"
+ i2, errRsp := procGetEndpoint(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ // nw by id and ep by name
+ delete(vars, urlNwName)
+ vars[urlNwID] = nid
+ i3, errRsp := procGetEndpoint(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ // nw by id and ep by id
+ delete(vars, urlEpName)
+ vars[urlEpID] = eid1
+ i4, errRsp := procGetEndpoint(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ id1 := i2e(i1).ID
+ if id1 != i2e(i2).ID || id1 != i2e(i3).ID || id1 != i2e(i4).ID {
+ t.Fatalf("Endpoints retrieved via different query parameters differ: %v, %v, %v, %v", i1, i2, i3, i4)
+ }
+
+ vars[urlNwName] = ""
+ _, errRsp = procGetEndpoints(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ delete(vars, urlNwName)
+ vars[urlNwID] = "fakeID"
+ _, errRsp = procGetEndpoints(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlNwID] = nid
+ _, errRsp = procGetEndpoints(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ vars[urlNwName] = "sh"
+ iepList, errRsp := procGetEndpoints(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ epList := i2eL(iepList)
+ if len(epList) != 2 {
+ t.Fatalf("Did not return the expected number (2) of endpoint resources: %d", len(epList))
+ }
+ if "sh" != epList[0].Network || "sh" != epList[1].Network {
+ t.Fatal("Did not find expected network name in endpoint resources")
+ }
+
+ vars = make(map[string]string)
+ vars[urlNwName] = ""
+ _, errRsp = procGetNetwork(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ vars[urlNwName] = "shhhhh"
+ _, errRsp = procGetNetwork(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ vars[urlNwName] = "sh"
+ inr1, errRsp := procGetNetwork(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ nr1 := i2n(inr1)
+
+ delete(vars, urlNwName)
+ vars[urlNwID] = "acacac"
+ _, errRsp = procGetNetwork(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure. Got: %v", errRsp)
+ }
+ vars[urlNwID] = nid
+ inr2, errRsp := procGetNetwork(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("procgetNetworkByName() != procgetNetworkById(), %v vs %v", inr1, inr2)
+ }
+ nr2 := i2n(inr2)
+ if nr1.Name != nr2.Name || nr1.Type != nr2.Type || nr1.ID != nr2.ID || len(nr1.Endpoints) != len(nr2.Endpoints) {
+ t.Fatalf("Get by name and Get failure: %v", errRsp)
+ }
+
+ if len(nr1.Endpoints) != 2 {
+ t.Fatalf("Did not find the expected number (2) of endpoint resources in the network resource: %d", len(nr1.Endpoints))
+ }
+ for _, er := range nr1.Endpoints {
+ if er.ID != eid1 && er.ID != eid2 {
+ t.Fatalf("Did not find the expected endpoint resources in the network resource: %v", nr1.Endpoints)
+ }
+ }
+
+ iList, errRsp := procGetNetworks(c, nil, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ netList := i2nL(iList)
+ if len(netList) != 1 {
+ t.Fatal("Did not return the expected number of network resources")
+ }
+ if nid != netList[0].ID {
+ t.Fatalf("Did not find expected network %s: %v", nid, netList)
+ }
+
+ _, errRsp = procDeleteNetwork(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlEpName] = "ep1"
+ _, errRsp = procDeleteEndpoint(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ delete(vars, urlEpName)
+ iepList, errRsp = procGetEndpoints(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ epList = i2eL(iepList)
+ if len(epList) != 1 {
+ t.Fatalf("Did not return the expected number (1) of endpoint resources: %d", len(epList))
+ }
+
+ vars[urlEpName] = "ep2"
+ _, errRsp = procDeleteEndpoint(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ iepList, errRsp = procGetEndpoints(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ epList = i2eL(iepList)
+ if len(epList) != 0 {
+ t.Fatalf("Did not return the expected number (0) of endpoint resources: %d", len(epList))
+ }
+
+ _, errRsp = procDeleteNetwork(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ iList, errRsp = procGetNetworks(c, nil, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ netList = i2nL(iList)
+ if len(netList) != 0 {
+ t.Fatal("Did not return the expected number of network resources")
+ }
+}
+
+func TestProcGetServices(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ // Create 2 networks
+ netName1 := "production"
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": netName1,
+ },
+ }
+ nw1, err := c.NewNetwork(bridgeNetType, netName1, "", libnetwork.NetworkOptionGeneric(netOption))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ netName2 := "workdev"
+ netOption = options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": netName2,
+ },
+ }
+ nw2, err := c.NewNetwork(bridgeNetType, netName2, "", libnetwork.NetworkOptionGeneric(netOption))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars := make(map[string]string)
+ li, errRsp := procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list := i2eL(li)
+ if len(list) != 0 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+
+ // Add a couple of services on one network and one on the other network
+ ep11, err := nw1.CreateEndpoint("db-prod")
+ if err != nil {
+ t.Fatal(err)
+ }
+ ep12, err := nw1.CreateEndpoint("web-prod")
+ if err != nil {
+ t.Fatal(err)
+ }
+ ep21, err := nw2.CreateEndpoint("db-dev")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ li, errRsp = procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list = i2eL(li)
+ if len(list) != 3 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+
+ // Filter by network
+ vars[urlNwName] = netName1
+ li, errRsp = procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list = i2eL(li)
+ if len(list) != 2 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+
+ vars[urlNwName] = netName2
+ li, errRsp = procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list = i2eL(li)
+ if len(list) != 1 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+
+ vars[urlNwName] = "unknown-network"
+ li, errRsp = procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list = i2eL(li)
+ if len(list) != 0 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+
+ // Query by name
+ delete(vars, urlNwName)
+ vars[urlEpName] = "db-prod"
+ li, errRsp = procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list = i2eL(li)
+ if len(list) != 1 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+
+ vars[urlEpName] = "no-service"
+ li, errRsp = procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list = i2eL(li)
+ if len(list) != 0 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+
+ // Query by id or partial id
+ delete(vars, urlEpName)
+ vars[urlEpPID] = ep12.ID()
+ li, errRsp = procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list = i2eL(li)
+ if len(list) != 1 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+ if list[0].ID != ep12.ID() {
+ t.Fatalf("Unexpected element in response: %v", list)
+ }
+
+ vars[urlEpPID] = "non-id"
+ li, errRsp = procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list = i2eL(li)
+ if len(list) != 0 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+
+ delete(vars, urlEpPID)
+ err = ep11.Delete(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = ep12.Delete(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = ep21.Delete(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ li, errRsp = procGetServices(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ list = i2eL(li)
+ if len(list) != 0 {
+ t.Fatalf("Unexpected services in response: %v", list)
+ }
+}
+
+func TestProcGetService(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ c, nw := createTestNetwork(t, "network")
+ defer c.Stop()
+ ep1, err := nw.CreateEndpoint("db")
+ if err != nil {
+ t.Fatal(err)
+ }
+ ep2, err := nw.CreateEndpoint("web")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars := map[string]string{urlEpID: ""}
+ _, errRsp := procGetService(c, vars, nil)
+ if errRsp.isOK() {
+ t.Fatal("Expected failure, but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d, but got: %d", http.StatusBadRequest, errRsp.StatusCode)
+ }
+
+ vars[urlEpID] = "unknown-service-id"
+ _, errRsp = procGetService(c, vars, nil)
+ if errRsp.isOK() {
+ t.Fatal("Expected failure, but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d. (%v)", http.StatusNotFound, errRsp.StatusCode, errRsp)
+ }
+
+ vars[urlEpID] = ep1.ID()
+ si, errRsp := procGetService(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ sv := i2e(si)
+ if sv.ID != ep1.ID() {
+ t.Fatalf("Unexpected service resource returned: %v", sv)
+ }
+
+ vars[urlEpID] = ep2.ID()
+ si, errRsp = procGetService(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ sv = i2e(si)
+ if sv.ID != ep2.ID() {
+ t.Fatalf("Unexpected service resource returned: %v", sv)
+ }
+}
+
+func TestProcPublishUnpublishService(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ c, _ := createTestNetwork(t, "network")
+ defer c.Stop()
+
+ vars := make(map[string]string)
+
+ vbad, err := json.Marshal("bad service create data")
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp := procPublishService(c, vars, vbad)
+ if errRsp == &createdResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+
+ b, err := json.Marshal(servicePublish{Name: ""})
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp = procPublishService(c, vars, b)
+ if errRsp == &createdResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+
+ b, err = json.Marshal(servicePublish{Name: "db"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp = procPublishService(c, vars, b)
+ if errRsp == &createdResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+
+ b, err = json.Marshal(servicePublish{Name: "db", Network: "unknown-network"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp = procPublishService(c, vars, b)
+ if errRsp == &createdResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+ }
+
+ b, err = json.Marshal(servicePublish{Name: "", Network: "network"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp = procPublishService(c, vars, b)
+ if errRsp == &createdResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+
+ b, err = json.Marshal(servicePublish{Name: "db", Network: "network"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp = procPublishService(c, vars, b)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ sp := servicePublish{
+ Name: "web",
+ Network: "network",
+ }
+ b, err = json.Marshal(sp)
+ if err != nil {
+ t.Fatal(err)
+ }
+ si, errRsp := procPublishService(c, vars, b)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ sid := i2s(si)
+
+ vars[urlEpID] = ""
+ _, errRsp = procUnpublishService(c, vars, nil)
+ if errRsp.isOK() {
+ t.Fatal("Expected failure but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+
+ vars[urlEpID] = "unknown-service-id"
+ _, errRsp = procUnpublishService(c, vars, nil)
+ if errRsp.isOK() {
+ t.Fatal("Expected failure but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+ }
+
+ vars[urlEpID] = sid
+ _, errRsp = procUnpublishService(c, vars, nil)
+ if !errRsp.isOK() {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ _, errRsp = procGetService(c, vars, nil)
+ if errRsp.isOK() {
+ t.Fatal("Expected failure, but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d. (%v)", http.StatusNotFound, errRsp.StatusCode, errRsp)
+ }
+}
+
+func TestAttachDetachBackend(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ c, nw := createTestNetwork(t, "network")
+ defer c.Stop()
+ ep1, err := nw.CreateEndpoint("db")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars := make(map[string]string)
+
+ vbad, err := json.Marshal("bad data")
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp := procAttachBackend(c, vars, vbad)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlEpName] = "endpoint"
+ bad, err := json.Marshal(endpointJoin{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp = procAttachBackend(c, vars, bad)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+ }
+
+ vars[urlEpID] = "db"
+ _, errRsp = procGetSandbox(c, vars, nil)
+ if errRsp.isOK() {
+ t.Fatalf("Expected failure. Got %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+ }
+
+ vars[urlEpName] = "db"
+ _, errRsp = procAttachBackend(c, vars, bad)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+
+ cid := "abcdefghi"
+ sbox, err := c.NewSandbox(cid)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sid := sbox.ID()
+ defer sbox.Delete()
+
+ jl := endpointJoin{SandboxID: sid}
+ jlb, err := json.Marshal(jl)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, errRsp = procAttachBackend(c, vars, jlb)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure, got: %v", errRsp)
+ }
+
+ sli, errRsp := procGetSandboxes(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure, got: %v", errRsp)
+ }
+ sl := i2sbL(sli)
+ if len(sl) != 1 {
+ t.Fatalf("Did not find expected number of sandboxes attached to the service: %d", len(sl))
+ }
+ if sl[0].ContainerID != cid {
+ t.Fatalf("Did not find expected sandbox attached to the service: %v", sl[0])
+ }
+
+ _, errRsp = procUnpublishService(c, vars, nil)
+ if errRsp.isOK() {
+ t.Fatal("Expected failure but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusForbidden {
+ t.Fatalf("Expected %d. Got: %v", http.StatusForbidden, errRsp)
+ }
+
+ vars[urlEpName] = "endpoint"
+ _, errRsp = procDetachBackend(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+ }
+
+ vars[urlEpName] = "db"
+ _, errRsp = procDetachBackend(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+
+ vars[urlSbID] = sid
+ _, errRsp = procDetachBackend(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure, got: %v", errRsp)
+ }
+
+ delete(vars, urlEpID)
+ si, errRsp := procGetSandbox(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure, got: %v", errRsp)
+ }
+ sb := i2sb(si)
+ if sb.ContainerID != cid {
+ t.Fatalf("Did not find expected sandbox. Got %v", sb)
+ }
+
+ err = ep1.Delete(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestDetectGetNetworksInvalidQueryComposition(t *testing.T) {
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ vars := map[string]string{urlNwName: "x", urlNwPID: "y"}
+ _, errRsp := procGetNetworks(c, vars, nil)
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+}
+
+func TestDetectGetEndpointsInvalidQueryComposition(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ c, _ := createTestNetwork(t, "network")
+ defer c.Stop()
+
+ vars := map[string]string{urlNwName: "network", urlEpName: "x", urlEpPID: "y"}
+ _, errRsp := procGetEndpoints(c, vars, nil)
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+}
+
+func TestDetectGetServicesInvalidQueryComposition(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ c, _ := createTestNetwork(t, "network")
+ defer c.Stop()
+
+ vars := map[string]string{urlNwName: "network", urlEpName: "x", urlEpPID: "y"}
+ _, errRsp := procGetServices(c, vars, nil)
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+ }
+}
+
+func TestFindNetworkUtilPanic(t *testing.T) {
+ defer checkPanic(t)
+ findNetwork(nil, "", -1)
+}
+
+func TestFindNetworkUtil(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ c, nw := createTestNetwork(t, "network")
+ defer c.Stop()
+
+ nid := nw.ID()
+
+ _, errRsp := findNetwork(c, "", byName)
+ if errRsp == &successResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d, but got: %d", http.StatusBadRequest, errRsp.StatusCode)
+ }
+
+ n, errRsp := findNetwork(c, nid, byID)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ if n == nil {
+ t.Fatal("Unexpected nil libnetwork.Network")
+ }
+ if nid != n.ID() {
+ t.Fatalf("Incorrect libnetwork.Network resource. It has different id: %v", n)
+ }
+ if "network" != n.Name() {
+ t.Fatalf("Incorrect libnetwork.Network resource. It has different name: %v", n)
+ }
+
+ n, errRsp = findNetwork(c, "network", byName)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ if n == nil {
+ t.Fatal("Unexpected nil libnetwork.Network")
+ }
+ if nid != n.ID() {
+ t.Fatalf("Incorrect libnetwork.Network resource. It has different id: %v", n)
+ }
+ if "network" != n.Name() {
+ t.Fatalf("Incorrect libnetwork.Network resource. It has different name: %v", n)
+ }
+
+ if err := n.Delete(); err != nil {
+ t.Fatalf("Failed to delete the network: %s", err.Error())
+ }
+
+ _, errRsp = findNetwork(c, nid, byID)
+ if errRsp == &successResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+ }
+
+ _, errRsp = findNetwork(c, "network", byName)
+ if errRsp == &successResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+ }
+}
+
+func TestCreateDeleteEndpoints(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ nc := networkCreate{Name: "firstNet", NetworkType: bridgeNetType}
+ body, err := json.Marshal(nc)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars := make(map[string]string)
+ i, errRsp := procCreateNetwork(c, vars, body)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ nid := i2s(i)
+
+ vbad, err := json.Marshal("bad endpoint create data")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars[urlNwName] = "firstNet"
+ _, errRsp = procCreateEndpoint(c, vars, vbad)
+ if errRsp == &createdResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+
+ b, err := json.Marshal(endpointCreate{Name: ""})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars[urlNwName] = "secondNet"
+ _, errRsp = procCreateEndpoint(c, vars, b)
+ if errRsp == &createdResponse {
+ t.Fatal("Expected to fail but succeeded")
+ }
+
+ vars[urlNwName] = "firstNet"
+ _, errRsp = procCreateEndpoint(c, vars, b)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure but succeeded: %v", errRsp)
+ }
+
+ b, err = json.Marshal(endpointCreate{Name: "firstEp"})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ i, errRsp = procCreateEndpoint(c, vars, b)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+ eid := i2s(i)
+
+ _, errRsp = findEndpoint(c, "myNet", "firstEp", byName, byName)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure but succeeded: %v", errRsp)
+ }
+
+ ep0, errRsp := findEndpoint(c, nid, "firstEp", byID, byName)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ ep1, errRsp := findEndpoint(c, "firstNet", "firstEp", byName, byName)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ ep2, errRsp := findEndpoint(c, nid, eid, byID, byID)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ ep3, errRsp := findEndpoint(c, "firstNet", eid, byName, byID)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ if ep0.ID() != ep1.ID() || ep0.ID() != ep2.ID() || ep0.ID() != ep3.ID() {
+ t.Fatalf("Different queries returned different endpoints: \nep0: %v\nep1: %v\nep2: %v\nep3: %v", ep0, ep1, ep2, ep3)
+ }
+
+ vars = make(map[string]string)
+ vars[urlNwName] = ""
+ vars[urlEpName] = "ep1"
+ _, errRsp = procDeleteEndpoint(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlNwName] = "firstNet"
+ vars[urlEpName] = ""
+ _, errRsp = procDeleteEndpoint(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlEpName] = "ep2"
+ _, errRsp = procDeleteEndpoint(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlEpName] = "firstEp"
+ _, errRsp = procDeleteEndpoint(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ _, errRsp = findEndpoint(c, "firstNet", "firstEp", byName, byName)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+}
+
+func TestJoinLeave(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ nb, err := json.Marshal(networkCreate{Name: "network", NetworkType: bridgeNetType})
+ if err != nil {
+ t.Fatal(err)
+ }
+ vars := make(map[string]string)
+ _, errRsp := procCreateNetwork(c, vars, nb)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ eb, err := json.Marshal(endpointCreate{Name: "endpoint"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ vars[urlNwName] = "network"
+ _, errRsp = procCreateEndpoint(c, vars, eb)
+ if errRsp != &createdResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ vbad, err := json.Marshal("bad data")
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp = procJoinEndpoint(c, vars, vbad)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlEpName] = "endpoint"
+ bad, err := json.Marshal(endpointJoin{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, errRsp = procJoinEndpoint(c, vars, bad)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ cid := "abcdefghi"
+ sb, err := c.NewSandbox(cid)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer sb.Delete()
+
+ jl := endpointJoin{SandboxID: sb.ID()}
+ jlb, err := json.Marshal(jl)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vars = make(map[string]string)
+ vars[urlNwName] = ""
+ vars[urlEpName] = ""
+ _, errRsp = procJoinEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlNwName] = "network"
+ vars[urlEpName] = ""
+ _, errRsp = procJoinEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlEpName] = "epoint"
+ _, errRsp = procJoinEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ // bad labels
+ vars[urlEpName] = "endpoint"
+ key, errRsp := procJoinEndpoint(c, vars, jlb)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure, got: %v", errRsp)
+ }
+
+ keyStr := i2s(key)
+ if keyStr == "" {
+ t.Fatal("Empty sandbox key")
+ }
+ _, errRsp = procDeleteEndpoint(c, vars, nil)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlNwName] = "network2"
+ _, errRsp = procLeaveEndpoint(c, vars, vbad)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ _, errRsp = procLeaveEndpoint(c, vars, bad)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ _, errRsp = procLeaveEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ vars = make(map[string]string)
+ vars[urlNwName] = ""
+ vars[urlEpName] = ""
+ _, errRsp = procLeaveEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ vars[urlNwName] = "network"
+ vars[urlEpName] = ""
+ _, errRsp = procLeaveEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ vars[urlEpName] = "2epoint"
+ _, errRsp = procLeaveEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+ vars[urlEpName] = "epoint"
+ vars[urlCnID] = "who"
+ _, errRsp = procLeaveEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ delete(vars, urlCnID)
+ vars[urlEpName] = "endpoint"
+ _, errRsp = procLeaveEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ vars[urlSbID] = sb.ID()
+ _, errRsp = procLeaveEndpoint(c, vars, jlb)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ _, errRsp = procLeaveEndpoint(c, vars, jlb)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, got: %v", errRsp)
+ }
+
+ _, errRsp = procDeleteEndpoint(c, vars, nil)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+}
+
+func TestFindEndpointUtilPanic(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+ defer checkPanic(t)
+ c, nw := createTestNetwork(t, "network")
+ defer c.Stop()
+
+ nid := nw.ID()
+ findEndpoint(c, nid, "", byID, -1)
+}
+
+func TestFindServiceUtilPanic(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+ defer checkPanic(t)
+ c, _ := createTestNetwork(t, "network")
+ defer c.Stop()
+
+ findService(c, "random_service", -1)
+}
+
+func TestFindEndpointUtil(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ c, nw := createTestNetwork(t, "network")
+ defer c.Stop()
+
+ nid := nw.ID()
+
+ ep, err := nw.CreateEndpoint("secondEp", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ eid := ep.ID()
+
+ _, errRsp := findEndpoint(c, nid, "", byID, byName)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, but got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusBadRequest {
+ t.Fatalf("Expected %d, but got: %d", http.StatusBadRequest, errRsp.StatusCode)
+ }
+
+ ep0, errRsp := findEndpoint(c, nid, "secondEp", byID, byName)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ ep1, errRsp := findEndpoint(c, "network", "secondEp", byName, byName)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ ep2, errRsp := findEndpoint(c, nid, eid, byID, byID)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ ep3, errRsp := findEndpoint(c, "network", eid, byName, byID)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ ep4, errRsp := findService(c, "secondEp", byName)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ ep5, errRsp := findService(c, eid, byID)
+ if errRsp != &successResponse {
+ t.Fatalf("Unexpected failure: %v", errRsp)
+ }
+
+ if ep0.ID() != ep1.ID() || ep0.ID() != ep2.ID() ||
+ ep0.ID() != ep3.ID() || ep0.ID() != ep4.ID() || ep0.ID() != ep5.ID() {
+ t.Fatal("Different queries returned different endpoints")
+ }
+
+ ep.Delete(false)
+
+ _, errRsp = findEndpoint(c, nid, "secondEp", byID, byName)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, but got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+ }
+
+ _, errRsp = findEndpoint(c, "network", "secondEp", byName, byName)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, but got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+ }
+
+ _, errRsp = findEndpoint(c, nid, eid, byID, byID)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, but got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+ }
+
+ _, errRsp = findEndpoint(c, "network", eid, byName, byID)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, but got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+ }
+
+ _, errRsp = findService(c, "secondEp", byName)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, but got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+ }
+
+ _, errRsp = findService(c, eid, byID)
+ if errRsp == &successResponse {
+ t.Fatalf("Expected failure, but got: %v", errRsp)
+ }
+ if errRsp.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+ }
+}
+
+func TestEndpointToService(t *testing.T) {
+ r := &responseStatus{Status: "this is one endpoint", StatusCode: http.StatusOK}
+ r = endpointToService(r)
+ if r.Status != "this is one service" {
+ t.Fatalf("endpointToService returned unexpected status string: %s", r.Status)
+ }
+
+ r = &responseStatus{Status: "this is one network", StatusCode: http.StatusOK}
+ r = endpointToService(r)
+ if r.Status != "this is one network" {
+ t.Fatalf("endpointToService returned unexpected status string: %s", r.Status)
+ }
+}
+
+func checkPanic(t *testing.T) {
+ if r := recover(); r != nil {
+ if _, ok := r.(runtime.Error); ok {
+ panic(r)
+ }
+ } else {
+ t.Fatal("Expected to panic, but succeeded")
+ }
+}
+
+func TestDetectNetworkTargetPanic(t *testing.T) {
+ defer checkPanic(t)
+ vars := make(map[string]string)
+ detectNetworkTarget(vars)
+}
+
+func TestDetectEndpointTargetPanic(t *testing.T) {
+ defer checkPanic(t)
+ vars := make(map[string]string)
+ detectEndpointTarget(vars)
+}
+
+func TestResponseStatus(t *testing.T) {
+ list := []int{
+ http.StatusBadGateway,
+ http.StatusBadRequest,
+ http.StatusConflict,
+ http.StatusContinue,
+ http.StatusExpectationFailed,
+ http.StatusForbidden,
+ http.StatusFound,
+ http.StatusGatewayTimeout,
+ http.StatusGone,
+ http.StatusHTTPVersionNotSupported,
+ http.StatusInternalServerError,
+ http.StatusLengthRequired,
+ http.StatusMethodNotAllowed,
+ http.StatusMovedPermanently,
+ http.StatusMultipleChoices,
+ http.StatusNoContent,
+ http.StatusNonAuthoritativeInfo,
+ http.StatusNotAcceptable,
+ http.StatusNotFound,
+ http.StatusNotModified,
+ http.StatusPartialContent,
+ http.StatusPaymentRequired,
+ http.StatusPreconditionFailed,
+ http.StatusProxyAuthRequired,
+ http.StatusRequestEntityTooLarge,
+ http.StatusRequestTimeout,
+ http.StatusRequestURITooLong,
+ http.StatusRequestedRangeNotSatisfiable,
+ http.StatusResetContent,
+ http.StatusServiceUnavailable,
+ http.StatusSwitchingProtocols,
+ http.StatusTemporaryRedirect,
+ http.StatusUnauthorized,
+ http.StatusUnsupportedMediaType,
+ http.StatusUseProxy,
+ }
+ for _, c := range list {
+ r := responseStatus{StatusCode: c}
+ if r.isOK() {
+ t.Fatalf("isOK() returned true for code% d", c)
+ }
+ }
+
+ r := responseStatus{StatusCode: http.StatusOK}
+ if !r.isOK() {
+ t.Fatal("isOK() failed")
+ }
+
+ r = responseStatus{StatusCode: http.StatusCreated}
+ if !r.isOK() {
+ t.Fatal("isOK() failed")
+ }
+}
+
+// Local structs for end to end testing of api.go
+type localReader struct {
+ data []byte
+ beBad bool
+}
+
+func newLocalReader(data []byte) *localReader {
+ lr := &localReader{data: make([]byte, len(data))}
+ copy(lr.data, data)
+ return lr
+}
+
+func (l *localReader) Read(p []byte) (n int, err error) {
+ if l.beBad {
+ return 0, errors.New("I am a bad reader")
+ }
+ if p == nil {
+ return -1, errors.New("nil buffer passed")
+ }
+ if l.data == nil || len(l.data) == 0 {
+ return 0, io.EOF
+ }
+ copy(p[:], l.data[:])
+ return len(l.data), io.EOF
+}
+
+type localResponseWriter struct {
+ body []byte
+ statusCode int
+}
+
+func newWriter() *localResponseWriter {
+ return &localResponseWriter{}
+}
+
+func (f *localResponseWriter) Header() http.Header {
+ return make(map[string][]string, 0)
+}
+
+func (f *localResponseWriter) Write(data []byte) (int, error) {
+ if data == nil {
+ return -1, errors.New("nil data passed")
+ }
+
+ f.body = make([]byte, len(data))
+ copy(f.body, data)
+
+ return len(f.body), nil
+}
+
+func (f *localResponseWriter) WriteHeader(c int) {
+ f.statusCode = c
+}
+
+func testWriteJSON(t *testing.T, testCode int, testData interface{}) {
+ testDataMarshalled, err := json.Marshal(testData)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rsp := newWriter()
+ writeJSON(rsp, testCode, testData)
+ if rsp.statusCode != testCode {
+ t.Fatalf("writeJSON() failed to set the status code. Expected %d. Got %d", testCode, rsp.statusCode)
+ }
+ // writeJSON calls json.Encode and it appends '\n' to the result,
+ // while json.Marshal not
+ expected := append(testDataMarshalled, byte('\n'))
+ if !bytes.Equal(expected, rsp.body) {
+ t.Fatalf("writeJSON() failed to set the body. Expected %q. Got %q", expected, rsp.body)
+ }
+}
+
+func TestWriteJSON(t *testing.T) {
+ testWriteJSON(t, 55, "test data as string")
+ testWriteJSON(t, 55, []byte("test data as bytes"))
+}
+
+func TestHttpHandlerUninit(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ h := &httpHandler{c: c}
+ h.initRouter()
+ if h.r == nil {
+ t.Fatal("initRouter() did not initialize the router")
+ }
+
+ rsp := newWriter()
+ req, err := http.NewRequest("GET", "/v1.19/networks", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ handleRequest := NewHTTPHandler(nil)
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusServiceUnavailable {
+ t.Fatalf("Expected (%d). Got (%d): %s", http.StatusServiceUnavailable, rsp.statusCode, rsp.body)
+ }
+
+ handleRequest = NewHTTPHandler(c)
+
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Expected (%d). Got: (%d): %s", http.StatusOK, rsp.statusCode, rsp.body)
+ }
+
+ var list []*networkResource
+ err = json.Unmarshal(rsp.body, &list)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(list) != 0 {
+ t.Fatalf("Expected empty list. Got %v", list)
+ }
+
+ n, err := c.NewNetwork(bridgeNetType, "didietro", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ nwr := buildNetworkResource(n)
+ expected, err := json.Marshal([]*networkResource{nwr})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+ if len(rsp.body) == 0 {
+ t.Fatal("Empty list of networks")
+ }
+ if bytes.Equal(rsp.body, expected) {
+ t.Fatal("Incorrect list of networks in response's body")
+ }
+}
+
+func TestHttpHandlerBadBody(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ rsp := newWriter()
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+ handleRequest := NewHTTPHandler(c)
+
+ req, err := http.NewRequest("POST", "/v1.19/networks", &localReader{beBad: true})
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusBadRequest {
+ t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusBadRequest, rsp.statusCode, string(rsp.body))
+ }
+
+ body := []byte{}
+ lr := newLocalReader(body)
+ req, err = http.NewRequest("POST", "/v1.19/networks", lr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusBadRequest {
+ t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusBadRequest, rsp.statusCode, string(rsp.body))
+ }
+}
+
+func TestEndToEnd(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ rsp := newWriter()
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ handleRequest := NewHTTPHandler(c)
+
+ dops := GetOpsMap("cdef", "1460")
+ nops := map[string]string{}
+
+ // Create network
+ nc := networkCreate{Name: "network-fiftyfive", NetworkType: bridgeNetType, DriverOpts: dops, NetworkOpts: nops}
+ body, err := json.Marshal(nc)
+ if err != nil {
+ t.Fatal(err)
+ }
+ lr := newLocalReader(body)
+ req, err := http.NewRequest("POST", "/v1.19/networks", lr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusCreated {
+ t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusCreated, rsp.statusCode, string(rsp.body))
+ }
+ if len(rsp.body) == 0 {
+ t.Fatal("Empty response body")
+ }
+
+ var nid string
+ err = json.Unmarshal(rsp.body, &nid)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Query networks collection
+ req, err = http.NewRequest("GET", "/v1.19/networks?name=", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+ }
+ var list []*networkResource
+ err = json.Unmarshal(rsp.body, &list)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(list) != 0 {
+ t.Fatalf("Expected empty list. Got %v", list)
+ }
+
+ req, err = http.NewRequest("GET", "/v1.19/networks", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ b0 := make([]byte, len(rsp.body))
+ copy(b0, rsp.body)
+
+ req, err = http.NewRequest("GET", "/v1.19/networks?name=network-fiftyfive", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ if !bytes.Equal(b0, rsp.body) {
+ t.Fatal("Expected same body from GET /networks and GET /networks?name=<nw> when only network <nw> exist.")
+ }
+
+ // Query network by name
+ req, err = http.NewRequest("GET", "/v1.19/networks?name=culo", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ err = json.Unmarshal(rsp.body, &list)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(list) != 0 {
+ t.Fatalf("Expected empty list. Got %v", list)
+ }
+
+ req, err = http.NewRequest("GET", "/v1.19/networks?name=network-fiftyfive", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ err = json.Unmarshal(rsp.body, &list)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(list) == 0 {
+ t.Fatal("Expected non empty list")
+ }
+ if list[0].Name != "network-fiftyfive" || nid != list[0].ID {
+ t.Fatalf("Incongruent resource found: %v", list[0])
+ }
+
+ // Query network by partial id
+ chars := []byte(nid)
+ partial := string(chars[0 : len(chars)/2])
+ req, err = http.NewRequest("GET", "/v1.19/networks?partial-id="+partial, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ err = json.Unmarshal(rsp.body, &list)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(list) == 0 {
+ t.Fatal("Expected non empty list")
+ }
+ if list[0].Name != "network-fiftyfive" || nid != list[0].ID {
+ t.Fatalf("Incongruent resource found: %v", list[0])
+ }
+
+ // Get network by id
+ req, err = http.NewRequest("GET", "/v1.19/networks/"+nid, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ var nwr networkResource
+ err = json.Unmarshal(rsp.body, &nwr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if nwr.Name != "network-fiftyfive" || nid != nwr.ID {
+ t.Fatalf("Incongruent resource found: %v", nwr)
+ }
+
+ // Create endpoint
+ eb, err := json.Marshal(endpointCreate{Name: "ep-TwentyTwo"})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ lr = newLocalReader(eb)
+ req, err = http.NewRequest("POST", "/v1.19/networks/"+nid+"/endpoints", lr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusCreated {
+ t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusCreated, rsp.statusCode, string(rsp.body))
+ }
+ if len(rsp.body) == 0 {
+ t.Fatal("Empty response body")
+ }
+
+ var eid string
+ err = json.Unmarshal(rsp.body, &eid)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Query endpoint(s)
+ req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints?name=bla", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+ var epList []*endpointResource
+ err = json.Unmarshal(rsp.body, &epList)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(epList) != 0 {
+ t.Fatalf("Expected empty list. Got %v", epList)
+ }
+
+ // Query endpoint by name
+ req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints?name=ep-TwentyTwo", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ err = json.Unmarshal(rsp.body, &epList)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(epList) == 0 {
+ t.Fatal("Empty response body")
+ }
+ if epList[0].Name != "ep-TwentyTwo" || eid != epList[0].ID {
+ t.Fatalf("Incongruent resource found: %v", epList[0])
+ }
+
+ // Query endpoint by partial id
+ chars = []byte(eid)
+ partial = string(chars[0 : len(chars)/2])
+ req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints?partial-id="+partial, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ err = json.Unmarshal(rsp.body, &epList)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(epList) == 0 {
+ t.Fatal("Empty response body")
+ }
+ if epList[0].Name != "ep-TwentyTwo" || eid != epList[0].ID {
+ t.Fatalf("Incongruent resource found: %v", epList[0])
+ }
+
+ // Get endpoint by id
+ req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints/"+eid, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ var epr endpointResource
+ err = json.Unmarshal(rsp.body, &epr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if epr.Name != "ep-TwentyTwo" || epr.ID != eid {
+ t.Fatalf("Incongruent resource found: %v", epr)
+ }
+
+ // Store two container ids and one partial ids
+ cid1 := "container10010000000"
+ cid2 := "container20010000000"
+ chars = []byte(cid1)
+ cpid1 := string(chars[0 : len(chars)/2])
+
+ // Create sandboxes
+ sb1, err := json.Marshal(sandboxCreate{
+ ContainerID: cid1,
+ PortMapping: getPortMapping(),
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ lr = newLocalReader(sb1)
+ req, err = http.NewRequest("POST", "/v5.22/sandboxes", lr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusCreated {
+ t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusCreated, rsp.statusCode, string(rsp.body))
+ }
+ if len(rsp.body) == 0 {
+ t.Fatal("Empty response body")
+ }
+ // Get sandbox id and partial id
+ var sid1 string
+ err = json.Unmarshal(rsp.body, &sid1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sb2, err := json.Marshal(sandboxCreate{
+ ContainerID: cid2,
+ ExposedPorts: getExposedPorts(),
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ lr = newLocalReader(sb2)
+ req, err = http.NewRequest("POST", "/v5.22/sandboxes", lr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusCreated {
+ t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusCreated, rsp.statusCode, string(rsp.body))
+ }
+ if len(rsp.body) == 0 {
+ t.Fatal("Empty response body")
+ }
+ // Get sandbox id and partial id
+ var sid2 string
+ err = json.Unmarshal(rsp.body, &sid2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ chars = []byte(sid2)
+ spid2 := string(chars[0 : len(chars)/2])
+
+ // Query sandboxes
+ req, err = http.NewRequest("GET", "/sandboxes", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ var sbList []*sandboxResource
+ err = json.Unmarshal(rsp.body, &sbList)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(sbList) != 2 {
+ t.Fatalf("Expected 2 elements in list. Got %v", sbList)
+ }
+
+ // Get sandbox by id
+ req, err = http.NewRequest("GET", "/sandboxes/"+sid1, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ var sbr sandboxResource
+ err = json.Unmarshal(rsp.body, &sbr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if sbr.ContainerID != cid1 {
+ t.Fatalf("Incongruent resource found: %v", sbr)
+ }
+
+ // Query sandbox by partial sandbox id
+ req, err = http.NewRequest("GET", "/sandboxes?partial-id="+spid2, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ err = json.Unmarshal(rsp.body, &sbList)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(sbList) == 0 {
+ t.Fatal("Empty response body")
+ }
+ if sbList[0].ID != sid2 {
+ t.Fatalf("Incongruent resource found: %v", sbList[0])
+ }
+
+ // Query sandbox by container id
+ req, err = http.NewRequest("GET", "/sandboxes?container-id="+cid2, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ err = json.Unmarshal(rsp.body, &sbList)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(sbList) == 0 {
+ t.Fatal("Empty response body")
+ }
+ if sbList[0].ContainerID != cid2 {
+ t.Fatalf("Incongruent resource found: %v", sbList[0])
+ }
+
+ // Query sandbox by partial container id
+ req, err = http.NewRequest("GET", "/sandboxes?partial-container-id="+cpid1, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+ if rsp.statusCode != http.StatusOK {
+ t.Fatalf("Unexpected failure: (%d): %s", rsp.statusCode, rsp.body)
+ }
+
+ err = json.Unmarshal(rsp.body, &sbList)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(sbList) == 0 {
+ t.Fatal("Empty response body")
+ }
+ if sbList[0].ContainerID != cid1 {
+ t.Fatalf("Incongruent resource found: %v", sbList[0])
+ }
+}
+
+func TestEndToEndErrorMessage(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ rsp := newWriter()
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ c, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+ handleRequest := NewHTTPHandler(c)
+
+ body := []byte{}
+ lr := newLocalReader(body)
+ req, err := http.NewRequest("POST", "/v1.19/networks", lr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handleRequest(rsp, req)
+
+ if len(rsp.body) == 0 {
+ t.Fatal("Empty response body.")
+ }
+ empty := []byte("\"\"")
+ if bytes.Equal(empty, bytes.TrimSpace(rsp.body)) {
+ t.Fatal("Empty response error message.")
+ }
+}
+
+type bre struct{}
+
+func (b *bre) Error() string {
+ return "I am a bad request error"
+}
+func (b *bre) BadRequest() {}
+
+type nfe struct{}
+
+func (n *nfe) Error() string {
+ return "I am a not found error"
+}
+func (n *nfe) NotFound() {}
+
+type forb struct{}
+
+func (f *forb) Error() string {
+ return "I am a bad request error"
+}
+func (f *forb) Forbidden() {}
+
+type notimpl struct{}
+
+func (nip *notimpl) Error() string {
+ return "I am a not implemented error"
+}
+func (nip *notimpl) NotImplemented() {}
+
+type inter struct{}
+
+func (it *inter) Error() string {
+ return "I am an internal error"
+}
+func (it *inter) Internal() {}
+
+type tout struct{}
+
+func (to *tout) Error() string {
+ return "I am a timeout error"
+}
+func (to *tout) Timeout() {}
+
+type noserv struct{}
+
+func (nos *noserv) Error() string {
+ return "I am a no service error"
+}
+func (nos *noserv) NoService() {}
+
+type notclassified struct{}
+
+func (noc *notclassified) Error() string {
+ return "I am a non classified error"
+}
+
+func TestErrorConversion(t *testing.T) {
+ if convertNetworkError(new(bre)).StatusCode != http.StatusBadRequest {
+ t.Fatal("Failed to recognize BadRequest error")
+ }
+
+ if convertNetworkError(new(nfe)).StatusCode != http.StatusNotFound {
+ t.Fatal("Failed to recognize NotFound error")
+ }
+
+ if convertNetworkError(new(forb)).StatusCode != http.StatusForbidden {
+ t.Fatal("Failed to recognize Forbidden error")
+ }
+
+ if convertNetworkError(new(notimpl)).StatusCode != http.StatusNotImplemented {
+ t.Fatal("Failed to recognize NotImplemented error")
+ }
+
+ if convertNetworkError(new(inter)).StatusCode != http.StatusInternalServerError {
+ t.Fatal("Failed to recognize Internal error")
+ }
+
+ if convertNetworkError(new(tout)).StatusCode != http.StatusRequestTimeout {
+ t.Fatal("Failed to recognize Timeout error")
+ }
+
+ if convertNetworkError(new(noserv)).StatusCode != http.StatusServiceUnavailable {
+ t.Fatal("Failed to recognize No Service error")
+ }
+
+ if convertNetworkError(new(notclassified)).StatusCode != http.StatusInternalServerError {
+ t.Fatal("Failed to recognize not classified error as Internal error")
+ }
+}
+
+func TestFieldRegex(t *testing.T) {
+ pr := regexp.MustCompile(regex)
+ qr := regexp.MustCompile(`^` + qregx + `$`) // mux compiles it like this
+
+ if pr.MatchString("") {
+ t.Fatal("Unexpected match")
+ }
+ if !qr.MatchString("") {
+ t.Fatal("Unexpected match failure")
+ }
+
+ if pr.MatchString(":") {
+ t.Fatal("Unexpected match")
+ }
+ if qr.MatchString(":") {
+ t.Fatal("Unexpected match")
+ }
+
+ if pr.MatchString(".") {
+ t.Fatal("Unexpected match")
+ }
+ if qr.MatchString(".") {
+ t.Fatal("Unexpected match")
+ }
+}
--- /dev/null
+package api
+
+import "github.com/docker/libnetwork/types"
+
+/***********
+ Resources
+************/
+
+// networkResource is the body of the "get network" http response message
+type networkResource struct {
+ Name string `json:"name"`
+ ID string `json:"id"`
+ Type string `json:"type"`
+ Endpoints []*endpointResource `json:"endpoints"`
+}
+
+// endpointResource is the body of the "get endpoint" http response message
+type endpointResource struct {
+ Name string `json:"name"`
+ ID string `json:"id"`
+ Network string `json:"network"`
+}
+
+// sandboxResource is the body of "get service backend" response message
+type sandboxResource struct {
+ ID string `json:"id"`
+ Key string `json:"key"`
+ ContainerID string `json:"container_id"`
+}
+
+/***********
+ Body types
+ ************/
+
+type ipamConf struct {
+ PreferredPool string
+ SubPool string
+ Gateway string
+ AuxAddresses map[string]string
+}
+
+// networkCreate is the expected body of the "create network" http request message
+type networkCreate struct {
+ Name string `json:"name"`
+ ID string `json:"id"`
+ NetworkType string `json:"network_type"`
+ IPv4Conf []ipamConf `json:"ipv4_configuration"`
+ DriverOpts map[string]string `json:"driver_opts"`
+ NetworkOpts map[string]string `json:"network_opts"`
+}
+
+// endpointCreate represents the body of the "create endpoint" http request message
+type endpointCreate struct {
+ Name string `json:"name"`
+ MyAliases []string `json:"my_aliases"`
+}
+
+// sandboxCreate is the expected body of the "create sandbox" http request message
+type sandboxCreate struct {
+ ContainerID string `json:"container_id"`
+ HostName string `json:"host_name"`
+ DomainName string `json:"domain_name"`
+ HostsPath string `json:"hosts_path"`
+ ResolvConfPath string `json:"resolv_conf_path"`
+ DNS []string `json:"dns"`
+ ExtraHosts []extraHost `json:"extra_hosts"`
+ UseDefaultSandbox bool `json:"use_default_sandbox"`
+ UseExternalKey bool `json:"use_external_key"`
+ ExposedPorts []types.TransportPort `json:"exposed_ports"`
+ PortMapping []types.PortBinding `json:"port_mapping"`
+}
+
+// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages
+type endpointJoin struct {
+ SandboxID string `json:"sandbox_id"`
+ Aliases []string `json:"aliases"`
+}
+
+// servicePublish represents the body of the "publish service" http request message
+type servicePublish struct {
+ Name string `json:"name"`
+ MyAliases []string `json:"my_aliases"`
+ Network string `json:"network_name"`
+}
+
+// serviceDelete represents the body of the "unpublish service" http request message
+type serviceDelete struct {
+ Name string `json:"name"`
+ Force bool `json:"force"`
+}
+
+// extraHost represents the extra host object
+type extraHost struct {
+ Name string `json:"name"`
+ Address string `json:"address"`
+}
--- /dev/null
+// Package bitseq provides a structure and utilities for representing long bitmask
+// as sequence of run-length encoded blocks. It operates directly on the encoded
+// representation, it does not decode/encode.
+package bitseq
+
+import (
+ "encoding/binary"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// block sequence constants
+// If needed we can think of making these configurable
+const (
+ blockLen = uint32(32)
+ blockBytes = uint64(blockLen / 8)
+ blockMAX = uint32(1<<blockLen - 1)
+ blockFirstBit = uint32(1) << (blockLen - 1)
+ invalidPos = uint64(0xFFFFFFFFFFFFFFFF)
+)
+
+var (
+ // ErrNoBitAvailable is returned when no more bits are available to set
+ ErrNoBitAvailable = errors.New("no bit available")
+ // ErrBitAllocated is returned when the specific bit requested is already set
+ ErrBitAllocated = errors.New("requested bit is already allocated")
+)
+
+// Handle contains the sequece representing the bitmask and its identifier
+type Handle struct {
+ bits uint64
+ unselected uint64
+ head *sequence
+ app string
+ id string
+ dbIndex uint64
+ dbExists bool
+ curr uint64
+ store datastore.DataStore
+ sync.Mutex
+}
+
+// NewHandle returns a thread-safe instance of the bitmask handler
+func NewHandle(app string, ds datastore.DataStore, id string, numElements uint64) (*Handle, error) {
+ h := &Handle{
+ app: app,
+ id: id,
+ store: ds,
+ bits: numElements,
+ unselected: numElements,
+ head: &sequence{
+ block: 0x0,
+ count: getNumBlocks(numElements),
+ },
+ }
+
+ if h.store == nil {
+ return h, nil
+ }
+
+ // Get the initial status from the ds if present.
+ if err := h.store.GetObject(datastore.Key(h.Key()...), h); err != nil && err != datastore.ErrKeyNotFound {
+ return nil, err
+ }
+
+ // If the handle is not in store, write it.
+ if !h.Exists() {
+ if err := h.writeToStore(); err != nil {
+ return nil, fmt.Errorf("failed to write bitsequence to store: %v", err)
+ }
+ }
+
+ return h, nil
+}
+
+// sequence represents a recurring sequence of 32 bits long bitmasks
+type sequence struct {
+ block uint32 // block is a symbol representing 4 byte long allocation bitmask
+ count uint64 // number of consecutive blocks (symbols)
+ next *sequence // next sequence
+}
+
+// String returns a string representation of the block sequence starting from this block
+func (s *sequence) toString() string {
+ var nextBlock string
+ if s.next == nil {
+ nextBlock = "end"
+ } else {
+ nextBlock = s.next.toString()
+ }
+ return fmt.Sprintf("(0x%x, %d)->%s", s.block, s.count, nextBlock)
+}
+
+// GetAvailableBit returns the position of the first unset bit in the bitmask represented by this sequence
+func (s *sequence) getAvailableBit(from uint64) (uint64, uint64, error) {
+ if s.block == blockMAX || s.count == 0 {
+ return invalidPos, invalidPos, ErrNoBitAvailable
+ }
+ bits := from
+ bitSel := blockFirstBit >> from
+ for bitSel > 0 && s.block&bitSel != 0 {
+ bitSel >>= 1
+ bits++
+ }
+ // Check if the loop exited because it could not
+ // find any available bit int block starting from
+ // "from". Return invalid pos in that case.
+ if bitSel == 0 {
+ return invalidPos, invalidPos, ErrNoBitAvailable
+ }
+ return bits / 8, bits % 8, nil
+}
+
+// GetCopy returns a copy of the linked list rooted at this node
+func (s *sequence) getCopy() *sequence {
+ n := &sequence{block: s.block, count: s.count}
+ pn := n
+ ps := s.next
+ for ps != nil {
+ pn.next = &sequence{block: ps.block, count: ps.count}
+ pn = pn.next
+ ps = ps.next
+ }
+ return n
+}
+
+// Equal checks if this sequence is equal to the passed one
+func (s *sequence) equal(o *sequence) bool {
+ this := s
+ other := o
+ for this != nil {
+ if other == nil {
+ return false
+ }
+ if this.block != other.block || this.count != other.count {
+ return false
+ }
+ this = this.next
+ other = other.next
+ }
+ // Check if other is longer than this
+ if other != nil {
+ return false
+ }
+ return true
+}
+
+// ToByteArray converts the sequence into a byte array
+func (s *sequence) toByteArray() ([]byte, error) {
+ var bb []byte
+
+ p := s
+ for p != nil {
+ b := make([]byte, 12)
+ binary.BigEndian.PutUint32(b[0:], p.block)
+ binary.BigEndian.PutUint64(b[4:], p.count)
+ bb = append(bb, b...)
+ p = p.next
+ }
+
+ return bb, nil
+}
+
+// fromByteArray construct the sequence from the byte array
+func (s *sequence) fromByteArray(data []byte) error {
+ l := len(data)
+ if l%12 != 0 {
+ return fmt.Errorf("cannot deserialize byte sequence of length %d (%v)", l, data)
+ }
+
+ p := s
+ i := 0
+ for {
+ p.block = binary.BigEndian.Uint32(data[i : i+4])
+ p.count = binary.BigEndian.Uint64(data[i+4 : i+12])
+ i += 12
+ if i == l {
+ break
+ }
+ p.next = &sequence{}
+ p = p.next
+ }
+
+ return nil
+}
+
+func (h *Handle) getCopy() *Handle {
+ return &Handle{
+ bits: h.bits,
+ unselected: h.unselected,
+ head: h.head.getCopy(),
+ app: h.app,
+ id: h.id,
+ dbIndex: h.dbIndex,
+ dbExists: h.dbExists,
+ store: h.store,
+ curr: h.curr,
+ }
+}
+
+// SetAnyInRange atomically sets the first unset bit in the specified range in the sequence and returns the corresponding ordinal
+func (h *Handle) SetAnyInRange(start, end uint64, serial bool) (uint64, error) {
+ if end < start || end >= h.bits {
+ return invalidPos, fmt.Errorf("invalid bit range [%d, %d]", start, end)
+ }
+ if h.Unselected() == 0 {
+ return invalidPos, ErrNoBitAvailable
+ }
+ return h.set(0, start, end, true, false, serial)
+}
+
+// SetAny atomically sets the first unset bit in the sequence and returns the corresponding ordinal
+func (h *Handle) SetAny(serial bool) (uint64, error) {
+ if h.Unselected() == 0 {
+ return invalidPos, ErrNoBitAvailable
+ }
+ return h.set(0, 0, h.bits-1, true, false, serial)
+}
+
+// Set atomically sets the corresponding bit in the sequence
+func (h *Handle) Set(ordinal uint64) error {
+ if err := h.validateOrdinal(ordinal); err != nil {
+ return err
+ }
+ _, err := h.set(ordinal, 0, 0, false, false, false)
+ return err
+}
+
+// Unset atomically unsets the corresponding bit in the sequence
+func (h *Handle) Unset(ordinal uint64) error {
+ if err := h.validateOrdinal(ordinal); err != nil {
+ return err
+ }
+ _, err := h.set(ordinal, 0, 0, false, true, false)
+ return err
+}
+
+// IsSet atomically checks if the ordinal bit is set. In case ordinal
+// is outside of the bit sequence limits, false is returned.
+func (h *Handle) IsSet(ordinal uint64) bool {
+ if err := h.validateOrdinal(ordinal); err != nil {
+ return false
+ }
+ h.Lock()
+ _, _, err := checkIfAvailable(h.head, ordinal)
+ h.Unlock()
+ return err != nil
+}
+
+func (h *Handle) runConsistencyCheck() bool {
+ corrupted := false
+ for p, c := h.head, h.head.next; c != nil; c = c.next {
+ if c.count == 0 {
+ corrupted = true
+ p.next = c.next
+ continue // keep same p
+ }
+ p = c
+ }
+ return corrupted
+}
+
+// CheckConsistency checks if the bit sequence is in an inconsistent state and attempts to fix it.
+// It looks for a corruption signature that may happen in docker 1.9.0 and 1.9.1.
+func (h *Handle) CheckConsistency() error {
+ for {
+ h.Lock()
+ store := h.store
+ h.Unlock()
+
+ if store != nil {
+ if err := store.GetObject(datastore.Key(h.Key()...), h); err != nil && err != datastore.ErrKeyNotFound {
+ return err
+ }
+ }
+
+ h.Lock()
+ nh := h.getCopy()
+ h.Unlock()
+
+ if !nh.runConsistencyCheck() {
+ return nil
+ }
+
+ if err := nh.writeToStore(); err != nil {
+ if _, ok := err.(types.RetryError); !ok {
+ return fmt.Errorf("internal failure while fixing inconsistent bitsequence: %v", err)
+ }
+ continue
+ }
+
+ logrus.Infof("Fixed inconsistent bit sequence in datastore:\n%s\n%s", h, nh)
+
+ h.Lock()
+ h.head = nh.head
+ h.Unlock()
+
+ return nil
+ }
+}
+
+// set/reset the bit
+func (h *Handle) set(ordinal, start, end uint64, any bool, release bool, serial bool) (uint64, error) {
+ var (
+ bitPos uint64
+ bytePos uint64
+ ret uint64
+ err error
+ )
+
+ for {
+ var store datastore.DataStore
+ curr := uint64(0)
+ h.Lock()
+ store = h.store
+ if store != nil {
+ h.Unlock() // The lock is acquired in the GetObject
+ if err := store.GetObject(datastore.Key(h.Key()...), h); err != nil && err != datastore.ErrKeyNotFound {
+ return ret, err
+ }
+ h.Lock() // Acquire the lock back
+ }
+ if serial {
+ curr = h.curr
+ }
+ // Get position if available
+ if release {
+ bytePos, bitPos = ordinalToPos(ordinal)
+ } else {
+ if any {
+ bytePos, bitPos, err = getAvailableFromCurrent(h.head, start, curr, end)
+ ret = posToOrdinal(bytePos, bitPos)
+ if err == nil {
+ h.curr = ret + 1
+ }
+ } else {
+ bytePos, bitPos, err = checkIfAvailable(h.head, ordinal)
+ ret = ordinal
+ }
+ }
+ if err != nil {
+ h.Unlock()
+ return ret, err
+ }
+
+ // Create a private copy of h and work on it
+ nh := h.getCopy()
+
+ nh.head = pushReservation(bytePos, bitPos, nh.head, release)
+ if release {
+ nh.unselected++
+ } else {
+ nh.unselected--
+ }
+
+ if h.store != nil {
+ h.Unlock()
+ // Attempt to write private copy to store
+ if err := nh.writeToStore(); err != nil {
+ if _, ok := err.(types.RetryError); !ok {
+ return ret, fmt.Errorf("internal failure while setting the bit: %v", err)
+ }
+ // Retry
+ continue
+ }
+ h.Lock()
+ }
+
+ // Previous atomic push was successful. Save private copy to local copy
+ h.unselected = nh.unselected
+ h.head = nh.head
+ h.dbExists = nh.dbExists
+ h.dbIndex = nh.dbIndex
+ h.Unlock()
+ return ret, nil
+ }
+}
+
+// checks is needed because to cover the case where the number of bits is not a multiple of blockLen
+func (h *Handle) validateOrdinal(ordinal uint64) error {
+ h.Lock()
+ defer h.Unlock()
+ if ordinal >= h.bits {
+ return errors.New("bit does not belong to the sequence")
+ }
+ return nil
+}
+
+// Destroy removes from the datastore the data belonging to this handle
+func (h *Handle) Destroy() error {
+ for {
+ if err := h.deleteFromStore(); err != nil {
+ if _, ok := err.(types.RetryError); !ok {
+ return fmt.Errorf("internal failure while destroying the sequence: %v", err)
+ }
+ // Fetch latest
+ if err := h.store.GetObject(datastore.Key(h.Key()...), h); err != nil {
+ if err == datastore.ErrKeyNotFound { // already removed
+ return nil
+ }
+ return fmt.Errorf("failed to fetch from store when destroying the sequence: %v", err)
+ }
+ continue
+ }
+ return nil
+ }
+}
+
+// ToByteArray converts this handle's data into a byte array
+func (h *Handle) ToByteArray() ([]byte, error) {
+
+ h.Lock()
+ defer h.Unlock()
+ ba := make([]byte, 16)
+ binary.BigEndian.PutUint64(ba[0:], h.bits)
+ binary.BigEndian.PutUint64(ba[8:], h.unselected)
+ bm, err := h.head.toByteArray()
+ if err != nil {
+ return nil, fmt.Errorf("failed to serialize head: %s", err.Error())
+ }
+ ba = append(ba, bm...)
+
+ return ba, nil
+}
+
+// FromByteArray reads his handle's data from a byte array
+func (h *Handle) FromByteArray(ba []byte) error {
+ if ba == nil {
+ return errors.New("nil byte array")
+ }
+
+ nh := &sequence{}
+ err := nh.fromByteArray(ba[16:])
+ if err != nil {
+ return fmt.Errorf("failed to deserialize head: %s", err.Error())
+ }
+
+ h.Lock()
+ h.head = nh
+ h.bits = binary.BigEndian.Uint64(ba[0:8])
+ h.unselected = binary.BigEndian.Uint64(ba[8:16])
+ h.Unlock()
+
+ return nil
+}
+
+// Bits returns the length of the bit sequence
+func (h *Handle) Bits() uint64 {
+ return h.bits
+}
+
+// Unselected returns the number of bits which are not selected
+func (h *Handle) Unselected() uint64 {
+ h.Lock()
+ defer h.Unlock()
+ return h.unselected
+}
+
+func (h *Handle) String() string {
+ h.Lock()
+ defer h.Unlock()
+ return fmt.Sprintf("App: %s, ID: %s, DBIndex: 0x%x, Bits: %d, Unselected: %d, Sequence: %s Curr:%d",
+ h.app, h.id, h.dbIndex, h.bits, h.unselected, h.head.toString(), h.curr)
+}
+
+// MarshalJSON encodes Handle into json message
+func (h *Handle) MarshalJSON() ([]byte, error) {
+ m := map[string]interface{}{
+ "id": h.id,
+ }
+
+ b, err := h.ToByteArray()
+ if err != nil {
+ return nil, err
+ }
+ m["sequence"] = b
+ return json.Marshal(m)
+}
+
+// UnmarshalJSON decodes json message into Handle
+func (h *Handle) UnmarshalJSON(data []byte) error {
+ var (
+ m map[string]interface{}
+ b []byte
+ err error
+ )
+ if err = json.Unmarshal(data, &m); err != nil {
+ return err
+ }
+ h.id = m["id"].(string)
+ bi, _ := json.Marshal(m["sequence"])
+ if err := json.Unmarshal(bi, &b); err != nil {
+ return err
+ }
+ return h.FromByteArray(b)
+}
+
+// getFirstAvailable looks for the first unset bit in passed mask starting from start
+func getFirstAvailable(head *sequence, start uint64) (uint64, uint64, error) {
+ // Find sequence which contains the start bit
+ byteStart, bitStart := ordinalToPos(start)
+ current, _, precBlocks, inBlockBytePos := findSequence(head, byteStart)
+ // Derive the this sequence offsets
+ byteOffset := byteStart - inBlockBytePos
+ bitOffset := inBlockBytePos*8 + bitStart
+ for current != nil {
+ if current.block != blockMAX {
+ // If the current block is not full, check if there is any bit
+ // from the current bit in the current block. If not, before proceeding to the
+ // next block node, make sure we check for available bit in the next
+ // instance of the same block. Due to RLE same block signature will be
+ // compressed.
+ retry:
+ bytePos, bitPos, err := current.getAvailableBit(bitOffset)
+ if err != nil && precBlocks == current.count-1 {
+ // This is the last instance in the same block node,
+ // so move to the next block.
+ goto next
+ }
+ if err != nil {
+ // There are some more instances of the same block, so add the offset
+ // and be optimistic that you will find the available bit in the next
+ // instance of the same block.
+ bitOffset = 0
+ byteOffset += blockBytes
+ precBlocks++
+ goto retry
+ }
+ return byteOffset + bytePos, bitPos, err
+ }
+ // Moving to next block: Reset bit offset.
+ next:
+ bitOffset = 0
+ byteOffset += (current.count * blockBytes) - (precBlocks * blockBytes)
+ precBlocks = 0
+ current = current.next
+ }
+ return invalidPos, invalidPos, ErrNoBitAvailable
+}
+
+// getAvailableFromCurrent will look for available ordinal from the current ordinal.
+// If none found then it will loop back to the start to check of the available bit.
+// This can be further optimized to check from start till curr in case of a rollover
+func getAvailableFromCurrent(head *sequence, start, curr, end uint64) (uint64, uint64, error) {
+ var bytePos, bitPos uint64
+ var err error
+ if curr != 0 && curr > start {
+ bytePos, bitPos, err = getFirstAvailable(head, curr)
+ ret := posToOrdinal(bytePos, bitPos)
+ if end < ret || err != nil {
+ goto begin
+ }
+ return bytePos, bitPos, nil
+ }
+
+begin:
+ bytePos, bitPos, err = getFirstAvailable(head, start)
+ ret := posToOrdinal(bytePos, bitPos)
+ if end < ret || err != nil {
+ return invalidPos, invalidPos, ErrNoBitAvailable
+ }
+ return bytePos, bitPos, nil
+}
+
+// checkIfAvailable checks if the bit correspondent to the specified ordinal is unset
+// If the ordinal is beyond the sequence limits, a negative response is returned
+func checkIfAvailable(head *sequence, ordinal uint64) (uint64, uint64, error) {
+ bytePos, bitPos := ordinalToPos(ordinal)
+
+ // Find the sequence containing this byte
+ current, _, _, inBlockBytePos := findSequence(head, bytePos)
+ if current != nil {
+ // Check whether the bit corresponding to the ordinal address is unset
+ bitSel := blockFirstBit >> (inBlockBytePos*8 + bitPos)
+ if current.block&bitSel == 0 {
+ return bytePos, bitPos, nil
+ }
+ }
+
+ return invalidPos, invalidPos, ErrBitAllocated
+}
+
+// Given the byte position and the sequences list head, return the pointer to the
+// sequence containing the byte (current), the pointer to the previous sequence,
+// the number of blocks preceding the block containing the byte inside the current sequence.
+// If bytePos is outside of the list, function will return (nil, nil, 0, invalidPos)
+func findSequence(head *sequence, bytePos uint64) (*sequence, *sequence, uint64, uint64) {
+ // Find the sequence containing this byte
+ previous := head
+ current := head
+ n := bytePos
+ for current.next != nil && n >= (current.count*blockBytes) { // Nil check for less than 32 addresses masks
+ n -= (current.count * blockBytes)
+ previous = current
+ current = current.next
+ }
+
+ // If byte is outside of the list, let caller know
+ if n >= (current.count * blockBytes) {
+ return nil, nil, 0, invalidPos
+ }
+
+ // Find the byte position inside the block and the number of blocks
+ // preceding the block containing the byte inside this sequence
+ precBlocks := n / blockBytes
+ inBlockBytePos := bytePos % blockBytes
+
+ return current, previous, precBlocks, inBlockBytePos
+}
+
+// PushReservation pushes the bit reservation inside the bitmask.
+// Given byte and bit positions, identify the sequence (current) which holds the block containing the affected bit.
+// Create a new block with the modified bit according to the operation (allocate/release).
+// Create a new sequence containing the new block and insert it in the proper position.
+// Remove current sequence if empty.
+// Check if new sequence can be merged with neighbour (previous/next) sequences.
+//
+//
+// Identify "current" sequence containing block:
+// [prev seq] [current seq] [next seq]
+//
+// Based on block position, resulting list of sequences can be any of three forms:
+//
+// block position Resulting list of sequences
+// A) block is first in current: [prev seq] [new] [modified current seq] [next seq]
+// B) block is last in current: [prev seq] [modified current seq] [new] [next seq]
+// C) block is in the middle of current: [prev seq] [curr pre] [new] [curr post] [next seq]
+func pushReservation(bytePos, bitPos uint64, head *sequence, release bool) *sequence {
+ // Store list's head
+ newHead := head
+
+ // Find the sequence containing this byte
+ current, previous, precBlocks, inBlockBytePos := findSequence(head, bytePos)
+ if current == nil {
+ return newHead
+ }
+
+ // Construct updated block
+ bitSel := blockFirstBit >> (inBlockBytePos*8 + bitPos)
+ newBlock := current.block
+ if release {
+ newBlock &^= bitSel
+ } else {
+ newBlock |= bitSel
+ }
+
+ // Quit if it was a redundant request
+ if current.block == newBlock {
+ return newHead
+ }
+
+ // Current sequence inevitably looses one block, upadate count
+ current.count--
+
+ // Create new sequence
+ newSequence := &sequence{block: newBlock, count: 1}
+
+ // Insert the new sequence in the list based on block position
+ if precBlocks == 0 { // First in sequence (A)
+ newSequence.next = current
+ if current == head {
+ newHead = newSequence
+ previous = newHead
+ } else {
+ previous.next = newSequence
+ }
+ removeCurrentIfEmpty(&newHead, newSequence, current)
+ mergeSequences(previous)
+ } else if precBlocks == current.count { // Last in sequence (B)
+ newSequence.next = current.next
+ current.next = newSequence
+ mergeSequences(current)
+ } else { // In between the sequence (C)
+ currPre := &sequence{block: current.block, count: precBlocks, next: newSequence}
+ currPost := current
+ currPost.count -= precBlocks
+ newSequence.next = currPost
+ if currPost == head {
+ newHead = currPre
+ } else {
+ previous.next = currPre
+ }
+ // No merging or empty current possible here
+ }
+
+ return newHead
+}
+
+// Removes the current sequence from the list if empty, adjusting the head pointer if needed
+func removeCurrentIfEmpty(head **sequence, previous, current *sequence) {
+ if current.count == 0 {
+ if current == *head {
+ *head = current.next
+ } else {
+ previous.next = current.next
+ current = current.next
+ }
+ }
+}
+
+// Given a pointer to a sequence, it checks if it can be merged with any following sequences
+// It stops when no more merging is possible.
+// TODO: Optimization: only attempt merge from start to end sequence, no need to scan till the end of the list
+func mergeSequences(seq *sequence) {
+ if seq != nil {
+ // Merge all what possible from seq
+ for seq.next != nil && seq.block == seq.next.block {
+ seq.count += seq.next.count
+ seq.next = seq.next.next
+ }
+ // Move to next
+ mergeSequences(seq.next)
+ }
+}
+
+func getNumBlocks(numBits uint64) uint64 {
+ numBlocks := numBits / uint64(blockLen)
+ if numBits%uint64(blockLen) != 0 {
+ numBlocks++
+ }
+ return numBlocks
+}
+
+func ordinalToPos(ordinal uint64) (uint64, uint64) {
+ return ordinal / 8, ordinal % 8
+}
+
+func posToOrdinal(bytePos, bitPos uint64) uint64 {
+ return bytePos*8 + bitPos
+}
--- /dev/null
+package bitseq
+
+import (
+ "fmt"
+ "io/ioutil"
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/docker/libkv/store"
+ "github.com/docker/libkv/store/boltdb"
+ "github.com/docker/libnetwork/datastore"
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+const (
+ defaultPrefix = "/tmp/libnetwork/test/bitseq"
+)
+
+func init() {
+ boltdb.Register()
+}
+
+func randomLocalStore() (datastore.DataStore, error) {
+ tmp, err := ioutil.TempFile("", "libnetwork-")
+ if err != nil {
+ return nil, fmt.Errorf("Error creating temp file: %v", err)
+ }
+ if err := tmp.Close(); err != nil {
+ return nil, fmt.Errorf("Error closing temp file: %v", err)
+ }
+ return datastore.NewDataStore(datastore.LocalScope, &datastore.ScopeCfg{
+ Client: datastore.ScopeClientCfg{
+ Provider: "boltdb",
+ Address: defaultPrefix + tmp.Name(),
+ Config: &store.Config{
+ Bucket: "libnetwork",
+ ConnectionTimeout: 3 * time.Second,
+ },
+ },
+ })
+}
+
+func TestSequenceGetAvailableBit(t *testing.T) {
+ input := []struct {
+ head *sequence
+ from uint64
+ bytePos uint64
+ bitPos uint64
+ }{
+ {&sequence{block: 0x0, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0x0, count: 1}, 0, 0, 0},
+ {&sequence{block: 0x0, count: 100}, 0, 0, 0},
+
+ {&sequence{block: 0x80000000, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0x80000000, count: 1}, 0, 0, 1},
+ {&sequence{block: 0x80000000, count: 100}, 0, 0, 1},
+
+ {&sequence{block: 0xFF000000, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFF000000, count: 1}, 0, 1, 0},
+ {&sequence{block: 0xFF000000, count: 100}, 0, 1, 0},
+
+ {&sequence{block: 0xFF800000, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFF800000, count: 1}, 0, 1, 1},
+ {&sequence{block: 0xFF800000, count: 100}, 0, 1, 1},
+
+ {&sequence{block: 0xFFC0FF00, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFC0FF00, count: 1}, 0, 1, 2},
+ {&sequence{block: 0xFFC0FF00, count: 100}, 0, 1, 2},
+
+ {&sequence{block: 0xFFE0FF00, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFE0FF00, count: 1}, 0, 1, 3},
+ {&sequence{block: 0xFFE0FF00, count: 100}, 0, 1, 3},
+
+ {&sequence{block: 0xFFFEFF00, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFEFF00, count: 1}, 0, 1, 7},
+ {&sequence{block: 0xFFFEFF00, count: 100}, 0, 1, 7},
+
+ {&sequence{block: 0xFFFFC0FF, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFFC0FF, count: 1}, 0, 2, 2},
+ {&sequence{block: 0xFFFFC0FF, count: 100}, 0, 2, 2},
+
+ {&sequence{block: 0xFFFFFF00, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFFFF00, count: 1}, 0, 3, 0},
+ {&sequence{block: 0xFFFFFF00, count: 100}, 0, 3, 0},
+
+ {&sequence{block: 0xFFFFFFFE, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFFFFFE, count: 1}, 0, 3, 7},
+ {&sequence{block: 0xFFFFFFFE, count: 100}, 0, 3, 7},
+
+ {&sequence{block: 0xFFFFFFFF, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFFFFFF, count: 1}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFFFFFF, count: 100}, 0, invalidPos, invalidPos},
+
+ // now test with offset
+ {&sequence{block: 0x0, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0x0, count: 0}, 31, invalidPos, invalidPos},
+ {&sequence{block: 0x0, count: 0}, 32, invalidPos, invalidPos},
+ {&sequence{block: 0x0, count: 1}, 0, 0, 0},
+ {&sequence{block: 0x0, count: 1}, 1, 0, 1},
+ {&sequence{block: 0x0, count: 1}, 31, 3, 7},
+ {&sequence{block: 0xF0FF0000, count: 1}, 0, 0, 4},
+ {&sequence{block: 0xF0FF0000, count: 1}, 8, 2, 0},
+ {&sequence{block: 0xFFFFFFFF, count: 1}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFFFFFF, count: 1}, 16, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFFFFFF, count: 1}, 31, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFFFFFE, count: 1}, 0, 3, 7},
+ {&sequence{block: 0xFFFFFFFF, count: 2}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xFFFFFFFF, count: 2}, 32, invalidPos, invalidPos},
+ }
+
+ for n, i := range input {
+ b, bb, err := i.head.getAvailableBit(i.from)
+ if b != i.bytePos || bb != i.bitPos {
+ t.Fatalf("Error in sequence.getAvailableBit(%d) (%d).\nExp: (%d, %d)\nGot: (%d, %d), err: %v", i.from, n, i.bytePos, i.bitPos, b, bb, err)
+ }
+ }
+}
+
+func TestSequenceEqual(t *testing.T) {
+ input := []struct {
+ first *sequence
+ second *sequence
+ areEqual bool
+ }{
+ {&sequence{block: 0x0, count: 8, next: nil}, &sequence{block: 0x0, count: 8}, true},
+ {&sequence{block: 0x0, count: 0, next: nil}, &sequence{block: 0x0, count: 0}, true},
+ {&sequence{block: 0x0, count: 2, next: nil}, &sequence{block: 0x0, count: 1, next: &sequence{block: 0x0, count: 1}}, false},
+ {&sequence{block: 0x0, count: 2, next: &sequence{block: 0x1, count: 1}}, &sequence{block: 0x0, count: 2}, false},
+
+ {&sequence{block: 0x12345678, count: 8, next: nil}, &sequence{block: 0x12345678, count: 8}, true},
+ {&sequence{block: 0x12345678, count: 8, next: nil}, &sequence{block: 0x12345678, count: 9}, false},
+ {&sequence{block: 0x12345678, count: 1, next: &sequence{block: 0XFFFFFFFF, count: 1}}, &sequence{block: 0x12345678, count: 1}, false},
+ {&sequence{block: 0x12345678, count: 1}, &sequence{block: 0x12345678, count: 1, next: &sequence{block: 0XFFFFFFFF, count: 1}}, false},
+ }
+
+ for n, i := range input {
+ if i.areEqual != i.first.equal(i.second) {
+ t.Fatalf("Error in sequence.equal() (%d).\nExp: %t\nGot: %t,", n, i.areEqual, !i.areEqual)
+ }
+ }
+}
+
+func TestSequenceCopy(t *testing.T) {
+ s := getTestSequence()
+ n := s.getCopy()
+ if !s.equal(n) {
+ t.Fatal("copy of s failed")
+ }
+ if n == s {
+ t.Fatal("not true copy of s")
+ }
+}
+
+func TestGetFirstAvailable(t *testing.T) {
+ input := []struct {
+ mask *sequence
+ bytePos uint64
+ bitPos uint64
+ start uint64
+ }{
+ {&sequence{block: 0xffffffff, count: 2048}, invalidPos, invalidPos, 0},
+ {&sequence{block: 0x0, count: 8}, 0, 0, 0},
+ {&sequence{block: 0x80000000, count: 8}, 0, 1, 0},
+ {&sequence{block: 0xC0000000, count: 8}, 0, 2, 0},
+ {&sequence{block: 0xE0000000, count: 8}, 0, 3, 0},
+ {&sequence{block: 0xF0000000, count: 8}, 0, 4, 0},
+ {&sequence{block: 0xF8000000, count: 8}, 0, 5, 0},
+ {&sequence{block: 0xFC000000, count: 8}, 0, 6, 0},
+ {&sequence{block: 0xFE000000, count: 8}, 0, 7, 0},
+ {&sequence{block: 0xFE000000, count: 8}, 3, 0, 24},
+
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x00000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 0, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 1, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC0000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 2, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xE0000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 3, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xF0000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 4, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xF8000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 5, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFC000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 6, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFE000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 7, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x0E000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 0, 16},
+
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFF000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 0, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFF800000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 1, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFC00000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 2, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFE00000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 3, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFF00000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 4, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFF80000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 5, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFFC0000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 6, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFFE0000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 7, 0},
+
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xfffffffe, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 7, 7, 0},
+
+ {&sequence{block: 0xffffffff, count: 2, next: &sequence{block: 0x0, count: 6}}, 8, 0, 0},
+ {&sequence{block: 0xfffcffff, count: 1, next: &sequence{block: 0x0, count: 6}}, 4, 0, 16},
+ {&sequence{block: 0xfffcffff, count: 1, next: &sequence{block: 0x0, count: 6}}, 1, 7, 15},
+ {&sequence{block: 0xfffcffff, count: 1, next: &sequence{block: 0x0, count: 6}}, 1, 6, 10},
+ {&sequence{block: 0xfffcfffe, count: 1, next: &sequence{block: 0x0, count: 6}}, 3, 7, 31},
+ {&sequence{block: 0xfffcffff, count: 1, next: &sequence{block: 0xffffffff, count: 6}}, invalidPos, invalidPos, 31},
+ }
+
+ for n, i := range input {
+ bytePos, bitPos, _ := getFirstAvailable(i.mask, i.start)
+ if bytePos != i.bytePos || bitPos != i.bitPos {
+ t.Fatalf("Error in (%d) getFirstAvailable(). Expected (%d, %d). Got (%d, %d)", n, i.bytePos, i.bitPos, bytePos, bitPos)
+ }
+ }
+}
+
+func TestFindSequence(t *testing.T) {
+ input := []struct {
+ head *sequence
+ bytePos uint64
+ precBlocks uint64
+ inBlockBytePos uint64
+ }{
+ {&sequence{block: 0xffffffff, count: 0}, 0, 0, invalidPos},
+ {&sequence{block: 0xffffffff, count: 0}, 31, 0, invalidPos},
+ {&sequence{block: 0xffffffff, count: 0}, 100, 0, invalidPos},
+
+ {&sequence{block: 0x0, count: 1}, 0, 0, 0},
+ {&sequence{block: 0x0, count: 1}, 1, 0, 1},
+ {&sequence{block: 0x0, count: 1}, 31, 0, invalidPos},
+ {&sequence{block: 0x0, count: 1}, 60, 0, invalidPos},
+
+ {&sequence{block: 0xffffffff, count: 10}, 0, 0, 0},
+ {&sequence{block: 0xffffffff, count: 10}, 3, 0, 3},
+ {&sequence{block: 0xffffffff, count: 10}, 4, 1, 0},
+ {&sequence{block: 0xffffffff, count: 10}, 7, 1, 3},
+ {&sequence{block: 0xffffffff, count: 10}, 8, 2, 0},
+ {&sequence{block: 0xffffffff, count: 10}, 39, 9, 3},
+
+ {&sequence{block: 0xffffffff, count: 10, next: &sequence{block: 0xcc000000, count: 10}}, 79, 9, 3},
+ {&sequence{block: 0xffffffff, count: 10, next: &sequence{block: 0xcc000000, count: 10}}, 80, 0, invalidPos},
+ }
+
+ for n, i := range input {
+ _, _, precBlocks, inBlockBytePos := findSequence(i.head, i.bytePos)
+ if precBlocks != i.precBlocks || inBlockBytePos != i.inBlockBytePos {
+ t.Fatalf("Error in (%d) findSequence(). Expected (%d, %d). Got (%d, %d)", n, i.precBlocks, i.inBlockBytePos, precBlocks, inBlockBytePos)
+ }
+ }
+}
+
+func TestCheckIfAvailable(t *testing.T) {
+ input := []struct {
+ head *sequence
+ ordinal uint64
+ bytePos uint64
+ bitPos uint64
+ }{
+ {&sequence{block: 0xffffffff, count: 0}, 0, invalidPos, invalidPos},
+ {&sequence{block: 0xffffffff, count: 0}, 31, invalidPos, invalidPos},
+ {&sequence{block: 0xffffffff, count: 0}, 100, invalidPos, invalidPos},
+
+ {&sequence{block: 0x0, count: 1}, 0, 0, 0},
+ {&sequence{block: 0x0, count: 1}, 1, 0, 1},
+ {&sequence{block: 0x0, count: 1}, 31, 3, 7},
+ {&sequence{block: 0x0, count: 1}, 60, invalidPos, invalidPos},
+
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x800000ff, count: 1}}, 31, invalidPos, invalidPos},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x800000ff, count: 1}}, 32, invalidPos, invalidPos},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x800000ff, count: 1}}, 33, 4, 1},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC00000ff, count: 1}}, 33, invalidPos, invalidPos},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC00000ff, count: 1}}, 34, 4, 2},
+
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC00000ff, count: 1, next: &sequence{block: 0x0, count: 1}}}, 55, 6, 7},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC00000ff, count: 1, next: &sequence{block: 0x0, count: 1}}}, 56, invalidPos, invalidPos},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC00000ff, count: 1, next: &sequence{block: 0x0, count: 1}}}, 63, invalidPos, invalidPos},
+
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC00000ff, count: 1, next: &sequence{block: 0x0, count: 1}}}, 64, 8, 0},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC00000ff, count: 1, next: &sequence{block: 0x0, count: 1}}}, 95, 11, 7},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC00000ff, count: 1, next: &sequence{block: 0x0, count: 1}}}, 96, invalidPos, invalidPos},
+ }
+
+ for n, i := range input {
+ bytePos, bitPos, err := checkIfAvailable(i.head, i.ordinal)
+ if bytePos != i.bytePos || bitPos != i.bitPos {
+ t.Fatalf("Error in (%d) checkIfAvailable(ord:%d). Expected (%d, %d). Got (%d, %d). err: %v", n, i.ordinal, i.bytePos, i.bitPos, bytePos, bitPos, err)
+ }
+ }
+}
+
+func TestMergeSequences(t *testing.T) {
+ input := []struct {
+ original *sequence
+ merged *sequence
+ }{
+ {&sequence{block: 0xFE000000, count: 8, next: &sequence{block: 0xFE000000, count: 2}}, &sequence{block: 0xFE000000, count: 10}},
+ {&sequence{block: 0xFFFFFFFF, count: 8, next: &sequence{block: 0xFFFFFFFF, count: 1}}, &sequence{block: 0xFFFFFFFF, count: 9}},
+ {&sequence{block: 0xFFFFFFFF, count: 1, next: &sequence{block: 0xFFFFFFFF, count: 8}}, &sequence{block: 0xFFFFFFFF, count: 9}},
+
+ {&sequence{block: 0xFFFFFFF0, count: 8, next: &sequence{block: 0xFFFFFFF0, count: 1}}, &sequence{block: 0xFFFFFFF0, count: 9}},
+ {&sequence{block: 0xFFFFFFF0, count: 1, next: &sequence{block: 0xFFFFFFF0, count: 8}}, &sequence{block: 0xFFFFFFF0, count: 9}},
+
+ {&sequence{block: 0xFE, count: 8, next: &sequence{block: 0xFE, count: 1, next: &sequence{block: 0xFE, count: 5}}}, &sequence{block: 0xFE, count: 14}},
+ {&sequence{block: 0xFE, count: 8, next: &sequence{block: 0xFE, count: 1, next: &sequence{block: 0xFE, count: 5, next: &sequence{block: 0xFF, count: 1}}}},
+ &sequence{block: 0xFE, count: 14, next: &sequence{block: 0xFF, count: 1}}},
+
+ // No merge
+ {&sequence{block: 0xFE, count: 8, next: &sequence{block: 0xF8, count: 1, next: &sequence{block: 0xFE, count: 5}}},
+ &sequence{block: 0xFE, count: 8, next: &sequence{block: 0xF8, count: 1, next: &sequence{block: 0xFE, count: 5}}}},
+
+ // No merge from head: // Merge function tries to merge from passed head. If it can't merge with next, it does not reattempt with next as head
+ {&sequence{block: 0xFE, count: 8, next: &sequence{block: 0xFF, count: 1, next: &sequence{block: 0xFF, count: 5}}},
+ &sequence{block: 0xFE, count: 8, next: &sequence{block: 0xFF, count: 6}}},
+ }
+
+ for n, i := range input {
+ mergeSequences(i.original)
+ for !i.merged.equal(i.original) {
+ t.Fatalf("Error in (%d) mergeSequences().\nExp: %s\nGot: %s,", n, i.merged.toString(), i.original.toString())
+ }
+ }
+}
+
+func TestPushReservation(t *testing.T) {
+ input := []struct {
+ mask *sequence
+ bytePos uint64
+ bitPos uint64
+ newMask *sequence
+ }{
+ // Create first sequence and fill in 8 addresses starting from address 0
+ {&sequence{block: 0x0, count: 8, next: nil}, 0, 0, &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 7, next: nil}}},
+ {&sequence{block: 0x80000000, count: 8}, 0, 1, &sequence{block: 0xC0000000, count: 1, next: &sequence{block: 0x80000000, count: 7, next: nil}}},
+ {&sequence{block: 0xC0000000, count: 8}, 0, 2, &sequence{block: 0xE0000000, count: 1, next: &sequence{block: 0xC0000000, count: 7, next: nil}}},
+ {&sequence{block: 0xE0000000, count: 8}, 0, 3, &sequence{block: 0xF0000000, count: 1, next: &sequence{block: 0xE0000000, count: 7, next: nil}}},
+ {&sequence{block: 0xF0000000, count: 8}, 0, 4, &sequence{block: 0xF8000000, count: 1, next: &sequence{block: 0xF0000000, count: 7, next: nil}}},
+ {&sequence{block: 0xF8000000, count: 8}, 0, 5, &sequence{block: 0xFC000000, count: 1, next: &sequence{block: 0xF8000000, count: 7, next: nil}}},
+ {&sequence{block: 0xFC000000, count: 8}, 0, 6, &sequence{block: 0xFE000000, count: 1, next: &sequence{block: 0xFC000000, count: 7, next: nil}}},
+ {&sequence{block: 0xFE000000, count: 8}, 0, 7, &sequence{block: 0xFF000000, count: 1, next: &sequence{block: 0xFE000000, count: 7, next: nil}}},
+
+ {&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 7}}, 0, 1, &sequence{block: 0xC0000000, count: 1, next: &sequence{block: 0x0, count: 7, next: nil}}},
+
+ // Create second sequence and fill in 8 addresses starting from address 32
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x00000000, count: 1, next: &sequence{block: 0xffffffff, count: 6, next: nil}}}, 4, 0,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 1,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC0000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xC0000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 2,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xE0000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xE0000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 3,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xF0000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xF0000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 4,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xF8000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xF8000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 5,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFC000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFC000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 6,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFE000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFE000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 4, 7,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFF000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ // fill in 8 addresses starting from address 40
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFF000000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 0,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFF800000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFF800000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 1,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFC00000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFC00000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 2,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFE00000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFE00000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 3,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFF00000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFF00000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 4,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFF80000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFF80000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 5,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFFC0000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFFC0000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 6,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFFE0000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFFE0000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}, 5, 7,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xFFFF0000, count: 1, next: &sequence{block: 0xffffffff, count: 6}}}},
+
+ // Insert new sequence
+ {&sequence{block: 0xffffffff, count: 2, next: &sequence{block: 0x0, count: 6}}, 8, 0,
+ &sequence{block: 0xffffffff, count: 2, next: &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5}}}},
+ {&sequence{block: 0xffffffff, count: 2, next: &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5}}}, 8, 1,
+ &sequence{block: 0xffffffff, count: 2, next: &sequence{block: 0xC0000000, count: 1, next: &sequence{block: 0x0, count: 5}}}},
+
+ // Merge affected with next
+ {&sequence{block: 0xffffffff, count: 7, next: &sequence{block: 0xfffffffe, count: 2, next: &sequence{block: 0xffffffff, count: 1}}}, 31, 7,
+ &sequence{block: 0xffffffff, count: 8, next: &sequence{block: 0xfffffffe, count: 1, next: &sequence{block: 0xffffffff, count: 1}}}},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xfffffffc, count: 1, next: &sequence{block: 0xfffffffe, count: 6}}}, 7, 6,
+ &sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xfffffffe, count: 7}}},
+
+ // Merge affected with next and next.next
+ {&sequence{block: 0xffffffff, count: 7, next: &sequence{block: 0xfffffffe, count: 1, next: &sequence{block: 0xffffffff, count: 1}}}, 31, 7,
+ &sequence{block: 0xffffffff, count: 9}},
+ {&sequence{block: 0xffffffff, count: 7, next: &sequence{block: 0xfffffffe, count: 1}}, 31, 7,
+ &sequence{block: 0xffffffff, count: 8}},
+
+ // Merge affected with previous and next
+ {&sequence{block: 0xffffffff, count: 7, next: &sequence{block: 0xfffffffe, count: 1, next: &sequence{block: 0xffffffff, count: 1}}}, 31, 7,
+ &sequence{block: 0xffffffff, count: 9}},
+
+ // Redundant push: No change
+ {&sequence{block: 0xffff0000, count: 1}, 0, 0, &sequence{block: 0xffff0000, count: 1}},
+ {&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 {
+ mask := pushReservation(i.bytePos, i.bitPos, i.mask, false)
+ if !mask.equal(i.newMask) {
+ t.Fatalf("Error in (%d) pushReservation():\n%s + (%d,%d):\nExp: %s\nGot: %s,",
+ n, i.mask.toString(), i.bytePos, i.bitPos, i.newMask.toString(), mask.toString())
+ }
+ }
+}
+
+func TestSerializeDeserialize(t *testing.T) {
+ s := getTestSequence()
+
+ data, err := s.toByteArray()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ r := &sequence{}
+ err = r.fromByteArray(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !s.equal(r) {
+ t.Fatalf("Sequences are different: \n%v\n%v", s, r)
+ }
+}
+
+func getTestSequence() *sequence {
+ // Returns a custom sequence of 1024 * 32 bits
+ return &sequence{
+ block: 0XFFFFFFFF,
+ count: 100,
+ next: &sequence{
+ block: 0xFFFFFFFE,
+ count: 1,
+ next: &sequence{
+ block: 0xFF000000,
+ count: 10,
+ next: &sequence{
+ block: 0XFFFFFFFF,
+ count: 50,
+ next: &sequence{
+ block: 0XFFFFFFFC,
+ count: 1,
+ next: &sequence{
+ block: 0xFF800000,
+ count: 1,
+ next: &sequence{
+ block: 0XFFFFFFFF,
+ count: 87,
+ next: &sequence{
+ block: 0x0,
+ count: 150,
+ next: &sequence{
+ block: 0XFFFFFFFF,
+ count: 200,
+ next: &sequence{
+ block: 0x0000FFFF,
+ count: 1,
+ next: &sequence{
+ block: 0x0,
+ count: 399,
+ next: &sequence{
+ block: 0XFFFFFFFF,
+ count: 23,
+ next: &sequence{
+ block: 0x1,
+ count: 1,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func TestSet(t *testing.T) {
+ hnd, err := NewHandle("", nil, "", 1024*32)
+ if err != nil {
+ t.Fatal(err)
+ }
+ hnd.head = getTestSequence()
+
+ firstAv := uint64(32*100 + 31)
+ last := uint64(1024*32 - 1)
+
+ if hnd.IsSet(100000) {
+ t.Fatal("IsSet() returned wrong result")
+ }
+
+ if !hnd.IsSet(0) {
+ t.Fatal("IsSet() returned wrong result")
+ }
+
+ if hnd.IsSet(firstAv) {
+ t.Fatal("IsSet() returned wrong result")
+ }
+
+ if !hnd.IsSet(last) {
+ t.Fatal("IsSet() returned wrong result")
+ }
+
+ if err := hnd.Set(0); err == nil {
+ t.Fatal("Expected failure, but succeeded")
+ }
+
+ os, err := hnd.SetAny(false)
+ if err != nil {
+ t.Fatalf("Unexpected failure: %v", err)
+ }
+ if os != firstAv {
+ t.Fatalf("SetAny returned unexpected ordinal. Expected %d. Got %d.", firstAv, os)
+ }
+ if !hnd.IsSet(firstAv) {
+ t.Fatal("IsSet() returned wrong result")
+ }
+
+ if err := hnd.Unset(firstAv); err != nil {
+ t.Fatalf("Unexpected failure: %v", err)
+ }
+
+ if hnd.IsSet(firstAv) {
+ t.Fatal("IsSet() returned wrong result")
+ }
+
+ if err := hnd.Set(firstAv); err != nil {
+ t.Fatalf("Unexpected failure: %v", err)
+ }
+
+ if err := hnd.Set(last); err == nil {
+ t.Fatal("Expected failure, but succeeded")
+ }
+}
+
+func TestSetUnset(t *testing.T) {
+ numBits := uint64(32 * blockLen)
+ hnd, err := NewHandle("", nil, "", numBits)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := hnd.Set(uint64(32 * blockLen)); err == nil {
+ t.Fatal("Expected failure, but succeeded")
+ }
+ if err := hnd.Unset(uint64(32 * blockLen)); err == nil {
+ t.Fatal("Expected failure, but succeeded")
+ }
+
+ // set and unset all one by one
+ for hnd.Unselected() > 0 {
+ if _, err := hnd.SetAny(false); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if _, err := hnd.SetAny(false); err != ErrNoBitAvailable {
+ t.Fatal("Expected error. Got success")
+ }
+ if _, err := hnd.SetAnyInRange(10, 20, false); err != ErrNoBitAvailable {
+ t.Fatal("Expected error. Got success")
+ }
+ if err := hnd.Set(50); err != ErrBitAllocated {
+ t.Fatalf("Expected error. Got %v: %s", err, hnd)
+ }
+ i := uint64(0)
+ for hnd.Unselected() < numBits {
+ if err := hnd.Unset(i); err != nil {
+ t.Fatal(err)
+ }
+ i++
+ }
+}
+
+func TestOffsetSetUnset(t *testing.T) {
+ numBits := uint64(32 * blockLen)
+ var o uint64
+ hnd, err := NewHandle("", nil, "", numBits)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // set and unset all one by one
+ for hnd.Unselected() > 0 {
+ if _, err := hnd.SetAny(false); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ if _, err := hnd.SetAny(false); err != ErrNoBitAvailable {
+ t.Fatal("Expected error. Got success")
+ }
+
+ if _, err := hnd.SetAnyInRange(10, 20, false); err != ErrNoBitAvailable {
+ t.Fatal("Expected error. Got success")
+ }
+
+ if err := hnd.Unset(288); err != nil {
+ t.Fatal(err)
+ }
+
+ //At this point sequence is (0xffffffff, 9)->(0x7fffffff, 1)->(0xffffffff, 22)->end
+ if o, err = hnd.SetAnyInRange(32, 500, false); err != nil {
+ t.Fatal(err)
+ }
+
+ if o != 288 {
+ t.Fatalf("Expected ordinal not received, Received:%d", o)
+ }
+}
+
+func TestSetInRange(t *testing.T) {
+ numBits := uint64(1024 * blockLen)
+ hnd, err := NewHandle("", nil, "", numBits)
+ if err != nil {
+ t.Fatal(err)
+ }
+ hnd.head = getTestSequence()
+
+ firstAv := uint64(100*blockLen + blockLen - 1)
+
+ if o, err := hnd.SetAnyInRange(4, 3, false); err == nil {
+ t.Fatalf("Expected failure. Got success with ordinal:%d", o)
+ }
+
+ if o, err := hnd.SetAnyInRange(0, numBits, false); err == nil {
+ t.Fatalf("Expected failure. Got success with ordinal:%d", o)
+ }
+
+ o, err := hnd.SetAnyInRange(100*uint64(blockLen), 101*uint64(blockLen), false)
+ if err != nil {
+ t.Fatalf("Unexpected failure: (%d, %v)", o, err)
+ }
+ if o != firstAv {
+ t.Fatalf("Unexpected ordinal: %d", o)
+ }
+
+ if o, err := hnd.SetAnyInRange(0, uint64(blockLen), false); err == nil {
+ t.Fatalf("Expected failure. Got success with ordinal:%d", o)
+ }
+
+ if o, err := hnd.SetAnyInRange(0, firstAv-1, false); err == nil {
+ t.Fatalf("Expected failure. Got success with ordinal:%d", o)
+ }
+
+ if o, err := hnd.SetAnyInRange(111*uint64(blockLen), 161*uint64(blockLen), false); err == nil {
+ t.Fatalf("Expected failure. Got success with ordinal:%d", o)
+ }
+
+ o, err = hnd.SetAnyInRange(161*uint64(blockLen), 162*uint64(blockLen), false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 161*uint64(blockLen)+30 {
+ t.Fatalf("Unexpected ordinal: %d", o)
+ }
+
+ o, err = hnd.SetAnyInRange(161*uint64(blockLen), 162*uint64(blockLen), false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 161*uint64(blockLen)+31 {
+ t.Fatalf("Unexpected ordinal: %d", o)
+ }
+
+ o, err = hnd.SetAnyInRange(161*uint64(blockLen), 162*uint64(blockLen), false)
+ if err == nil {
+ t.Fatalf("Expected failure. Got success with ordinal:%d", o)
+ }
+
+ if _, err := hnd.SetAnyInRange(0, numBits-1, false); err != nil {
+ t.Fatalf("Unexpected failure: %v", err)
+ }
+
+ // set one bit using the set range with 1 bit size range
+ if _, err := hnd.SetAnyInRange(uint64(163*blockLen-1), uint64(163*blockLen-1), false); err != nil {
+ t.Fatal(err)
+ }
+
+ // create a non multiple of 32 mask
+ hnd, err = NewHandle("", nil, "", 30)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // set all bit in the first range
+ for hnd.Unselected() > 22 {
+ if o, err := hnd.SetAnyInRange(0, 7, false); err != nil {
+ t.Fatalf("Unexpected failure: (%d, %v)", o, err)
+ }
+ }
+ // try one more set, which should fail
+ o, err = hnd.SetAnyInRange(0, 7, false)
+ if err == nil {
+ t.Fatalf("Expected failure. Got success with ordinal:%d", o)
+ }
+ if err != ErrNoBitAvailable {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ // set all bit in a second range
+ for hnd.Unselected() > 14 {
+ if o, err := hnd.SetAnyInRange(8, 15, false); err != nil {
+ t.Fatalf("Unexpected failure: (%d, %v)", o, err)
+ }
+ }
+
+ // try one more set, which should fail
+ o, err = hnd.SetAnyInRange(0, 15, false)
+ if err == nil {
+ t.Fatalf("Expected failure. Got success with ordinal:%d", o)
+ }
+ if err != ErrNoBitAvailable {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ // set all bit in a range which includes the last bit
+ for hnd.Unselected() > 12 {
+ if o, err := hnd.SetAnyInRange(28, 29, false); err != nil {
+ t.Fatalf("Unexpected failure: (%d, %v)", o, err)
+ }
+ }
+ o, err = hnd.SetAnyInRange(28, 29, false)
+ if err == nil {
+ t.Fatalf("Expected failure. Got success with ordinal:%d", o)
+ }
+ if err != ErrNoBitAvailable {
+ 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, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 128 {
+ t.Fatalf("Unexpected ordinal: %d", o)
+ }
+
+ o, err = hnd.SetAnyInRange(128, 255, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if o != 129 {
+ t.Fatalf("Unexpected ordinal: %d", o)
+ }
+
+ o, err = hnd.SetAnyInRange(246, 255, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 246 {
+ t.Fatalf("Unexpected ordinal: %d", o)
+ }
+
+ o, err = hnd.SetAnyInRange(246, 255, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 247 {
+ t.Fatalf("Unexpected ordinal: %d", o)
+ }
+}
+
+func TestMethods(t *testing.T) {
+ numBits := uint64(256 * blockLen)
+ hnd, err := NewHandle("path/to/data", nil, "sequence1", uint64(numBits))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if hnd.Bits() != numBits {
+ t.Fatalf("Unexpected bit number: %d", hnd.Bits())
+ }
+
+ if hnd.Unselected() != numBits {
+ t.Fatalf("Unexpected bit number: %d", hnd.Unselected())
+ }
+
+ exp := "(0x0, 256)->end"
+ if hnd.head.toString() != exp {
+ t.Fatalf("Unexpected sequence string: %s", hnd.head.toString())
+ }
+
+ for i := 0; i < 192; i++ {
+ _, err := hnd.SetAny(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ exp = "(0xffffffff, 6)->(0x0, 250)->end"
+ if hnd.head.toString() != exp {
+ t.Fatalf("Unexpected sequence string: %s", hnd.head.toString())
+ }
+}
+
+func TestRandomAllocateDeallocate(t *testing.T) {
+ ds, err := randomLocalStore()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ numBits := int(16 * blockLen)
+ hnd, err := NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ seed := time.Now().Unix()
+ rand.Seed(seed)
+
+ // Allocate all bits using a random pattern
+ pattern := rand.Perm(numBits)
+ for _, bit := range pattern {
+ err := hnd.Set(uint64(bit))
+ if err != nil {
+ t.Fatalf("Unexpected failure on allocation of %d: %v.\nSeed: %d.\n%s", bit, err, seed, hnd)
+ }
+ }
+ if hnd.Unselected() != 0 {
+ t.Fatalf("Expected full sequence. Instead found %d free bits. Seed: %d.\n%s", hnd.unselected, seed, hnd)
+ }
+ if hnd.head.toString() != "(0xffffffff, 16)->end" {
+ t.Fatalf("Unexpected db: %s", hnd.head.toString())
+ }
+
+ // Deallocate all bits using a random pattern
+ pattern = rand.Perm(numBits)
+ for _, bit := range pattern {
+ err := hnd.Unset(uint64(bit))
+ if err != nil {
+ t.Fatalf("Unexpected failure on deallocation of %d: %v.\nSeed: %d.\n%s", bit, err, seed, hnd)
+ }
+ }
+ if hnd.Unselected() != uint64(numBits) {
+ t.Fatalf("Expected full sequence. Instead found %d free bits. Seed: %d.\n%s", hnd.unselected, seed, hnd)
+ }
+ if hnd.head.toString() != "(0x0, 16)->end" {
+ t.Fatalf("Unexpected db: %s", hnd.head.toString())
+ }
+
+ err = hnd.Destroy()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestAllocateRandomDeallocate(t *testing.T) {
+ ds, err := randomLocalStore()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ numBlocks := uint32(8)
+ numBits := int(numBlocks * blockLen)
+ hnd, err := NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := &sequence{block: 0xffffffff, count: uint64(numBlocks / 2), next: &sequence{block: 0x0, count: uint64(numBlocks / 2)}}
+
+ // Allocate first half of the bits
+ for i := 0; i < numBits/2; i++ {
+ _, err := hnd.SetAny(false)
+ if err != nil {
+ t.Fatalf("Unexpected failure on allocation %d: %v\n%s", i, err, hnd)
+ }
+ }
+ if hnd.Unselected() != uint64(numBits/2) {
+ t.Fatalf("Expected full sequence. Instead found %d free bits. %s", hnd.unselected, hnd)
+ }
+ if !hnd.head.equal(expected) {
+ t.Fatalf("Unexpected sequence. Got:\n%s", hnd)
+ }
+
+ seed := time.Now().Unix()
+ rand.Seed(seed)
+
+ // Deallocate half of the allocated bits following a random pattern
+ pattern := rand.Perm(numBits / 2)
+ for i := 0; i < numBits/4; i++ {
+ bit := pattern[i]
+ err := hnd.Unset(uint64(bit))
+ if err != nil {
+ t.Fatalf("Unexpected failure on deallocation of %d: %v.\nSeed: %d.\n%s", bit, err, seed, hnd)
+ }
+ }
+ if hnd.Unselected() != uint64(3*numBits/4) {
+ t.Fatalf("Expected full sequence. Instead found %d free bits.\nSeed: %d.\n%s", hnd.unselected, seed, hnd)
+ }
+
+ // Request a quarter of bits
+ for i := 0; i < numBits/4; i++ {
+ _, err := hnd.SetAny(false)
+ if err != nil {
+ t.Fatalf("Unexpected failure on allocation %d: %v\nSeed: %d\n%s", i, err, seed, hnd)
+ }
+ }
+ if hnd.Unselected() != uint64(numBits/2) {
+ t.Fatalf("Expected half sequence. Instead found %d free bits.\nSeed: %d\n%s", hnd.unselected, seed, hnd)
+ }
+ if !hnd.head.equal(expected) {
+ t.Fatalf("Unexpected sequence. Got:\n%s", hnd)
+ }
+
+ err = hnd.Destroy()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestAllocateRandomDeallocateSerialize(t *testing.T) {
+ ds, err := randomLocalStore()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ numBlocks := uint32(8)
+ numBits := int(numBlocks * blockLen)
+ hnd, err := NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := &sequence{block: 0xffffffff, count: uint64(numBlocks / 2), next: &sequence{block: 0x0, count: uint64(numBlocks / 2)}}
+
+ // Allocate first half of the bits
+ for i := 0; i < numBits/2; i++ {
+ _, err := hnd.SetAny(true)
+ if err != nil {
+ t.Fatalf("Unexpected failure on allocation %d: %v\n%s", i, err, hnd)
+ }
+ }
+
+ if hnd.Unselected() != uint64(numBits/2) {
+ t.Fatalf("Expected full sequence. Instead found %d free bits. %s", hnd.unselected, hnd)
+ }
+ if !hnd.head.equal(expected) {
+ t.Fatalf("Unexpected sequence. Got:\n%s", hnd)
+ }
+
+ seed := time.Now().Unix()
+ rand.Seed(seed)
+
+ // Deallocate half of the allocated bits following a random pattern
+ pattern := rand.Perm(numBits / 2)
+ for i := 0; i < numBits/4; i++ {
+ bit := pattern[i]
+ err := hnd.Unset(uint64(bit))
+ if err != nil {
+ t.Fatalf("Unexpected failure on deallocation of %d: %v.\nSeed: %d.\n%s", bit, err, seed, hnd)
+ }
+ }
+ if hnd.Unselected() != uint64(3*numBits/4) {
+ t.Fatalf("Expected full sequence. Instead found %d free bits.\nSeed: %d.\n%s", hnd.unselected, seed, hnd)
+ }
+
+ // Request a quarter of bits
+ for i := 0; i < numBits/4; i++ {
+ _, err := hnd.SetAny(true)
+ if err != nil {
+ t.Fatalf("Unexpected failure on allocation %d: %v\nSeed: %d\n%s", i, err, seed, hnd)
+ }
+ }
+ if hnd.Unselected() != uint64(numBits/2) {
+ t.Fatalf("Expected half sequence. Instead found %d free bits.\nSeed: %d\n%s", hnd.unselected, seed, hnd)
+ }
+
+ err = hnd.Destroy()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestRetrieveFromStore(t *testing.T) {
+ ds, err := randomLocalStore()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ numBits := int(8 * blockLen)
+ hnd, err := NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Allocate first half of the bits
+ for i := 0; i < numBits/2; i++ {
+ _, err := hnd.SetAny(false)
+ if err != nil {
+ t.Fatalf("Unexpected failure on allocation %d: %v\n%s", i, err, hnd)
+ }
+ }
+ hnd0 := hnd.String()
+
+ // Retrieve same handle
+ hnd, err = NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits))
+ if err != nil {
+ t.Fatal(err)
+ }
+ hnd1 := hnd.String()
+
+ if hnd1 != hnd0 {
+ t.Fatalf("%v\n%v", hnd0, hnd1)
+ }
+
+ err = hnd.Destroy()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestIsCorrupted(t *testing.T) {
+ ds, err := randomLocalStore()
+ if err != nil {
+ t.Fatal(err)
+ }
+ // Negative test
+ hnd, err := NewHandle("bitseq-test/data/", ds, "test_corrupted", 1024)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if hnd.runConsistencyCheck() {
+ t.Fatalf("Unexpected corrupted for %s", hnd)
+ }
+
+ if err := hnd.CheckConsistency(); err != nil {
+ t.Fatal(err)
+ }
+
+ hnd.Set(0)
+ if hnd.runConsistencyCheck() {
+ t.Fatalf("Unexpected corrupted for %s", hnd)
+ }
+
+ hnd.Set(1023)
+ if hnd.runConsistencyCheck() {
+ t.Fatalf("Unexpected corrupted for %s", hnd)
+ }
+
+ if err := hnd.CheckConsistency(); err != nil {
+ t.Fatal(err)
+ }
+
+ // Try real corrupted ipam handles found in the local store files reported by three docker users,
+ // plus a generic ipam handle from docker 1.9.1. This last will fail as well, because of how the
+ // last node in the sequence is expressed (This is true for IPAM handle only, because of the broadcast
+ // address reservation: last bit). This will allow an application using bitseq that runs a consistency
+ // check to detect and replace the 1.9.0/1 old vulnerable handle with the new one.
+ input := []*Handle{
+ {
+ id: "LocalDefault/172.17.0.0/16",
+ bits: 65536,
+ unselected: 65412,
+ head: &sequence{
+ block: 0xffffffff,
+ count: 3,
+ next: &sequence{
+ block: 0xffffffbf,
+ count: 0,
+ next: &sequence{
+ block: 0xfe98816e,
+ count: 1,
+ next: &sequence{
+ block: 0xffffffff,
+ count: 0,
+ next: &sequence{
+ block: 0xe3bc0000,
+ count: 1,
+ next: &sequence{
+ block: 0x0,
+ count: 2042,
+ next: &sequence{
+ block: 0x1, count: 1,
+ next: &sequence{
+ block: 0x0, count: 0,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: "LocalDefault/172.17.0.0/16",
+ bits: 65536,
+ unselected: 65319,
+ head: &sequence{
+ block: 0xffffffff,
+ count: 7,
+ next: &sequence{
+ block: 0xffffff7f,
+ count: 0,
+ next: &sequence{
+ block: 0xffffffff,
+ count: 0,
+ next: &sequence{
+ block: 0x2000000,
+ count: 1,
+ next: &sequence{
+ block: 0x0,
+ count: 2039,
+ next: &sequence{
+ block: 0x1,
+ count: 1,
+ next: &sequence{
+ block: 0x0,
+ count: 0,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: "LocalDefault/172.17.0.0/16",
+ bits: 65536,
+ unselected: 65456,
+ head: &sequence{
+ block: 0xffffffff, count: 2,
+ next: &sequence{
+ block: 0xfffbffff, count: 0,
+ next: &sequence{
+ block: 0xffd07000, count: 1,
+ next: &sequence{
+ block: 0x0, count: 333,
+ next: &sequence{
+ block: 0x40000000, count: 1,
+ next: &sequence{
+ block: 0x0, count: 1710,
+ next: &sequence{
+ block: 0x1, count: 1,
+ next: &sequence{
+ block: 0x0, count: 0,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for idx, hnd := range input {
+ if !hnd.runConsistencyCheck() {
+ t.Fatalf("Expected corrupted for (%d): %s", idx, hnd)
+ }
+ if hnd.runConsistencyCheck() {
+ t.Fatalf("Sequence still marked corrupted (%d): %s", idx, hnd)
+ }
+ }
+}
+
+func testSetRollover(t *testing.T, serial bool) {
+ ds, err := randomLocalStore()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ numBlocks := uint32(8)
+ numBits := int(numBlocks * blockLen)
+ hnd, err := NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Allocate first half of the bits
+ for i := 0; i < numBits/2; i++ {
+ _, err := hnd.SetAny(serial)
+ if err != nil {
+ t.Fatalf("Unexpected failure on allocation %d: %v\n%s", i, err, hnd)
+ }
+ }
+
+ if hnd.Unselected() != uint64(numBits/2) {
+ t.Fatalf("Expected full sequence. Instead found %d free bits. %s", hnd.unselected, hnd)
+ }
+
+ seed := time.Now().Unix()
+ rand.Seed(seed)
+
+ // Deallocate half of the allocated bits following a random pattern
+ pattern := rand.Perm(numBits / 2)
+ for i := 0; i < numBits/4; i++ {
+ bit := pattern[i]
+ err := hnd.Unset(uint64(bit))
+ if err != nil {
+ t.Fatalf("Unexpected failure on deallocation of %d: %v.\nSeed: %d.\n%s", bit, err, seed, hnd)
+ }
+ }
+ if hnd.Unselected() != uint64(3*numBits/4) {
+ t.Fatalf("Unexpected free bits: found %d free bits.\nSeed: %d.\n%s", hnd.unselected, seed, hnd)
+ }
+
+ //request to allocate for remaining half of the bits
+ for i := 0; i < numBits/2; i++ {
+ _, err := hnd.SetAny(serial)
+ if err != nil {
+ t.Fatalf("Unexpected failure on allocation %d: %v\nSeed: %d\n%s", i, err, seed, hnd)
+ }
+ }
+
+ //At this point all the bits must be allocated except the randomly unallocated bits
+ //which were unallocated in the first half of the bit sequence
+ if hnd.Unselected() != uint64(numBits/4) {
+ t.Fatalf("Unexpected number of unselected bits %d, Expected %d", hnd.Unselected(), numBits/4)
+ }
+
+ for i := 0; i < numBits/4; i++ {
+ _, err := hnd.SetAny(serial)
+ if err != nil {
+ t.Fatalf("Unexpected failure on allocation %d: %v\nSeed: %d\n%s", i, err, seed, hnd)
+ }
+ }
+ //Now requesting to allocate the unallocated random bits (qurter of the number of bits) should
+ //leave no more bits that can be allocated.
+ if hnd.Unselected() != 0 {
+ t.Fatalf("Unexpected number of unselected bits %d, Expected %d", hnd.Unselected(), 0)
+ }
+
+ err = hnd.Destroy()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestSetRollover(t *testing.T) {
+ testSetRollover(t, false)
+}
+
+func TestSetRolloverSerial(t *testing.T) {
+ testSetRollover(t, true)
+}
+
+func TestGetFirstAvailableFromCurrent(t *testing.T) {
+ input := []struct {
+ mask *sequence
+ bytePos uint64
+ bitPos uint64
+ start uint64
+ curr uint64
+ end uint64
+ }{
+ {&sequence{block: 0xffffffff, count: 2048}, invalidPos, invalidPos, 0, 0, 65536},
+ {&sequence{block: 0x0, count: 8}, 0, 0, 0, 0, 256},
+ {&sequence{block: 0x80000000, count: 8}, 1, 0, 0, 8, 256},
+ {&sequence{block: 0xC0000000, count: 8}, 0, 2, 0, 2, 256},
+ {&sequence{block: 0xE0000000, count: 8}, 0, 3, 0, 0, 256},
+ {&sequence{block: 0xFFFB1FFF, count: 8}, 2, 0, 14, 0, 256},
+ {&sequence{block: 0xFFFFFFFE, count: 8}, 3, 7, 0, 0, 256},
+
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0x00000000, count: 1, next: &sequence{block: 0xffffffff, count: 14}}}, 4, 0, 0, 32, 512},
+ {&sequence{block: 0xfffeffff, count: 1, next: &sequence{block: 0xffffffff, count: 15}}, 1, 7, 0, 16, 512},
+ {&sequence{block: 0xfffeffff, count: 15, next: &sequence{block: 0xffffffff, count: 1}}, 5, 7, 0, 16, 512},
+ {&sequence{block: 0xfffeffff, count: 15, next: &sequence{block: 0xffffffff, count: 1}}, 9, 7, 0, 48, 512},
+ {&sequence{block: 0xffffffff, count: 2, next: &sequence{block: 0xffffffef, count: 14}}, 19, 3, 0, 124, 512},
+ {&sequence{block: 0xfffeffff, count: 15, next: &sequence{block: 0x0fffffff, count: 1}}, 60, 0, 0, 480, 512},
+ {&sequence{block: 0xffffffff, count: 1, next: &sequence{block: 0xfffeffff, count: 14, next: &sequence{block: 0xffffffff, count: 1}}}, 17, 7, 0, 124, 512},
+ {&sequence{block: 0xfffffffb, count: 1, next: &sequence{block: 0xffffffff, count: 14, next: &sequence{block: 0xffffffff, count: 1}}}, 3, 5, 0, 124, 512},
+ {&sequence{block: 0xfffffffb, count: 1, next: &sequence{block: 0xfffeffff, count: 14, next: &sequence{block: 0xffffffff, count: 1}}}, 13, 7, 0, 80, 512},
+ }
+
+ for n, i := range input {
+ bytePos, bitPos, _ := getAvailableFromCurrent(i.mask, i.start, i.curr, i.end)
+ if bytePos != i.bytePos || bitPos != i.bitPos {
+ t.Fatalf("Error in (%d) getFirstAvailable(). Expected (%d, %d). Got (%d, %d)", n, i.bytePos, i.bitPos, bytePos, bitPos)
+ }
+ }
+}
--- /dev/null
+package bitseq
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/types"
+)
+
+// Key provides the Key to be used in KV Store
+func (h *Handle) Key() []string {
+ h.Lock()
+ defer h.Unlock()
+ return []string{h.app, h.id}
+}
+
+// KeyPrefix returns the immediate parent key that can be used for tree walk
+func (h *Handle) KeyPrefix() []string {
+ h.Lock()
+ defer h.Unlock()
+ return []string{h.app}
+}
+
+// Value marshals the data to be stored in the KV store
+func (h *Handle) Value() []byte {
+ b, err := json.Marshal(h)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+// SetValue unmarshals the data from the KV store
+func (h *Handle) SetValue(value []byte) error {
+ return json.Unmarshal(value, h)
+}
+
+// Index returns the latest DB Index as seen by this object
+func (h *Handle) Index() uint64 {
+ h.Lock()
+ defer h.Unlock()
+ return h.dbIndex
+}
+
+// SetIndex method allows the datastore to store the latest DB Index into this object
+func (h *Handle) SetIndex(index uint64) {
+ h.Lock()
+ h.dbIndex = index
+ h.dbExists = true
+ h.Unlock()
+}
+
+// Exists method is true if this object has been stored in the DB.
+func (h *Handle) Exists() bool {
+ h.Lock()
+ defer h.Unlock()
+ return h.dbExists
+}
+
+// New method returns a handle based on the receiver handle
+func (h *Handle) New() datastore.KVObject {
+ h.Lock()
+ defer h.Unlock()
+
+ return &Handle{
+ app: h.app,
+ store: h.store,
+ }
+}
+
+// CopyTo deep copies the handle into the passed destination object
+func (h *Handle) CopyTo(o datastore.KVObject) error {
+ h.Lock()
+ defer h.Unlock()
+
+ dstH := o.(*Handle)
+ if h == dstH {
+ return nil
+ }
+ dstH.Lock()
+ dstH.bits = h.bits
+ dstH.unselected = h.unselected
+ dstH.head = h.head.getCopy()
+ dstH.app = h.app
+ dstH.id = h.id
+ dstH.dbIndex = h.dbIndex
+ dstH.dbExists = h.dbExists
+ dstH.store = h.store
+ dstH.curr = h.curr
+ dstH.Unlock()
+
+ return nil
+}
+
+// Skip provides a way for a KV Object to avoid persisting it in the KV Store
+func (h *Handle) Skip() bool {
+ return false
+}
+
+// DataScope method returns the storage scope of the datastore
+func (h *Handle) DataScope() string {
+ h.Lock()
+ defer h.Unlock()
+
+ return h.store.Scope()
+}
+
+func (h *Handle) fromDsValue(value []byte) error {
+ var ba []byte
+ if err := json.Unmarshal(value, &ba); err != nil {
+ return fmt.Errorf("failed to decode json: %s", err.Error())
+ }
+ if err := h.FromByteArray(ba); err != nil {
+ return fmt.Errorf("failed to decode handle: %s", err.Error())
+ }
+ return nil
+}
+
+func (h *Handle) writeToStore() error {
+ h.Lock()
+ store := h.store
+ h.Unlock()
+ if store == nil {
+ return nil
+ }
+ err := store.PutObjectAtomic(h)
+ if err == datastore.ErrKeyModified {
+ return types.RetryErrorf("failed to perform atomic write (%v). Retry might fix the error", err)
+ }
+ return err
+}
+
+func (h *Handle) deleteFromStore() error {
+ h.Lock()
+ store := h.store
+ h.Unlock()
+ if store == nil {
+ return nil
+ }
+ return store.DeleteObjectAtomic(h)
+}
--- /dev/null
+package client
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "reflect"
+ "strings"
+
+ flag "github.com/docker/libnetwork/client/mflag"
+)
+
+// CallFunc provides environment specific call utility to invoke backend functions from UI
+type CallFunc func(string, string, interface{}, map[string][]string) (io.ReadCloser, http.Header, int, error)
+
+// NetworkCli is the UI object for network subcmds
+type NetworkCli struct {
+ out io.Writer
+ err io.Writer
+ call CallFunc
+}
+
+// NewNetworkCli is a convenient function to create a NetworkCli object
+func NewNetworkCli(out, err io.Writer, call CallFunc) *NetworkCli {
+ return &NetworkCli{
+ out: out,
+ err: err,
+ call: call,
+ }
+}
+
+// getMethod is Borrowed from Docker UI which uses reflection to identify the UI Handler
+func (cli *NetworkCli) getMethod(args ...string) (func(string, ...string) error, bool) {
+ camelArgs := make([]string, len(args))
+ for i, s := range args {
+ if len(s) == 0 {
+ return nil, false
+ }
+ camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
+ }
+ methodName := "Cmd" + strings.Join(camelArgs, "")
+ method := reflect.ValueOf(cli).MethodByName(methodName)
+ if !method.IsValid() {
+ return nil, false
+ }
+ return method.Interface().(func(string, ...string) error), true
+}
+
+// Cmd is borrowed from Docker UI and acts as the entry point for network UI commands.
+// network UI commands are designed to be invoked from multiple parent chains
+func (cli *NetworkCli) Cmd(chain string, args ...string) error {
+ if len(args) > 2 {
+ method, exists := cli.getMethod(args[:3]...)
+ if exists {
+ return method(chain+" "+args[0]+" "+args[1], args[3:]...)
+ }
+ }
+ if len(args) > 1 {
+ method, exists := cli.getMethod(args[:2]...)
+ if exists {
+ return method(chain+" "+args[0], args[2:]...)
+ }
+ }
+ if len(args) > 0 {
+ method, exists := cli.getMethod(args[0])
+ if !exists {
+ return fmt.Errorf("%s: '%s' is not a %s command. See '%s --help'", chain, args[0], chain, chain)
+ }
+ return method(chain, args[1:]...)
+ }
+ flag.Usage()
+ return nil
+}
+
+// Subcmd is borrowed from Docker UI and performs the same function of configuring the subCmds
+func (cli *NetworkCli) Subcmd(chain, name, signature, description string, exitOnError bool) *flag.FlagSet {
+ var errorHandling flag.ErrorHandling
+ if exitOnError {
+ errorHandling = flag.ExitOnError
+ } else {
+ errorHandling = flag.ContinueOnError
+ }
+ flags := flag.NewFlagSet(name, errorHandling)
+ flags.Usage = func() {
+ flags.ShortUsage()
+ flags.PrintDefaults()
+ }
+ flags.ShortUsage = func() {
+ options := ""
+ if signature != "" {
+ signature = " " + signature
+ }
+ if flags.FlagCountUndeprecated() > 0 {
+ options = " [OPTIONS]"
+ }
+ fmt.Fprintf(cli.out, "\nUsage: %s %s%s%s\n\n%s\n\n", chain, name, options, signature, description)
+ flags.SetOutput(cli.out)
+ }
+ return flags
+}
+
+func readBody(stream io.ReadCloser, hdr http.Header, statusCode int, err error) ([]byte, int, error) {
+ if stream != nil {
+ defer stream.Close()
+ }
+ if err != nil {
+ return nil, statusCode, err
+ }
+ body, err := ioutil.ReadAll(stream)
+ if err != nil {
+ return nil, -1, err
+ }
+ return body, statusCode, nil
+}
--- /dev/null
+package client
+
+import (
+ "bytes"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func TestClientServiceInvalidCommand(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "service", "invalid")
+ if err == nil {
+ t.Fatal("Passing invalid commands must fail")
+ }
+}
+
+func TestClientServiceCreate(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "service", "publish", mockServiceName+"."+mockNwName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestClientServiceRm(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "service", "unpublish", mockServiceName+"."+mockNwName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestClientServiceLs(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "service", "ls")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestClientServiceInfo(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "service", "info", mockServiceName+"."+mockNwName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestClientServiceInfoById(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "service", "info", mockServiceID+"."+mockNwName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestClientServiceJoin(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "service", "attach", mockContainerID, mockServiceName+"."+mockNwName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestClientServiceLeave(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "service", "detach", mockContainerID, mockServiceName+"."+mockNwName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Docker Flag processing in flag.go uses os.Exit() frequently, even for --help
+// TODO : Handle the --help test-case in the IT when CLI is available
+/*
+func TestClientNetworkServiceCreateHelp(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
+ return nil, 0, nil
+ }
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "create", "--help")
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
+}
+*/
+
+// Docker flag processing in flag.go uses os.Exit(1) for incorrect parameter case.
+// TODO : Handle the missing argument case in the IT when CLI is available
+/*
+func TestClientNetworkServiceCreateMissingArgument(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
+ return nil, 0, nil
+ }
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "create")
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+}
+*/
--- /dev/null
+package client
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "strings"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+// nopCloser is used to provide a dummy CallFunc for Cmd()
+type nopCloser struct {
+ io.Reader
+}
+
+func (nopCloser) Close() error { return nil }
+
+func TestMain(m *testing.M) {
+ setupMockHTTPCallback()
+ os.Exit(m.Run())
+}
+
+var callbackFunc func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error)
+var mockNwJSON, mockNwListJSON, mockServiceJSON, mockServiceListJSON, mockSbJSON, mockSbListJSON []byte
+var mockNwName = "test"
+var mockNwID = "2a3456789"
+var mockServiceName = "testSrv"
+var mockServiceID = "2a3456789"
+var mockContainerID = "2a3456789"
+var mockSandboxID = "2b3456789"
+
+func setupMockHTTPCallback() {
+ var list []networkResource
+ nw := networkResource{Name: mockNwName, ID: mockNwID}
+ mockNwJSON, _ = json.Marshal(nw)
+ list = append(list, nw)
+ mockNwListJSON, _ = json.Marshal(list)
+
+ var srvList []serviceResource
+ ep := serviceResource{Name: mockServiceName, ID: mockServiceID, Network: mockNwName}
+ mockServiceJSON, _ = json.Marshal(ep)
+ srvList = append(srvList, ep)
+ mockServiceListJSON, _ = json.Marshal(srvList)
+
+ var sbxList []SandboxResource
+ sb := SandboxResource{ID: mockSandboxID, ContainerID: mockContainerID}
+ mockSbJSON, _ = json.Marshal(sb)
+ sbxList = append(sbxList, sb)
+ mockSbListJSON, _ = json.Marshal(sbxList)
+
+ dummyHTTPHdr := http.Header{}
+
+ callbackFunc = func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
+ var rsp string
+ switch method {
+ case "GET":
+ if strings.Contains(path, fmt.Sprintf("networks?name=%s", mockNwName)) {
+ rsp = string(mockNwListJSON)
+ } else if strings.Contains(path, "networks?name=") {
+ rsp = "[]"
+ } else if strings.Contains(path, fmt.Sprintf("networks?partial-id=%s", mockNwID)) {
+ rsp = string(mockNwListJSON)
+ } else if strings.Contains(path, "networks?partial-id=") {
+ rsp = "[]"
+ } else if strings.HasSuffix(path, "networks") {
+ rsp = string(mockNwListJSON)
+ } else if strings.HasSuffix(path, "networks/"+mockNwID) {
+ rsp = string(mockNwJSON)
+ } else if strings.Contains(path, fmt.Sprintf("services?name=%s", mockServiceName)) {
+ rsp = string(mockServiceListJSON)
+ } else if strings.Contains(path, "services?name=") {
+ rsp = "[]"
+ } else if strings.Contains(path, fmt.Sprintf("services?partial-id=%s", mockServiceID)) {
+ rsp = string(mockServiceListJSON)
+ } else if strings.Contains(path, "services?partial-id=") {
+ rsp = "[]"
+ } else if strings.HasSuffix(path, "services") {
+ rsp = string(mockServiceListJSON)
+ } else if strings.HasSuffix(path, "services/"+mockServiceID) {
+ rsp = string(mockServiceJSON)
+ } else if strings.Contains(path, "containers") {
+ return nopCloser{bytes.NewBufferString("")}, dummyHTTPHdr, 400, fmt.Errorf("Bad Request")
+ } else if strings.Contains(path, fmt.Sprintf("sandboxes?container-id=%s", mockContainerID)) {
+ rsp = string(mockSbListJSON)
+ } else if strings.Contains(path, fmt.Sprintf("sandboxes?partial-container-id=%s", mockContainerID)) {
+ rsp = string(mockSbListJSON)
+ }
+ case "POST":
+ var data []byte
+ if strings.HasSuffix(path, "networks") {
+ data, _ = json.Marshal(mockNwID)
+ } else if strings.HasSuffix(path, "services") {
+ data, _ = json.Marshal(mockServiceID)
+ } else if strings.HasSuffix(path, "backend") {
+ data, _ = json.Marshal(mockSandboxID)
+ }
+ rsp = string(data)
+ case "PUT":
+ case "DELETE":
+ rsp = ""
+ }
+ return nopCloser{bytes.NewBufferString(rsp)}, dummyHTTPHdr, 200, nil
+ }
+}
+
+func TestClientDummyCommand(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "dummy")
+ if err == nil {
+ t.Fatal("Incorrect Command must fail")
+ }
+}
+
+func TestClientNetworkInvalidCommand(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "invalid")
+ if err == nil {
+ t.Fatal("Passing invalid commands must fail")
+ }
+}
+
+func TestClientNetworkCreate(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "create", mockNwName)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+}
+
+func TestClientNetworkCreateWithDriver(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "create", "-f=dummy", mockNwName)
+ if err == nil {
+ t.Fatal("Passing incorrect flags to the create command must fail")
+ }
+
+ err = cli.Cmd("docker", "network", "create", "-d=dummy", mockNwName)
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
+}
+
+func TestClientNetworkRm(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "rm", mockNwName)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+}
+
+func TestClientNetworkLs(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "ls")
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+}
+
+func TestClientNetworkInfo(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "info", mockNwName)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+}
+
+func TestClientNetworkInfoById(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "info", mockNwID)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+}
+
+// Docker Flag processing in flag.go uses os.Exit() frequently, even for --help
+// TODO : Handle the --help test-case in the IT when CLI is available
+/*
+func TestClientNetworkServiceCreateHelp(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
+ return nil, 0, nil
+ }
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "create", "--help")
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
+}
+*/
+
+// Docker flag processing in flag.go uses os.Exit(1) for incorrect parameter case.
+// TODO : Handle the missing argument case in the IT when CLI is available
+/*
+func TestClientNetworkServiceCreateMissingArgument(t *testing.T) {
+ var out, errOut bytes.Buffer
+ cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
+ return nil, 0, nil
+ }
+ cli := NewNetworkCli(&out, &errOut, callbackFunc)
+
+ err := cli.Cmd("docker", "network", "create")
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+}
+*/
--- /dev/null
+Copyright (c) 2014-2016 The Docker & Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+Package mflag (aka multiple-flag) implements command-line flag parsing.
+It's an **hacky** fork of the [official golang package](http://golang.org/pkg/flag/)
+
+It adds:
+
+* both short and long flag version
+`./example -s red` `./example --string blue`
+
+* multiple names for the same option
+```
+$>./example -h
+Usage of example:
+ -s, --string="": a simple string
+```
+
+___
+It is very flexible on purpose, so you can do things like:
+```
+$>./example -h
+Usage of example:
+ -s, -string, --string="": a simple string
+```
+
+Or:
+```
+$>./example -h
+Usage of example:
+ -oldflag, --newflag="": a simple string
+```
+
+You can also hide some flags from the usage, so if we want only `--newflag`:
+```
+$>./example -h
+Usage of example:
+ --newflag="": a simple string
+$>./example -oldflag str
+str
+```
+
+See [example.go](example/example.go) for more details.
--- /dev/null
+package main
+
+import (
+ "fmt"
+
+ flag "github.com/docker/libnetwork/client/mflag"
+)
+
+var (
+ i int
+ str string
+ b, b2, h bool
+)
+
+func init() {
+ flag.Bool([]string{"#hp", "#-help"}, false, "display the help")
+ flag.BoolVar(&b, []string{"b", "#bal", "#bol", "-bal"}, false, "a simple bool")
+ flag.BoolVar(&b, []string{"g", "#gil"}, false, "a simple bool")
+ flag.BoolVar(&b2, []string{"#-bool"}, false, "a simple bool")
+ flag.IntVar(&i, []string{"-integer", "-number"}, -1, "a simple integer")
+ flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage
+ flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help")
+ flag.StringVar(&str, []string{"mode"}, "mode1", "set the mode\nmode1: use the mode1\nmode2: use the mode2\nmode3: use the mode3")
+ flag.Parse()
+}
+func main() {
+ if h {
+ flag.PrintDefaults()
+ } else {
+ fmt.Printf("s/#hidden/-string: %s\n", str)
+ fmt.Printf("b: %t\n", b)
+ fmt.Printf("-bool: %t\n", b2)
+ fmt.Printf("s/#hidden/-string(via lookup): %s\n", flag.Lookup("s").Value.String())
+ fmt.Printf("ARGS: %v\n", flag.Args())
+ }
+}
--- /dev/null
+// Copyright 2014-2016 The Docker & Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package mflag implements command-line flag parsing.
+//
+// Usage:
+//
+// Define flags using flag.String(), Bool(), Int(), etc.
+//
+// This declares an integer flag, -f or --flagname, stored in the pointer ip, with type *int.
+// import "flag /github.com/docker/libnetwork/client/mflag"
+// var ip = flag.Int([]string{"f", "-flagname"}, 1234, "help message for flagname")
+// If you like, you can bind the flag to a variable using the Var() functions.
+// var flagvar int
+// func init() {
+// // -flaghidden will work, but will be hidden from the usage
+// flag.IntVar(&flagvar, []string{"f", "#flaghidden", "-flagname"}, 1234, "help message for flagname")
+// }
+// Or you can create custom flags that satisfy the Value interface (with
+// pointer receivers) and couple them to flag parsing by
+// flag.Var(&flagVal, []string{"name"}, "help message for flagname")
+// For such flags, the default value is just the initial value of the variable.
+//
+// You can also add "deprecated" flags, they are still usable, but are not shown
+// in the usage and will display a warning when you try to use them. `#` before
+// an option means this option is deprecated, if there is an following option
+// without `#` ahead, then that's the replacement, if not, it will just be removed:
+// var ip = flag.Int([]string{"#f", "#flagname", "-flagname"}, 1234, "help message for flagname")
+// this will display: `Warning: '-f' is deprecated, it will be replaced by '--flagname' soon. See usage.` or
+// this will display: `Warning: '-flagname' is deprecated, it will be replaced by '--flagname' soon. See usage.`
+// var ip = flag.Int([]string{"f", "#flagname"}, 1234, "help message for flagname")
+// will display: `Warning: '-flagname' is deprecated, it will be removed soon. See usage.`
+// so you can only use `-f`.
+//
+// You can also group one letter flags, bif you declare
+// var v = flag.Bool([]string{"v", "-verbose"}, false, "help message for verbose")
+// var s = flag.Bool([]string{"s", "-slow"}, false, "help message for slow")
+// you will be able to use the -vs or -sv
+//
+// After all flags are defined, call
+// flag.Parse()
+// to parse the command line into the defined flags.
+//
+// Flags may then be used directly. If you're using the flags themselves,
+// they are all pointers; if you bind to variables, they're values.
+// fmt.Println("ip has value ", *ip)
+// fmt.Println("flagvar has value ", flagvar)
+//
+// After parsing, the arguments after the flag are available as the
+// slice flag.Args() or individually as flag.Arg(i).
+// The arguments are indexed from 0 through flag.NArg()-1.
+//
+// Command line flag syntax:
+// -flag
+// -flag=x
+// -flag="x"
+// -flag='x'
+// -flag x // non-boolean flags only
+// One or two minus signs may be used; they are equivalent.
+// The last form is not permitted for boolean flags because the
+// meaning of the command
+// cmd -x *
+// will change if there is a file called 0, false, etc. You must
+// use the -flag=false form to turn off a boolean flag.
+//
+// Flag parsing stops just before the first non-flag argument
+// ("-" is a non-flag argument) or after the terminator "--".
+//
+// Integer flags accept 1234, 0664, 0x1234 and may be negative.
+// Boolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False.
+// Duration flags accept any input valid for time.ParseDuration.
+//
+// The default set of command-line flags is controlled by
+// top-level functions. The FlagSet type allows one to define
+// independent sets of flags, such as to implement subcommands
+// in a command-line interface. The methods of FlagSet are
+// analogous to the top-level functions for the command-line
+// flag set.
+
+package mflag
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "text/tabwriter"
+ "time"
+
+ "github.com/docker/docker/pkg/homedir"
+)
+
+// ErrHelp is the error returned if the flag -help is invoked but no such flag is defined.
+var ErrHelp = errors.New("flag: help requested")
+
+// ErrRetry is the error returned if you need to try letter by letter
+var ErrRetry = errors.New("flag: retry")
+
+// -- bool Value
+type boolValue bool
+
+func newBoolValue(val bool, p *bool) *boolValue {
+ *p = val
+ return (*boolValue)(p)
+}
+
+func (b *boolValue) Set(s string) error {
+ v, err := strconv.ParseBool(s)
+ *b = boolValue(v)
+ return err
+}
+
+func (b *boolValue) Get() interface{} { return bool(*b) }
+
+func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) }
+
+func (b *boolValue) IsBoolFlag() bool { return true }
+
+// optional interface to indicate boolean flags that can be
+// supplied without "=value" text
+type boolFlag interface {
+ Value
+ IsBoolFlag() bool
+}
+
+// -- int Value
+type intValue int
+
+func newIntValue(val int, p *int) *intValue {
+ *p = val
+ return (*intValue)(p)
+}
+
+func (i *intValue) Set(s string) error {
+ v, err := strconv.ParseInt(s, 0, 64)
+ *i = intValue(v)
+ return err
+}
+
+func (i *intValue) Get() interface{} { return int(*i) }
+
+func (i *intValue) String() string { return fmt.Sprintf("%v", *i) }
+
+// -- int64 Value
+type int64Value int64
+
+func newInt64Value(val int64, p *int64) *int64Value {
+ *p = val
+ return (*int64Value)(p)
+}
+
+func (i *int64Value) Set(s string) error {
+ v, err := strconv.ParseInt(s, 0, 64)
+ *i = int64Value(v)
+ return err
+}
+
+func (i *int64Value) Get() interface{} { return int64(*i) }
+
+func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) }
+
+// -- uint Value
+type uintValue uint
+
+func newUintValue(val uint, p *uint) *uintValue {
+ *p = val
+ return (*uintValue)(p)
+}
+
+func (i *uintValue) Set(s string) error {
+ v, err := strconv.ParseUint(s, 0, 64)
+ *i = uintValue(v)
+ return err
+}
+
+func (i *uintValue) Get() interface{} { return uint(*i) }
+
+func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) }
+
+// -- uint64 Value
+type uint64Value uint64
+
+func newUint64Value(val uint64, p *uint64) *uint64Value {
+ *p = val
+ return (*uint64Value)(p)
+}
+
+func (i *uint64Value) Set(s string) error {
+ v, err := strconv.ParseUint(s, 0, 64)
+ *i = uint64Value(v)
+ return err
+}
+
+func (i *uint64Value) Get() interface{} { return uint64(*i) }
+
+func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) }
+
+// -- uint16 Value
+type uint16Value uint16
+
+func newUint16Value(val uint16, p *uint16) *uint16Value {
+ *p = val
+ return (*uint16Value)(p)
+}
+
+func (i *uint16Value) Set(s string) error {
+ v, err := strconv.ParseUint(s, 0, 16)
+ *i = uint16Value(v)
+ return err
+}
+
+func (i *uint16Value) Get() interface{} { return uint16(*i) }
+
+func (i *uint16Value) String() string { return fmt.Sprintf("%v", *i) }
+
+// -- string Value
+type stringValue string
+
+func newStringValue(val string, p *string) *stringValue {
+ *p = val
+ return (*stringValue)(p)
+}
+
+func (s *stringValue) Set(val string) error {
+ *s = stringValue(val)
+ return nil
+}
+
+func (s *stringValue) Get() interface{} { return string(*s) }
+
+func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) }
+
+// -- float64 Value
+type float64Value float64
+
+func newFloat64Value(val float64, p *float64) *float64Value {
+ *p = val
+ return (*float64Value)(p)
+}
+
+func (f *float64Value) Set(s string) error {
+ v, err := strconv.ParseFloat(s, 64)
+ *f = float64Value(v)
+ return err
+}
+
+func (f *float64Value) Get() interface{} { return float64(*f) }
+
+func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) }
+
+// -- time.Duration Value
+type durationValue time.Duration
+
+func newDurationValue(val time.Duration, p *time.Duration) *durationValue {
+ *p = val
+ return (*durationValue)(p)
+}
+
+func (d *durationValue) Set(s string) error {
+ v, err := time.ParseDuration(s)
+ *d = durationValue(v)
+ return err
+}
+
+func (d *durationValue) Get() interface{} { return time.Duration(*d) }
+
+func (d *durationValue) String() string { return (*time.Duration)(d).String() }
+
+// Value is the interface to the dynamic value stored in a flag.
+// (The default value is represented as a string.)
+//
+// If a Value has an IsBoolFlag() bool method returning true,
+// the command-line parser makes -name equivalent to -name=true
+// rather than using the next command-line argument.
+type Value interface {
+ String() string
+ Set(string) error
+}
+
+// Getter is an interface that allows the contents of a Value to be retrieved.
+// It wraps the Value interface, rather than being part of it, because it
+// appeared after Go 1 and its compatibility rules. All Value types provided
+// by this package satisfy the Getter interface.
+type Getter interface {
+ Value
+ Get() interface{}
+}
+
+// ErrorHandling defines how to handle flag parsing errors.
+type ErrorHandling int
+
+// ErrorHandling strategies available when a flag parsing error occurs
+const (
+ ContinueOnError ErrorHandling = iota
+ ExitOnError
+ PanicOnError
+)
+
+// A FlagSet represents a set of defined flags. The zero value of a FlagSet
+// has no name and has ContinueOnError error handling.
+type FlagSet struct {
+ // Usage is the function called when an error occurs while parsing flags.
+ // The field is a function (not a method) that may be changed to point to
+ // a custom error handler.
+ Usage func()
+ ShortUsage func()
+
+ name string
+ parsed bool
+ actual map[string]*Flag
+ formal map[string]*Flag
+ args []string // arguments after flags
+ errorHandling ErrorHandling
+ output io.Writer // nil means stderr; use Out() accessor
+ nArgRequirements []nArgRequirement
+}
+
+// A Flag represents the state of a flag.
+type Flag struct {
+ Names []string // name as it appears on command line
+ Usage string // help message
+ Value Value // value as set
+ DefValue string // default value (as text); for usage message
+}
+
+type flagSlice []string
+
+func (p flagSlice) Len() int { return len(p) }
+func (p flagSlice) Less(i, j int) bool {
+ pi, pj := strings.TrimPrefix(p[i], "-"), strings.TrimPrefix(p[j], "-")
+ lpi, lpj := strings.ToLower(pi), strings.ToLower(pj)
+ if lpi != lpj {
+ return lpi < lpj
+ }
+ return pi < pj
+}
+func (p flagSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+// sortFlags returns the flags as a slice in lexicographical sorted order.
+func sortFlags(flags map[string]*Flag) []*Flag {
+ var list flagSlice
+
+ // The sorted list is based on the first name, when flag map might use the other names.
+ nameMap := make(map[string]string)
+
+ for n, f := range flags {
+ fName := strings.TrimPrefix(f.Names[0], "#")
+ nameMap[fName] = n
+ if len(f.Names) == 1 {
+ list = append(list, fName)
+ continue
+ }
+
+ found := false
+ for _, name := range list {
+ if name == fName {
+ found = true
+ break
+ }
+ }
+ if !found {
+ list = append(list, fName)
+ }
+ }
+ sort.Sort(list)
+ result := make([]*Flag, len(list))
+ for i, name := range list {
+ result[i] = flags[nameMap[name]]
+ }
+ return result
+}
+
+// Name returns the name of the FlagSet.
+func (fs *FlagSet) Name() string {
+ return fs.name
+}
+
+// Out returns the destination for usage and error messages.
+func (fs *FlagSet) Out() io.Writer {
+ if fs.output == nil {
+ return os.Stderr
+ }
+ return fs.output
+}
+
+// SetOutput sets the destination for usage and error messages.
+// If output is nil, os.Stderr is used.
+func (fs *FlagSet) SetOutput(output io.Writer) {
+ fs.output = output
+}
+
+// VisitAll visits the flags in lexicographical order, calling fn for each.
+// It visits all flags, even those not set.
+func (fs *FlagSet) VisitAll(fn func(*Flag)) {
+ for _, flag := range sortFlags(fs.formal) {
+ fn(flag)
+ }
+}
+
+// VisitAll visits the command-line flags in lexicographical order, calling
+// fn for each. It visits all flags, even those not set.
+func VisitAll(fn func(*Flag)) {
+ CommandLine.VisitAll(fn)
+}
+
+// Visit visits the flags in lexicographical order, calling fn for each.
+// It visits only those flags that have been set.
+func (fs *FlagSet) Visit(fn func(*Flag)) {
+ for _, flag := range sortFlags(fs.actual) {
+ fn(flag)
+ }
+}
+
+// Visit visits the command-line flags in lexicographical order, calling fn
+// for each. It visits only those flags that have been set.
+func Visit(fn func(*Flag)) {
+ CommandLine.Visit(fn)
+}
+
+// Lookup returns the Flag structure of the named flag, returning nil if none exists.
+func (fs *FlagSet) Lookup(name string) *Flag {
+ return fs.formal[name]
+}
+
+// IsSet indicates whether the specified flag is set in the given FlagSet
+func (fs *FlagSet) IsSet(name string) bool {
+ return fs.actual[name] != nil
+}
+
+// Lookup returns the Flag structure of the named command-line flag,
+// returning nil if none exists.
+func Lookup(name string) *Flag {
+ return CommandLine.formal[name]
+}
+
+// IsSet indicates whether the specified flag was specified at all on the cmd line.
+func IsSet(name string) bool {
+ return CommandLine.IsSet(name)
+}
+
+type nArgRequirementType int
+
+// Indicator used to pass to BadArgs function
+const (
+ Exact nArgRequirementType = iota
+ Max
+ Min
+)
+
+type nArgRequirement struct {
+ Type nArgRequirementType
+ N int
+}
+
+// Require adds a requirement about the number of arguments for the FlagSet.
+// The first parameter can be Exact, Max, or Min to respectively specify the exact,
+// the maximum, or the minimal number of arguments required.
+// The actual check is done in FlagSet.CheckArgs().
+func (fs *FlagSet) Require(nArgRequirementType nArgRequirementType, nArg int) {
+ fs.nArgRequirements = append(fs.nArgRequirements, nArgRequirement{nArgRequirementType, nArg})
+}
+
+// CheckArgs uses the requirements set by FlagSet.Require() to validate
+// the number of arguments. If the requirements are not met,
+// an error message string is returned.
+func (fs *FlagSet) CheckArgs() (message string) {
+ for _, req := range fs.nArgRequirements {
+ var arguments string
+ if req.N == 1 {
+ arguments = "1 argument"
+ } else {
+ arguments = fmt.Sprintf("%d arguments", req.N)
+ }
+
+ str := func(kind string) string {
+ return fmt.Sprintf("%q requires %s%s", fs.name, kind, arguments)
+ }
+
+ switch req.Type {
+ case Exact:
+ if fs.NArg() != req.N {
+ return str("")
+ }
+ case Max:
+ if fs.NArg() > req.N {
+ return str("a maximum of ")
+ }
+ case Min:
+ if fs.NArg() < req.N {
+ return str("a minimum of ")
+ }
+ }
+ }
+ return ""
+}
+
+// Set sets the value of the named flag.
+func (fs *FlagSet) Set(name, value string) error {
+ flag, ok := fs.formal[name]
+ if !ok {
+ return fmt.Errorf("no such flag -%v", name)
+ }
+ if err := flag.Value.Set(value); err != nil {
+ return err
+ }
+ if fs.actual == nil {
+ fs.actual = make(map[string]*Flag)
+ }
+ fs.actual[name] = flag
+ return nil
+}
+
+// Set sets the value of the named command-line flag.
+func Set(name, value string) error {
+ return CommandLine.Set(name, value)
+}
+
+// isZeroValue guesses whether the string represents the zero
+// value for a flag. It is not accurate but in practice works OK.
+func isZeroValue(value string) bool {
+ switch value {
+ case "false":
+ return true
+ case "":
+ return true
+ case "0":
+ return true
+ }
+ return false
+}
+
+// PrintDefaults prints, to standard error unless configured
+// otherwise, the default values of all defined flags in the set.
+func (fs *FlagSet) PrintDefaults() {
+ writer := tabwriter.NewWriter(fs.Out(), 20, 1, 3, ' ', 0)
+ home := homedir.Get()
+
+ // Don't substitute when HOME is /
+ if runtime.GOOS != "windows" && home == "/" {
+ home = ""
+ }
+
+ // Add a blank line between cmd description and list of options
+ if fs.FlagCount() > 0 {
+ fmt.Fprintln(writer, "")
+ }
+
+ fs.VisitAll(func(flag *Flag) {
+ names := []string{}
+ for _, name := range flag.Names {
+ if name[0] != '#' {
+ names = append(names, name)
+ }
+ }
+ if len(names) > 0 && len(flag.Usage) > 0 {
+ val := flag.DefValue
+
+ if home != "" && strings.HasPrefix(val, home) {
+ val = homedir.GetShortcutString() + val[len(home):]
+ }
+
+ if isZeroValue(val) {
+ format := " -%s"
+ fmt.Fprintf(writer, format, strings.Join(names, ", -"))
+ } else {
+ format := " -%s=%s"
+ fmt.Fprintf(writer, format, strings.Join(names, ", -"), val)
+ }
+ for _, line := range strings.Split(flag.Usage, "\n") {
+ fmt.Fprintln(writer, "\t", line)
+ }
+ }
+ })
+ writer.Flush()
+}
+
+// PrintDefaults prints to standard error the default values of all defined command-line flags.
+func PrintDefaults() {
+ CommandLine.PrintDefaults()
+}
+
+// defaultUsage is the default function to print a usage message.
+func defaultUsage(fs *FlagSet) {
+ if fs.name == "" {
+ fmt.Fprintf(fs.Out(), "Usage:\n")
+ } else {
+ fmt.Fprintf(fs.Out(), "Usage of %s:\n", fs.name)
+ }
+ fs.PrintDefaults()
+}
+
+// NOTE: Usage is not just defaultUsage(CommandLine)
+// because it serves (via godoc flag Usage) as the example
+// for how to write your own usage function.
+
+// Usage prints to standard error a usage message documenting all defined command-line flags.
+// The function is a variable that may be changed to point to a custom function.
+var Usage = func() {
+ fmt.Fprintf(CommandLine.Out(), "Usage of %s:\n", os.Args[0])
+ PrintDefaults()
+}
+
+// ShortUsage prints to standard error a usage message documenting the standard command layout
+// The function is a variable that may be changed to point to a custom function.
+var ShortUsage = func() {
+ fmt.Fprintf(CommandLine.output, "Usage of %s:\n", os.Args[0])
+}
+
+// FlagCount returns the number of flags that have been defined.
+func (fs *FlagSet) FlagCount() int { return len(sortFlags(fs.formal)) }
+
+// FlagCountUndeprecated returns the number of undeprecated flags that have been defined.
+func (fs *FlagSet) FlagCountUndeprecated() int {
+ count := 0
+ for _, flag := range sortFlags(fs.formal) {
+ for _, name := range flag.Names {
+ if name[0] != '#' {
+ count++
+ break
+ }
+ }
+ }
+ return count
+}
+
+// NFlag returns the number of flags that have been set.
+func (fs *FlagSet) NFlag() int { return len(fs.actual) }
+
+// NFlag returns the number of command-line flags that have been set.
+func NFlag() int { return len(CommandLine.actual) }
+
+// Arg returns the i'th argument. Arg(0) is the first remaining argument
+// after flags have been processed.
+func (fs *FlagSet) Arg(i int) string {
+ if i < 0 || i >= len(fs.args) {
+ return ""
+ }
+ return fs.args[i]
+}
+
+// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument
+// after flags have been processed.
+func Arg(i int) string {
+ return CommandLine.Arg(i)
+}
+
+// NArg is the number of arguments remaining after flags have been processed.
+func (fs *FlagSet) NArg() int { return len(fs.args) }
+
+// NArg is the number of arguments remaining after flags have been processed.
+func NArg() int { return len(CommandLine.args) }
+
+// Args returns the non-flag arguments.
+func (fs *FlagSet) Args() []string { return fs.args }
+
+// Args returns the non-flag command-line arguments.
+func Args() []string { return CommandLine.args }
+
+// BoolVar defines a bool flag with specified name, default value, and usage string.
+// The argument p points to a bool variable in which to store the value of the flag.
+func (fs *FlagSet) BoolVar(p *bool, names []string, value bool, usage string) {
+ fs.Var(newBoolValue(value, p), names, usage)
+}
+
+// BoolVar defines a bool flag with specified name, default value, and usage string.
+// The argument p points to a bool variable in which to store the value of the flag.
+func BoolVar(p *bool, names []string, value bool, usage string) {
+ CommandLine.Var(newBoolValue(value, p), names, usage)
+}
+
+// Bool defines a bool flag with specified name, default value, and usage string.
+// The return value is the address of a bool variable that stores the value of the flag.
+func (fs *FlagSet) Bool(names []string, value bool, usage string) *bool {
+ p := new(bool)
+ fs.BoolVar(p, names, value, usage)
+ return p
+}
+
+// Bool defines a bool flag with specified name, default value, and usage string.
+// The return value is the address of a bool variable that stores the value of the flag.
+func Bool(names []string, value bool, usage string) *bool {
+ return CommandLine.Bool(names, value, usage)
+}
+
+// IntVar defines an int flag with specified name, default value, and usage string.
+// The argument p points to an int variable in which to store the value of the flag.
+func (fs *FlagSet) IntVar(p *int, names []string, value int, usage string) {
+ fs.Var(newIntValue(value, p), names, usage)
+}
+
+// IntVar defines an int flag with specified name, default value, and usage string.
+// The argument p points to an int variable in which to store the value of the flag.
+func IntVar(p *int, names []string, value int, usage string) {
+ CommandLine.Var(newIntValue(value, p), names, usage)
+}
+
+// Int defines an int flag with specified name, default value, and usage string.
+// The return value is the address of an int variable that stores the value of the flag.
+func (fs *FlagSet) Int(names []string, value int, usage string) *int {
+ p := new(int)
+ fs.IntVar(p, names, value, usage)
+ return p
+}
+
+// Int defines an int flag with specified name, default value, and usage string.
+// The return value is the address of an int variable that stores the value of the flag.
+func Int(names []string, value int, usage string) *int {
+ return CommandLine.Int(names, value, usage)
+}
+
+// Int64Var defines an int64 flag with specified name, default value, and usage string.
+// The argument p points to an int64 variable in which to store the value of the flag.
+func (fs *FlagSet) Int64Var(p *int64, names []string, value int64, usage string) {
+ fs.Var(newInt64Value(value, p), names, usage)
+}
+
+// Int64Var defines an int64 flag with specified name, default value, and usage string.
+// The argument p points to an int64 variable in which to store the value of the flag.
+func Int64Var(p *int64, names []string, value int64, usage string) {
+ CommandLine.Var(newInt64Value(value, p), names, usage)
+}
+
+// Int64 defines an int64 flag with specified name, default value, and usage string.
+// The return value is the address of an int64 variable that stores the value of the flag.
+func (fs *FlagSet) Int64(names []string, value int64, usage string) *int64 {
+ p := new(int64)
+ fs.Int64Var(p, names, value, usage)
+ return p
+}
+
+// Int64 defines an int64 flag with specified name, default value, and usage string.
+// The return value is the address of an int64 variable that stores the value of the flag.
+func Int64(names []string, value int64, usage string) *int64 {
+ return CommandLine.Int64(names, value, usage)
+}
+
+// UintVar defines a uint flag with specified name, default value, and usage string.
+// The argument p points to a uint variable in which to store the value of the flag.
+func (fs *FlagSet) UintVar(p *uint, names []string, value uint, usage string) {
+ fs.Var(newUintValue(value, p), names, usage)
+}
+
+// UintVar defines a uint flag with specified name, default value, and usage string.
+// The argument p points to a uint variable in which to store the value of the flag.
+func UintVar(p *uint, names []string, value uint, usage string) {
+ CommandLine.Var(newUintValue(value, p), names, usage)
+}
+
+// Uint defines a uint flag with specified name, default value, and usage string.
+// The return value is the address of a uint variable that stores the value of the flag.
+func (fs *FlagSet) Uint(names []string, value uint, usage string) *uint {
+ p := new(uint)
+ fs.UintVar(p, names, value, usage)
+ return p
+}
+
+// Uint defines a uint flag with specified name, default value, and usage string.
+// The return value is the address of a uint variable that stores the value of the flag.
+func Uint(names []string, value uint, usage string) *uint {
+ return CommandLine.Uint(names, value, usage)
+}
+
+// Uint64Var defines a uint64 flag with specified name, default value, and usage string.
+// The argument p points to a uint64 variable in which to store the value of the flag.
+func (fs *FlagSet) Uint64Var(p *uint64, names []string, value uint64, usage string) {
+ fs.Var(newUint64Value(value, p), names, usage)
+}
+
+// Uint64Var defines a uint64 flag with specified name, default value, and usage string.
+// The argument p points to a uint64 variable in which to store the value of the flag.
+func Uint64Var(p *uint64, names []string, value uint64, usage string) {
+ CommandLine.Var(newUint64Value(value, p), names, usage)
+}
+
+// Uint64 defines a uint64 flag with specified name, default value, and usage string.
+// The return value is the address of a uint64 variable that stores the value of the flag.
+func (fs *FlagSet) Uint64(names []string, value uint64, usage string) *uint64 {
+ p := new(uint64)
+ fs.Uint64Var(p, names, value, usage)
+ return p
+}
+
+// Uint64 defines a uint64 flag with specified name, default value, and usage string.
+// The return value is the address of a uint64 variable that stores the value of the flag.
+func Uint64(names []string, value uint64, usage string) *uint64 {
+ return CommandLine.Uint64(names, value, usage)
+}
+
+// Uint16Var defines a uint16 flag with specified name, default value, and usage string.
+// The argument p points to a uint16 variable in which to store the value of the flag.
+func (fs *FlagSet) Uint16Var(p *uint16, names []string, value uint16, usage string) {
+ fs.Var(newUint16Value(value, p), names, usage)
+}
+
+// Uint16Var defines a uint16 flag with specified name, default value, and usage string.
+// The argument p points to a uint16 variable in which to store the value of the flag.
+func Uint16Var(p *uint16, names []string, value uint16, usage string) {
+ CommandLine.Var(newUint16Value(value, p), names, usage)
+}
+
+// Uint16 defines a uint16 flag with specified name, default value, and usage string.
+// The return value is the address of a uint16 variable that stores the value of the flag.
+func (fs *FlagSet) Uint16(names []string, value uint16, usage string) *uint16 {
+ p := new(uint16)
+ fs.Uint16Var(p, names, value, usage)
+ return p
+}
+
+// Uint16 defines a uint16 flag with specified name, default value, and usage string.
+// The return value is the address of a uint16 variable that stores the value of the flag.
+func Uint16(names []string, value uint16, usage string) *uint16 {
+ return CommandLine.Uint16(names, value, usage)
+}
+
+// StringVar defines a string flag with specified name, default value, and usage string.
+// The argument p points to a string variable in which to store the value of the flag.
+func (fs *FlagSet) StringVar(p *string, names []string, value string, usage string) {
+ fs.Var(newStringValue(value, p), names, usage)
+}
+
+// StringVar defines a string flag with specified name, default value, and usage string.
+// The argument p points to a string variable in which to store the value of the flag.
+func StringVar(p *string, names []string, value string, usage string) {
+ CommandLine.Var(newStringValue(value, p), names, usage)
+}
+
+// String defines a string flag with specified name, default value, and usage string.
+// The return value is the address of a string variable that stores the value of the flag.
+func (fs *FlagSet) String(names []string, value string, usage string) *string {
+ p := new(string)
+ fs.StringVar(p, names, value, usage)
+ return p
+}
+
+// String defines a string flag with specified name, default value, and usage string.
+// The return value is the address of a string variable that stores the value of the flag.
+func String(names []string, value string, usage string) *string {
+ return CommandLine.String(names, value, usage)
+}
+
+// Float64Var defines a float64 flag with specified name, default value, and usage string.
+// The argument p points to a float64 variable in which to store the value of the flag.
+func (fs *FlagSet) Float64Var(p *float64, names []string, value float64, usage string) {
+ fs.Var(newFloat64Value(value, p), names, usage)
+}
+
+// Float64Var defines a float64 flag with specified name, default value, and usage string.
+// The argument p points to a float64 variable in which to store the value of the flag.
+func Float64Var(p *float64, names []string, value float64, usage string) {
+ CommandLine.Var(newFloat64Value(value, p), names, usage)
+}
+
+// Float64 defines a float64 flag with specified name, default value, and usage string.
+// The return value is the address of a float64 variable that stores the value of the flag.
+func (fs *FlagSet) Float64(names []string, value float64, usage string) *float64 {
+ p := new(float64)
+ fs.Float64Var(p, names, value, usage)
+ return p
+}
+
+// Float64 defines a float64 flag with specified name, default value, and usage string.
+// The return value is the address of a float64 variable that stores the value of the flag.
+func Float64(names []string, value float64, usage string) *float64 {
+ return CommandLine.Float64(names, value, usage)
+}
+
+// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
+// The argument p points to a time.Duration variable in which to store the value of the flag.
+func (fs *FlagSet) DurationVar(p *time.Duration, names []string, value time.Duration, usage string) {
+ fs.Var(newDurationValue(value, p), names, usage)
+}
+
+// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
+// The argument p points to a time.Duration variable in which to store the value of the flag.
+func DurationVar(p *time.Duration, names []string, value time.Duration, usage string) {
+ CommandLine.Var(newDurationValue(value, p), names, usage)
+}
+
+// Duration defines a time.Duration flag with specified name, default value, and usage string.
+// The return value is the address of a time.Duration variable that stores the value of the flag.
+func (fs *FlagSet) Duration(names []string, value time.Duration, usage string) *time.Duration {
+ p := new(time.Duration)
+ fs.DurationVar(p, names, value, usage)
+ return p
+}
+
+// Duration defines a time.Duration flag with specified name, default value, and usage string.
+// The return value is the address of a time.Duration variable that stores the value of the flag.
+func Duration(names []string, value time.Duration, usage string) *time.Duration {
+ return CommandLine.Duration(names, value, usage)
+}
+
+// Var defines a flag with the specified name and usage string. The type and
+// value of the flag are represented by the first argument, of type Value, which
+// typically holds a user-defined implementation of Value. For instance, the
+// caller could create a flag that turns a comma-separated string into a slice
+// of strings by giving the slice the methods of Value; in particular, Set would
+// decompose the comma-separated string into the slice.
+func (fs *FlagSet) Var(value Value, names []string, usage string) {
+ // Remember the default value as a string; it won't change.
+ flag := &Flag{names, usage, value, value.String()}
+ for _, name := range names {
+ name = strings.TrimPrefix(name, "#")
+ _, alreadythere := fs.formal[name]
+ if alreadythere {
+ var msg string
+ if fs.name == "" {
+ msg = fmt.Sprintf("flag redefined: %s", name)
+ } else {
+ msg = fmt.Sprintf("%s flag redefined: %s", fs.name, name)
+ }
+ fmt.Fprintln(fs.Out(), msg)
+ panic(msg) // Happens only if flags are declared with identical names
+ }
+ if fs.formal == nil {
+ fs.formal = make(map[string]*Flag)
+ }
+ fs.formal[name] = flag
+ }
+}
+
+// Var defines a flag with the specified name and usage string. The type and
+// value of the flag are represented by the first argument, of type Value, which
+// typically holds a user-defined implementation of Value. For instance, the
+// caller could create a flag that turns a comma-separated string into a slice
+// of strings by giving the slice the methods of Value; in particular, Set would
+// decompose the comma-separated string into the slice.
+func Var(value Value, names []string, usage string) {
+ CommandLine.Var(value, names, usage)
+}
+
+// failf prints to standard error a formatted error and usage message and
+// returns the error.
+func (fs *FlagSet) failf(format string, a ...interface{}) error {
+ err := fmt.Errorf(format, a...)
+ fmt.Fprintln(fs.Out(), err)
+ if os.Args[0] == fs.name {
+ fmt.Fprintf(fs.Out(), "See '%s --help'.\n", os.Args[0])
+ } else {
+ fmt.Fprintf(fs.Out(), "See '%s %s --help'.\n", os.Args[0], fs.name)
+ }
+ return err
+}
+
+// usage calls the Usage method for the flag set, or the usage function if
+// the flag set is CommandLine.
+func (fs *FlagSet) usage() {
+ if fs == CommandLine {
+ Usage()
+ } else if fs.Usage == nil {
+ defaultUsage(fs)
+ } else {
+ fs.Usage()
+ }
+}
+
+func trimQuotes(str string) string {
+ if len(str) == 0 {
+ return str
+ }
+ type quote struct {
+ start, end byte
+ }
+
+ // All valid quote types.
+ quotes := []quote{
+ // Double quotes
+ {
+ start: '"',
+ end: '"',
+ },
+
+ // Single quotes
+ {
+ start: '\'',
+ end: '\'',
+ },
+ }
+
+ for _, quote := range quotes {
+ // Only strip if outermost match.
+ if str[0] == quote.start && str[len(str)-1] == quote.end {
+ str = str[1 : len(str)-1]
+ break
+ }
+ }
+
+ return str
+}
+
+// parseOne parses one flag. It reports whether a flag was seen.
+func (fs *FlagSet) parseOne() (bool, string, error) {
+ if len(fs.args) == 0 {
+ return false, "", nil
+ }
+ s := fs.args[0]
+ if len(s) == 0 || s[0] != '-' || len(s) == 1 {
+ return false, "", nil
+ }
+ if s[1] == '-' && len(s) == 2 { // "--" terminates the flags
+ fs.args = fs.args[1:]
+ return false, "", nil
+ }
+ name := s[1:]
+ if len(name) == 0 || name[0] == '=' {
+ return false, "", fs.failf("bad flag syntax: %s", s)
+ }
+
+ // it's a flag. does it have an argument?
+ fs.args = fs.args[1:]
+ hasValue := false
+ value := ""
+ if i := strings.Index(name, "="); i != -1 {
+ value = trimQuotes(name[i+1:])
+ hasValue = true
+ name = name[:i]
+ }
+
+ m := fs.formal
+ flag, alreadythere := m[name] // BUG
+ if !alreadythere {
+ if name == "-help" || name == "help" || name == "h" { // special case for nice help message.
+ fs.usage()
+ return false, "", ErrHelp
+ }
+ if len(name) > 0 && name[0] == '-' {
+ return false, "", fs.failf("flag provided but not defined: -%s", name)
+ }
+ return false, name, ErrRetry
+ }
+ if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg
+ if hasValue {
+ if err := fv.Set(value); err != nil {
+ return false, "", fs.failf("invalid boolean value %q for -%s: %v", value, name, err)
+ }
+ } else {
+ fv.Set("true")
+ }
+ } else {
+ // It must have a value, which might be the next argument.
+ if !hasValue && len(fs.args) > 0 {
+ // value is the next arg
+ hasValue = true
+ value, fs.args = fs.args[0], fs.args[1:]
+ }
+ if !hasValue {
+ return false, "", fs.failf("flag needs an argument: -%s", name)
+ }
+ if err := flag.Value.Set(value); err != nil {
+ return false, "", fs.failf("invalid value %q for flag -%s: %v", value, name, err)
+ }
+ }
+ if fs.actual == nil {
+ fs.actual = make(map[string]*Flag)
+ }
+ fs.actual[name] = flag
+ for i, n := range flag.Names {
+ if n == fmt.Sprintf("#%s", name) {
+ replacement := ""
+ for j := i; j < len(flag.Names); j++ {
+ if flag.Names[j][0] != '#' {
+ replacement = flag.Names[j]
+ break
+ }
+ }
+ if replacement != "" {
+ fmt.Fprintf(fs.Out(), "Warning: '-%s' is deprecated, it will be replaced by '-%s' soon. See usage.\n", name, replacement)
+ } else {
+ fmt.Fprintf(fs.Out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name)
+ }
+ }
+ }
+ return true, "", nil
+}
+
+// Parse parses flag definitions from the argument list, which should not
+// include the command name. Must be called after all flags in the FlagSet
+// are defined and before flags are accessed by the program.
+// The return value will be ErrHelp if -help was set but not defined.
+func (fs *FlagSet) Parse(arguments []string) error {
+ fs.parsed = true
+ fs.args = arguments
+ for {
+ seen, name, err := fs.parseOne()
+ if seen {
+ continue
+ }
+ if err == nil {
+ break
+ }
+ if err == ErrRetry {
+ if len(name) > 1 {
+ err = nil
+ for _, letter := range strings.Split(name, "") {
+ fs.args = append([]string{"-" + letter}, fs.args...)
+ seen2, _, err2 := fs.parseOne()
+ if seen2 {
+ continue
+ }
+ if err2 != nil {
+ err = fs.failf("flag provided but not defined: -%s", name)
+ break
+ }
+ }
+ if err == nil {
+ continue
+ }
+ } else {
+ err = fs.failf("flag provided but not defined: -%s", name)
+ }
+ }
+ switch fs.errorHandling {
+ case ContinueOnError:
+ return err
+ case ExitOnError:
+ os.Exit(125)
+ case PanicOnError:
+ panic(err)
+ }
+ }
+ return nil
+}
+
+// ParseFlags is a utility function that adds a help flag if withHelp is true,
+// calls fs.Parse(args) and prints a relevant error message if there are
+// incorrect number of arguments. It returns error only if error handling is
+// set to ContinueOnError and parsing fails. If error handling is set to
+// ExitOnError, it's safe to ignore the return value.
+func (fs *FlagSet) ParseFlags(args []string, withHelp bool) error {
+ var help *bool
+ if withHelp {
+ help = fs.Bool([]string{"#help", "-help"}, false, "Print usage")
+ }
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ if help != nil && *help {
+ fs.SetOutput(os.Stdout)
+ fs.Usage()
+ os.Exit(0)
+ }
+ if str := fs.CheckArgs(); str != "" {
+ fs.SetOutput(os.Stderr)
+ fs.ReportError(str, withHelp)
+ fs.ShortUsage()
+ os.Exit(1)
+ }
+ return nil
+}
+
+// ReportError is a utility method that prints a user-friendly message
+// containing the error that occurred during parsing and a suggestion to get help
+func (fs *FlagSet) ReportError(str string, withHelp bool) {
+ if withHelp {
+ if os.Args[0] == fs.Name() {
+ str += ".\nSee '" + os.Args[0] + " --help'"
+ } else {
+ str += ".\nSee '" + os.Args[0] + " " + fs.Name() + " --help'"
+ }
+ }
+ fmt.Fprintf(fs.Out(), "%s: %s.\n", os.Args[0], str)
+}
+
+// Parsed reports whether fs.Parse has been called.
+func (fs *FlagSet) Parsed() bool {
+ return fs.parsed
+}
+
+// Parse parses the command-line flags from os.Args[1:]. Must be called
+// after all flags are defined and before flags are accessed by the program.
+func Parse() {
+ // Ignore errors; CommandLine is set for ExitOnError.
+ CommandLine.Parse(os.Args[1:])
+}
+
+// Parsed returns true if the command-line flags have been parsed.
+func Parsed() bool {
+ return CommandLine.Parsed()
+}
+
+// CommandLine is the default set of command-line flags, parsed from os.Args.
+// The top-level functions such as BoolVar, Arg, and on are wrappers for the
+// methods of CommandLine.
+var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
+
+// NewFlagSet returns a new, empty flag set with the specified name and
+// error handling property.
+func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
+ f := &FlagSet{
+ name: name,
+ errorHandling: errorHandling,
+ }
+ return f
+}
+
+// Init sets the name and error handling property for a flag set.
+// By default, the zero FlagSet uses an empty name and the
+// ContinueOnError error handling policy.
+func (fs *FlagSet) Init(name string, errorHandling ErrorHandling) {
+ fs.name = name
+ fs.errorHandling = errorHandling
+}
+
+type mergeVal struct {
+ Value
+ key string
+ fset *FlagSet
+}
+
+func (v mergeVal) Set(s string) error {
+ return v.fset.Set(v.key, s)
+}
+
+func (v mergeVal) IsBoolFlag() bool {
+ if b, ok := v.Value.(boolFlag); ok {
+ return b.IsBoolFlag()
+ }
+ return false
+}
+
+// Name returns the name of a mergeVal.
+// If the original value had a name, return the original name,
+// otherwise, return the key assigned to this mergeVal.
+func (v mergeVal) Name() string {
+ type namedValue interface {
+ Name() string
+ }
+ if nVal, ok := v.Value.(namedValue); ok {
+ return nVal.Name()
+ }
+ return v.key
+}
+
+// Merge is an helper function that merges n FlagSets into a single dest FlagSet
+// In case of name collision between the flagsets it will apply
+// the destination FlagSet's errorHandling behavior.
+func Merge(dest *FlagSet, flagsets ...*FlagSet) error {
+ for _, fset := range flagsets {
+ if fset.formal == nil {
+ continue
+ }
+ for k, f := range fset.formal {
+ if _, ok := dest.formal[k]; ok {
+ var err error
+ if fset.name == "" {
+ err = fmt.Errorf("flag redefined: %s", k)
+ } else {
+ err = fmt.Errorf("%s flag redefined: %s", fset.name, k)
+ }
+ fmt.Fprintln(fset.Out(), err.Error())
+ // Happens only if flags are declared with identical names
+ switch dest.errorHandling {
+ case ContinueOnError:
+ return err
+ case ExitOnError:
+ os.Exit(2)
+ case PanicOnError:
+ panic(err)
+ }
+ }
+ newF := *f
+ newF.Value = mergeVal{f.Value, k, fset}
+ if dest.formal == nil {
+ dest.formal = make(map[string]*Flag)
+ }
+ dest.formal[k] = &newF
+ }
+ }
+ return nil
+}
+
+// IsEmpty reports if the FlagSet is actually empty.
+func (fs *FlagSet) IsEmpty() bool {
+ return len(fs.actual) == 0
+}
--- /dev/null
+// Copyright 2014-2016 The Docker & Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mflag
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+// ResetForTesting clears all flag state and sets the usage function as directed.
+// After calling ResetForTesting, parse errors in flag handling will not
+// exit the program.
+func ResetForTesting(usage func()) {
+ CommandLine = NewFlagSet(os.Args[0], ContinueOnError)
+ Usage = usage
+}
+func boolString(s string) string {
+ if s == "0" {
+ return "false"
+ }
+ return "true"
+}
+
+func TestEverything(t *testing.T) {
+ ResetForTesting(nil)
+ Bool([]string{"test_bool"}, false, "bool value")
+ Int([]string{"test_int"}, 0, "int value")
+ Int64([]string{"test_int64"}, 0, "int64 value")
+ Uint([]string{"test_uint"}, 0, "uint value")
+ Uint64([]string{"test_uint64"}, 0, "uint64 value")
+ String([]string{"test_string"}, "0", "string value")
+ Float64([]string{"test_float64"}, 0, "float64 value")
+ Duration([]string{"test_duration"}, 0, "time.Duration value")
+
+ m := make(map[string]*Flag)
+ desired := "0"
+ visitor := func(f *Flag) {
+ for _, name := range f.Names {
+ if len(name) > 5 && name[0:5] == "test_" {
+ m[name] = f
+ ok := false
+ switch {
+ case f.Value.String() == desired:
+ ok = true
+ case name == "test_bool" && f.Value.String() == boolString(desired):
+ ok = true
+ case name == "test_duration" && f.Value.String() == desired+"s":
+ ok = true
+ }
+ if !ok {
+ t.Error("Visit: bad value", f.Value.String(), "for", name)
+ }
+ }
+ }
+ }
+ VisitAll(visitor)
+ if len(m) != 8 {
+ t.Error("VisitAll misses some flags")
+ for k, v := range m {
+ t.Log(k, *v)
+ }
+ }
+ m = make(map[string]*Flag)
+ Visit(visitor)
+ if len(m) != 0 {
+ t.Error("Visit sees unset flags")
+ for k, v := range m {
+ t.Log(k, *v)
+ }
+ }
+ // Now set all flags
+ Set("test_bool", "true")
+ Set("test_int", "1")
+ Set("test_int64", "1")
+ Set("test_uint", "1")
+ Set("test_uint64", "1")
+ Set("test_string", "1")
+ Set("test_float64", "1")
+ Set("test_duration", "1s")
+ desired = "1"
+ Visit(visitor)
+ if len(m) != 8 {
+ t.Error("Visit fails after set")
+ for k, v := range m {
+ t.Log(k, *v)
+ }
+ }
+ // Now test they're visited in sort order.
+ var flagNames []string
+ Visit(func(f *Flag) {
+ for _, name := range f.Names {
+ flagNames = append(flagNames, name)
+ }
+ })
+ if !sort.StringsAreSorted(flagNames) {
+ t.Errorf("flag names not sorted: %v", flagNames)
+ }
+}
+
+func TestGet(t *testing.T) {
+ ResetForTesting(nil)
+ Bool([]string{"test_bool"}, true, "bool value")
+ Int([]string{"test_int"}, 1, "int value")
+ Int64([]string{"test_int64"}, 2, "int64 value")
+ Uint([]string{"test_uint"}, 3, "uint value")
+ Uint64([]string{"test_uint64"}, 4, "uint64 value")
+ String([]string{"test_string"}, "5", "string value")
+ Float64([]string{"test_float64"}, 6, "float64 value")
+ Duration([]string{"test_duration"}, 7, "time.Duration value")
+
+ visitor := func(f *Flag) {
+ for _, name := range f.Names {
+ if len(name) > 5 && name[0:5] == "test_" {
+ g, ok := f.Value.(Getter)
+ if !ok {
+ t.Errorf("Visit: value does not satisfy Getter: %T", f.Value)
+ return
+ }
+ switch name {
+ case "test_bool":
+ ok = g.Get() == true
+ case "test_int":
+ ok = g.Get() == int(1)
+ case "test_int64":
+ ok = g.Get() == int64(2)
+ case "test_uint":
+ ok = g.Get() == uint(3)
+ case "test_uint64":
+ ok = g.Get() == uint64(4)
+ case "test_string":
+ ok = g.Get() == "5"
+ case "test_float64":
+ ok = g.Get() == float64(6)
+ case "test_duration":
+ ok = g.Get() == time.Duration(7)
+ }
+ if !ok {
+ t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), name)
+ }
+ }
+ }
+ }
+ VisitAll(visitor)
+}
+
+func testParse(f *FlagSet, t *testing.T) {
+ if f.Parsed() {
+ t.Error("f.Parse() = true before Parse")
+ }
+ boolFlag := f.Bool([]string{"bool"}, false, "bool value")
+ bool2Flag := f.Bool([]string{"bool2"}, false, "bool2 value")
+ f.Bool([]string{"bool3"}, false, "bool3 value")
+ bool4Flag := f.Bool([]string{"bool4"}, false, "bool4 value")
+ intFlag := f.Int([]string{"-int"}, 0, "int value")
+ int64Flag := f.Int64([]string{"-int64"}, 0, "int64 value")
+ uintFlag := f.Uint([]string{"uint"}, 0, "uint value")
+ uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value")
+ stringFlag := f.String([]string{"string"}, "0", "string value")
+ f.String([]string{"string2"}, "0", "string2 value")
+ singleQuoteFlag := f.String([]string{"squote"}, "", "single quoted value")
+ doubleQuoteFlag := f.String([]string{"dquote"}, "", "double quoted value")
+ mixedQuoteFlag := f.String([]string{"mquote"}, "", "mixed quoted value")
+ mixed2QuoteFlag := f.String([]string{"mquote2"}, "", "mixed2 quoted value")
+ nestedQuoteFlag := f.String([]string{"nquote"}, "", "nested quoted value")
+ nested2QuoteFlag := f.String([]string{"nquote2"}, "", "nested2 quoted value")
+ float64Flag := f.Float64([]string{"float64"}, 0, "float64 value")
+ durationFlag := f.Duration([]string{"duration"}, 5*time.Second, "time.Duration value")
+ extra := "one-extra-argument"
+ args := []string{
+ "-bool",
+ "-bool2=true",
+ "-bool4=false",
+ "--int", "22",
+ "--int64", "0x23",
+ "-uint", "24",
+ "--uint64", "25",
+ "-string", "hello",
+ "-squote='single'",
+ `-dquote="double"`,
+ `-mquote='mixed"`,
+ `-mquote2="mixed2'`,
+ `-nquote="'single nested'"`,
+ `-nquote2='"double nested"'`,
+ "-float64", "2718e28",
+ "-duration", "2m",
+ extra,
+ }
+ if err := f.Parse(args); err != nil {
+ t.Fatal(err)
+ }
+ if !f.Parsed() {
+ t.Error("f.Parse() = false after Parse")
+ }
+ if *boolFlag != true {
+ t.Error("bool flag should be true, is ", *boolFlag)
+ }
+ if *bool2Flag != true {
+ t.Error("bool2 flag should be true, is ", *bool2Flag)
+ }
+ if !f.IsSet("bool2") {
+ t.Error("bool2 should be marked as set")
+ }
+ if f.IsSet("bool3") {
+ t.Error("bool3 should not be marked as set")
+ }
+ if !f.IsSet("bool4") {
+ t.Error("bool4 should be marked as set")
+ }
+ if *bool4Flag != false {
+ t.Error("bool4 flag should be false, is ", *bool4Flag)
+ }
+ if *intFlag != 22 {
+ t.Error("int flag should be 22, is ", *intFlag)
+ }
+ if *int64Flag != 0x23 {
+ t.Error("int64 flag should be 0x23, is ", *int64Flag)
+ }
+ if *uintFlag != 24 {
+ t.Error("uint flag should be 24, is ", *uintFlag)
+ }
+ if *uint64Flag != 25 {
+ t.Error("uint64 flag should be 25, is ", *uint64Flag)
+ }
+ if *stringFlag != "hello" {
+ t.Error("string flag should be `hello`, is ", *stringFlag)
+ }
+ if !f.IsSet("string") {
+ t.Error("string flag should be marked as set")
+ }
+ if f.IsSet("string2") {
+ t.Error("string2 flag should not be marked as set")
+ }
+ if *singleQuoteFlag != "single" {
+ t.Error("single quote string flag should be `single`, is ", *singleQuoteFlag)
+ }
+ if *doubleQuoteFlag != "double" {
+ t.Error("double quote string flag should be `double`, is ", *doubleQuoteFlag)
+ }
+ if *mixedQuoteFlag != `'mixed"` {
+ t.Error("mixed quote string flag should be `'mixed\"`, is ", *mixedQuoteFlag)
+ }
+ if *mixed2QuoteFlag != `"mixed2'` {
+ t.Error("mixed2 quote string flag should be `\"mixed2'`, is ", *mixed2QuoteFlag)
+ }
+ if *nestedQuoteFlag != "'single nested'" {
+ t.Error("nested quote string flag should be `'single nested'`, is ", *nestedQuoteFlag)
+ }
+ if *nested2QuoteFlag != `"double nested"` {
+ t.Error("double quote string flag should be `\"double nested\"`, is ", *nested2QuoteFlag)
+ }
+ if *float64Flag != 2718e28 {
+ t.Error("float64 flag should be 2718e28, is ", *float64Flag)
+ }
+ if *durationFlag != 2*time.Minute {
+ t.Error("duration flag should be 2m, is ", *durationFlag)
+ }
+ if len(f.Args()) != 1 {
+ t.Error("expected one argument, got", len(f.Args()))
+ } else if f.Args()[0] != extra {
+ t.Errorf("expected argument %q got %q", extra, f.Args()[0])
+ }
+}
+
+func testPanic(f *FlagSet, t *testing.T) {
+ f.Int([]string{"-int"}, 0, "int value")
+ if f.Parsed() {
+ t.Error("f.Parse() = true before Parse")
+ }
+ args := []string{
+ "-int", "21",
+ }
+ f.Parse(args)
+}
+
+func TestParsePanic(t *testing.T) {
+ ResetForTesting(func() {})
+ testPanic(CommandLine, t)
+}
+
+func TestParse(t *testing.T) {
+ ResetForTesting(func() { t.Error("bad parse") })
+ testParse(CommandLine, t)
+}
+
+func TestFlagSetParse(t *testing.T) {
+ testParse(NewFlagSet("test", ContinueOnError), t)
+}
+
+// Declare a user-defined flag type.
+type flagVar []string
+
+func (f *flagVar) String() string {
+ return fmt.Sprint([]string(*f))
+}
+
+func (f *flagVar) Set(value string) error {
+ *f = append(*f, value)
+ return nil
+}
+
+func TestUserDefined(t *testing.T) {
+ var flags FlagSet
+ flags.Init("test", ContinueOnError)
+ var v flagVar
+ flags.Var(&v, []string{"v"}, "usage")
+ if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil {
+ t.Error(err)
+ }
+ if len(v) != 3 {
+ t.Fatal("expected 3 args; got ", len(v))
+ }
+ expect := "[1 2 3]"
+ if v.String() != expect {
+ t.Errorf("expected value %q got %q", expect, v.String())
+ }
+}
+
+// Declare a user-defined boolean flag type.
+type boolFlagVar struct {
+ count int
+}
+
+func (b *boolFlagVar) String() string {
+ return fmt.Sprintf("%d", b.count)
+}
+
+func (b *boolFlagVar) Set(value string) error {
+ if value == "true" {
+ b.count++
+ }
+ return nil
+}
+
+func (b *boolFlagVar) IsBoolFlag() bool {
+ return b.count < 4
+}
+
+func TestUserDefinedBool(t *testing.T) {
+ var flags FlagSet
+ flags.Init("test", ContinueOnError)
+ var b boolFlagVar
+ var err error
+ flags.Var(&b, []string{"b"}, "usage")
+ if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil {
+ if b.count < 4 {
+ t.Error(err)
+ }
+ }
+
+ if b.count != 4 {
+ t.Errorf("want: %d; got: %d", 4, b.count)
+ }
+
+ if err == nil {
+ t.Error("expected error; got none")
+ }
+}
+
+func TestSetOutput(t *testing.T) {
+ var flags FlagSet
+ var buf bytes.Buffer
+ flags.SetOutput(&buf)
+ flags.Init("test", ContinueOnError)
+ flags.Parse([]string{"-unknown"})
+ if out := buf.String(); !strings.Contains(out, "-unknown") {
+ t.Logf("expected output mentioning unknown; got %q", out)
+ }
+}
+
+// This tests that one can reset the flags. This still works but not well, and is
+// superseded by FlagSet.
+func TestChangingArgs(t *testing.T) {
+ ResetForTesting(func() { t.Fatal("bad parse") })
+ oldArgs := os.Args
+ defer func() { os.Args = oldArgs }()
+ os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"}
+ before := Bool([]string{"before"}, false, "")
+ if err := CommandLine.Parse(os.Args[1:]); err != nil {
+ t.Fatal(err)
+ }
+ cmd := Arg(0)
+ os.Args = Args()
+ after := Bool([]string{"after"}, false, "")
+ Parse()
+ args := Args()
+
+ if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" {
+ t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args)
+ }
+}
+
+// Test that -help invokes the usage message and returns ErrHelp.
+func TestHelp(t *testing.T) {
+ var helpCalled = false
+ fs := NewFlagSet("help test", ContinueOnError)
+ fs.Usage = func() { helpCalled = true }
+ var flag bool
+ fs.BoolVar(&flag, []string{"flag"}, false, "regular flag")
+ // Regular flag invocation should work
+ err := fs.Parse([]string{"-flag=true"})
+ if err != nil {
+ t.Fatal("expected no error; got ", err)
+ }
+ if !flag {
+ t.Error("flag was not set by -flag")
+ }
+ if helpCalled {
+ t.Error("help called for regular flag")
+ helpCalled = false // reset for next test
+ }
+ // Help flag should work as expected.
+ err = fs.Parse([]string{"-help"})
+ if err == nil {
+ t.Fatal("error expected")
+ }
+ if err != ErrHelp {
+ t.Fatal("expected ErrHelp; got ", err)
+ }
+ if !helpCalled {
+ t.Fatal("help was not called")
+ }
+ // If we define a help flag, that should override.
+ var help bool
+ fs.BoolVar(&help, []string{"help"}, false, "help flag")
+ helpCalled = false
+ err = fs.Parse([]string{"-help"})
+ if err != nil {
+ t.Fatal("expected no error for defined -help; got ", err)
+ }
+ if helpCalled {
+ t.Fatal("help was called; should not have been for defined help flag")
+ }
+}
+
+// Test the flag count functions.
+func TestFlagCounts(t *testing.T) {
+ fs := NewFlagSet("help test", ContinueOnError)
+ var flag bool
+ fs.BoolVar(&flag, []string{"flag1"}, false, "regular flag")
+ fs.BoolVar(&flag, []string{"#deprecated1"}, false, "regular flag")
+ fs.BoolVar(&flag, []string{"f", "flag2"}, false, "regular flag")
+ fs.BoolVar(&flag, []string{"#d", "#deprecated2"}, false, "regular flag")
+ fs.BoolVar(&flag, []string{"flag3"}, false, "regular flag")
+ fs.BoolVar(&flag, []string{"g", "#flag4", "-flag4"}, false, "regular flag")
+
+ if fs.FlagCount() != 6 {
+ t.Fatal("FlagCount wrong. ", fs.FlagCount())
+ }
+ if fs.FlagCountUndeprecated() != 4 {
+ t.Fatal("FlagCountUndeprecated wrong. ", fs.FlagCountUndeprecated())
+ }
+ if fs.NFlag() != 0 {
+ t.Fatal("NFlag wrong. ", fs.NFlag())
+ }
+ err := fs.Parse([]string{"-fd", "-g", "-flag4"})
+ if err != nil {
+ t.Fatal("expected no error for defined -help; got ", err)
+ }
+ if fs.NFlag() != 4 {
+ t.Fatal("NFlag wrong. ", fs.NFlag())
+ }
+}
+
+// Show up bug in sortFlags
+func TestSortFlags(t *testing.T) {
+ fs := NewFlagSet("help TestSortFlags", ContinueOnError)
+
+ var err error
+
+ var b bool
+ fs.BoolVar(&b, []string{"b", "-banana"}, false, "usage")
+
+ err = fs.Parse([]string{"--banana=true"})
+ if err != nil {
+ t.Fatal("expected no error; got ", err)
+ }
+
+ count := 0
+
+ fs.VisitAll(func(flag *Flag) {
+ count++
+ if flag == nil {
+ t.Fatal("VisitAll should not return a nil flag")
+ }
+ })
+ flagcount := fs.FlagCount()
+ if flagcount != count {
+ t.Fatalf("FlagCount (%d) != number (%d) of elements visited", flagcount, count)
+ }
+ // Make sure its idempotent
+ if flagcount != fs.FlagCount() {
+ t.Fatalf("FlagCount (%d) != fs.FlagCount() (%d) of elements visited", flagcount, fs.FlagCount())
+ }
+
+ count = 0
+ fs.Visit(func(flag *Flag) {
+ count++
+ if flag == nil {
+ t.Fatal("Visit should not return a nil flag")
+ }
+ })
+ nflag := fs.NFlag()
+ if nflag != count {
+ t.Fatalf("NFlag (%d) != number (%d) of elements visited", nflag, count)
+ }
+ if nflag != fs.NFlag() {
+ t.Fatalf("NFlag (%d) != fs.NFlag() (%d) of elements visited", nflag, fs.NFlag())
+ }
+}
+
+func TestMergeFlags(t *testing.T) {
+ base := NewFlagSet("base", ContinueOnError)
+ base.String([]string{"f"}, "", "")
+
+ fs := NewFlagSet("test", ContinueOnError)
+ Merge(fs, base)
+ if len(fs.formal) != 1 {
+ t.Fatalf("FlagCount (%d) != number (1) of elements merged", len(fs.formal))
+ }
+}
--- /dev/null
+package client
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/docker/docker/pkg/stringid"
+ flag "github.com/docker/libnetwork/client/mflag"
+ "github.com/docker/libnetwork/netlabel"
+)
+
+type command struct {
+ name string
+ description string
+}
+
+var (
+ networkCommands = []command{
+ {"create", "Create a network"},
+ {"rm", "Remove a network"},
+ {"ls", "List all networks"},
+ {"info", "Display information of a network"},
+ }
+)
+
+// CmdNetwork handles the root Network UI
+func (cli *NetworkCli) CmdNetwork(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "network", "COMMAND [OPTIONS] [arg...]", networkUsage(chain), false)
+ cmd.Require(flag.Min, 1)
+ err := cmd.ParseFlags(args, true)
+ if err == nil {
+ cmd.Usage()
+ return fmt.Errorf("invalid command : %v", args)
+ }
+ return err
+}
+
+// CmdNetworkCreate handles Network Create UI
+func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "create", "NETWORK-NAME", "Creates a new network with a name specified by the user", false)
+ flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network")
+ flID := cmd.String([]string{"-id"}, "", "Network ID string")
+ flOpts := cmd.String([]string{"o", "-opt"}, "", "Network options")
+ flInternal := cmd.Bool([]string{"-internal"}, false, "Config the network to be internal")
+ flIPv6 := cmd.Bool([]string{"-ipv6"}, false, "Enable IPv6 on the network")
+ flSubnet := cmd.String([]string{"-subnet"}, "", "Subnet option")
+ flRange := cmd.String([]string{"-ip-range"}, "", "Range option")
+
+ cmd.Require(flag.Exact, 1)
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+ networkOpts := make(map[string]string)
+ if *flInternal {
+ networkOpts[netlabel.Internal] = "true"
+ }
+ if *flIPv6 {
+ networkOpts[netlabel.EnableIPv6] = "true"
+ }
+
+ driverOpts := make(map[string]string)
+ if *flOpts != "" {
+ opts := strings.Split(*flOpts, ",")
+ for _, opt := range opts {
+ driverOpts[netlabel.Key(opt)] = netlabel.Value(opt)
+ }
+ }
+
+ var icList []ipamConf
+ if *flSubnet != "" {
+ ic := ipamConf{
+ PreferredPool: *flSubnet,
+ }
+
+ if *flRange != "" {
+ ic.SubPool = *flRange
+ }
+
+ icList = append(icList, ic)
+ }
+
+ // Construct network create request body
+ nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, ID: *flID, IPv4Conf: icList, DriverOpts: driverOpts, NetworkOpts: networkOpts}
+ obj, _, err := readBody(cli.call("POST", "/networks", nc, nil))
+ if err != nil {
+ return err
+ }
+ var replyID string
+ err = json.Unmarshal(obj, &replyID)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(cli.out, "%s\n", replyID)
+ return nil
+}
+
+// CmdNetworkRm handles Network Delete UI
+func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "rm", "NETWORK", "Deletes a network", false)
+ cmd.Require(flag.Exact, 1)
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+ id, err := lookupNetworkID(cli, cmd.Arg(0))
+ if err != nil {
+ return err
+ }
+ _, _, err = readBody(cli.call("DELETE", "/networks/"+id, nil, nil))
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// CmdNetworkLs handles Network List UI
+func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "ls", "", "Lists all the networks created by the user", false)
+ quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
+ nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created")
+ last := cmd.Int([]string{"n"}, -1, "Show n last created networks")
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+ obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
+ if err != nil {
+ return err
+ }
+ if *last == -1 && *nLatest {
+ *last = 1
+ }
+
+ var networkResources []networkResource
+ err = json.Unmarshal(obj, &networkResources)
+ if err != nil {
+ return err
+ }
+
+ wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
+
+ // unless quiet (-q) is specified, print field titles
+ if !*quiet {
+ fmt.Fprintln(wr, "NETWORK ID\tNAME\tTYPE")
+ }
+
+ for _, networkResource := range networkResources {
+ ID := networkResource.ID
+ netName := networkResource.Name
+ if !*noTrunc {
+ ID = stringid.TruncateID(ID)
+ }
+ if *quiet {
+ fmt.Fprintln(wr, ID)
+ continue
+ }
+ netType := networkResource.Type
+ fmt.Fprintf(wr, "%s\t%s\t%s\t",
+ ID,
+ netName,
+ netType)
+ fmt.Fprint(wr, "\n")
+ }
+ wr.Flush()
+ return nil
+}
+
+// CmdNetworkInfo handles Network Info UI
+func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "info", "NETWORK", "Displays detailed information on a network", false)
+ cmd.Require(flag.Exact, 1)
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+
+ id, err := lookupNetworkID(cli, cmd.Arg(0))
+ if err != nil {
+ return err
+ }
+
+ obj, _, err := readBody(cli.call("GET", "/networks/"+id, nil, nil))
+ if err != nil {
+ return err
+ }
+ networkResource := &networkResource{}
+ if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
+ return err
+ }
+ fmt.Fprintf(cli.out, "Network Id: %s\n", networkResource.ID)
+ fmt.Fprintf(cli.out, "Name: %s\n", networkResource.Name)
+ fmt.Fprintf(cli.out, "Type: %s\n", networkResource.Type)
+ if networkResource.Services != nil {
+ for _, serviceResource := range networkResource.Services {
+ fmt.Fprintf(cli.out, " Service Id: %s\n", serviceResource.ID)
+ fmt.Fprintf(cli.out, "\tName: %s\n", serviceResource.Name)
+ }
+ }
+
+ return nil
+}
+
+// Helper function to predict if a string is a name or id or partial-id
+// This provides a best-effort mechanism to identify an id with the help of GET Filter APIs
+// Being a UI, its most likely that name will be used by the user, which is used to lookup
+// the corresponding ID. If ID is not found, this function will assume that the passed string
+// is an ID by itself.
+
+func lookupNetworkID(cli *NetworkCli, nameID string) (string, error) {
+ obj, statusCode, err := readBody(cli.call("GET", "/networks?name="+nameID, nil, nil))
+ if err != nil {
+ return "", err
+ }
+
+ if statusCode != http.StatusOK {
+ return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
+ }
+
+ var list []*networkResource
+ err = json.Unmarshal(obj, &list)
+ if err != nil {
+ return "", err
+ }
+ if len(list) > 0 {
+ // name query filter will always return a single-element collection
+ return list[0].ID, nil
+ }
+
+ // Check for Partial-id
+ obj, statusCode, err = readBody(cli.call("GET", "/networks?partial-id="+nameID, nil, nil))
+ if err != nil {
+ return "", err
+ }
+
+ if statusCode != http.StatusOK {
+ return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
+ }
+
+ err = json.Unmarshal(obj, &list)
+ if err != nil {
+ return "", err
+ }
+ if len(list) == 0 {
+ return "", fmt.Errorf("resource not found %s", nameID)
+ }
+ if len(list) > 1 {
+ return "", fmt.Errorf("multiple Networks matching the partial identifier (%s). Please use full identifier", nameID)
+ }
+ return list[0].ID, nil
+}
+
+func networkUsage(chain string) string {
+ help := "Commands:\n"
+
+ for _, cmd := range networkCommands {
+ help += fmt.Sprintf(" %-25.25s%s\n", cmd.name, cmd.description)
+ }
+
+ help += fmt.Sprintf("\nRun '%s network COMMAND --help' for more information on a command.", chain)
+ return help
+}
--- /dev/null
+package client
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/docker/docker/opts"
+ "github.com/docker/docker/pkg/stringid"
+ flag "github.com/docker/libnetwork/client/mflag"
+ "github.com/docker/libnetwork/netutils"
+)
+
+var (
+ serviceCommands = []command{
+ {"publish", "Publish a service"},
+ {"unpublish", "Remove a service"},
+ {"attach", "Attach a backend (container) to the service"},
+ {"detach", "Detach the backend from the service"},
+ {"ls", "Lists all services"},
+ {"info", "Display information about a service"},
+ }
+)
+
+func lookupServiceID(cli *NetworkCli, nwName, svNameID string) (string, error) {
+ // Sanity Check
+ obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/networks?name=%s", nwName), nil, nil))
+ if err != nil {
+ return "", err
+ }
+ var nwList []networkResource
+ if err = json.Unmarshal(obj, &nwList); err != nil {
+ return "", err
+ }
+ if len(nwList) == 0 {
+ return "", fmt.Errorf("Network %s does not exist", nwName)
+ }
+
+ if nwName == "" {
+ obj, _, err := readBody(cli.call("GET", "/networks/"+nwList[0].ID, nil, nil))
+ if err != nil {
+ return "", err
+ }
+ networkResource := &networkResource{}
+ if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
+ return "", err
+ }
+ nwName = networkResource.Name
+ }
+
+ // Query service by name
+ obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/services?name=%s", svNameID), nil, nil))
+ if err != nil {
+ return "", err
+ }
+
+ if statusCode != http.StatusOK {
+ return "", fmt.Errorf("name query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
+ }
+
+ var list []*serviceResource
+ if err = json.Unmarshal(obj, &list); err != nil {
+ return "", err
+ }
+ for _, sr := range list {
+ if sr.Network == nwName {
+ return sr.ID, nil
+ }
+ }
+
+ // Query service by Partial-id (this covers full id as well)
+ obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/services?partial-id=%s", svNameID), nil, nil))
+ if err != nil {
+ return "", err
+ }
+
+ if statusCode != http.StatusOK {
+ return "", fmt.Errorf("partial-id match query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
+ }
+
+ if err = json.Unmarshal(obj, &list); err != nil {
+ return "", err
+ }
+ for _, sr := range list {
+ if sr.Network == nwName {
+ return sr.ID, nil
+ }
+ }
+
+ return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName)
+}
+
+func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) {
+ // Container is a Docker resource, ask docker about it.
+ // In case of connection error, we assume we are running in dnet and return whatever was passed to us
+ obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/containers/%s/json", cnNameID), nil, nil))
+ if err != nil {
+ // We are probably running outside of docker
+ return cnNameID, nil
+ }
+
+ var x map[string]interface{}
+ err = json.Unmarshal(obj, &x)
+ if err != nil {
+ return "", err
+ }
+ if iid, ok := x["Id"]; ok {
+ if id, ok := iid.(string); ok {
+ return id, nil
+ }
+ return "", errors.New("Unexpected data type for container ID in json response")
+ }
+ return "", errors.New("Cannot find container ID in json response")
+}
+
+func lookupSandboxID(cli *NetworkCli, containerID string) (string, error) {
+ obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/sandboxes?partial-container-id=%s", containerID), nil, nil))
+ if err != nil {
+ return "", err
+ }
+
+ var sandboxList []SandboxResource
+ err = json.Unmarshal(obj, &sandboxList)
+ if err != nil {
+ return "", err
+ }
+
+ if len(sandboxList) == 0 {
+ return "", fmt.Errorf("cannot find sandbox for container: %s", containerID)
+ }
+
+ return sandboxList[0].ID, nil
+}
+
+// CmdService handles the service UI
+func (cli *NetworkCli) CmdService(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false)
+ cmd.Require(flag.Min, 1)
+ err := cmd.ParseFlags(args, true)
+ if err == nil {
+ cmd.Usage()
+ return fmt.Errorf("Invalid command : %v", args)
+ }
+ return err
+}
+
+// Parse service name for "SERVICE[.NETWORK]" format
+func parseServiceName(name string) (string, string) {
+ s := strings.Split(name, ".")
+ var sName, nName string
+ if len(s) > 1 {
+ nName = s[len(s)-1]
+ sName = strings.Join(s[:len(s)-1], ".")
+ } else {
+ sName = s[0]
+ }
+ return sName, nName
+}
+
+// CmdServicePublish handles service create UI
+func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "publish", "SERVICE[.NETWORK]", "Publish a new service on a network", false)
+ flAlias := opts.NewListOpts(netutils.ValidateAlias)
+ cmd.Var(&flAlias, []string{"-alias"}, "Add alias to self")
+ cmd.Require(flag.Exact, 1)
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+
+ sn, nn := parseServiceName(cmd.Arg(0))
+ sc := serviceCreate{Name: sn, Network: nn, MyAliases: flAlias.GetAll()}
+ obj, _, err := readBody(cli.call("POST", "/services", sc, nil))
+ if err != nil {
+ return err
+ }
+
+ var replyID string
+ err = json.Unmarshal(obj, &replyID)
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintf(cli.out, "%s\n", replyID)
+ return nil
+}
+
+// CmdServiceUnpublish handles service delete UI
+func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "unpublish", "SERVICE[.NETWORK]", "Removes a service", false)
+ force := cmd.Bool([]string{"f", "-force"}, false, "force unpublish service")
+ cmd.Require(flag.Exact, 1)
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+
+ sn, nn := parseServiceName(cmd.Arg(0))
+ serviceID, err := lookupServiceID(cli, nn, sn)
+ if err != nil {
+ return err
+ }
+
+ sd := serviceDelete{Name: sn, Force: *force}
+ _, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, sd, nil))
+
+ return err
+}
+
+// CmdServiceLs handles service list UI
+func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "ls", "SERVICE", "Lists all the services on a network", false)
+ flNetwork := cmd.String([]string{"net", "-network"}, "", "Only show the services that are published on the specified network")
+ quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
+
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+
+ var obj []byte
+ if *flNetwork == "" {
+ obj, _, err = readBody(cli.call("GET", "/services", nil, nil))
+ } else {
+ obj, _, err = readBody(cli.call("GET", "/services?network="+*flNetwork, nil, nil))
+ }
+ if err != nil {
+ return err
+ }
+
+ var serviceResources []serviceResource
+ err = json.Unmarshal(obj, &serviceResources)
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+
+ wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
+ // unless quiet (-q) is specified, print field titles
+ if !*quiet {
+ fmt.Fprintln(wr, "SERVICE ID\tNAME\tNETWORK\tCONTAINER\tSANDBOX")
+ }
+
+ for _, sr := range serviceResources {
+ ID := sr.ID
+ bkID, sbID, err := getBackendID(cli, ID)
+ if err != nil {
+ return err
+ }
+ if !*noTrunc {
+ ID = stringid.TruncateID(ID)
+ bkID = stringid.TruncateID(bkID)
+ sbID = stringid.TruncateID(sbID)
+ }
+ if !*quiet {
+ fmt.Fprintf(wr, "%s\t%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID, sbID)
+ } else {
+ fmt.Fprintln(wr, ID)
+ }
+ }
+ wr.Flush()
+
+ return nil
+}
+
+func getBackendID(cli *NetworkCli, servID string) (string, string, error) {
+ var (
+ obj []byte
+ err error
+ bk string
+ sb string
+ )
+
+ if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
+ var sr SandboxResource
+ if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&sr); err == nil {
+ bk = sr.ContainerID
+ sb = sr.ID
+ } else {
+ // Only print a message, don't make the caller cli fail for this
+ fmt.Fprintf(cli.out, "Failed to retrieve backend list for service %s (%v)\n", servID, err)
+ }
+ }
+
+ return bk, sb, err
+}
+
+// CmdServiceInfo handles service info UI
+func (cli *NetworkCli) CmdServiceInfo(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "info", "SERVICE[.NETWORK]", "Displays detailed information about a service", false)
+ cmd.Require(flag.Min, 1)
+
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+
+ sn, nn := parseServiceName(cmd.Arg(0))
+ serviceID, err := lookupServiceID(cli, nn, sn)
+ if err != nil {
+ return err
+ }
+
+ obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil))
+ if err != nil {
+ return err
+ }
+
+ sr := &serviceResource{}
+ if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil {
+ return err
+ }
+
+ fmt.Fprintf(cli.out, "Service Id: %s\n", sr.ID)
+ fmt.Fprintf(cli.out, "\tName: %s\n", sr.Name)
+ fmt.Fprintf(cli.out, "\tNetwork: %s\n", sr.Network)
+
+ return nil
+}
+
+// CmdServiceAttach handles service attach UI
+func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE[.NETWORK]", "Sets a container as a service backend", false)
+ flAlias := opts.NewListOpts(netutils.ValidateAlias)
+ cmd.Var(&flAlias, []string{"-alias"}, "Add alias for another container")
+ cmd.Require(flag.Min, 2)
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+
+ containerID, err := lookupContainerID(cli, cmd.Arg(0))
+ if err != nil {
+ return err
+ }
+
+ sandboxID, err := lookupSandboxID(cli, containerID)
+ if err != nil {
+ return err
+ }
+
+ sn, nn := parseServiceName(cmd.Arg(1))
+ serviceID, err := lookupServiceID(cli, nn, sn)
+ if err != nil {
+ return err
+ }
+
+ nc := serviceAttach{SandboxID: sandboxID, Aliases: flAlias.GetAll()}
+
+ _, _, err = readBody(cli.call("POST", "/services/"+serviceID+"/backend", nc, nil))
+
+ return err
+}
+
+// CmdServiceDetach handles service detach UI
+func (cli *NetworkCli) CmdServiceDetach(chain string, args ...string) error {
+ cmd := cli.Subcmd(chain, "detach", "CONTAINER SERVICE", "Removes a container from service backend", false)
+ cmd.Require(flag.Min, 2)
+ err := cmd.ParseFlags(args, true)
+ if err != nil {
+ return err
+ }
+
+ sn, nn := parseServiceName(cmd.Arg(1))
+ containerID, err := lookupContainerID(cli, cmd.Arg(0))
+ if err != nil {
+ return err
+ }
+
+ sandboxID, err := lookupSandboxID(cli, containerID)
+ if err != nil {
+ return err
+ }
+
+ serviceID, err := lookupServiceID(cli, nn, sn)
+ if err != nil {
+ return err
+ }
+
+ _, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+sandboxID, nil, nil))
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func serviceUsage(chain string) string {
+ help := "Commands:\n"
+
+ for _, cmd := range serviceCommands {
+ help += fmt.Sprintf(" %-10.10s%s\n", cmd.name, cmd.description)
+ }
+
+ help += fmt.Sprintf("\nRun '%s service COMMAND --help' for more information on a command.", chain)
+ return help
+}
--- /dev/null
+package client
+
+import "github.com/docker/libnetwork/types"
+
+/***********
+ Resources
+************/
+
+// networkResource is the body of the "get network" http response message
+type networkResource struct {
+ Name string `json:"name"`
+ ID string `json:"id"`
+ Type string `json:"type"`
+ Services []*serviceResource `json:"services"`
+}
+
+// serviceResource is the body of the "get service" http response message
+type serviceResource struct {
+ Name string `json:"name"`
+ ID string `json:"id"`
+ Network string `json:"network"`
+}
+
+// SandboxResource is the body of "get service backend" response message
+type SandboxResource struct {
+ ID string `json:"id"`
+ Key string `json:"key"`
+ ContainerID string `json:"container_id"`
+}
+
+/***********
+ Body types
+ ************/
+type ipamConf struct {
+ PreferredPool string
+ SubPool string
+ Gateway string
+ AuxAddresses map[string]string
+}
+
+// networkCreate is the expected body of the "create network" http request message
+type networkCreate struct {
+ Name string `json:"name"`
+ ID string `json:"id"`
+ NetworkType string `json:"network_type"`
+ IPv4Conf []ipamConf `json:"ipv4_configuration"`
+ DriverOpts map[string]string `json:"driver_opts"`
+ NetworkOpts map[string]string `json:"network_opts"`
+}
+
+// serviceCreate represents the body of the "publish service" http request message
+type serviceCreate struct {
+ Name string `json:"name"`
+ MyAliases []string `json:"my_aliases"`
+ Network string `json:"network_name"`
+}
+
+// serviceDelete represents the body of the "unpublish service" http request message
+type serviceDelete struct {
+ Name string `json:"name"`
+ Force bool `json:"force"`
+}
+
+// serviceAttach represents the expected body of the "attach/detach sandbox to/from service" http request messages
+type serviceAttach struct {
+ SandboxID string `json:"sandbox_id"`
+ Aliases []string `json:"aliases"`
+}
+
+// SandboxCreate is the body of the "post /sandboxes" http request message
+type SandboxCreate struct {
+ ContainerID string `json:"container_id"`
+ HostName string `json:"host_name"`
+ DomainName string `json:"domain_name"`
+ HostsPath string `json:"hosts_path"`
+ ResolvConfPath string `json:"resolv_conf_path"`
+ DNS []string `json:"dns"`
+ ExtraHosts []extraHost `json:"extra_hosts"`
+ UseDefaultSandbox bool `json:"use_default_sandbox"`
+ ExposedPorts []types.TransportPort `json:"exposed_ports"`
+ PortMapping []types.PortBinding `json:"port_mapping"`
+}
+
+// extraHost represents the extra host object
+type extraHost struct {
+ Name string `json:"name"`
+ Address string `json:"address"`
+}
+
+// sandboxParentUpdate is the object carrying the information about the
+// sandbox parent that needs to be updated.
+type sandboxParentUpdate struct {
+ ContainerID string `json:"container_id"`
+ Name string `json:"name"`
+ Address string `json:"address"`
+}
--- /dev/null
+package cluster
+
+import (
+ "context"
+
+ "github.com/docker/docker/api/types/network"
+)
+
+const (
+ // EventSocketChange control socket changed
+ EventSocketChange = iota
+ // EventNodeReady cluster node in ready state
+ EventNodeReady
+ // EventNodeLeave node is leaving the cluster
+ EventNodeLeave
+ // EventNetworkKeysAvailable network keys correctly configured in the networking layer
+ EventNetworkKeysAvailable
+)
+
+// ConfigEventType type of the event produced by the cluster
+type ConfigEventType uint8
+
+// Provider provides clustering config details
+type Provider interface {
+ IsManager() bool
+ IsAgent() bool
+ GetLocalAddress() string
+ GetListenAddress() string
+ GetAdvertiseAddress() string
+ GetDataPathAddress() string
+ GetRemoteAddressList() []string
+ ListenClusterEvents() <-chan ConfigEventType
+ AttachNetwork(string, string, []string) (*network.NetworkingConfig, error)
+ DetachNetwork(string, string) error
+ UpdateAttachment(string, string, *network.NetworkingConfig) error
+ WaitForDetachment(context.Context, string, string, string, string) error
+}
--- /dev/null
+FROM alpine
+RUN apk add --no-cache curl
+COPY diagnosticClient /usr/local/bin/diagnosticClient
+ENTRYPOINT ["/usr/local/bin/diagnosticClient"]
--- /dev/null
+FROM docker:17.12-dind
+RUN apk add --no-cache curl
+ENV DIND_CLIENT=true
+COPY daemon.json /etc/docker/daemon.json
+COPY diagnosticClient /usr/local/bin/diagnosticClient
--- /dev/null
+---
+description: Learn to use the built-in network debugger to debug overlay networking problems
+keywords: network, troubleshooting, debug
+title: Debug overlay or swarm networking issues
+---
+
+**WARNING**
+This tool can change the internal state of the libnetwork API, be really mindful
+on its use and read carefully the following guide. Improper use of it will damage
+or permanently destroy the network configuration.
+
+
+Docker CE 17.12 and higher introduce a network debugging tool designed to help
+debug issues with overlay networks and swarm services running on Linux hosts.
+When enabled, a network diagnostic server listens on the specified port and
+provides diagnostic information. The network debugging tool should only be
+started to debug specific issues, and should not be left running all the time.
+
+Information about networks is stored in the database, which can be examined using
+the API. Currently the database contains information about the overlay network
+as well as the service discovery data.
+
+The Docker API exposes endpoints to query and control the network debugging
+tool. CLI integration is provided as a preview, but the implementation is not
+yet considered stable and commands and options may change without notice.
+
+The tool is available into 2 forms:
+1) client only: dockereng/network-diagnostic:onlyclient
+2) docker in docker version: dockereng/network-diagnostic:17.12-dind
+The latter allows to use the tool with a cluster running an engine older than 17.12
+
+## Enable the diagnostic server
+
+The tool currently only works on Docker hosts running on Linux. To enable it on a node
+follow the step below.
+
+1. Set the `network-diagnostic-port` to a port which is free on the Docker
+ host, in the `/etc/docker/daemon.json` configuration file.
+
+ ```json
+ “network-diagnostic-port”: <port>
+ ```
+
+2. Get the process ID (PID) of the `dockerd` process. It is the second field in
+ the output, and is typically a number from 2 to 6 digits long.
+
+ ```bash
+ $ ps aux |grep dockerd | grep -v grep
+ ```
+
+3. Reload the Docker configuration without restarting Docker, by sending the
+ `HUP` signal to the PID you found in the previous step.
+
+ ```bash
+ kill -HUP <pid-of-dockerd>
+ ```
+
+If systemd is used the command `systemctl reload docker` will be enough
+
+
+A message like the following will appear in the Docker host logs:
+
+```none
+Starting the diagnostic server listening on <port> for commands
+```
+
+## Disable the diagnostic tool
+
+Repeat these steps for each node participating in the swarm.
+
+1. Remove the `network-diagnostic-port` key from the `/etc/docker/daemon.json`
+ configuration file.
+
+2. Get the process ID (PID) of the `dockerd` process. It is the second field in
+ the output, and is typically a number from 2 to 6 digits long.
+
+ ```bash
+ $ ps aux |grep dockerd | grep -v grep
+ ```
+
+3. Reload the Docker configuration without restarting Docker, by sending the
+ `HUP` signal to the PID you found in the previous step.
+
+ ```bash
+ kill -HUP <pid-of-dockerd>
+ ```
+
+A message like the following will appear in the Docker host logs:
+
+```none
+Disabling the diagnostic server
+```
+
+## Access the diagnostic tool's API
+
+The network diagnostic tool exposes its own RESTful API. To access the API,
+send a HTTP request to the port where the tool is listening. The following
+commands assume the tool is listening on port 2000.
+
+Examples are not given for every endpoint.
+
+### Get help
+
+```bash
+$ curl localhost:2000/help
+
+OK
+/updateentry
+/getentry
+/gettable
+/leavenetwork
+/createentry
+/help
+/clusterpeers
+/ready
+/joinnetwork
+/deleteentry
+/networkpeers
+/
+/join
+```
+
+### Join or leave the network database cluster
+
+```bash
+$ curl localhost:2000/join?members=ip1,ip2,...
+```
+
+```bash
+$ curl localhost:2000/leave?members=ip1,ip2,...
+```
+
+`ip1`, `ip2`, ... are the swarm node ips (usually one is enough)
+
+### Join or leave a network
+
+```bash
+$ curl localhost:2000/joinnetwork?nid=<network id>
+```
+
+```bash
+$ curl localhost:2000/leavenetwork?nid=<network id>
+```
+
+`network id` can be retrieved on the manager with `docker network ls --no-trunc` and has
+to be the full length identifier
+
+### List cluster peers
+
+```bash
+$ curl localhost:2000/clusterpeers
+```
+
+### List nodes connected to a given network
+
+```bash
+$ curl localhost:2000/networkpeers?nid=<network id>
+```
+`network id` can be retrieved on the manager with `docker network ls --no-trunc` and has
+to be the full length identifier
+
+### Dump database tables
+
+The tables are called `endpoint_table` and `overlay_peer_table`.
+The `overlay_peer_table` contains all the overlay forwarding information
+The `endpoint_table` contains all the service discovery information
+
+```bash
+$ curl localhost:2000/gettable?nid=<network id>&tname=<table name>
+```
+
+### Interact with a specific database table
+
+The tables are called `endpoint_table` and `overlay_peer_table`.
+
+```bash
+$ curl localhost:2000/<method>?nid=<network id>&tname=<table name>&key=<key>[&value=<value>]
+```
+
+Note:
+operations on tables have node ownership, this means that are going to remain persistent till
+the node that inserted them is part of the cluster
+
+## Access the diagnostic tool's CLI
+
+The CLI is provided as a preview and is not yet stable. Commands or options may
+change at any time.
+
+The CLI executable is called `diagnosticClient` and is made available using a
+standalone container.
+
+`docker run --net host dockereng/network-diagnostic:onlyclient -v -net <full network id> -t sd`
+
+The following flags are supported:
+
+| Flag | Description |
+|---------------|-------------------------------------------------|
+| -t <string> | Table one of `sd` or `overlay`. |
+| -ip <string> | The IP address to query. Defaults to 127.0.0.1. |
+| -net <string> | The target network ID. |
+| -port <int> | The target port. (default port is 2000) |
+| -a | Join/leave network |
+| -v | Enable verbose output. |
+
+*NOTE*
+By default the tool won't try to join the network. This is following the intent to not change
+the state on which the node is when the diagnostic client is run. This means that it is safe
+to run the diagnosticClient against a running daemon because it will just dump the current state.
+When using instead the diagnosticClient in the containerized version the flag `-a` MUST be passed
+to avoid retrieving empty results. On the other side using the `-a` flag against a loaded daemon
+will have the undesirable side effect to leave the network and so cutting down the data path for
+that daemon.
+
+### Container version of the diagnostic tool
+
+The CLI is provided as a container with a 17.12 engine that needs to run using privileged mode.
+*NOTE*
+Remember that table operations have ownership, so any `create entry` will be persistent till
+the diagnostic container is part of the swarm.
+
+1. Make sure that the node where the diagnostic client will run is not part of the swarm, if so do `docker swarm leave -f`
+
+2. To run the container, use a command like the following:
+
+ ```bash
+ $ docker container run --name net-diagnostic -d --privileged --network host dockereng/network-diagnostic:17.12-dind
+ ```
+
+3. Connect to the container using `docker exec -it <container-ID> sh`,
+ and start the server using the following command:
+
+ ```bash
+ $ kill -HUP 1
+ ```
+
+4. Join the diagnostic container to the swarm, then run the diagnostic CLI within the container.
+
+ ```bash
+ $ ./diagnosticClient <flags>...
+ ```
+
+4. When finished debugging, leave the swarm and stop the container.
+
+### Examples
+
+The following commands dump the service discovery table and verify node
+ownership.
+
+*NOTE*
+Remember to use the full network ID, you can easily find that with `docker network ls --no-trunc`
+
+**Service discovery and load balancer:**
+
+```bash
+$ diagnostiClient -t sd -v -net n8a8ie6tb3wr2e260vxj8ncy4 -a
+```
+
+**Overlay network:**
+
+```bash
+$ diagnostiClient -port 2001 -t overlay -v -net n8a8ie6tb3wr2e260vxj8ncy4 -a
+```
--- /dev/null
+{
+ "debug": true,
+ "network-diagnostic-port": 2000
+}
--- /dev/null
+package main
+
+import (
+ "bufio"
+ "encoding/base64"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strings"
+
+ "github.com/docker/libnetwork"
+ "github.com/docker/libnetwork/diagnostic"
+ "github.com/docker/libnetwork/drivers/overlay"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ readyPath = "http://%s:%d/ready"
+ joinNetwork = "http://%s:%d/joinnetwork?nid=%s"
+ leaveNetwork = "http://%s:%d/leavenetwork?nid=%s"
+ clusterPeers = "http://%s:%d/clusterpeers?json"
+ networkPeers = "http://%s:%d/networkpeers?nid=%s&json"
+ dumpTable = "http://%s:%d/gettable?nid=%s&tname=%s&json"
+ deleteEntry = "http://%s:%d/deleteentry?nid=%s&tname=%s&key=%s&json"
+)
+
+func httpIsOk(body io.ReadCloser) {
+ b, err := ioutil.ReadAll(body)
+ if err != nil {
+ logrus.Fatalf("Failed the body parse %s", err)
+ }
+ if !strings.Contains(string(b), "OK") {
+ logrus.Fatalf("Server not ready %s", b)
+ }
+ body.Close()
+}
+
+func main() {
+ ipPtr := flag.String("ip", "127.0.0.1", "ip address")
+ portPtr := flag.Int("port", 2000, "port")
+ networkPtr := flag.String("net", "", "target network")
+ tablePtr := flag.String("t", "", "table to process <sd/overlay>")
+ remediatePtr := flag.Bool("r", false, "perform remediation deleting orphan entries")
+ joinPtr := flag.Bool("a", false, "join/leave network")
+ verbosePtr := flag.Bool("v", false, "verbose output")
+
+ flag.Parse()
+
+ if *verbosePtr {
+ logrus.SetLevel(logrus.DebugLevel)
+ }
+
+ if _, ok := os.LookupEnv("DIND_CLIENT"); !ok && *joinPtr {
+ logrus.Fatal("you are not using the client in docker in docker mode, the use of the -a flag can be disruptive, " +
+ "please remove it (doc:https://github.com/docker/libnetwork/blob/master/cmd/diagnostic/README.md)")
+ }
+
+ logrus.Infof("Connecting to %s:%d checking ready", *ipPtr, *portPtr)
+ resp, err := http.Get(fmt.Sprintf(readyPath, *ipPtr, *portPtr))
+ if err != nil {
+ logrus.WithError(err).Fatalf("The connection failed")
+ }
+ httpIsOk(resp.Body)
+
+ clusterPeers := fetchNodePeers(*ipPtr, *portPtr, "")
+ var networkPeers map[string]string
+ var joinedNetwork bool
+ if *networkPtr != "" {
+ if *joinPtr {
+ logrus.Infof("Joining the network:%q", *networkPtr)
+ resp, err = http.Get(fmt.Sprintf(joinNetwork, *ipPtr, *portPtr, *networkPtr))
+ if err != nil {
+ logrus.WithError(err).Fatalf("Failed joining the network")
+ }
+ httpIsOk(resp.Body)
+ joinedNetwork = true
+ }
+
+ networkPeers = fetchNodePeers(*ipPtr, *portPtr, *networkPtr)
+ if len(networkPeers) == 0 {
+ logrus.Warnf("There is no peer on network %q, check the network ID, and verify that is the non truncated version", *networkPtr)
+ }
+ }
+
+ switch *tablePtr {
+ case "sd":
+ fetchTable(*ipPtr, *portPtr, *networkPtr, "endpoint_table", clusterPeers, networkPeers, *remediatePtr)
+ case "overlay":
+ fetchTable(*ipPtr, *portPtr, *networkPtr, "overlay_peer_table", clusterPeers, networkPeers, *remediatePtr)
+ }
+
+ if joinedNetwork {
+ logrus.Infof("Leaving the network:%q", *networkPtr)
+ resp, err = http.Get(fmt.Sprintf(leaveNetwork, *ipPtr, *portPtr, *networkPtr))
+ if err != nil {
+ logrus.WithError(err).Fatalf("Failed leaving the network")
+ }
+ httpIsOk(resp.Body)
+ }
+}
+
+func fetchNodePeers(ip string, port int, network string) map[string]string {
+ if network == "" {
+ logrus.Infof("Fetch cluster peers")
+ } else {
+ logrus.Infof("Fetch peers network:%q", network)
+ }
+
+ var path string
+ if network != "" {
+ path = fmt.Sprintf(networkPeers, ip, port, network)
+ } else {
+ path = fmt.Sprintf(clusterPeers, ip, port)
+ }
+
+ resp, err := http.Get(path)
+ if err != nil {
+ logrus.WithError(err).Fatalf("Failed fetching path")
+ }
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ logrus.WithError(err).Fatalf("Failed the body parse")
+ }
+
+ output := diagnostic.HTTPResult{Details: &diagnostic.TablePeersResult{}}
+ err = json.Unmarshal(body, &output)
+ if err != nil {
+ logrus.WithError(err).Fatalf("Failed the json unmarshalling")
+ }
+
+ logrus.Debugf("Parsing JSON response")
+ result := make(map[string]string, output.Details.(*diagnostic.TablePeersResult).Length)
+ for _, v := range output.Details.(*diagnostic.TablePeersResult).Elements {
+ logrus.Debugf("name:%s ip:%s", v.Name, v.IP)
+ result[v.Name] = v.IP
+ }
+ return result
+}
+
+func fetchTable(ip string, port int, network, tableName string, clusterPeers, networkPeers map[string]string, remediate bool) {
+ logrus.Infof("Fetch %s table and check owners", tableName)
+ resp, err := http.Get(fmt.Sprintf(dumpTable, ip, port, network, tableName))
+ if err != nil {
+ logrus.WithError(err).Fatalf("Failed fetching endpoint table")
+ }
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ logrus.WithError(err).Fatalf("Failed the body parse")
+ }
+
+ output := diagnostic.HTTPResult{Details: &diagnostic.TableEndpointsResult{}}
+ err = json.Unmarshal(body, &output)
+ if err != nil {
+ logrus.WithError(err).Fatalf("Failed the json unmarshalling")
+ }
+
+ logrus.Debug("Parsing data structures")
+ var orphanKeys []string
+ for _, v := range output.Details.(*diagnostic.TableEndpointsResult).Elements {
+ decoded, err := base64.StdEncoding.DecodeString(v.Value)
+ if err != nil {
+ logrus.WithError(err).Errorf("Failed decoding entry")
+ continue
+ }
+ switch tableName {
+ case "endpoint_table":
+ var elem libnetwork.EndpointRecord
+ elem.Unmarshal(decoded)
+ logrus.Debugf("key:%s value:%+v owner:%s", v.Key, elem, v.Owner)
+ case "overlay_peer_table":
+ var elem overlay.PeerRecord
+ elem.Unmarshal(decoded)
+ logrus.Debugf("key:%s value:%+v owner:%s", v.Key, elem, v.Owner)
+ }
+
+ if _, ok := networkPeers[v.Owner]; !ok {
+ logrus.Warnf("The element with key:%s does not belong to any node on this network", v.Key)
+ orphanKeys = append(orphanKeys, v.Key)
+ }
+ if _, ok := clusterPeers[v.Owner]; !ok {
+ logrus.Warnf("The element with key:%s does not belong to any node on this cluster", v.Key)
+ }
+ }
+
+ if len(orphanKeys) > 0 && remediate {
+ logrus.Warnf("The following keys:%v results as orphan, do you want to proceed with the deletion (this operation is irreversible)? [Yes/No]", orphanKeys)
+ reader := bufio.NewReader(os.Stdin)
+ text, _ := reader.ReadString('\n')
+ text = strings.Replace(text, "\n", "", -1)
+ if strings.Compare(text, "Yes") == 0 {
+ for _, k := range orphanKeys {
+ resp, err := http.Get(fmt.Sprintf(deleteEntry, ip, port, network, tableName, k))
+ if err != nil {
+ logrus.WithError(err).Errorf("Failed deleting entry k:%s", k)
+ break
+ }
+ resp.Body.Close()
+ }
+ } else {
+ logrus.Infof("Deletion skipped")
+ }
+ }
+}
--- /dev/null
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+
+ "github.com/codegangsta/cli"
+ "github.com/docker/docker/pkg/term"
+ "github.com/docker/libnetwork/client"
+)
+
+var (
+ containerCreateCommand = cli.Command{
+ Name: "create",
+ Usage: "Create a container",
+ Action: runContainerCreate,
+ }
+
+ containerRmCommand = cli.Command{
+ Name: "rm",
+ Usage: "Remove a container",
+ Action: runContainerRm,
+ }
+
+ containerCommands = []cli.Command{
+ containerCreateCommand,
+ containerRmCommand,
+ }
+
+ dnetCommands = []cli.Command{
+ createDockerCommand("network"),
+ createDockerCommand("service"),
+ {
+ Name: "container",
+ Usage: "Container management commands",
+ Subcommands: containerCommands,
+ },
+ }
+)
+
+func runContainerCreate(c *cli.Context) {
+ if len(c.Args()) == 0 {
+ fmt.Println("Please provide container id argument")
+ os.Exit(1)
+ }
+
+ sc := client.SandboxCreate{ContainerID: c.Args()[0]}
+ obj, _, err := readBody(epConn.httpCall("POST", "/sandboxes", sc, nil))
+ if err != nil {
+ fmt.Printf("POST failed during create container: %v\n", err)
+ os.Exit(1)
+ }
+
+ var replyID string
+ err = json.Unmarshal(obj, &replyID)
+ if err != nil {
+ fmt.Printf("Unmarshall of response failed during create container: %v\n", err)
+ os.Exit(1)
+ }
+
+ fmt.Printf("%s\n", replyID)
+
+}
+
+func runContainerRm(c *cli.Context) {
+ var sbList []*client.SandboxResource
+
+ if len(c.Args()) == 0 {
+ fmt.Println("Please provide container id argument")
+ os.Exit(1)
+ }
+
+ obj, _, err := readBody(epConn.httpCall("GET", "/sandboxes?partial-container-id="+c.Args()[0], nil, nil))
+ if err != nil {
+ fmt.Printf("GET failed during container id lookup: %v\n", err)
+ os.Exit(1)
+ }
+
+ err = json.Unmarshal(obj, &sbList)
+ if err != nil {
+ fmt.Printf("Unmarshall of container id lookup response failed: %v", err)
+ os.Exit(1)
+ }
+
+ if len(sbList) == 0 {
+ fmt.Printf("No sandbox for container %s found\n", c.Args()[0])
+ os.Exit(1)
+ }
+
+ _, _, err = readBody(epConn.httpCall("DELETE", "/sandboxes/"+sbList[0].ID, nil, nil))
+ if err != nil {
+ fmt.Printf("DELETE of sandbox id %s failed: %v", sbList[0].ID, err)
+ os.Exit(1)
+ }
+}
+
+func runDockerCommand(c *cli.Context, cmd string) {
+ _, stdout, stderr := term.StdStreams()
+ oldcli := client.NewNetworkCli(stdout, stderr, epConn.httpCall)
+ var args []string
+ args = append(args, cmd)
+ if c.Bool("h") {
+ args = append(args, "--help")
+ } else {
+ args = append(args, c.Args()...)
+ }
+ if err := oldcli.Cmd("dnet", args...); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+}
+
+func createDockerCommand(cmd string) cli.Command {
+ return cli.Command{
+ Name: cmd,
+ Usage: fmt.Sprintf("%s management commands", cmd),
+ SkipFlagParsing: true,
+ Action: func(c *cli.Context) {
+ runDockerCommand(c, cmd)
+ },
+ Subcommands: []cli.Command{
+ {
+ Name: "h, -help",
+ Usage: fmt.Sprintf("%s help", cmd),
+ },
+ },
+ }
+}
+
+func readBody(stream io.ReadCloser, hdr http.Header, statusCode int, err error) ([]byte, int, error) {
+ if stream != nil {
+ defer stream.Close()
+ }
+ if err != nil {
+ return nil, statusCode, err
+ }
+ body, err := ioutil.ReadAll(stream)
+ if err != nil {
+ return nil, -1, err
+ }
+ return body, statusCode, nil
+}
--- /dev/null
+package main
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/signal"
+ "strings"
+ "syscall"
+ "time"
+
+ "github.com/BurntSushi/toml"
+ "github.com/codegangsta/cli"
+ "github.com/docker/docker/opts"
+ "github.com/docker/docker/pkg/discovery"
+ "github.com/docker/docker/pkg/reexec"
+
+ "github.com/docker/docker/api/types/network"
+ "github.com/docker/docker/pkg/term"
+ "github.com/docker/libnetwork"
+ "github.com/docker/libnetwork/api"
+ "github.com/docker/libnetwork/cluster"
+ "github.com/docker/libnetwork/config"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/types"
+ "github.com/gorilla/mux"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // DefaultHTTPHost is used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
+ DefaultHTTPHost = "0.0.0.0"
+ // DefaultHTTPPort is the default http port used by dnet
+ DefaultHTTPPort = 2385
+ // DefaultUnixSocket exported
+ DefaultUnixSocket = "/var/run/dnet.sock"
+ cfgFileEnv = "LIBNETWORK_CFG"
+ defaultCfgFile = "/etc/default/libnetwork.toml"
+ defaultHeartbeat = time.Duration(10) * time.Second
+ ttlFactor = 2
+)
+
+var epConn *dnetConnection
+
+func main() {
+ if reexec.Init() {
+ return
+ }
+
+ _, stdout, stderr := term.StdStreams()
+ logrus.SetOutput(stderr)
+
+ err := dnetApp(stdout, stderr)
+ if err != nil {
+ os.Exit(1)
+ }
+}
+
+// ParseConfig parses the libnetwork configuration file
+func (d *dnetConnection) parseOrchestrationConfig(tomlCfgFile string) error {
+ dummy := &dnetConnection{}
+
+ if _, err := toml.DecodeFile(tomlCfgFile, dummy); err != nil {
+ return err
+ }
+
+ if dummy.Orchestration != nil {
+ d.Orchestration = dummy.Orchestration
+ }
+ return nil
+}
+
+func (d *dnetConnection) parseConfig(cfgFile string) (*config.Config, error) {
+ if strings.Trim(cfgFile, " ") == "" {
+ cfgFile = os.Getenv(cfgFileEnv)
+ if strings.Trim(cfgFile, " ") == "" {
+ cfgFile = defaultCfgFile
+ }
+ }
+
+ if err := d.parseOrchestrationConfig(cfgFile); err != nil {
+ return nil, err
+ }
+ return config.ParseConfig(cfgFile)
+}
+
+func processConfig(cfg *config.Config) []config.Option {
+ options := []config.Option{}
+ if cfg == nil {
+ return options
+ }
+
+ dn := "bridge"
+ if strings.TrimSpace(cfg.Daemon.DefaultNetwork) != "" {
+ dn = cfg.Daemon.DefaultNetwork
+ }
+ options = append(options, config.OptionDefaultNetwork(dn))
+
+ dd := "bridge"
+ if strings.TrimSpace(cfg.Daemon.DefaultDriver) != "" {
+ dd = cfg.Daemon.DefaultDriver
+ }
+ options = append(options, config.OptionDefaultDriver(dd))
+
+ if cfg.Daemon.Labels != nil {
+ options = append(options, config.OptionLabels(cfg.Daemon.Labels))
+ }
+
+ if dcfg, ok := cfg.Scopes[datastore.GlobalScope]; ok && dcfg.IsValid() {
+ options = append(options, config.OptionKVProvider(dcfg.Client.Provider))
+ options = append(options, config.OptionKVProviderURL(dcfg.Client.Address))
+ }
+
+ dOptions, err := startDiscovery(&cfg.Cluster)
+ if err != nil {
+ logrus.Infof("Skipping discovery : %s", err.Error())
+ } else {
+ options = append(options, dOptions...)
+ }
+
+ return options
+}
+
+func startDiscovery(cfg *config.ClusterCfg) ([]config.Option, error) {
+ if cfg == nil {
+ return nil, errors.New("discovery requires a valid configuration")
+ }
+
+ hb := time.Duration(cfg.Heartbeat) * time.Second
+ if hb == 0 {
+ hb = defaultHeartbeat
+ }
+ logrus.Infof("discovery : %s %s", cfg.Discovery, hb.String())
+ d, err := discovery.New(cfg.Discovery, hb, ttlFactor*hb, map[string]string{})
+ if err != nil {
+ return nil, err
+ }
+
+ if cfg.Address == "" {
+ iface, err := net.InterfaceByName("eth0")
+ if err != nil {
+ return nil, err
+ }
+ addrs, err := iface.Addrs()
+ if err != nil || len(addrs) == 0 {
+ return nil, err
+ }
+ ip, _, _ := net.ParseCIDR(addrs[0].String())
+ cfg.Address = ip.String()
+ }
+
+ if ip := net.ParseIP(cfg.Address); ip == nil {
+ return nil, errors.New("address config should be either ipv4 or ipv6 address")
+ }
+
+ if err := d.Register(cfg.Address + ":0"); err != nil {
+ return nil, err
+ }
+
+ options := []config.Option{config.OptionDiscoveryWatcher(d), config.OptionDiscoveryAddress(cfg.Address)}
+ go func() {
+ for {
+ select {
+ case <-time.After(hb):
+ if err := d.Register(cfg.Address + ":0"); err != nil {
+ logrus.Warn(err)
+ }
+ }
+ }
+ }()
+ return options, nil
+}
+
+func dnetApp(stdout, stderr io.Writer) error {
+ app := cli.NewApp()
+
+ app.Name = "dnet"
+ app.Usage = "A self-sufficient runtime for container networking."
+ app.Flags = dnetFlags
+ app.Before = processFlags
+ app.Commands = dnetCommands
+
+ app.Run(os.Args)
+ return nil
+}
+
+func createDefaultNetwork(c libnetwork.NetworkController) {
+ nw := c.Config().Daemon.DefaultNetwork
+ d := c.Config().Daemon.DefaultDriver
+ createOptions := []libnetwork.NetworkOption{}
+ genericOption := options.Generic{}
+
+ if nw != "" && d != "" {
+ // Bridge driver is special due to legacy reasons
+ if d == "bridge" {
+ genericOption[netlabel.GenericData] = map[string]string{
+ "BridgeName": "docker0",
+ "DefaultBridge": "true",
+ }
+ createOptions = append(createOptions,
+ libnetwork.NetworkOptionGeneric(genericOption),
+ ipamOption(nw))
+ }
+
+ if n, err := c.NetworkByName(nw); err == nil {
+ logrus.Debugf("Default network %s already present. Deleting it", nw)
+ if err = n.Delete(); err != nil {
+ logrus.Debugf("Network could not be deleted: %v", err)
+ return
+ }
+ }
+
+ _, err := c.NewNetwork(d, nw, "", createOptions...)
+ if err != nil {
+ logrus.Errorf("Error creating default network : %s : %v", nw, err)
+ }
+ }
+}
+
+type dnetConnection struct {
+ // proto holds the client protocol i.e. unix.
+ proto string
+ // addr holds the client address.
+ addr string
+ Orchestration *NetworkOrchestration
+ configEvent chan cluster.ConfigEventType
+}
+
+// NetworkOrchestration exported
+type NetworkOrchestration struct {
+ Agent bool
+ Manager bool
+ Bind string
+ Peer string
+}
+
+func (d *dnetConnection) dnetDaemon(cfgFile string) error {
+ if err := startTestDriver(); err != nil {
+ return fmt.Errorf("failed to start test driver: %v", err)
+ }
+
+ cfg, err := d.parseConfig(cfgFile)
+ var cOptions []config.Option
+ if err == nil {
+ cOptions = processConfig(cfg)
+ } else {
+ logrus.Errorf("Error parsing config %v", err)
+ }
+
+ bridgeConfig := options.Generic{
+ "EnableIPForwarding": true,
+ "EnableIPTables": true,
+ }
+
+ bridgeOption := options.Generic{netlabel.GenericData: bridgeConfig}
+
+ cOptions = append(cOptions, config.OptionDriverConfig("bridge", bridgeOption))
+
+ controller, err := libnetwork.New(cOptions...)
+ if err != nil {
+ fmt.Println("Error starting dnetDaemon :", err)
+ return err
+ }
+ controller.SetClusterProvider(d)
+
+ if d.Orchestration.Agent || d.Orchestration.Manager {
+ d.configEvent <- cluster.EventNodeReady
+ }
+
+ createDefaultNetwork(controller)
+ httpHandler := api.NewHTTPHandler(controller)
+ r := mux.NewRouter().StrictSlash(false)
+ post := r.PathPrefix("/{.*}/networks").Subrouter()
+ post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
+ post = r.PathPrefix("/networks").Subrouter()
+ post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
+ post = r.PathPrefix("/{.*}/services").Subrouter()
+ post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
+ post = r.PathPrefix("/services").Subrouter()
+ post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
+ post = r.PathPrefix("/{.*}/sandboxes").Subrouter()
+ post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
+ post = r.PathPrefix("/sandboxes").Subrouter()
+ post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
+
+ handleSignals(controller)
+ setupDumpStackTrap()
+
+ return http.ListenAndServe(d.addr, r)
+}
+
+func (d *dnetConnection) IsManager() bool {
+ return d.Orchestration.Manager
+}
+
+func (d *dnetConnection) IsAgent() bool {
+ return d.Orchestration.Agent
+}
+
+func (d *dnetConnection) GetAdvertiseAddress() string {
+ return d.Orchestration.Bind
+}
+
+func (d *dnetConnection) GetDataPathAddress() string {
+ return d.Orchestration.Bind
+}
+
+func (d *dnetConnection) GetLocalAddress() string {
+ return d.Orchestration.Bind
+}
+
+func (d *dnetConnection) GetListenAddress() string {
+ return d.Orchestration.Bind
+}
+
+func (d *dnetConnection) GetRemoteAddressList() []string {
+ return []string{d.Orchestration.Peer}
+}
+
+func (d *dnetConnection) ListenClusterEvents() <-chan cluster.ConfigEventType {
+ return d.configEvent
+}
+
+func (d *dnetConnection) AttachNetwork(string, string, []string) (*network.NetworkingConfig, error) {
+ return nil, nil
+}
+
+func (d *dnetConnection) DetachNetwork(string, string) error {
+ return nil
+}
+
+func (d *dnetConnection) UpdateAttachment(string, string, *network.NetworkingConfig) error {
+ return nil
+}
+
+func (d *dnetConnection) WaitForDetachment(context.Context, string, string, string, string) error {
+ return nil
+}
+
+func handleSignals(controller libnetwork.NetworkController) {
+ c := make(chan os.Signal, 1)
+ signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}
+ signal.Notify(c, signals...)
+ go func() {
+ for range c {
+ controller.Stop()
+ os.Exit(0)
+ }
+ }()
+}
+
+func startTestDriver() error {
+ mux := http.NewServeMux()
+ server := httptest.NewServer(mux)
+ if server == nil {
+ return errors.New("Failed to start an HTTP Server")
+ }
+
+ mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType)
+ })
+
+ mux.HandleFunc(fmt.Sprintf("/%s.GetCapabilities", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprint(w, `{"Scope":"global"}`)
+ })
+
+ mux.HandleFunc(fmt.Sprintf("/%s.CreateNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprint(w, "null")
+ })
+
+ mux.HandleFunc(fmt.Sprintf("/%s.DeleteNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprint(w, "null")
+ })
+
+ mux.HandleFunc(fmt.Sprintf("/%s.CreateEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprint(w, "null")
+ })
+
+ mux.HandleFunc(fmt.Sprintf("/%s.DeleteEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprint(w, "null")
+ })
+
+ mux.HandleFunc(fmt.Sprintf("/%s.Join", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprint(w, "null")
+ })
+
+ mux.HandleFunc(fmt.Sprintf("/%s.Leave", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprint(w, "null")
+ })
+
+ if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
+ return err
+ }
+
+ return ioutil.WriteFile("/etc/docker/plugins/test.spec", []byte(server.URL), 0644)
+}
+
+func newDnetConnection(val string) (*dnetConnection, error) {
+ url, err := opts.ParseHost(false, val)
+ if err != nil {
+ return nil, err
+ }
+ protoAddrParts := strings.SplitN(url, "://", 2)
+ if len(protoAddrParts) != 2 {
+ return nil, errors.New("bad format, expected tcp://ADDR")
+ }
+ if strings.ToLower(protoAddrParts[0]) != "tcp" {
+ return nil, errors.New("dnet currently only supports tcp transport")
+ }
+
+ return &dnetConnection{protoAddrParts[0], protoAddrParts[1], &NetworkOrchestration{}, make(chan cluster.ConfigEventType, 10)}, nil
+}
+
+func (d *dnetConnection) httpCall(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
+ var in io.Reader
+ in, err := encodeData(data)
+ if err != nil {
+ return nil, nil, -1, err
+ }
+
+ req, err := http.NewRequest(method, fmt.Sprintf("%s", path), in)
+ if err != nil {
+ return nil, nil, -1, err
+ }
+
+ setupRequestHeaders(method, data, req, headers)
+
+ req.URL.Host = d.addr
+ req.URL.Scheme = "http"
+
+ httpClient := &http.Client{}
+ resp, err := httpClient.Do(req)
+ statusCode := -1
+ if resp != nil {
+ statusCode = resp.StatusCode
+ }
+ if err != nil {
+ return nil, nil, statusCode, fmt.Errorf("error when trying to connect: %v", err)
+ }
+
+ if statusCode < 200 || statusCode >= 400 {
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, nil, statusCode, err
+ }
+ return nil, nil, statusCode, fmt.Errorf("error : %s", bytes.TrimSpace(body))
+ }
+
+ return resp.Body, resp.Header, statusCode, nil
+}
+
+func setupRequestHeaders(method string, data interface{}, req *http.Request, headers map[string][]string) {
+ if data != nil {
+ if headers == nil {
+ headers = make(map[string][]string)
+ }
+ headers["Content-Type"] = []string{"application/json"}
+ }
+
+ expectedPayload := (method == "POST" || method == "PUT")
+
+ if expectedPayload && req.Header.Get("Content-Type") == "" {
+ req.Header.Set("Content-Type", "text/plain")
+ }
+
+ if headers != nil {
+ for k, v := range headers {
+ req.Header[k] = v
+ }
+ }
+}
+
+func encodeData(data interface{}) (*bytes.Buffer, error) {
+ params := bytes.NewBuffer(nil)
+ if data != nil {
+ if err := json.NewEncoder(params).Encode(data); err != nil {
+ return nil, err
+ }
+ }
+ return params, nil
+}
+
+func ipamOption(bridgeName string) libnetwork.NetworkOption {
+ if nws, _, err := netutils.ElectInterfaceAddresses(bridgeName); err == nil {
+ ipamV4Conf := &libnetwork.IpamConf{PreferredPool: nws[0].String()}
+ hip, _ := types.GetHostPartIP(nws[0].IP, nws[0].Mask)
+ if hip.IsGlobalUnicast() {
+ ipamV4Conf.Gateway = nws[0].IP.String()
+ }
+ return libnetwork.NetworkOptionIpam("default", "", []*libnetwork.IpamConf{ipamV4Conf}, nil, nil)
+ }
+ return nil
+}
--- /dev/null
+package main
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+
+ psignal "github.com/docker/docker/pkg/signal"
+)
+
+func setupDumpStackTrap() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, syscall.SIGUSR1)
+ go func() {
+ for range c {
+ psignal.DumpStacks("")
+ }
+ }()
+}
--- /dev/null
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/docker/docker/pkg/signal"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/windows"
+)
+
+// Copied over from docker/daemon/debugtrap_windows.go
+func setupDumpStackTrap() {
+ go func() {
+ sa := windows.SecurityAttributes{
+ Length: 0,
+ }
+ ev, _ := windows.UTF16PtrFromString("Global\\docker-daemon-" + fmt.Sprint(os.Getpid()))
+ if h, _ := windows.CreateEvent(&sa, 0, 0, ev); h != 0 {
+ logrus.Debugf("Stackdump - waiting signal at %s", ev)
+ for {
+ windows.WaitForSingleObject(h, windows.INFINITE)
+ signal.DumpStacks("")
+ }
+ }
+ }()
+}
--- /dev/null
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/codegangsta/cli"
+ "github.com/sirupsen/logrus"
+)
+
+var (
+ dnetFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "d, -daemon",
+ Usage: "Enable daemon mode",
+ },
+ cli.StringFlag{
+ Name: "H, -host",
+ Value: "",
+ Usage: "Daemon socket to connect to",
+ },
+ cli.StringFlag{
+ Name: "l, -log-level",
+ Value: "info",
+ Usage: "Set the logging level",
+ },
+ cli.BoolFlag{
+ Name: "D, -debug",
+ Usage: "Enable debug mode",
+ },
+ cli.StringFlag{
+ Name: "c, -cfg-file",
+ Value: "/etc/default/libnetwork.toml",
+ Usage: "Configuration file",
+ },
+ }
+)
+
+func processFlags(c *cli.Context) error {
+ var err error
+
+ if c.String("l") != "" {
+ lvl, err := logrus.ParseLevel(c.String("l"))
+ if err != nil {
+ fmt.Printf("Unable to parse logging level: %s\n", c.String("l"))
+ os.Exit(1)
+ }
+ logrus.SetLevel(lvl)
+ } else {
+ logrus.SetLevel(logrus.InfoLevel)
+ }
+
+ if c.Bool("D") {
+ logrus.SetLevel(logrus.DebugLevel)
+ }
+
+ hostFlag := c.String("H")
+ if hostFlag == "" {
+ defaultHost := os.Getenv("DNET_HOST")
+ if defaultHost == "" {
+ // TODO : Add UDS support
+ defaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
+ }
+ hostFlag = defaultHost
+ }
+
+ epConn, err = newDnetConnection(hostFlag)
+ if err != nil {
+ if c.Bool("d") {
+ logrus.Error(err)
+ } else {
+ fmt.Println(err)
+ }
+ os.Exit(1)
+ }
+
+ if c.Bool("d") {
+ err = epConn.dnetDaemon(c.String("c"))
+ if err != nil {
+ logrus.Errorf("dnet Daemon exited with an error : %v", err)
+ os.Exit(1)
+ }
+ os.Exit(1)
+ }
+
+ return nil
+}
--- /dev/null
+title = "LibNetwork Configuration file"
+
+[daemon]
+ debug = false
+[cluster]
+ discovery = "consul://localhost:8500"
+ Address = "1.1.1.1"
+ Heartbeat = 20
+[datastore]
+ embedded = false
+[datastore.client]
+ provider = "consul"
+ Address = "localhost:8500"
+[orchestration]
+ agent = true
+ peer="2.2.2.2"
--- /dev/null
+FROM alpine
+
+RUN apk --no-cache add curl
+
+COPY testMain /app/
+
+WORKDIR app
+
+ENTRYPOINT ["/app/testMain"]
--- /dev/null
+SERVER
+
+cd test/networkdb
+env GOOS=linux go build -v testMain.go && docker build -t dockereng/e2e-networkdb .
+(only for testkit case) docker push dockereng/e2e-networkdb
+
+Run server: docker service create --name testdb --network net1 --replicas 3 --env TASK_ID="{{.Task.ID}}" -p mode=host,target=8000 dockereng/e2e-networkdb server 8000
+
+CLIENT
+
+cd test/networkdb
+Join cluster: docker run -it --network net1 dockereng/e2e-networkdb client join testdb 8000
+Join network: docker run -it --network net1 dockereng/e2e-networkdb client join-network testdb 8000 test
+Run test: docker run -it --network net1 dockereng/e2e-networkdb client write-delete-unique-keys testdb 8000 test tableBla 3 10
+check table: curl "localhost:32768/gettable?nid=test&tname=table_name"
--- /dev/null
+package dbclient
+
+import (
+ "context"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/sirupsen/logrus"
+)
+
+var servicePort string
+
+const totalWrittenKeys string = "totalKeys"
+
+type resultTuple struct {
+ id string
+ result int
+}
+
+func httpGetFatalError(ip, port, path string) {
+ body, err := httpGet(ip, port, path)
+ if err != nil || !strings.Contains(string(body), "OK") {
+ log.Fatalf("[%s] error %s %s", path, err, body)
+ }
+}
+
+func httpGet(ip, port, path string) ([]byte, error) {
+ resp, err := http.Get("http://" + ip + ":" + port + path)
+ if err != nil {
+ logrus.Errorf("httpGet error:%s", err)
+ return nil, err
+ }
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ return body, err
+}
+
+func joinCluster(ip, port string, members []string, doneCh chan resultTuple) {
+ httpGetFatalError(ip, port, "/join?members="+strings.Join(members, ","))
+
+ if doneCh != nil {
+ doneCh <- resultTuple{id: ip, result: 0}
+ }
+}
+
+func joinNetwork(ip, port, network string, doneCh chan resultTuple) {
+ httpGetFatalError(ip, port, "/joinnetwork?nid="+network)
+
+ if doneCh != nil {
+ doneCh <- resultTuple{id: ip, result: 0}
+ }
+}
+
+func leaveNetwork(ip, port, network string, doneCh chan resultTuple) {
+ httpGetFatalError(ip, port, "/leavenetwork?nid="+network)
+
+ if doneCh != nil {
+ doneCh <- resultTuple{id: ip, result: 0}
+ }
+}
+
+func writeTableKey(ip, port, networkName, tableName, key string) {
+ createPath := "/createentry?unsafe&nid=" + networkName + "&tname=" + tableName + "&value=v&key="
+ httpGetFatalError(ip, port, createPath+key)
+}
+
+func deleteTableKey(ip, port, networkName, tableName, key string) {
+ deletePath := "/deleteentry?nid=" + networkName + "&tname=" + tableName + "&key="
+ httpGetFatalError(ip, port, deletePath+key)
+}
+
+func clusterPeersNumber(ip, port string, doneCh chan resultTuple) {
+ body, err := httpGet(ip, port, "/clusterpeers")
+
+ if err != nil {
+ logrus.Errorf("clusterPeers %s there was an error: %s", ip, err)
+ doneCh <- resultTuple{id: ip, result: -1}
+ return
+ }
+ peersRegexp := regexp.MustCompile(`total entries: ([0-9]+)`)
+ peersNum, _ := strconv.Atoi(peersRegexp.FindStringSubmatch(string(body))[1])
+
+ doneCh <- resultTuple{id: ip, result: peersNum}
+}
+
+func networkPeersNumber(ip, port, networkName string, doneCh chan resultTuple) {
+ body, err := httpGet(ip, port, "/networkpeers?nid="+networkName)
+
+ if err != nil {
+ logrus.Errorf("networkPeersNumber %s there was an error: %s", ip, err)
+ doneCh <- resultTuple{id: ip, result: -1}
+ return
+ }
+ peersRegexp := regexp.MustCompile(`total entries: ([0-9]+)`)
+ peersNum, _ := strconv.Atoi(peersRegexp.FindStringSubmatch(string(body))[1])
+
+ doneCh <- resultTuple{id: ip, result: peersNum}
+}
+
+func dbTableEntriesNumber(ip, port, networkName, tableName string, doneCh chan resultTuple) {
+ body, err := httpGet(ip, port, "/gettable?nid="+networkName+"&tname="+tableName)
+
+ if err != nil {
+ logrus.Errorf("tableEntriesNumber %s there was an error: %s", ip, err)
+ doneCh <- resultTuple{id: ip, result: -1}
+ return
+ }
+ elementsRegexp := regexp.MustCompile(`total entries: ([0-9]+)`)
+ entriesNum, _ := strconv.Atoi(elementsRegexp.FindStringSubmatch(string(body))[1])
+ doneCh <- resultTuple{id: ip, result: entriesNum}
+}
+
+func dbEntriesNumber(ip, port, networkName string, doneCh chan resultTuple) {
+ body, err := httpGet(ip, port, "/networkstats?nid="+networkName)
+
+ if err != nil {
+ logrus.Errorf("entriesNumber %s there was an error: %s", ip, err)
+ doneCh <- resultTuple{id: ip, result: -1}
+ return
+ }
+ elementsRegexp := regexp.MustCompile(`entries: ([0-9]+)`)
+ entriesNum, _ := strconv.Atoi(elementsRegexp.FindStringSubmatch(string(body))[1])
+ doneCh <- resultTuple{id: ip, result: entriesNum}
+}
+
+func dbQueueLength(ip, port, networkName string, doneCh chan resultTuple) {
+ body, err := httpGet(ip, port, "/networkstats?nid="+networkName)
+
+ if err != nil {
+ logrus.Errorf("queueLength %s there was an error: %s", ip, err)
+ doneCh <- resultTuple{id: ip, result: -1}
+ return
+ }
+ elementsRegexp := regexp.MustCompile(`qlen: ([0-9]+)`)
+ entriesNum, _ := strconv.Atoi(elementsRegexp.FindStringSubmatch(string(body))[1])
+ doneCh <- resultTuple{id: ip, result: entriesNum}
+}
+
+func clientWatchTable(ip, port, networkName, tableName string, doneCh chan resultTuple) {
+ httpGetFatalError(ip, port, "/watchtable?nid="+networkName+"&tname="+tableName)
+ if doneCh != nil {
+ doneCh <- resultTuple{id: ip, result: 0}
+ }
+}
+
+func clientTableEntriesNumber(ip, port, networkName, tableName string, doneCh chan resultTuple) {
+ body, err := httpGet(ip, port, "/watchedtableentries?nid="+networkName+"&tname="+tableName)
+
+ if err != nil {
+ logrus.Errorf("clientTableEntriesNumber %s there was an error: %s", ip, err)
+ doneCh <- resultTuple{id: ip, result: -1}
+ return
+ }
+ elementsRegexp := regexp.MustCompile(`total elements: ([0-9]+)`)
+ entriesNum, _ := strconv.Atoi(elementsRegexp.FindStringSubmatch(string(body))[1])
+ doneCh <- resultTuple{id: ip, result: entriesNum}
+}
+
+func writeKeysNumber(ip, port, networkName, tableName, key string, number int, doneCh chan resultTuple) {
+ x := 0
+ for ; x < number; x++ {
+ k := key + strconv.Itoa(x)
+ // write key
+ writeTableKey(ip, port, networkName, tableName, k)
+ }
+ doneCh <- resultTuple{id: ip, result: x}
+}
+
+func deleteKeysNumber(ip, port, networkName, tableName, key string, number int, doneCh chan resultTuple) {
+ x := 0
+ for ; x < number; x++ {
+ k := key + strconv.Itoa(x)
+ // write key
+ deleteTableKey(ip, port, networkName, tableName, k)
+ }
+ doneCh <- resultTuple{id: ip, result: x}
+}
+
+func writeUniqueKeys(ctx context.Context, ip, port, networkName, tableName, key string, doneCh chan resultTuple) {
+ for x := 0; ; x++ {
+ select {
+ case <-ctx.Done():
+ doneCh <- resultTuple{id: ip, result: x}
+ return
+ default:
+ k := key + strconv.Itoa(x)
+ // write key
+ writeTableKey(ip, port, networkName, tableName, k)
+ // give time to send out key writes
+ time.Sleep(100 * time.Millisecond)
+ }
+ }
+}
+
+func writeDeleteUniqueKeys(ctx context.Context, ip, port, networkName, tableName, key string, doneCh chan resultTuple) {
+ for x := 0; ; x++ {
+ select {
+ case <-ctx.Done():
+ doneCh <- resultTuple{id: ip, result: x}
+ return
+ default:
+ k := key + strconv.Itoa(x)
+ // write key
+ writeTableKey(ip, port, networkName, tableName, k)
+ // give time to send out key writes
+ time.Sleep(100 * time.Millisecond)
+ // delete key
+ deleteTableKey(ip, port, networkName, tableName, k)
+ }
+ }
+}
+
+func writeDeleteLeaveJoin(ctx context.Context, ip, port, networkName, tableName, key string, doneCh chan resultTuple) {
+ for x := 0; ; x++ {
+ select {
+ case <-ctx.Done():
+ doneCh <- resultTuple{id: ip, result: x}
+ return
+ default:
+ k := key + strconv.Itoa(x)
+ // write key
+ writeTableKey(ip, port, networkName, tableName, k)
+ time.Sleep(100 * time.Millisecond)
+ // delete key
+ deleteTableKey(ip, port, networkName, tableName, k)
+ // give some time
+ time.Sleep(100 * time.Millisecond)
+ // leave network
+ leaveNetwork(ip, port, networkName, nil)
+ // join network
+ joinNetwork(ip, port, networkName, nil)
+ }
+ }
+}
+
+func ready(ip, port string, doneCh chan resultTuple) {
+ for {
+ body, err := httpGet(ip, port, "/ready")
+ if err != nil || !strings.Contains(string(body), "OK") {
+ time.Sleep(500 * time.Millisecond)
+ continue
+ }
+ // success
+ break
+ }
+ // notify the completion
+ doneCh <- resultTuple{id: ip, result: 0}
+}
+
+func checkTable(ctx context.Context, ips []string, port, networkName, tableName string, expectedEntries int, fn func(string, string, string, string, chan resultTuple)) (opTime time.Duration) {
+ startTime := time.Now().UnixNano()
+ var successTime int64
+
+ // Loop for 2 minutes to guarantee that the result is stable
+ for {
+ select {
+ case <-ctx.Done():
+ // Validate test success, if the time is set means that all the tables are empty
+ if successTime != 0 {
+ opTime = time.Duration(successTime-startTime) / time.Millisecond
+ logrus.Infof("Check table passed, the cluster converged in %d msec", opTime)
+ return
+ }
+ log.Fatal("Test failed, there is still entries in the tables of the nodes")
+ default:
+ logrus.Infof("Checking table %s expected %d", tableName, expectedEntries)
+ doneCh := make(chan resultTuple, len(ips))
+ for _, ip := range ips {
+ go fn(ip, servicePort, networkName, tableName, doneCh)
+ }
+
+ nodesWithCorrectEntriesNum := 0
+ for i := len(ips); i > 0; i-- {
+ tableEntries := <-doneCh
+ logrus.Infof("Node %s has %d entries", tableEntries.id, tableEntries.result)
+ if tableEntries.result == expectedEntries {
+ nodesWithCorrectEntriesNum++
+ }
+ }
+ close(doneCh)
+ if nodesWithCorrectEntriesNum == len(ips) {
+ if successTime == 0 {
+ successTime = time.Now().UnixNano()
+ logrus.Infof("Success after %d msec", time.Duration(successTime-startTime)/time.Millisecond)
+ }
+ } else {
+ successTime = 0
+ }
+ time.Sleep(10 * time.Second)
+ }
+ }
+}
+
+func waitWriters(parallelWriters int, mustWrite bool, doneCh chan resultTuple) map[string]int {
+ var totalKeys int
+ resultTable := make(map[string]int)
+ for i := 0; i < parallelWriters; i++ {
+ logrus.Infof("Waiting for %d workers", parallelWriters-i)
+ workerReturn := <-doneCh
+ totalKeys += workerReturn.result
+ if mustWrite && workerReturn.result == 0 {
+ log.Fatalf("The worker %s did not write any key %d == 0", workerReturn.id, workerReturn.result)
+ }
+ if !mustWrite && workerReturn.result != 0 {
+ log.Fatalf("The worker %s was supposed to return 0 instead %d != 0", workerReturn.id, workerReturn.result)
+ }
+ if mustWrite {
+ resultTable[workerReturn.id] = workerReturn.result
+ logrus.Infof("The worker %s wrote %d keys", workerReturn.id, workerReturn.result)
+ }
+ }
+ resultTable[totalWrittenKeys] = totalKeys
+ return resultTable
+}
+
+// ready
+func doReady(ips []string) {
+ doneCh := make(chan resultTuple, len(ips))
+ // check all the nodes
+ for _, ip := range ips {
+ go ready(ip, servicePort, doneCh)
+ }
+ // wait for the readiness of all nodes
+ for i := len(ips); i > 0; i-- {
+ <-doneCh
+ }
+ close(doneCh)
+}
+
+// join
+func doJoin(ips []string) {
+ doneCh := make(chan resultTuple, len(ips))
+ // check all the nodes
+ for i, ip := range ips {
+ members := append([]string(nil), ips[:i]...)
+ members = append(members, ips[i+1:]...)
+ go joinCluster(ip, servicePort, members, doneCh)
+ }
+ // wait for the readiness of all nodes
+ for i := len(ips); i > 0; i-- {
+ <-doneCh
+ }
+ close(doneCh)
+}
+
+// cluster-peers expectedNumberPeers maxRetry
+func doClusterPeers(ips []string, args []string) {
+ doneCh := make(chan resultTuple, len(ips))
+ expectedPeers, _ := strconv.Atoi(args[0])
+ maxRetry, _ := strconv.Atoi(args[1])
+ for retry := 0; retry < maxRetry; retry++ {
+ // check all the nodes
+ for _, ip := range ips {
+ go clusterPeersNumber(ip, servicePort, doneCh)
+ }
+ var failed bool
+ // wait for the readiness of all nodes
+ for i := len(ips); i > 0; i-- {
+ node := <-doneCh
+ if node.result != expectedPeers {
+ failed = true
+ if retry == maxRetry-1 {
+ log.Fatalf("Expected peers from %s missmatch %d != %d", node.id, expectedPeers, node.result)
+ } else {
+ logrus.Warnf("Expected peers from %s missmatch %d != %d", node.id, expectedPeers, node.result)
+ }
+ time.Sleep(1 * time.Second)
+ }
+ }
+ // check if needs retry
+ if !failed {
+ break
+ }
+ }
+ close(doneCh)
+}
+
+// join-network networkName
+func doJoinNetwork(ips []string, args []string) {
+ doneCh := make(chan resultTuple, len(ips))
+ // check all the nodes
+ for _, ip := range ips {
+ go joinNetwork(ip, servicePort, args[0], doneCh)
+ }
+ // wait for the readiness of all nodes
+ for i := len(ips); i > 0; i-- {
+ <-doneCh
+ }
+ close(doneCh)
+}
+
+// leave-network networkName
+func doLeaveNetwork(ips []string, args []string) {
+ doneCh := make(chan resultTuple, len(ips))
+ // check all the nodes
+ for _, ip := range ips {
+ go leaveNetwork(ip, servicePort, args[0], doneCh)
+ }
+ // wait for the readiness of all nodes
+ for i := len(ips); i > 0; i-- {
+ <-doneCh
+ }
+ close(doneCh)
+}
+
+// network-peers networkName expectedNumberPeers maxRetry
+func doNetworkPeers(ips []string, args []string) {
+ doneCh := make(chan resultTuple, len(ips))
+ networkName := args[0]
+ expectedPeers, _ := strconv.Atoi(args[1])
+ maxRetry, _ := strconv.Atoi(args[2])
+ for retry := 0; retry < maxRetry; retry++ {
+ // check all the nodes
+ for _, ip := range ips {
+ go networkPeersNumber(ip, servicePort, networkName, doneCh)
+ }
+ var failed bool
+ // wait for the readiness of all nodes
+ for i := len(ips); i > 0; i-- {
+ node := <-doneCh
+ if node.result != expectedPeers {
+ failed = true
+ if retry == maxRetry-1 {
+ log.Fatalf("Expected peers from %s missmatch %d != %d", node.id, expectedPeers, node.result)
+ } else {
+ logrus.Warnf("Expected peers from %s missmatch %d != %d", node.id, expectedPeers, node.result)
+ }
+ time.Sleep(1 * time.Second)
+ }
+ }
+ // check if needs retry
+ if !failed {
+ break
+ }
+ }
+ close(doneCh)
+}
+
+// network-stats-queue networkName <gt/lt> queueSize
+func doNetworkStatsQueue(ips []string, args []string) {
+ doneCh := make(chan resultTuple, len(ips))
+ networkName := args[0]
+ comparison := args[1]
+ size, _ := strconv.Atoi(args[2])
+
+ // check all the nodes
+ for _, ip := range ips {
+ go dbQueueLength(ip, servicePort, networkName, doneCh)
+ }
+
+ var avgQueueSize int
+ // wait for the readiness of all nodes
+ for i := len(ips); i > 0; i-- {
+ node := <-doneCh
+ switch comparison {
+ case "lt":
+ if node.result > size {
+ log.Fatalf("Expected queue size from %s to be %d < %d", node.id, node.result, size)
+ }
+ case "gt":
+ if node.result < size {
+ log.Fatalf("Expected queue size from %s to be %d > %d", node.id, node.result, size)
+ }
+ default:
+ log.Fatal("unknown comparison operator")
+ }
+ avgQueueSize += node.result
+ }
+ close(doneCh)
+ avgQueueSize /= len(ips)
+ fmt.Fprintf(os.Stderr, "doNetworkStatsQueue succeeded with avg queue:%d", avgQueueSize)
+}
+
+// write-keys networkName tableName parallelWriters numberOfKeysEach
+func doWriteKeys(ips []string, args []string) {
+ networkName := args[0]
+ tableName := args[1]
+ parallelWriters, _ := strconv.Atoi(args[2])
+ numberOfKeys, _ := strconv.Atoi(args[3])
+
+ doneCh := make(chan resultTuple, parallelWriters)
+ // Enable watch of tables from clients
+ for i := 0; i < parallelWriters; i++ {
+ go clientWatchTable(ips[i], servicePort, networkName, tableName, doneCh)
+ }
+ waitWriters(parallelWriters, false, doneCh)
+
+ // Start parallel writers that will create and delete unique keys
+ defer close(doneCh)
+ for i := 0; i < parallelWriters; i++ {
+ key := "key-" + strconv.Itoa(i) + "-"
+ logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
+ go writeKeysNumber(ips[i], servicePort, networkName, tableName, key, numberOfKeys, doneCh)
+ }
+
+ // Sync with all the writers
+ keyMap := waitWriters(parallelWriters, true, doneCh)
+ logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
+
+ // check table entries for 2 minutes
+ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
+ opTime := checkTable(ctx, ips, servicePort, networkName, tableName, keyMap[totalWrittenKeys], dbTableEntriesNumber)
+ cancel()
+ fmt.Fprintf(os.Stderr, "doWriteKeys succeeded in %d msec", opTime)
+}
+
+// delete-keys networkName tableName parallelWriters numberOfKeysEach
+func doDeleteKeys(ips []string, args []string) {
+ networkName := args[0]
+ tableName := args[1]
+ parallelWriters, _ := strconv.Atoi(args[2])
+ numberOfKeys, _ := strconv.Atoi(args[3])
+
+ doneCh := make(chan resultTuple, parallelWriters)
+ // Enable watch of tables from clients
+ for i := 0; i < parallelWriters; i++ {
+ go clientWatchTable(ips[i], servicePort, networkName, tableName, doneCh)
+ }
+ waitWriters(parallelWriters, false, doneCh)
+
+ // Start parallel writers that will create and delete unique keys
+ defer close(doneCh)
+ for i := 0; i < parallelWriters; i++ {
+ key := "key-" + strconv.Itoa(i) + "-"
+ logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
+ go deleteKeysNumber(ips[i], servicePort, networkName, tableName, key, numberOfKeys, doneCh)
+ }
+
+ // Sync with all the writers
+ keyMap := waitWriters(parallelWriters, true, doneCh)
+ logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
+
+ // check table entries for 2 minutes
+ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
+ opTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
+ cancel()
+ fmt.Fprintf(os.Stderr, "doDeletekeys succeeded in %d msec", opTime)
+}
+
+// write-delete-unique-keys networkName tableName numParallelWriters writeTimeSec
+func doWriteDeleteUniqueKeys(ips []string, args []string) {
+ networkName := args[0]
+ tableName := args[1]
+ parallelWriters, _ := strconv.Atoi(args[2])
+ writeTimeSec, _ := strconv.Atoi(args[3])
+
+ doneCh := make(chan resultTuple, parallelWriters)
+ // Enable watch of tables from clients
+ for i := 0; i < parallelWriters; i++ {
+ go clientWatchTable(ips[i], servicePort, networkName, tableName, doneCh)
+ }
+ waitWriters(parallelWriters, false, doneCh)
+
+ // Start parallel writers that will create and delete unique keys
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
+ for i := 0; i < parallelWriters; i++ {
+ key := "key-" + strconv.Itoa(i) + "-"
+ logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
+ go writeDeleteUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
+ }
+
+ // Sync with all the writers
+ keyMap := waitWriters(parallelWriters, true, doneCh)
+ cancel()
+ logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
+
+ // check table entries for 2 minutes
+ ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
+ opDBTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
+ cancel()
+ ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second)
+ opClientTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, clientTableEntriesNumber)
+ cancel()
+ fmt.Fprintf(os.Stderr, "doWriteDeleteUniqueKeys succeeded in %d msec and client %d msec", opDBTime, opClientTime)
+}
+
+// write-unique-keys networkName tableName numParallelWriters writeTimeSec
+func doWriteUniqueKeys(ips []string, args []string) {
+ networkName := args[0]
+ tableName := args[1]
+ parallelWriters, _ := strconv.Atoi(args[2])
+ writeTimeSec, _ := strconv.Atoi(args[3])
+
+ doneCh := make(chan resultTuple, parallelWriters)
+ // Enable watch of tables from clients
+ for i := 0; i < parallelWriters; i++ {
+ go clientWatchTable(ips[i], servicePort, networkName, tableName, doneCh)
+ }
+ waitWriters(parallelWriters, false, doneCh)
+
+ // Start parallel writers that will create and delete unique keys
+ defer close(doneCh)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
+ for i := 0; i < parallelWriters; i++ {
+ key := "key-" + strconv.Itoa(i) + "-"
+ logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
+ go writeUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
+ }
+
+ // Sync with all the writers
+ keyMap := waitWriters(parallelWriters, true, doneCh)
+ cancel()
+ logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
+
+ // check table entries for 2 minutes
+ ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
+ opTime := checkTable(ctx, ips, servicePort, networkName, tableName, keyMap[totalWrittenKeys], dbTableEntriesNumber)
+ cancel()
+ fmt.Fprintf(os.Stderr, "doWriteUniqueKeys succeeded in %d msec", opTime)
+}
+
+// write-delete-leave-join networkName tableName numParallelWriters writeTimeSec
+func doWriteDeleteLeaveJoin(ips []string, args []string) {
+ networkName := args[0]
+ tableName := args[1]
+ parallelWriters, _ := strconv.Atoi(args[2])
+ writeTimeSec, _ := strconv.Atoi(args[3])
+
+ // Start parallel writers that will create and delete unique keys
+ doneCh := make(chan resultTuple, parallelWriters)
+ defer close(doneCh)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
+ for i := 0; i < parallelWriters; i++ {
+ key := "key-" + strconv.Itoa(i) + "-"
+ logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
+ go writeDeleteLeaveJoin(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
+ }
+
+ // Sync with all the writers
+ keyMap := waitWriters(parallelWriters, true, doneCh)
+ cancel()
+ logrus.Infof("Written a total of %d keys on the cluster", keyMap["totalKeys"])
+
+ // check table entries for 2 minutes
+ ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
+ opTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
+ cancel()
+ fmt.Fprintf(os.Stderr, "doWriteDeleteLeaveJoin succeeded in %d msec", opTime)
+}
+
+// write-delete-wait-leave-join networkName tableName numParallelWriters writeTimeSec
+func doWriteDeleteWaitLeaveJoin(ips []string, args []string) {
+ networkName := args[0]
+ tableName := args[1]
+ parallelWriters, _ := strconv.Atoi(args[2])
+ writeTimeSec, _ := strconv.Atoi(args[3])
+
+ // Start parallel writers that will create and delete unique keys
+ doneCh := make(chan resultTuple, parallelWriters)
+ defer close(doneCh)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
+ for i := 0; i < parallelWriters; i++ {
+ key := "key-" + strconv.Itoa(i) + "-"
+ logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
+ go writeDeleteUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
+ }
+
+ // Sync with all the writers
+ keyMap := waitWriters(parallelWriters, true, doneCh)
+ cancel()
+ logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
+
+ // The writers will leave the network
+ for i := 0; i < parallelWriters; i++ {
+ logrus.Infof("worker leaveNetwork: %d on IP:%s", i, ips[i])
+ go leaveNetwork(ips[i], servicePort, networkName, doneCh)
+ }
+ waitWriters(parallelWriters, false, doneCh)
+
+ // Give some time
+ time.Sleep(100 * time.Millisecond)
+
+ // The writers will join the network
+ for i := 0; i < parallelWriters; i++ {
+ logrus.Infof("worker joinNetwork: %d on IP:%s", i, ips[i])
+ go joinNetwork(ips[i], servicePort, networkName, doneCh)
+ }
+ waitWriters(parallelWriters, false, doneCh)
+
+ // check table entries for 2 minutes
+ ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
+ opTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
+ cancel()
+ fmt.Fprintf(os.Stderr, "doWriteDeleteWaitLeaveJoin succeeded in %d msec", opTime)
+}
+
+// write-wait-leave networkName tableName numParallelWriters writeTimeSec
+func doWriteWaitLeave(ips []string, args []string) {
+ networkName := args[0]
+ tableName := args[1]
+ parallelWriters, _ := strconv.Atoi(args[2])
+ writeTimeSec, _ := strconv.Atoi(args[3])
+
+ // Start parallel writers that will create and delete unique keys
+ doneCh := make(chan resultTuple, parallelWriters)
+ defer close(doneCh)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
+ for i := 0; i < parallelWriters; i++ {
+ key := "key-" + strconv.Itoa(i) + "-"
+ logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
+ go writeUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
+ }
+
+ // Sync with all the writers
+ keyMap := waitWriters(parallelWriters, true, doneCh)
+ cancel()
+ logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
+
+ // The writers will leave the network
+ for i := 0; i < parallelWriters; i++ {
+ logrus.Infof("worker leaveNetwork: %d on IP:%s", i, ips[i])
+ go leaveNetwork(ips[i], servicePort, networkName, doneCh)
+ }
+ waitWriters(parallelWriters, false, doneCh)
+
+ // check table entries for 2 minutes
+ ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
+ opTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
+ cancel()
+ fmt.Fprintf(os.Stderr, "doWriteLeaveJoin succeeded in %d msec", opTime)
+}
+
+// write-wait-leave-join networkName tableName numParallelWriters writeTimeSec numParallelLeaver
+func doWriteWaitLeaveJoin(ips []string, args []string) {
+ networkName := args[0]
+ tableName := args[1]
+ parallelWriters, _ := strconv.Atoi(args[2])
+ writeTimeSec, _ := strconv.Atoi(args[3])
+ parallelLeaver, _ := strconv.Atoi(args[4])
+
+ // Start parallel writers that will create and delete unique keys
+ doneCh := make(chan resultTuple, parallelWriters)
+ defer close(doneCh)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
+ for i := 0; i < parallelWriters; i++ {
+ key := "key-" + strconv.Itoa(i) + "-"
+ logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
+ go writeUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
+ }
+
+ // Sync with all the writers
+ keyMap := waitWriters(parallelWriters, true, doneCh)
+ cancel()
+ logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
+
+ keysExpected := keyMap[totalWrittenKeys]
+ // The Leavers will leave the network
+ for i := 0; i < parallelLeaver; i++ {
+ logrus.Infof("worker leaveNetwork: %d on IP:%s", i, ips[i])
+ go leaveNetwork(ips[i], servicePort, networkName, doneCh)
+ // Once a node leave all the keys written previously will be deleted, so the expected keys will consider that as removed
+ keysExpected -= keyMap[ips[i]]
+ }
+ waitWriters(parallelLeaver, false, doneCh)
+
+ // Give some time
+ time.Sleep(100 * time.Millisecond)
+
+ // The writers will join the network
+ for i := 0; i < parallelLeaver; i++ {
+ logrus.Infof("worker joinNetwork: %d on IP:%s", i, ips[i])
+ go joinNetwork(ips[i], servicePort, networkName, doneCh)
+ }
+ waitWriters(parallelLeaver, false, doneCh)
+
+ // check table entries for 2 minutes
+ ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
+ opTime := checkTable(ctx, ips, servicePort, networkName, tableName, keysExpected, dbTableEntriesNumber)
+ cancel()
+ fmt.Fprintf(os.Stderr, "doWriteWaitLeaveJoin succeeded in %d msec", opTime)
+}
+
+var cmdArgChec = map[string]int{
+ "debug": 0,
+ "fail": 0,
+ "ready": 2,
+ "join": 2,
+ "leave": 2,
+ "join-network": 3,
+ "leave-network": 3,
+ "cluster-peers": 5,
+ "network-peers": 5,
+ "write-delete-unique-keys": 7,
+}
+
+// Client is a client
+func Client(args []string) {
+ logrus.Infof("[CLIENT] Starting with arguments %v", args)
+ command := args[0]
+
+ if len(args) < cmdArgChec[command] {
+ log.Fatalf("Command %s requires %d arguments, passed %d, aborting...", command, cmdArgChec[command], len(args))
+ }
+
+ switch command {
+ case "debug":
+ time.Sleep(1 * time.Hour)
+ os.Exit(0)
+ case "fail":
+ log.Fatalf("Test error condition with message: error error error")
+ }
+
+ serviceName := args[1]
+ ips, _ := net.LookupHost("tasks." + serviceName)
+ logrus.Infof("got the ips %v", ips)
+ if len(ips) == 0 {
+ log.Fatalf("Cannot resolve any IP for the service tasks.%s", serviceName)
+ }
+ servicePort = args[2]
+ commandArgs := args[3:]
+ logrus.Infof("Executing %s with args:%v", command, commandArgs)
+ switch command {
+ case "ready":
+ doReady(ips)
+ case "join":
+ doJoin(ips)
+ case "leave":
+
+ case "cluster-peers":
+ // cluster-peers maxRetry
+ doClusterPeers(ips, commandArgs)
+
+ case "join-network":
+ // join-network networkName
+ doJoinNetwork(ips, commandArgs)
+ case "leave-network":
+ // leave-network networkName
+ doLeaveNetwork(ips, commandArgs)
+ case "network-peers":
+ // network-peers networkName expectedNumberPeers maxRetry
+ doNetworkPeers(ips, commandArgs)
+ // case "network-stats-entries":
+ // // network-stats-entries networkName maxRetry
+ // doNetworkPeers(ips, commandArgs)
+ case "network-stats-queue":
+ // network-stats-queue networkName <lt/gt> queueSize
+ doNetworkStatsQueue(ips, commandArgs)
+
+ case "write-keys":
+ // write-keys networkName tableName parallelWriters numberOfKeysEach
+ doWriteKeys(ips, commandArgs)
+ case "delete-keys":
+ // delete-keys networkName tableName parallelWriters numberOfKeysEach
+ doDeleteKeys(ips, commandArgs)
+ case "write-unique-keys":
+ // write-delete-unique-keys networkName tableName numParallelWriters writeTimeSec
+ doWriteUniqueKeys(ips, commandArgs)
+ case "write-delete-unique-keys":
+ // write-delete-unique-keys networkName tableName numParallelWriters writeTimeSec
+ doWriteDeleteUniqueKeys(ips, commandArgs)
+ case "write-delete-leave-join":
+ // write-delete-leave-join networkName tableName numParallelWriters writeTimeSec
+ doWriteDeleteLeaveJoin(ips, commandArgs)
+ case "write-delete-wait-leave-join":
+ // write-delete-wait-leave-join networkName tableName numParallelWriters writeTimeSec
+ doWriteDeleteWaitLeaveJoin(ips, commandArgs)
+ case "write-wait-leave":
+ // write-wait-leave networkName tableName numParallelWriters writeTimeSec
+ doWriteWaitLeave(ips, commandArgs)
+ case "write-wait-leave-join":
+ // write-wait-leave networkName tableName numParallelWriters writeTimeSec
+ doWriteWaitLeaveJoin(ips, commandArgs)
+ default:
+ log.Fatalf("Command %s not recognized", command)
+ }
+}
--- /dev/null
+package dbserver
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "strconv"
+
+ "github.com/docker/libnetwork/cmd/networkdb-test/dummyclient"
+ "github.com/docker/libnetwork/diagnostic"
+ "github.com/docker/libnetwork/networkdb"
+ "github.com/sirupsen/logrus"
+)
+
+var nDB *networkdb.NetworkDB
+var server *diagnostic.Server
+var ipAddr string
+
+var testerPaths2Func = map[string]diagnostic.HTTPHandlerFunc{
+ "/myip": ipaddress,
+}
+
+func ipaddress(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "%s\n", ipAddr)
+}
+
+// Server starts the server
+func Server(args []string) {
+ logrus.Infof("[SERVER] Starting with arguments %v", args)
+ if len(args) < 1 {
+ log.Fatal("Port number is a mandatory argument, aborting...")
+ }
+ port, _ := strconv.Atoi(args[0])
+ var localNodeName string
+ var ok bool
+ if localNodeName, ok = os.LookupEnv("TASK_ID"); !ok {
+ log.Fatal("TASK_ID environment variable not set, aborting...")
+ }
+ logrus.Infof("[SERVER] Starting node %s on port %d", localNodeName, port)
+
+ ip, err := getIPInterface("eth0")
+ if err != nil {
+ logrus.Errorf("%s There was a problem with the IP %s\n", localNodeName, err)
+ return
+ }
+ ipAddr = ip
+ logrus.Infof("%s uses IP %s\n", localNodeName, ipAddr)
+
+ server = diagnostic.New()
+ server.Init()
+ conf := networkdb.DefaultConfig()
+ conf.Hostname = localNodeName
+ conf.AdvertiseAddr = ipAddr
+ conf.BindAddr = ipAddr
+ nDB, err = networkdb.New(conf)
+ if err != nil {
+ logrus.Infof("%s error in the DB init %s\n", localNodeName, err)
+ return
+ }
+
+ // Register network db handlers
+ server.RegisterHandler(nDB, networkdb.NetDbPaths2Func)
+ server.RegisterHandler(nil, testerPaths2Func)
+ server.RegisterHandler(nDB, dummyclient.DummyClientPaths2Func)
+ server.EnableDiagnostic("", port)
+ // block here
+ select {}
+}
+
+func getIPInterface(name string) (string, error) {
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ return "", err
+ }
+ for _, iface := range ifaces {
+ if iface.Name != name {
+ continue // not the name specified
+ }
+
+ if iface.Flags&net.FlagUp == 0 {
+ return "", errors.New("Interfaces is down")
+ }
+
+ addrs, err := iface.Addrs()
+ if err != nil {
+ return "", err
+ }
+ for _, addr := range addrs {
+ var ip net.IP
+ switch v := addr.(type) {
+ case *net.IPNet:
+ ip = v.IP
+ case *net.IPAddr:
+ ip = v.IP
+ }
+ if ip == nil || ip.IsLoopback() {
+ continue
+ }
+ ip = ip.To4()
+ if ip == nil {
+ continue
+ }
+ return ip.String(), nil
+ }
+ return "", errors.New("Interfaces does not have a valid IPv4")
+ }
+ return "", errors.New("Interface not found")
+}
--- /dev/null
+package dummyclient
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+
+ events "github.com/docker/go-events"
+ "github.com/docker/libnetwork/diagnostic"
+ "github.com/docker/libnetwork/networkdb"
+ "github.com/sirupsen/logrus"
+)
+
+// DummyClientPaths2Func exported paths for the client
+var DummyClientPaths2Func = map[string]diagnostic.HTTPHandlerFunc{
+ "/watchtable": watchTable,
+ "/watchedtableentries": watchTableEntries,
+}
+
+const (
+ missingParameter = "missing parameter"
+)
+
+type tableHandler struct {
+ cancelWatch func()
+ entries map[string]string
+}
+
+var clientWatchTable = map[string]tableHandler{}
+
+func watchTable(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ if len(r.Form["tname"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name", r.URL.Path))
+ diagnostic.HTTPReply(w, rsp, &diagnostic.JSONOutput{})
+ return
+ }
+
+ tableName := r.Form["tname"][0]
+ if _, ok := clientWatchTable[tableName]; ok {
+ fmt.Fprintf(w, "OK\n")
+ return
+ }
+
+ nDB, ok := ctx.(*networkdb.NetworkDB)
+ if ok {
+ ch, cancel := nDB.Watch(tableName, "", "")
+ clientWatchTable[tableName] = tableHandler{cancelWatch: cancel, entries: make(map[string]string)}
+ go handleTableEvents(tableName, ch)
+
+ fmt.Fprintf(w, "OK\n")
+ }
+}
+
+func watchTableEntries(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ if len(r.Form["tname"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name", r.URL.Path))
+ diagnostic.HTTPReply(w, rsp, &diagnostic.JSONOutput{})
+ return
+ }
+
+ tableName := r.Form["tname"][0]
+ table, ok := clientWatchTable[tableName]
+ if !ok {
+ fmt.Fprintf(w, "Table %s not watched\n", tableName)
+ return
+ }
+
+ fmt.Fprintf(w, "total elements: %d\n", len(table.entries))
+ i := 0
+ for k, v := range table.entries {
+ fmt.Fprintf(w, "%d) k:`%s` -> v:`%s`\n", i, k, v)
+ i++
+ }
+}
+
+func handleTableEvents(tableName string, ch *events.Channel) {
+ var (
+ // nid string
+ eid string
+ value []byte
+ isAdd bool
+ )
+
+ logrus.Infof("Started watching table:%s", tableName)
+ for {
+ select {
+ case <-ch.Done():
+ logrus.Infof("End watching %s", tableName)
+ return
+
+ case evt := <-ch.C:
+ logrus.Infof("Recevied new event on:%s", tableName)
+ switch event := evt.(type) {
+ case networkdb.CreateEvent:
+ // nid = event.NetworkID
+ eid = event.Key
+ value = event.Value
+ isAdd = true
+ case networkdb.DeleteEvent:
+ // nid = event.NetworkID
+ eid = event.Key
+ value = event.Value
+ isAdd = false
+ default:
+ log.Fatalf("Unexpected table event = %#v", event)
+ }
+ if isAdd {
+ // logrus.Infof("Add %s %s", tableName, eid)
+ clientWatchTable[tableName].entries[eid] = string(value)
+ } else {
+ // logrus.Infof("Del %s %s", tableName, eid)
+ delete(clientWatchTable[tableName].entries, eid)
+ }
+ }
+ }
+}
--- /dev/null
+package main
+
+import (
+ "log"
+ "os"
+
+ "github.com/docker/libnetwork/cmd/networkdb-test/dbclient"
+ "github.com/docker/libnetwork/cmd/networkdb-test/dbserver"
+ "github.com/sirupsen/logrus"
+)
+
+func main() {
+ formatter := &logrus.TextFormatter{
+ FullTimestamp: true,
+ }
+ logrus.SetFormatter(formatter)
+ logrus.Infof("Starting the image with these args: %v", os.Args)
+ if len(os.Args) < 1 {
+ log.Fatal("You need at least 1 argument [client/server]")
+ }
+
+ switch os.Args[1] {
+ case "server":
+ dbserver.Server(os.Args[2:])
+ case "client":
+ dbclient.Client(os.Args[2:])
+ }
+}
--- /dev/null
+package main
+
+import (
+ "fmt"
+ "net"
+ "os"
+ "os/signal"
+
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/drivers/overlay"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+ "github.com/vishvananda/netlink"
+)
+
+type router struct {
+ d driverapi.Driver
+}
+
+type endpoint struct {
+ addr *net.IPNet
+ mac net.HardwareAddr
+ name string
+}
+
+func (r *router) GetPluginGetter() plugingetter.PluginGetter {
+ return nil
+}
+
+func (r *router) RegisterDriver(name string, driver driverapi.Driver, c driverapi.Capability) error {
+ r.d = driver
+ return nil
+}
+
+func (ep *endpoint) Interface() driverapi.InterfaceInfo {
+ return nil
+}
+
+func (ep *endpoint) SetMacAddress(mac net.HardwareAddr) error {
+ if ep.mac != nil {
+ return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", ep.mac, mac)
+ }
+ if mac == nil {
+ return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
+ }
+ ep.mac = types.GetMacCopy(mac)
+ return nil
+}
+
+func (ep *endpoint) SetIPAddress(address *net.IPNet) error {
+ if address.IP == nil {
+ return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
+ }
+ if address.IP.To4() == nil {
+ return types.NotImplementedErrorf("do not support ipv6 yet")
+ }
+ if ep.addr != nil {
+ return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with %s.", ep.addr, address)
+ }
+ ep.addr = types.GetIPNetCopy(address)
+ return nil
+}
+
+func (ep *endpoint) MacAddress() net.HardwareAddr {
+ return types.GetMacCopy(ep.mac)
+}
+
+func (ep *endpoint) Address() *net.IPNet {
+ return types.GetIPNetCopy(ep.addr)
+}
+
+func (ep *endpoint) AddressIPv6() *net.IPNet {
+ return nil
+}
+
+func (ep *endpoint) InterfaceName() driverapi.InterfaceNameInfo {
+ return ep
+}
+
+func (ep *endpoint) SetNames(srcName, dstPrefix string) error {
+ ep.name = srcName
+ return nil
+}
+
+func (ep *endpoint) SetGateway(net.IP) error {
+ return nil
+}
+
+func (ep *endpoint) SetGatewayIPv6(net.IP) error {
+ return nil
+}
+
+func (ep *endpoint) AddStaticRoute(destination *net.IPNet, routeType int,
+ nextHop net.IP) error {
+ return nil
+}
+
+func (ep *endpoint) AddTableEntry(tableName string, key string, value []byte) error {
+ return nil
+}
+
+func (ep *endpoint) DisableGatewayService() {}
+
+func main() {
+ if reexec.Init() {
+ return
+ }
+
+ opt := make(map[string]interface{})
+ if len(os.Args) > 1 {
+ opt[netlabel.OverlayBindInterface] = os.Args[1]
+ }
+ if len(os.Args) > 2 {
+ opt[netlabel.OverlayNeighborIP] = os.Args[2]
+ }
+ if len(os.Args) > 3 {
+ opt[netlabel.GlobalKVProvider] = os.Args[3]
+ }
+ if len(os.Args) > 4 {
+ opt[netlabel.GlobalKVProviderURL] = os.Args[4]
+ }
+
+ r := &router{}
+ if err := overlay.Init(r, opt); err != nil {
+ fmt.Printf("Failed to initialize overlay driver: %v\n", err)
+ os.Exit(1)
+ }
+
+ if err := r.d.CreateNetwork("testnetwork",
+ map[string]interface{}{}, nil, nil, nil); err != nil {
+ fmt.Printf("Failed to create network in the driver: %v\n", err)
+ os.Exit(1)
+ }
+
+ ep := &endpoint{}
+ if err := r.d.CreateEndpoint("testnetwork", "testep",
+ ep, map[string]interface{}{}); err != nil {
+ fmt.Printf("Failed to create endpoint in the driver: %v\n", err)
+ os.Exit(1)
+ }
+
+ if err := r.d.Join("testnetwork", "testep",
+ "", ep, map[string]interface{}{}); err != nil {
+ fmt.Printf("Failed to join an endpoint in the driver: %v\n", err)
+ os.Exit(1)
+ }
+
+ link, err := netlink.LinkByName(ep.name)
+ if err != nil {
+ fmt.Printf("Failed to find the container interface with name %s: %v\n",
+ ep.name, err)
+ os.Exit(1)
+ }
+
+ ipAddr := &netlink.Addr{IPNet: ep.addr, Label: ""}
+ if err := netlink.AddrAdd(link, ipAddr); err != nil {
+ fmt.Printf("Failed to add address to the interface: %v\n", err)
+ os.Exit(1)
+ }
+
+ sigCh := make(chan os.Signal, 1)
+ signal.Notify(sigCh, os.Interrupt, os.Kill)
+
+ for {
+ select {
+ case <-sigCh:
+ r.d.Leave("testnetwork", "testep")
+ overlay.Fini(r.d)
+ os.Exit(0)
+ }
+ }
+}
--- /dev/null
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "net"
+ "os"
+ "os/signal"
+ "syscall"
+
+ "github.com/ishidawataru/sctp"
+)
+
+func main() {
+ f := os.NewFile(3, "signal-parent")
+ host, container := parseHostContainerAddrs()
+
+ p, err := NewProxy(host, container)
+ if err != nil {
+ fmt.Fprintf(f, "1\n%s", err)
+ f.Close()
+ os.Exit(1)
+ }
+ go handleStopSignals(p)
+ fmt.Fprint(f, "0\n")
+ f.Close()
+
+ // Run will block until the proxy stops
+ p.Run()
+}
+
+// parseHostContainerAddrs parses the flags passed on reexec to create the TCP/UDP/SCTP
+// net.Addrs to map the host and container ports
+func parseHostContainerAddrs() (host net.Addr, container net.Addr) {
+ var (
+ proto = flag.String("proto", "tcp", "proxy protocol")
+ hostIP = flag.String("host-ip", "", "host ip")
+ hostPort = flag.Int("host-port", -1, "host port")
+ containerIP = flag.String("container-ip", "", "container ip")
+ containerPort = flag.Int("container-port", -1, "container port")
+ )
+
+ flag.Parse()
+
+ switch *proto {
+ case "tcp":
+ host = &net.TCPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
+ container = &net.TCPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
+ case "udp":
+ host = &net.UDPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
+ container = &net.UDPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
+ case "sctp":
+ host = &sctp.SCTPAddr{IP: []net.IP{net.ParseIP(*hostIP)}, Port: *hostPort}
+ container = &sctp.SCTPAddr{IP: []net.IP{net.ParseIP(*containerIP)}, Port: *containerPort}
+ default:
+ log.Fatalf("unsupported protocol %s", *proto)
+ }
+
+ return host, container
+}
+
+func handleStopSignals(p Proxy) {
+ s := make(chan os.Signal, 10)
+ signal.Notify(s, os.Interrupt, syscall.SIGTERM)
+
+ for range s {
+ p.Close()
+
+ os.Exit(0)
+ }
+}
--- /dev/null
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/ishidawataru/sctp"
+ // this takes care of the incontainer flag
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")
+var testBufSize = len(testBuf)
+
+type EchoServer interface {
+ Run()
+ Close()
+ LocalAddr() net.Addr
+}
+
+type EchoServerOptions struct {
+ TCPHalfClose bool
+}
+
+type StreamEchoServer struct {
+ listener net.Listener
+ testCtx *testing.T
+ opts EchoServerOptions
+}
+
+type UDPEchoServer struct {
+ conn net.PacketConn
+ testCtx *testing.T
+}
+
+func NewEchoServer(t *testing.T, proto, address string, opts EchoServerOptions) EchoServer {
+ var server EchoServer
+ if !strings.HasPrefix(proto, "tcp") && opts.TCPHalfClose {
+ t.Fatalf("TCPHalfClose is not supported for %s", proto)
+ }
+
+ switch {
+ case strings.HasPrefix(proto, "tcp"):
+ listener, err := net.Listen(proto, address)
+ if err != nil {
+ t.Fatal(err)
+ }
+ server = &StreamEchoServer{listener: listener, testCtx: t, opts: opts}
+ case strings.HasPrefix(proto, "udp"):
+ socket, err := net.ListenPacket(proto, address)
+ if err != nil {
+ t.Fatal(err)
+ }
+ server = &UDPEchoServer{conn: socket, testCtx: t}
+ case strings.HasPrefix(proto, "sctp"):
+ addr, err := sctp.ResolveSCTPAddr(proto, address)
+ if err != nil {
+ t.Fatal(err)
+ }
+ listener, err := sctp.ListenSCTP(proto, addr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ server = &StreamEchoServer{listener: listener, testCtx: t}
+ default:
+ t.Fatalf("unknown protocol: %s", proto)
+ }
+ return server
+}
+
+func (server *StreamEchoServer) Run() {
+ go func() {
+ for {
+ client, err := server.listener.Accept()
+ if err != nil {
+ return
+ }
+ go func(client net.Conn) {
+ if server.opts.TCPHalfClose {
+ data, err := ioutil.ReadAll(client)
+ if err != nil {
+ server.testCtx.Logf("io.ReadAll() failed for the client: %v\n", err.Error())
+ }
+ if _, err := client.Write(data); err != nil {
+ server.testCtx.Logf("can't echo to the client: %v\n", err.Error())
+ }
+ client.(*net.TCPConn).CloseWrite()
+ } else {
+ if _, err := io.Copy(client, client); err != nil {
+ server.testCtx.Logf("can't echo to the client: %v\n", err.Error())
+ }
+ client.Close()
+ }
+ }(client)
+ }
+ }()
+}
+
+func (server *StreamEchoServer) LocalAddr() net.Addr { return server.listener.Addr() }
+func (server *StreamEchoServer) Close() { server.listener.Close() }
+
+func (server *UDPEchoServer) Run() {
+ go func() {
+ readBuf := make([]byte, 1024)
+ for {
+ read, from, err := server.conn.ReadFrom(readBuf)
+ if err != nil {
+ return
+ }
+ for i := 0; i != read; {
+ written, err := server.conn.WriteTo(readBuf[i:read], from)
+ if err != nil {
+ break
+ }
+ i += written
+ }
+ }
+ }()
+}
+
+func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() }
+func (server *UDPEchoServer) Close() { server.conn.Close() }
+
+func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string, halfClose bool) {
+ defer proxy.Close()
+ go proxy.Run()
+ var client net.Conn
+ var err error
+ if strings.HasPrefix(proto, "sctp") {
+ var a *sctp.SCTPAddr
+ a, err = sctp.ResolveSCTPAddr(proto, addr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ client, err = sctp.DialSCTP(proto, nil, a)
+ } else {
+ client, err = net.Dial(proto, addr)
+ }
+
+ if err != nil {
+ t.Fatalf("Can't connect to the proxy: %v", err)
+ }
+ defer client.Close()
+ client.SetDeadline(time.Now().Add(10 * time.Second))
+ if _, err = client.Write(testBuf); err != nil {
+ t.Fatal(err)
+ }
+ if halfClose {
+ if proto != "tcp" {
+ t.Fatalf("halfClose is not supported for %s", proto)
+ }
+ client.(*net.TCPConn).CloseWrite()
+ }
+ recvBuf := make([]byte, testBufSize)
+ if _, err = client.Read(recvBuf); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(testBuf, recvBuf) {
+ t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
+ }
+}
+
+func testProxy(t *testing.T, proto string, proxy Proxy, halfClose bool) {
+ testProxyAt(t, proto, proxy, proxy.FrontendAddr().String(), halfClose)
+}
+
+func testTCP4Proxy(t *testing.T, halfClose bool) {
+ backend := NewEchoServer(t, "tcp", "127.0.0.1:0", EchoServerOptions{TCPHalfClose: halfClose})
+ defer backend.Close()
+ backend.Run()
+ frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
+ if err != nil {
+ t.Fatal(err)
+ }
+ testProxy(t, "tcp", proxy, halfClose)
+}
+
+func TestTCP4Proxy(t *testing.T) {
+ testTCP4Proxy(t, false)
+}
+
+func TestTCP4ProxyHalfClose(t *testing.T) {
+ testTCP4Proxy(t, true)
+}
+
+func TestTCP6Proxy(t *testing.T) {
+ t.Skip("Need to start CI docker with --ipv6")
+ backend := NewEchoServer(t, "tcp", "[::1]:0", EchoServerOptions{})
+ defer backend.Close()
+ backend.Run()
+ frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
+ if err != nil {
+ t.Fatal(err)
+ }
+ testProxy(t, "tcp", proxy, false)
+}
+
+func TestTCPDualStackProxy(t *testing.T) {
+ // If I understand `godoc -src net favoriteAddrFamily` (used by the
+ // net.Listen* functions) correctly this should work, but it doesn't.
+ t.Skip("No support for dual stack yet")
+ backend := NewEchoServer(t, "tcp", "[::1]:0", EchoServerOptions{})
+ defer backend.Close()
+ backend.Run()
+ frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
+ if err != nil {
+ t.Fatal(err)
+ }
+ ipv4ProxyAddr := &net.TCPAddr{
+ IP: net.IPv4(127, 0, 0, 1),
+ Port: proxy.FrontendAddr().(*net.TCPAddr).Port,
+ }
+ testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String(), false)
+}
+
+func TestUDP4Proxy(t *testing.T) {
+ backend := NewEchoServer(t, "udp", "127.0.0.1:0", EchoServerOptions{})
+ defer backend.Close()
+ backend.Run()
+ frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
+ if err != nil {
+ t.Fatal(err)
+ }
+ testProxy(t, "udp", proxy, false)
+}
+
+func TestUDP6Proxy(t *testing.T) {
+ t.Skip("Need to start CI docker with --ipv6")
+ backend := NewEchoServer(t, "udp", "[::1]:0", EchoServerOptions{})
+ defer backend.Close()
+ backend.Run()
+ frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0}
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
+ if err != nil {
+ t.Fatal(err)
+ }
+ testProxy(t, "udp", proxy, false)
+}
+
+func TestUDPWriteError(t *testing.T) {
+ frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
+ // Hopefully, this port will be free: */
+ backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587}
+ proxy, err := NewProxy(frontendAddr, backendAddr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer proxy.Close()
+ go proxy.Run()
+ client, err := net.Dial("udp", "127.0.0.1:25587")
+ if err != nil {
+ t.Fatalf("Can't connect to the proxy: %v", err)
+ }
+ defer client.Close()
+ // Make sure the proxy doesn't stop when there is no actual backend:
+ client.Write(testBuf)
+ client.Write(testBuf)
+ backend := NewEchoServer(t, "udp", "127.0.0.1:25587", EchoServerOptions{})
+ defer backend.Close()
+ backend.Run()
+ client.SetDeadline(time.Now().Add(10 * time.Second))
+ if _, err = client.Write(testBuf); err != nil {
+ t.Fatal(err)
+ }
+ recvBuf := make([]byte, testBufSize)
+ if _, err = client.Read(recvBuf); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(testBuf, recvBuf) {
+ t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
+ }
+}
+
+func TestSCTP4Proxy(t *testing.T) {
+ backend := NewEchoServer(t, "sctp", "127.0.0.1:0", EchoServerOptions{})
+ defer backend.Close()
+ backend.Run()
+ frontendAddr := &sctp.SCTPAddr{IP: []net.IP{net.IPv4(127, 0, 0, 1)}, Port: 0}
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
+ if err != nil {
+ t.Fatal(err)
+ }
+ testProxy(t, "sctp", proxy, false)
+}
+
+func TestSCTP6Proxy(t *testing.T) {
+ t.Skip("Need to start CI docker with --ipv6")
+ backend := NewEchoServer(t, "sctp", "[::1]:0", EchoServerOptions{})
+ defer backend.Close()
+ backend.Run()
+ frontendAddr := &sctp.SCTPAddr{IP: []net.IP{net.IPv6loopback}, Port: 0}
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
+ if err != nil {
+ t.Fatal(err)
+ }
+ testProxy(t, "sctp", proxy, false)
+}
--- /dev/null
+// docker-proxy provides a network Proxy interface and implementations for TCP
+// and UDP.
+package main
+
+import (
+ "net"
+
+ "github.com/ishidawataru/sctp"
+)
+
+// Proxy defines the behavior of a proxy. It forwards traffic back and forth
+// between two endpoints : the frontend and the backend.
+// It can be used to do software port-mapping between two addresses.
+// e.g. forward all traffic between the frontend (host) 127.0.0.1:3000
+// to the backend (container) at 172.17.42.108:4000.
+type Proxy interface {
+ // Run starts forwarding traffic back and forth between the front
+ // and back-end addresses.
+ Run()
+ // Close stops forwarding traffic and close both ends of the Proxy.
+ Close()
+ // FrontendAddr returns the address on which the proxy is listening.
+ FrontendAddr() net.Addr
+ // BackendAddr returns the proxied address.
+ BackendAddr() net.Addr
+}
+
+// NewProxy creates a Proxy according to the specified frontendAddr and backendAddr.
+func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
+ switch frontendAddr.(type) {
+ case *net.UDPAddr:
+ return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
+ case *net.TCPAddr:
+ return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
+ case *sctp.SCTPAddr:
+ return NewSCTPProxy(frontendAddr.(*sctp.SCTPAddr), backendAddr.(*sctp.SCTPAddr))
+ default:
+ panic("Unsupported protocol")
+ }
+}
--- /dev/null
+package main
+
+import (
+ "io"
+ "log"
+ "net"
+ "sync"
+
+ "github.com/ishidawataru/sctp"
+)
+
+// SCTPProxy is a proxy for SCTP connections. It implements the Proxy interface to
+// handle SCTP traffic forwarding between the frontend and backend addresses.
+type SCTPProxy struct {
+ listener *sctp.SCTPListener
+ frontendAddr *sctp.SCTPAddr
+ backendAddr *sctp.SCTPAddr
+}
+
+// NewSCTPProxy creates a new SCTPProxy.
+func NewSCTPProxy(frontendAddr, backendAddr *sctp.SCTPAddr) (*SCTPProxy, error) {
+ listener, err := sctp.ListenSCTP("sctp", frontendAddr)
+ if err != nil {
+ return nil, err
+ }
+ // If the port in frontendAddr was 0 then ListenSCTP will have a picked
+ // a port to listen on, hence the call to Addr to get that actual port:
+ return &SCTPProxy{
+ listener: listener,
+ frontendAddr: listener.Addr().(*sctp.SCTPAddr),
+ backendAddr: backendAddr,
+ }, nil
+}
+
+func (proxy *SCTPProxy) clientLoop(client *sctp.SCTPConn, quit chan bool) {
+ backend, err := sctp.DialSCTP("sctp", nil, proxy.backendAddr)
+ if err != nil {
+ log.Printf("Can't forward traffic to backend sctp/%v: %s\n", proxy.backendAddr, err)
+ client.Close()
+ return
+ }
+ clientC := sctp.NewSCTPSndRcvInfoWrappedConn(client)
+ backendC := sctp.NewSCTPSndRcvInfoWrappedConn(backend)
+
+ var wg sync.WaitGroup
+ var broker = func(to, from net.Conn) {
+ io.Copy(to, from)
+ from.Close()
+ to.Close()
+ wg.Done()
+ }
+
+ wg.Add(2)
+ go broker(clientC, backendC)
+ go broker(backendC, clientC)
+
+ finish := make(chan struct{})
+ go func() {
+ wg.Wait()
+ close(finish)
+ }()
+
+ select {
+ case <-quit:
+ case <-finish:
+ }
+ clientC.Close()
+ backendC.Close()
+ <-finish
+}
+
+// Run starts forwarding the traffic using SCTP.
+func (proxy *SCTPProxy) Run() {
+ quit := make(chan bool)
+ defer close(quit)
+ for {
+ client, err := proxy.listener.Accept()
+ if err != nil {
+ log.Printf("Stopping proxy on sctp/%v for sctp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
+ return
+ }
+ go proxy.clientLoop(client.(*sctp.SCTPConn), quit)
+ }
+}
+
+// Close stops forwarding the traffic.
+func (proxy *SCTPProxy) Close() { proxy.listener.Close() }
+
+// FrontendAddr returns the SCTP address on which the proxy is listening.
+func (proxy *SCTPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
+
+// BackendAddr returns the SCTP proxied address.
+func (proxy *SCTPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
--- /dev/null
+package main
+
+import (
+ "net"
+)
+
+// StubProxy is a proxy that is a stub (does nothing).
+type StubProxy struct {
+ frontendAddr net.Addr
+ backendAddr net.Addr
+}
+
+// Run does nothing.
+func (p *StubProxy) Run() {}
+
+// Close does nothing.
+func (p *StubProxy) Close() {}
+
+// FrontendAddr returns the frontend address.
+func (p *StubProxy) FrontendAddr() net.Addr { return p.frontendAddr }
+
+// BackendAddr returns the backend address.
+func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr }
+
+// NewStubProxy creates a new StubProxy
+func NewStubProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
+ return &StubProxy{
+ frontendAddr: frontendAddr,
+ backendAddr: backendAddr,
+ }, nil
+}
--- /dev/null
+package main
+
+import (
+ "io"
+ "log"
+ "net"
+ "sync"
+)
+
+// TCPProxy is a proxy for TCP connections. It implements the Proxy interface to
+// handle TCP traffic forwarding between the frontend and backend addresses.
+type TCPProxy struct {
+ listener *net.TCPListener
+ frontendAddr *net.TCPAddr
+ backendAddr *net.TCPAddr
+}
+
+// NewTCPProxy creates a new TCPProxy.
+func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
+ listener, err := net.ListenTCP("tcp", frontendAddr)
+ if err != nil {
+ return nil, err
+ }
+ // If the port in frontendAddr was 0 then ListenTCP will have a picked
+ // a port to listen on, hence the call to Addr to get that actual port:
+ return &TCPProxy{
+ listener: listener,
+ frontendAddr: listener.Addr().(*net.TCPAddr),
+ backendAddr: backendAddr,
+ }, nil
+}
+
+func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
+ backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
+ if err != nil {
+ log.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err)
+ client.Close()
+ return
+ }
+
+ var wg sync.WaitGroup
+ var broker = func(to, from *net.TCPConn) {
+ io.Copy(to, from)
+ from.CloseRead()
+ to.CloseWrite()
+ wg.Done()
+ }
+
+ wg.Add(2)
+ go broker(client, backend)
+ go broker(backend, client)
+
+ finish := make(chan struct{})
+ go func() {
+ wg.Wait()
+ close(finish)
+ }()
+
+ select {
+ case <-quit:
+ case <-finish:
+ }
+ client.Close()
+ backend.Close()
+ <-finish
+}
+
+// Run starts forwarding the traffic using TCP.
+func (proxy *TCPProxy) Run() {
+ quit := make(chan bool)
+ defer close(quit)
+ for {
+ client, err := proxy.listener.Accept()
+ if err != nil {
+ log.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
+ return
+ }
+ go proxy.clientLoop(client.(*net.TCPConn), quit)
+ }
+}
+
+// Close stops forwarding the traffic.
+func (proxy *TCPProxy) Close() { proxy.listener.Close() }
+
+// FrontendAddr returns the TCP address on which the proxy is listening.
+func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
+
+// BackendAddr returns the TCP proxied address.
+func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
--- /dev/null
+package main
+
+import (
+ "encoding/binary"
+ "log"
+ "net"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+)
+
+const (
+ // UDPConnTrackTimeout is the timeout used for UDP connection tracking
+ UDPConnTrackTimeout = 90 * time.Second
+ // UDPBufSize is the buffer size for the UDP proxy
+ UDPBufSize = 65507
+)
+
+// A net.Addr where the IP is split into two fields so you can use it as a key
+// in a map:
+type connTrackKey struct {
+ IPHigh uint64
+ IPLow uint64
+ Port int
+}
+
+func newConnTrackKey(addr *net.UDPAddr) *connTrackKey {
+ if len(addr.IP) == net.IPv4len {
+ return &connTrackKey{
+ IPHigh: 0,
+ IPLow: uint64(binary.BigEndian.Uint32(addr.IP)),
+ Port: addr.Port,
+ }
+ }
+ return &connTrackKey{
+ IPHigh: binary.BigEndian.Uint64(addr.IP[:8]),
+ IPLow: binary.BigEndian.Uint64(addr.IP[8:]),
+ Port: addr.Port,
+ }
+}
+
+type connTrackMap map[connTrackKey]*net.UDPConn
+
+// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy
+// interface to handle UDP traffic forwarding between the frontend and backend
+// addresses.
+type UDPProxy struct {
+ listener *net.UDPConn
+ frontendAddr *net.UDPAddr
+ backendAddr *net.UDPAddr
+ connTrackTable connTrackMap
+ connTrackLock sync.Mutex
+}
+
+// NewUDPProxy creates a new UDPProxy.
+func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) {
+ listener, err := net.ListenUDP("udp", frontendAddr)
+ if err != nil {
+ return nil, err
+ }
+ return &UDPProxy{
+ listener: listener,
+ frontendAddr: listener.LocalAddr().(*net.UDPAddr),
+ backendAddr: backendAddr,
+ connTrackTable: make(connTrackMap),
+ }, nil
+}
+
+func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) {
+ defer func() {
+ proxy.connTrackLock.Lock()
+ delete(proxy.connTrackTable, *clientKey)
+ proxy.connTrackLock.Unlock()
+ proxyConn.Close()
+ }()
+
+ readBuf := make([]byte, UDPBufSize)
+ for {
+ proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout))
+ again:
+ read, err := proxyConn.Read(readBuf)
+ if err != nil {
+ if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED {
+ // This will happen if the last write failed
+ // (e.g: nothing is actually listening on the
+ // proxied port on the container), ignore it
+ // and continue until UDPConnTrackTimeout
+ // expires:
+ goto again
+ }
+ return
+ }
+ for i := 0; i != read; {
+ written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr)
+ if err != nil {
+ return
+ }
+ i += written
+ }
+ }
+}
+
+// Run starts forwarding the traffic using UDP.
+func (proxy *UDPProxy) Run() {
+ readBuf := make([]byte, UDPBufSize)
+ for {
+ read, from, err := proxy.listener.ReadFromUDP(readBuf)
+ if err != nil {
+ // NOTE: Apparently ReadFrom doesn't return
+ // ECONNREFUSED like Read do (see comment in
+ // UDPProxy.replyLoop)
+ if !isClosedError(err) {
+ log.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
+ }
+ break
+ }
+
+ fromKey := newConnTrackKey(from)
+ proxy.connTrackLock.Lock()
+ proxyConn, hit := proxy.connTrackTable[*fromKey]
+ if !hit {
+ proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr)
+ if err != nil {
+ log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
+ proxy.connTrackLock.Unlock()
+ continue
+ }
+ proxy.connTrackTable[*fromKey] = proxyConn
+ go proxy.replyLoop(proxyConn, from, fromKey)
+ }
+ proxy.connTrackLock.Unlock()
+ for i := 0; i != read; {
+ written, err := proxyConn.Write(readBuf[i:read])
+ if err != nil {
+ log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
+ break
+ }
+ i += written
+ }
+ }
+}
+
+// Close stops forwarding the traffic.
+func (proxy *UDPProxy) Close() {
+ proxy.listener.Close()
+ proxy.connTrackLock.Lock()
+ defer proxy.connTrackLock.Unlock()
+ for _, conn := range proxy.connTrackTable {
+ conn.Close()
+ }
+}
+
+// FrontendAddr returns the UDP address on which the proxy is listening.
+func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
+
+// BackendAddr returns the proxied UDP address.
+func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
+
+func isClosedError(err error) bool {
+ /* This comparison is ugly, but unfortunately, net.go doesn't export errClosing.
+ * See:
+ * http://golang.org/src/pkg/net/net.go
+ * https://code.google.com/p/go/issues/detail?id=4337
+ * https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ
+ */
+ return strings.HasSuffix(err.Error(), "use of closed network connection")
+}
--- /dev/null
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork"
+ "github.com/docker/libnetwork/config"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/options"
+)
+
+func main() {
+ if reexec.Init() {
+ return
+ }
+
+ // Select and configure the network driver
+ networkType := "bridge"
+
+ // Create a new controller instance
+ driverOptions := options.Generic{}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = driverOptions
+ controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption))
+ if err != nil {
+ log.Fatalf("libnetwork.New: %s", err)
+ }
+
+ // Create a network for containers to join.
+ // NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can use.
+ network, err := controller.NewNetwork(networkType, "network1", "")
+ if err != nil {
+ log.Fatalf("controller.NewNetwork: %s", err)
+ }
+
+ // For each new container: allocate IP and interfaces. The returned network
+ // settings will be used for container infos (inspect and such), as well as
+ // iptables rules for port publishing. This info is contained or accessible
+ // from the returned endpoint.
+ ep, err := network.CreateEndpoint("Endpoint1")
+ if err != nil {
+ log.Fatalf("network.CreateEndpoint: %s", err)
+ }
+
+ // Create the sandbox for the container.
+ // NewSandbox accepts Variadic optional arguments which libnetwork can use.
+ sbx, err := controller.NewSandbox("container1",
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"))
+ if err != nil {
+ log.Fatalf("controller.NewSandbox: %s", err)
+ }
+
+ // A sandbox can join the endpoint via the join api.
+ err = ep.Join(sbx)
+ if err != nil {
+ log.Fatalf("ep.Join: %s", err)
+ }
+
+ // libnetwork client can check the endpoint's operational data via the Info() API
+ epInfo, err := ep.DriverInfo()
+ if err != nil {
+ log.Fatalf("ep.DriverInfo: %s", err)
+ }
+
+ macAddress, ok := epInfo[netlabel.MacAddress]
+ if !ok {
+ log.Fatal("failed to get mac address from endpoint info")
+ }
+
+ fmt.Printf("Joined endpoint %s (%s) to sandbox %s (%s)\n", ep.Name(), macAddress, sbx.ContainerID(), sbx.Key())
+}
--- /dev/null
+FROM alpine:3.7
+ENV PACKAGES="\
+ musl \
+ linux-headers \
+ build-base \
+ util-linux \
+ bash \
+ git \
+ ca-certificates \
+ python2 \
+ python2-dev \
+ py-setuptools \
+ iproute2 \
+ curl \
+ strace \
+ drill \
+ ipvsadm \
+ iperf \
+ ethtool \
+"
+
+RUN echo \
+ && apk add --no-cache $PACKAGES \
+ && if [[ ! -e /usr/bin/python ]]; then ln -sf /usr/bin/python2.7 /usr/bin/python; fi \
+ && if [[ ! -e /usr/bin/python-config ]]; then ln -sf /usr/bin/python2.7-config /usr/bin/python-config; fi \
+ && if [[ ! -e /usr/bin/easy_install ]]; then ln -sf /usr/bin/easy_install-2.7 /usr/bin/easy_install; fi \
+ && easy_install pip \
+ && pip install --upgrade pip \
+ && if [[ ! -e /usr/bin/pip ]]; then ln -sf /usr/bin/pip2.7 /usr/bin/pip; fi \
+ && echo
+
+ADD ssd.py /
+RUN pip install git+git://github.com/docker/docker-py.git
+ENTRYPOINT [ "python", "/ssd.py"]
--- /dev/null
+# Docker Swarm Service Driller(ssd)
+
+ssd is a troubleshooting utility for Docker swarm networks.
+
+### control-plane and datapath consistency check on a node
+ssd checks for the consistency between docker network control-plane (from the docker daemon in-memory state) and kernel data path programming. Currently the tool checks only for the consistency of the Load balancer (implemented using IPVS).
+
+In a three node swarm cluser ssd status for a overlay network `ov2` which has three services running, each replicated to 3 instances.
+
+````bash
+vagrant@net-1:~/code/go/src/github.com/docker/docker-e2e/tests$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /var/run/docker/netns:/var/run/docker/netns --privileged --net=host sanimej/ssd ov2
+Verifying LB programming for containers on network ov2
+Verifying container /s2.3.ltrdwef0iqf90rqauw3ehcs56...
+service s2... OK
+service s3... OK
+service s1... OK
+Verifying container /s3.3.nyhwvdvnocb4wftyhb8dr4fj8...
+service s2... OK
+service s3... OK
+service s1... OK
+Verifying container /s1.3.wwx5tuxhnvoz5vrb8ohphby0r...
+service s2... OK
+service s3... OK
+service s1... OK
+Verifying LB programming for containers on network ingress
+Verifying container Ingress...
+service web... OK
+````
+
+ssd checks the required iptables programming to direct an incoming packet with the <host ip>:<published port> to the right <backend ip>:<target port>
+
+### control-plane consistency check across nodes in a cluster
+
+Docker networking uses a gossip protocol to synchronize networking state across nodes in a cluster. ssd's `gossip-consistency` command verifies if the state maintained by all the nodes are consistent.
+
+````bash
+In a three node cluster with services running on an overlay network ov2 ssd consistency-checker shows
+
+vagrant@net-1:~/code/go/src/github.com/docker/docker-e2e/tests$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /var/run/docker/netns:/var/run/docker/netns --privileged sanimej/ssd ov2 gossip-consistency
+Node id: sjfp0ca8f43rvnab6v7f21gq0 gossip hash c57d89094dbb574a37930393278dc282
+
+Node id: bg228r3q9095grj4wxkqs80oe gossip hash c57d89094dbb574a37930393278dc282
+
+Node id: 6jylcraipcv2pxdricqe77j5q gossip hash c57d89094dbb574a37930393278dc282
+````
+
+This is hash digest of the control-plane state for the network `ov2` from all the cluster nodes. If the values have a mismatch `docker network inspect --verbose` on the individual nodes can help in identifying what the specific difference is.
--- /dev/null
+#!/usr/bin/python
+
+import sys, signal, time, os
+import docker
+import re
+import subprocess
+import json
+import hashlib
+
+ipv4match = re.compile(
+ r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]).' +
+ r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]).' +
+ r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]).' +
+ r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])'
+)
+
+def which(name, defaultPath=""):
+ if defaultPath and os.path.exists(defaultPath):
+ return defaultPath
+ for path in os.getenv("PATH").split(os.path.pathsep):
+ fullPath = path + os.sep + name
+ if os.path.exists(fullPath):
+ return fullPath
+
+def check_iptables(name, plist):
+ replace = (':', ',')
+ ports = []
+ for port in plist:
+ for r in replace:
+ port = port.replace(r, ' ')
+
+ p = port.split()
+ ports.append((p[1], p[3]))
+
+ # get the ingress sandbox's docker_gwbridge network IP.
+ # published ports get DNAT'ed to this IP.
+ ip = subprocess.check_output([ which("nsenter","/usr/bin/nsenter"), '--net=/var/run/docker/netns/ingress_sbox', which("bash", "/bin/bash"), '-c', 'ifconfig eth1 | grep \"inet\\ addr\" | cut -d: -f2 | cut -d\" \" -f1'])
+ ip = ip.rstrip()
+
+ for p in ports:
+ rule = which("iptables", "/sbin/iptables") + '-t nat -C DOCKER-INGRESS -p tcp --dport {0} -j DNAT --to {1}:{2}'.format(p[1], ip, p[1])
+ try:
+ subprocess.check_output([which("bash", "/bin/bash"), "-c", rule])
+ except subprocess.CalledProcessError as e:
+ print "Service {0}: host iptables DNAT rule for port {1} -> ingress sandbox {2}:{3} missing".format(name, p[1], ip, p[1])
+
+def get_namespaces(data, ingress=False):
+ if ingress is True:
+ return {"Ingress":"/var/run/docker/netns/ingress_sbox"}
+ else:
+ spaces =[]
+ for c in data["Containers"]:
+ sandboxes = {str(c) for c in data["Containers"]}
+
+ containers = {}
+ for s in sandboxes:
+ spaces.append(str(cli.inspect_container(s)["NetworkSettings"]["SandboxKey"]))
+ inspect = cli.inspect_container(s)
+ containers[str(inspect["Name"])] = str(inspect["NetworkSettings"]["SandboxKey"])
+ return containers
+
+
+def check_network(nw_name, ingress=False):
+
+ print "Verifying LB programming for containers on network %s" % nw_name
+
+ data = cli.inspect_network(nw_name, verbose=True)
+
+ if "Services" in data.keys():
+ services = data["Services"]
+ else:
+ print "Network %s has no services. Skipping check" % nw_name
+ return
+
+ fwmarks = {str(service): str(svalue["LocalLBIndex"]) for service, svalue in services.items()}
+
+ stasks = {}
+ for service, svalue in services.items():
+ if service == "":
+ continue
+ tasks = []
+ for task in svalue["Tasks"]:
+ tasks.append(str(task["EndpointIP"]))
+ stasks[fwmarks[str(service)]] = tasks
+
+ # for services in ingress network verify the iptables rules
+ # that direct ingress (published port) to backend (target port)
+ if ingress is True:
+ check_iptables(service, svalue["Ports"])
+
+ containers = get_namespaces(data, ingress)
+ for container, namespace in containers.items():
+ print "Verifying container %s..." % container
+ ipvs = subprocess.check_output([which("nsenter","/usr/bin/nsenter"), '--net=%s' % namespace, which("ipvsadm","/usr/sbin/ipvsadm"), '-ln'])
+
+ mark = ""
+ realmark = {}
+ for line in ipvs.splitlines():
+ if "FWM" in line:
+ mark = re.findall("[0-9]+", line)[0]
+ realmark[str(mark)] = []
+ elif "->" in line:
+ if mark == "":
+ continue
+ ip = ipv4match.search(line)
+ if ip is not None:
+ realmark[mark].append(format(ip.group(0)))
+ else:
+ mark = ""
+ for key in realmark.keys():
+ if key not in stasks:
+ print "LB Index %s" % key, "present in IPVS but missing in docker daemon"
+ del realmark[key]
+
+ for key in stasks.keys():
+ if key not in realmark:
+ print "LB Index %s" % key, "present in docker daemon but missing in IPVS"
+ del stasks[key]
+
+ for key in realmark:
+ service = "--Invalid--"
+ for sname, idx in fwmarks.items():
+ if key == idx:
+ service = sname
+ if len(set(realmark[key])) != len(set(stasks[key])):
+ print "Incorrect LB Programming for service %s" % service
+ print "control-plane backend tasks:"
+ for task in stasks[key]:
+ print task
+ print "kernel IPVS backend tasks:"
+ for task in realmark[key]:
+ print task
+ else:
+ print "service %s... OK" % service
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print 'Usage: ssd.py network-name [gossip-consistency]'
+ sys.exit()
+
+ cli = docker.APIClient(base_url='unix://var/run/docker.sock', version='auto')
+ if len(sys.argv) == 3:
+ command = sys.argv[2]
+ else:
+ command = 'default'
+
+ if command == 'gossip-consistency':
+ cspec = docker.types.ContainerSpec(
+ image='docker/ssd',
+ args=[sys.argv[1], 'gossip-hash'],
+ mounts=[docker.types.Mount('/var/run/docker.sock', '/var/run/docker.sock', type='bind')]
+ )
+ mode = docker.types.ServiceMode(
+ mode='global'
+ )
+ task_template = docker.types.TaskTemplate(cspec)
+
+ cli.create_service(task_template, name='gossip-hash', mode=mode)
+ #TODO change to a deterministic way to check if the service is up.
+ time.sleep(5)
+ output = cli.service_logs('gossip-hash', stdout=True, stderr=True, details=True)
+ for line in output:
+ print("Node id: %s gossip hash %s" % (line[line.find("=")+1:line.find(",")], line[line.find(" ")+1:]))
+ if cli.remove_service('gossip-hash') is not True:
+ print("Deleting gossip-hash service failed")
+ elif command == 'gossip-hash':
+ data = cli.inspect_network(sys.argv[1], verbose=True)
+ services = data["Services"]
+ md5 = hashlib.md5()
+ entries = []
+ for service, value in services.items():
+ entries.append(service)
+ entries.append(value["VIP"])
+ for task in value["Tasks"]:
+ for key, val in task.items():
+ if isinstance(val, dict):
+ for k, v in val.items():
+ entries.append(v)
+ else:
+ entries.append(val)
+ entries.sort()
+ for e in entries:
+ md5.update(e)
+ print(md5.hexdigest())
+ sys.stdout.flush()
+ while True:
+ signal.pause()
+ elif command == 'default':
+ if sys.argv[1] == "ingress":
+ check_network("ingress", ingress=True)
+ else:
+ check_network(sys.argv[1])
+ check_network("ingress", ingress=True)
--- /dev/null
+package config
+
+import (
+ "strings"
+
+ "github.com/BurntSushi/toml"
+ "github.com/docker/docker/pkg/discovery"
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/go-connections/tlsconfig"
+ "github.com/docker/libkv/store"
+ "github.com/docker/libnetwork/cluster"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/ipamutils"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/osl"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ warningThNetworkControlPlaneMTU = 1500
+ minimumNetworkControlPlaneMTU = 500
+)
+
+// Config encapsulates configurations of various Libnetwork components
+type Config struct {
+ Daemon DaemonCfg
+ Cluster ClusterCfg
+ Scopes map[string]*datastore.ScopeCfg
+ ActiveSandboxes map[string]interface{}
+ PluginGetter plugingetter.PluginGetter
+}
+
+// DaemonCfg represents libnetwork core configuration
+type DaemonCfg struct {
+ Debug bool
+ Experimental bool
+ DataDir string
+ DefaultNetwork string
+ DefaultDriver string
+ Labels []string
+ DriverCfg map[string]interface{}
+ ClusterProvider cluster.Provider
+ NetworkControlPlaneMTU int
+ DefaultAddressPool []*ipamutils.NetworkToSplit
+}
+
+// ClusterCfg represents cluster configuration
+type ClusterCfg struct {
+ Watcher discovery.Watcher
+ Address string
+ Discovery string
+ Heartbeat uint64
+}
+
+// LoadDefaultScopes loads default scope configs for scopes which
+// doesn't have explicit user specified configs.
+func (c *Config) LoadDefaultScopes(dataDir string) {
+ for k, v := range datastore.DefaultScopes(dataDir) {
+ if _, ok := c.Scopes[k]; !ok {
+ c.Scopes[k] = v
+ }
+ }
+}
+
+// ParseConfig parses the libnetwork configuration file
+func ParseConfig(tomlCfgFile string) (*Config, error) {
+ cfg := &Config{
+ Scopes: map[string]*datastore.ScopeCfg{},
+ }
+
+ if _, err := toml.DecodeFile(tomlCfgFile, cfg); err != nil {
+ return nil, err
+ }
+
+ cfg.LoadDefaultScopes(cfg.Daemon.DataDir)
+ return cfg, nil
+}
+
+// ParseConfigOptions parses the configuration options and returns
+// a reference to the corresponding Config structure
+func ParseConfigOptions(cfgOptions ...Option) *Config {
+ cfg := &Config{
+ Daemon: DaemonCfg{
+ DriverCfg: make(map[string]interface{}),
+ },
+ Scopes: make(map[string]*datastore.ScopeCfg),
+ }
+
+ cfg.ProcessOptions(cfgOptions...)
+ cfg.LoadDefaultScopes(cfg.Daemon.DataDir)
+
+ return cfg
+}
+
+// Option is an option setter function type used to pass various configurations
+// to the controller
+type Option func(c *Config)
+
+// OptionDefaultNetwork function returns an option setter for a default network
+func OptionDefaultNetwork(dn string) Option {
+ return func(c *Config) {
+ logrus.Debugf("Option DefaultNetwork: %s", dn)
+ c.Daemon.DefaultNetwork = strings.TrimSpace(dn)
+ }
+}
+
+// OptionDefaultDriver function returns an option setter for default driver
+func OptionDefaultDriver(dd string) Option {
+ return func(c *Config) {
+ logrus.Debugf("Option DefaultDriver: %s", dd)
+ c.Daemon.DefaultDriver = strings.TrimSpace(dd)
+ }
+}
+
+// OptionDefaultAddressPoolConfig function returns an option setter for default address pool
+func OptionDefaultAddressPoolConfig(addressPool []*ipamutils.NetworkToSplit) Option {
+ return func(c *Config) {
+ c.Daemon.DefaultAddressPool = addressPool
+ }
+}
+
+// OptionDriverConfig returns an option setter for driver configuration.
+func OptionDriverConfig(networkType string, config map[string]interface{}) Option {
+ return func(c *Config) {
+ c.Daemon.DriverCfg[networkType] = config
+ }
+}
+
+// OptionLabels function returns an option setter for labels
+func OptionLabels(labels []string) Option {
+ return func(c *Config) {
+ for _, label := range labels {
+ if strings.HasPrefix(label, netlabel.Prefix) {
+ c.Daemon.Labels = append(c.Daemon.Labels, label)
+ }
+ }
+ }
+}
+
+// OptionKVProvider function returns an option setter for kvstore provider
+func OptionKVProvider(provider string) Option {
+ return func(c *Config) {
+ logrus.Debugf("Option OptionKVProvider: %s", provider)
+ if _, ok := c.Scopes[datastore.GlobalScope]; !ok {
+ c.Scopes[datastore.GlobalScope] = &datastore.ScopeCfg{}
+ }
+ c.Scopes[datastore.GlobalScope].Client.Provider = strings.TrimSpace(provider)
+ }
+}
+
+// OptionKVProviderURL function returns an option setter for kvstore url
+func OptionKVProviderURL(url string) Option {
+ return func(c *Config) {
+ logrus.Debugf("Option OptionKVProviderURL: %s", url)
+ if _, ok := c.Scopes[datastore.GlobalScope]; !ok {
+ c.Scopes[datastore.GlobalScope] = &datastore.ScopeCfg{}
+ }
+ c.Scopes[datastore.GlobalScope].Client.Address = strings.TrimSpace(url)
+ }
+}
+
+// OptionKVOpts function returns an option setter for kvstore options
+func OptionKVOpts(opts map[string]string) Option {
+ return func(c *Config) {
+ if opts["kv.cacertfile"] != "" && opts["kv.certfile"] != "" && opts["kv.keyfile"] != "" {
+ logrus.Info("Option Initializing KV with TLS")
+ tlsConfig, err := tlsconfig.Client(tlsconfig.Options{
+ CAFile: opts["kv.cacertfile"],
+ CertFile: opts["kv.certfile"],
+ KeyFile: opts["kv.keyfile"],
+ })
+ if err != nil {
+ logrus.Errorf("Unable to set up TLS: %s", err)
+ return
+ }
+ if _, ok := c.Scopes[datastore.GlobalScope]; !ok {
+ c.Scopes[datastore.GlobalScope] = &datastore.ScopeCfg{}
+ }
+ if c.Scopes[datastore.GlobalScope].Client.Config == nil {
+ c.Scopes[datastore.GlobalScope].Client.Config = &store.Config{TLS: tlsConfig}
+ } else {
+ c.Scopes[datastore.GlobalScope].Client.Config.TLS = tlsConfig
+ }
+ // Workaround libkv/etcd bug for https
+ c.Scopes[datastore.GlobalScope].Client.Config.ClientTLS = &store.ClientTLSConfig{
+ CACertFile: opts["kv.cacertfile"],
+ CertFile: opts["kv.certfile"],
+ KeyFile: opts["kv.keyfile"],
+ }
+ } else {
+ logrus.Info("Option Initializing KV without TLS")
+ }
+ }
+}
+
+// OptionDiscoveryWatcher function returns an option setter for discovery watcher
+func OptionDiscoveryWatcher(watcher discovery.Watcher) Option {
+ return func(c *Config) {
+ c.Cluster.Watcher = watcher
+ }
+}
+
+// OptionDiscoveryAddress function returns an option setter for self discovery address
+func OptionDiscoveryAddress(address string) Option {
+ return func(c *Config) {
+ c.Cluster.Address = address
+ }
+}
+
+// OptionDataDir function returns an option setter for data folder
+func OptionDataDir(dataDir string) Option {
+ return func(c *Config) {
+ c.Daemon.DataDir = dataDir
+ }
+}
+
+// OptionExecRoot function returns an option setter for exec root folder
+func OptionExecRoot(execRoot string) Option {
+ return func(c *Config) {
+ osl.SetBasePath(execRoot)
+ }
+}
+
+// OptionPluginGetter returns a plugingetter for remote drivers.
+func OptionPluginGetter(pg plugingetter.PluginGetter) Option {
+ return func(c *Config) {
+ c.PluginGetter = pg
+ }
+}
+
+// OptionExperimental function returns an option setter for experimental daemon
+func OptionExperimental(exp bool) Option {
+ return func(c *Config) {
+ logrus.Debugf("Option Experimental: %v", exp)
+ c.Daemon.Experimental = exp
+ }
+}
+
+// OptionNetworkControlPlaneMTU function returns an option setter for control plane MTU
+func OptionNetworkControlPlaneMTU(exp int) Option {
+ return func(c *Config) {
+ logrus.Debugf("Network Control Plane MTU: %d", exp)
+ if exp < warningThNetworkControlPlaneMTU {
+ logrus.Warnf("Received a MTU of %d, this value is very low, the network control plane can misbehave,"+
+ " defaulting to minimum value (%d)", exp, minimumNetworkControlPlaneMTU)
+ if exp < minimumNetworkControlPlaneMTU {
+ exp = minimumNetworkControlPlaneMTU
+ }
+ }
+ c.Daemon.NetworkControlPlaneMTU = exp
+ }
+}
+
+// ProcessOptions processes options and stores it in config
+func (c *Config) ProcessOptions(options ...Option) {
+ for _, opt := range options {
+ if opt != nil {
+ opt(c)
+ }
+ }
+}
+
+// IsValidName validates configuration objects supported by libnetwork
+func IsValidName(name string) bool {
+ return strings.TrimSpace(name) != ""
+}
+
+// OptionLocalKVProvider function returns an option setter for kvstore provider
+func OptionLocalKVProvider(provider string) Option {
+ return func(c *Config) {
+ logrus.Debugf("Option OptionLocalKVProvider: %s", provider)
+ if _, ok := c.Scopes[datastore.LocalScope]; !ok {
+ c.Scopes[datastore.LocalScope] = &datastore.ScopeCfg{}
+ }
+ c.Scopes[datastore.LocalScope].Client.Provider = strings.TrimSpace(provider)
+ }
+}
+
+// OptionLocalKVProviderURL function returns an option setter for kvstore url
+func OptionLocalKVProviderURL(url string) Option {
+ return func(c *Config) {
+ logrus.Debugf("Option OptionLocalKVProviderURL: %s", url)
+ if _, ok := c.Scopes[datastore.LocalScope]; !ok {
+ c.Scopes[datastore.LocalScope] = &datastore.ScopeCfg{}
+ }
+ c.Scopes[datastore.LocalScope].Client.Address = strings.TrimSpace(url)
+ }
+}
+
+// OptionLocalKVProviderConfig function returns an option setter for kvstore config
+func OptionLocalKVProviderConfig(config *store.Config) Option {
+ return func(c *Config) {
+ logrus.Debugf("Option OptionLocalKVProviderConfig: %v", config)
+ if _, ok := c.Scopes[datastore.LocalScope]; !ok {
+ c.Scopes[datastore.LocalScope] = &datastore.ScopeCfg{}
+ }
+ c.Scopes[datastore.LocalScope].Client.Config = config
+ }
+}
+
+// OptionActiveSandboxes function returns an option setter for passing the sandboxes
+// which were active during previous daemon life
+func OptionActiveSandboxes(sandboxes map[string]interface{}) Option {
+ return func(c *Config) {
+ c.ActiveSandboxes = sandboxes
+ }
+}
--- /dev/null
+package config
+
+import (
+ "io/ioutil"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/netlabel"
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func TestInvalidConfig(t *testing.T) {
+ _, err := ParseConfig("invalid.toml")
+ if err == nil {
+ t.Fatal("Invalid Configuration file must fail")
+ }
+}
+
+func TestConfig(t *testing.T) {
+ _, err := ParseConfig("libnetwork.toml")
+ if err != nil {
+ t.Fatal("Error parsing a valid configuration file :", err)
+ }
+}
+
+func TestOptionsLabels(t *testing.T) {
+ c := &Config{}
+ l := []string{
+ "com.docker.network.key1=value1",
+ "com.docker.storage.key1=value1",
+ "com.docker.network.driver.key1=value1",
+ "com.docker.network.driver.key2=value2",
+ }
+ f := OptionLabels(l)
+ f(c)
+ if len(c.Daemon.Labels) != 3 {
+ t.Fatalf("Expecting 3 labels, seen %d", len(c.Daemon.Labels))
+ }
+ for _, l := range c.Daemon.Labels {
+ if !strings.HasPrefix(l, netlabel.Prefix) {
+ t.Fatalf("config must accept only libnetwork labels. Not : %s", l)
+ }
+ }
+}
+
+func TestValidName(t *testing.T) {
+ if !IsValidName("test") {
+ t.Fatal("Name validation fails for a name that must be accepted")
+ }
+ if IsValidName("") {
+ t.Fatal("Name validation succeeds for a case when it is expected to fail")
+ }
+ if IsValidName(" ") {
+ t.Fatal("Name validation succeeds for a case when it is expected to fail")
+ }
+}
+
+func TestTLSConfiguration(t *testing.T) {
+ cert := `-----BEGIN CERTIFICATE-----
+MIIDCDCCAfKgAwIBAgIICifG7YeiQOEwCwYJKoZIhvcNAQELMBIxEDAOBgNVBAMT
+B1Rlc3QgQ0EwHhcNMTUxMDAxMjMwMDAwWhcNMjAwOTI5MjMwMDAwWjASMRAwDgYD
+VQQDEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wRC
+O+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4+zE9h80aC4hz+6caRpds
++J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhRSoSi3nY+B7F2E8cuz14q
+V2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZrpXUyXxAvzXfpFXo1RhSb
+UywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUerVYrCPq8vqfn//01qz55
+Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHojxOpXTBepUCIJLbtNnWFT
+V44t9gh5IqIWtoBReQIDAQABo2YwZDAOBgNVHQ8BAf8EBAMCAAYwEgYDVR0TAQH/
+BAgwBgEB/wIBAjAdBgNVHQ4EFgQUZKUI8IIjIww7X/6hvwggQK4bD24wHwYDVR0j
+BBgwFoAUZKUI8IIjIww7X/6hvwggQK4bD24wCwYJKoZIhvcNAQELA4IBAQDES2cz
+7sCQfDCxCIWH7X8kpi/JWExzUyQEJ0rBzN1m3/x8ySRxtXyGekimBqQwQdFqlwMI
+xzAQKkh3ue8tNSzRbwqMSyH14N1KrSxYS9e9szJHfUasoTpQGPmDmGIoRJuq1h6M
+ej5x1SCJ7GWCR6xEXKUIE9OftXm9TdFzWa7Ja3OHz/mXteii8VXDuZ5ACq6EE5bY
+8sP4gcICfJ5fTrpTlk9FIqEWWQrCGa5wk95PGEj+GJpNogjXQ97wVoo/Y3p1brEn
+t5zjN9PAq4H1fuCMdNNA+p1DHNwd+ELTxcMAnb2ajwHvV6lKPXutrTFc4umJToBX
+FpTxDmJHEV4bzUzh
+-----END CERTIFICATE-----
+`
+ key := `-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA1wRCO+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4
++zE9h80aC4hz+6caRpds+J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhR
+SoSi3nY+B7F2E8cuz14qV2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZr
+pXUyXxAvzXfpFXo1RhSbUywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUe
+rVYrCPq8vqfn//01qz55Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHoj
+xOpXTBepUCIJLbtNnWFTV44t9gh5IqIWtoBReQIDAQABAoIBAHSWipORGp/uKFXj
+i/mut776x8ofsAxhnLBARQr93ID+i49W8H7EJGkOfaDjTICYC1dbpGrri61qk8sx
+qX7p3v/5NzKwOIfEpirgwVIqSNYe/ncbxnhxkx6tXtUtFKmEx40JskvSpSYAhmmO
+1XSx0E/PWaEN/nLgX/f1eWJIlxlQkk3QeqL+FGbCXI48DEtlJ9+MzMu4pAwZTpj5
+5qtXo5JJ0jRGfJVPAOznRsYqv864AhMdMIWguzk6EGnbaCWwPcfcn+h9a5LMdony
+MDHfBS7bb5tkF3+AfnVY3IBMVx7YlsD9eAyajlgiKu4zLbwTRHjXgShy+4Oussz0
+ugNGnkECgYEA/hi+McrZC8C4gg6XqK8+9joD8tnyDZDz88BQB7CZqABUSwvjDqlP
+L8hcwo/lzvjBNYGkqaFPUICGWKjeCtd8pPS2DCVXxDQX4aHF1vUur0uYNncJiV3N
+XQz4Iemsa6wnKf6M67b5vMXICw7dw0HZCdIHD1hnhdtDz0uVpeevLZ8CgYEA2KCT
+Y43lorjrbCgMqtlefkr3GJA9dey+hTzCiWEOOqn9RqGoEGUday0sKhiLofOgmN2B
+LEukpKIey8s+Q/cb6lReajDVPDsMweX8i7hz3Wa4Ugp4Xa5BpHqu8qIAE2JUZ7bU
+t88aQAYE58pUF+/Lq1QzAQdrjjzQBx6SrBxieecCgYEAvukoPZEC8mmiN1VvbTX+
+QFHmlZha3QaDxChB+QUe7bMRojEUL/fVnzkTOLuVFqSfxevaI/km9n0ac5KtAchV
+xjp2bTnBb5EUQFqjopYktWA+xO07JRJtMfSEmjZPbbay1kKC7rdTfBm961EIHaRj
+xZUf6M+rOE8964oGrdgdLlECgYEA046GQmx6fh7/82FtdZDRQp9tj3SWQUtSiQZc
+qhO59Lq8mjUXz+MgBuJXxkiwXRpzlbaFB0Bca1fUoYw8o915SrDYf/Zu2OKGQ/qa
+V81sgiVmDuEgycR7YOlbX6OsVUHrUlpwhY3hgfMe6UtkMvhBvHF/WhroBEIJm1pV
+PXZ/CbMCgYEApNWVktFBjOaYfY6SNn4iSts1jgsQbbpglg3kT7PLKjCAhI6lNsbk
+dyT7ut01PL6RaW4SeQWtrJIVQaM6vF3pprMKqlc5XihOGAmVqH7rQx9rtQB5TicL
+BFrwkQE4HQtQBV60hYQUzzlSk44VFDz+jxIEtacRHaomDRh2FtOTz+I=
+-----END RSA PRIVATE KEY-----
+`
+ certFile, err := ioutil.TempFile("", "cert")
+ if err != nil {
+ t.Fatalf("Failed to setup temp file: %s", err)
+ }
+ defer os.Remove(certFile.Name())
+ certFile.Write([]byte(cert))
+ certFile.Close()
+ keyFile, err := ioutil.TempFile("", "key")
+ if err != nil {
+ t.Fatalf("Failed to setup temp file: %s", err)
+ }
+ defer os.Remove(keyFile.Name())
+ keyFile.Write([]byte(key))
+ keyFile.Close()
+
+ c := &Config{Scopes: map[string]*datastore.ScopeCfg{}}
+ l := map[string]string{
+ "kv.cacertfile": certFile.Name(),
+ "kv.certfile": certFile.Name(),
+ "kv.keyfile": keyFile.Name(),
+ }
+ f := OptionKVOpts(l)
+ f(c)
+ if _, ok := c.Scopes[datastore.GlobalScope]; !ok {
+ t.Fatal("GlobalScope not established")
+ }
+
+ if c.Scopes[datastore.GlobalScope].Client.Config.TLS == nil {
+ t.Fatal("TLS is nil")
+ }
+ if c.Scopes[datastore.GlobalScope].Client.Config.TLS.RootCAs == nil {
+ t.Fatal("TLS.RootCAs is nil")
+ }
+ if len(c.Scopes[datastore.GlobalScope].Client.Config.TLS.Certificates) != 1 {
+ t.Fatal("TLS.Certificates is not length 1")
+ }
+}
--- /dev/null
+title = "LibNetwork Configuration file"
+
+[daemon]
+ debug = false
+[cluster]
+ discovery = "token://swarm-discovery-token"
+ Address = "Cluster-wide reachable Host IP"
+[datastore]
+ embedded = false
+[datastore.client]
+ provider = "consul"
+ Address = "localhost:8500"
--- /dev/null
+/*
+Package libnetwork provides the basic functionality and extension points to
+create network namespaces and allocate interfaces for containers to use.
+
+ networkType := "bridge"
+
+ // Create a new controller instance
+ driverOptions := options.Generic{}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = driverOptions
+ controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption))
+ if err != nil {
+ return
+ }
+
+ // Create a network for containers to join.
+ // NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can make use of
+ network, err := controller.NewNetwork(networkType, "network1", "")
+ if err != nil {
+ return
+ }
+
+ // For each new container: allocate IP and interfaces. The returned network
+ // settings will be used for container infos (inspect and such), as well as
+ // iptables rules for port publishing. This info is contained or accessible
+ // from the returned endpoint.
+ ep, err := network.CreateEndpoint("Endpoint1")
+ if err != nil {
+ return
+ }
+
+ // Create the sandbox for the container.
+ // NewSandbox accepts Variadic optional arguments which libnetwork can use.
+ sbx, err := controller.NewSandbox("container1",
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"))
+
+ // A sandbox can join the endpoint via the join api.
+ err = ep.Join(sbx)
+ if err != nil {
+ return
+ }
+*/
+package libnetwork
+
+import (
+ "fmt"
+ "net"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/docker/docker/pkg/discovery"
+ "github.com/docker/docker/pkg/locker"
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/docker/pkg/plugins"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/libnetwork/cluster"
+ "github.com/docker/libnetwork/config"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/diagnostic"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/drvregistry"
+ "github.com/docker/libnetwork/hostdiscovery"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// NetworkController provides the interface for controller instance which manages
+// networks.
+type NetworkController interface {
+ // ID provides a unique identity for the controller
+ ID() string
+
+ // BuiltinDrivers returns list of builtin drivers
+ BuiltinDrivers() []string
+
+ // BuiltinIPAMDrivers returns list of builtin ipam drivers
+ BuiltinIPAMDrivers() []string
+
+ // Config method returns the bootup configuration for the controller
+ Config() config.Config
+
+ // Create a new network. The options parameter carries network specific options.
+ NewNetwork(networkType, name string, id string, options ...NetworkOption) (Network, error)
+
+ // Networks returns the list of Network(s) managed by this controller.
+ Networks() []Network
+
+ // WalkNetworks uses the provided function to walk the Network(s) managed by this controller.
+ WalkNetworks(walker NetworkWalker)
+
+ // NetworkByName returns the Network which has the passed name. If not found, the error ErrNoSuchNetwork is returned.
+ NetworkByName(name string) (Network, error)
+
+ // NetworkByID returns the Network which has the passed id. If not found, the error ErrNoSuchNetwork is returned.
+ NetworkByID(id string) (Network, error)
+
+ // NewSandbox creates a new network sandbox for the passed container id
+ NewSandbox(containerID string, options ...SandboxOption) (Sandbox, error)
+
+ // Sandboxes returns the list of Sandbox(s) managed by this controller.
+ Sandboxes() []Sandbox
+
+ // WalkSandboxes uses the provided function to walk the Sandbox(s) managed by this controller.
+ WalkSandboxes(walker SandboxWalker)
+
+ // SandboxByID returns the Sandbox which has the passed id. If not found, a types.NotFoundError is returned.
+ SandboxByID(id string) (Sandbox, error)
+
+ // SandboxDestroy destroys a sandbox given a container ID
+ SandboxDestroy(id string) error
+
+ // Stop network controller
+ Stop()
+
+ // ReloadConfiguration updates the controller configuration
+ ReloadConfiguration(cfgOptions ...config.Option) error
+
+ // SetClusterProvider sets cluster provider
+ SetClusterProvider(provider cluster.Provider)
+
+ // Wait for agent initialization complete in libnetwork controller
+ AgentInitWait()
+
+ // Wait for agent to stop if running
+ AgentStopWait()
+
+ // SetKeys configures the encryption key for gossip and overlay data path
+ SetKeys(keys []*types.EncryptionKey) error
+
+ // StartDiagnostic start the network diagnostic mode
+ StartDiagnostic(port int)
+ // StopDiagnostic start the network diagnostic mode
+ StopDiagnostic()
+ // IsDiagnosticEnabled returns true if the diagnostic is enabled
+ IsDiagnosticEnabled() bool
+}
+
+// NetworkWalker is a client provided function which will be used to walk the Networks.
+// When the function returns true, the walk will stop.
+type NetworkWalker func(nw Network) bool
+
+// SandboxWalker is a client provided function which will be used to walk the Sandboxes.
+// When the function returns true, the walk will stop.
+type SandboxWalker func(sb Sandbox) bool
+
+type sandboxTable map[string]*sandbox
+
+type controller struct {
+ id string
+ drvRegistry *drvregistry.DrvRegistry
+ sandboxes sandboxTable
+ cfg *config.Config
+ stores []datastore.DataStore
+ discovery hostdiscovery.HostDiscovery
+ extKeyListener net.Listener
+ watchCh chan *endpoint
+ unWatchCh chan *endpoint
+ svcRecords map[string]svcInfo
+ nmap map[string]*netWatch
+ serviceBindings map[serviceKey]*service
+ defOsSbox osl.Sandbox
+ ingressSandbox *sandbox
+ sboxOnce sync.Once
+ agent *agent
+ networkLocker *locker.Locker
+ agentInitDone chan struct{}
+ agentStopDone chan struct{}
+ keys []*types.EncryptionKey
+ clusterConfigAvailable bool
+ DiagnosticServer *diagnostic.Server
+ sync.Mutex
+}
+
+type initializer struct {
+ fn drvregistry.InitFunc
+ ntype string
+}
+
+// New creates a new instance of network controller.
+func New(cfgOptions ...config.Option) (NetworkController, error) {
+ c := &controller{
+ id: stringid.GenerateRandomID(),
+ cfg: config.ParseConfigOptions(cfgOptions...),
+ sandboxes: sandboxTable{},
+ svcRecords: make(map[string]svcInfo),
+ serviceBindings: make(map[serviceKey]*service),
+ agentInitDone: make(chan struct{}),
+ networkLocker: locker.New(),
+ DiagnosticServer: diagnostic.New(),
+ }
+ c.DiagnosticServer.Init()
+
+ if err := c.initStores(); err != nil {
+ return nil, err
+ }
+
+ drvRegistry, err := drvregistry.New(c.getStore(datastore.LocalScope), c.getStore(datastore.GlobalScope), c.RegisterDriver, nil, c.cfg.PluginGetter)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, i := range getInitializers(c.cfg.Daemon.Experimental) {
+ var dcfg map[string]interface{}
+
+ // External plugins don't need config passed through daemon. They can
+ // bootstrap themselves
+ if i.ntype != "remote" {
+ dcfg = c.makeDriverConfig(i.ntype)
+ }
+
+ if err := drvRegistry.AddDriver(i.ntype, i.fn, dcfg); err != nil {
+ return nil, err
+ }
+ }
+
+ if err = initIPAMDrivers(drvRegistry, nil, c.getStore(datastore.GlobalScope), c.cfg.Daemon.DefaultAddressPool); err != nil {
+ return nil, err
+ }
+
+ c.drvRegistry = drvRegistry
+
+ if c.cfg != nil && c.cfg.Cluster.Watcher != nil {
+ if err := c.initDiscovery(c.cfg.Cluster.Watcher); err != nil {
+ // Failing to initialize discovery is a bad situation to be in.
+ // But it cannot fail creating the Controller
+ logrus.Errorf("Failed to Initialize Discovery : %v", err)
+ }
+ }
+
+ c.WalkNetworks(populateSpecial)
+
+ // Reserve pools first before doing cleanup. Otherwise the
+ // cleanups of endpoint/network and sandbox below will
+ // generate many unnecessary warnings
+ c.reservePools()
+
+ // Cleanup resources
+ c.sandboxCleanup(c.cfg.ActiveSandboxes)
+ c.cleanupLocalEndpoints()
+ c.networkCleanup()
+
+ if err := c.startExternalKeyListener(); err != nil {
+ return nil, err
+ }
+
+ return c, nil
+}
+
+func (c *controller) SetClusterProvider(provider cluster.Provider) {
+ var sameProvider bool
+ c.Lock()
+ // Avoids to spawn multiple goroutine for the same cluster provider
+ if c.cfg.Daemon.ClusterProvider == provider {
+ // If the cluster provider is already set, there is already a go routine spawned
+ // that is listening for events, so nothing to do here
+ sameProvider = true
+ } else {
+ c.cfg.Daemon.ClusterProvider = provider
+ }
+ c.Unlock()
+
+ if provider == nil || sameProvider {
+ return
+ }
+ // We don't want to spawn a new go routine if the previous one did not exit yet
+ c.AgentStopWait()
+ go c.clusterAgentInit()
+}
+
+func isValidClusteringIP(addr string) bool {
+ return addr != "" && !net.ParseIP(addr).IsLoopback() && !net.ParseIP(addr).IsUnspecified()
+}
+
+// libnetwork side of agent depends on the keys. On the first receipt of
+// keys setup the agent. For subsequent key set handle the key change
+func (c *controller) SetKeys(keys []*types.EncryptionKey) error {
+ subsysKeys := make(map[string]int)
+ for _, key := range keys {
+ if key.Subsystem != subsysGossip &&
+ key.Subsystem != subsysIPSec {
+ return fmt.Errorf("key received for unrecognized subsystem")
+ }
+ subsysKeys[key.Subsystem]++
+ }
+ for s, count := range subsysKeys {
+ if count != keyringSize {
+ return fmt.Errorf("incorrect number of keys for subsystem %v", s)
+ }
+ }
+
+ agent := c.getAgent()
+
+ if agent == nil {
+ c.Lock()
+ c.keys = keys
+ c.Unlock()
+ return nil
+ }
+ return c.handleKeyChange(keys)
+}
+
+func (c *controller) getAgent() *agent {
+ c.Lock()
+ defer c.Unlock()
+ return c.agent
+}
+
+func (c *controller) clusterAgentInit() {
+ clusterProvider := c.cfg.Daemon.ClusterProvider
+ var keysAvailable bool
+ for {
+ eventType := <-clusterProvider.ListenClusterEvents()
+ // The events: EventSocketChange, EventNodeReady and EventNetworkKeysAvailable are not ordered
+ // when all the condition for the agent initialization are met then proceed with it
+ switch eventType {
+ case cluster.EventNetworkKeysAvailable:
+ // Validates that the keys are actually available before starting the initialization
+ // This will handle old spurious messages left on the channel
+ c.Lock()
+ keysAvailable = c.keys != nil
+ c.Unlock()
+ fallthrough
+ case cluster.EventSocketChange, cluster.EventNodeReady:
+ if keysAvailable && !c.isDistributedControl() {
+ c.agentOperationStart()
+ if err := c.agentSetup(clusterProvider); err != nil {
+ c.agentStopComplete()
+ } else {
+ c.agentInitComplete()
+ }
+ }
+ case cluster.EventNodeLeave:
+ c.agentOperationStart()
+ c.Lock()
+ c.keys = nil
+ c.Unlock()
+
+ // We are leaving the cluster. Make sure we
+ // close the gossip so that we stop all
+ // incoming gossip updates before cleaning up
+ // any remaining service bindings. But before
+ // deleting the networks since the networks
+ // should still be present when cleaning up
+ // service bindings
+ c.agentClose()
+ c.cleanupServiceDiscovery("")
+ c.cleanupServiceBindings("")
+
+ c.agentStopComplete()
+
+ return
+ }
+ }
+}
+
+// AgentInitWait waits for agent initialization to be completed in the controller.
+func (c *controller) AgentInitWait() {
+ c.Lock()
+ agentInitDone := c.agentInitDone
+ c.Unlock()
+
+ if agentInitDone != nil {
+ <-agentInitDone
+ }
+}
+
+// AgentStopWait waits for the Agent stop to be completed in the controller
+func (c *controller) AgentStopWait() {
+ c.Lock()
+ agentStopDone := c.agentStopDone
+ c.Unlock()
+ if agentStopDone != nil {
+ <-agentStopDone
+ }
+}
+
+// agentOperationStart marks the start of an Agent Init or Agent Stop
+func (c *controller) agentOperationStart() {
+ c.Lock()
+ if c.agentInitDone == nil {
+ c.agentInitDone = make(chan struct{})
+ }
+ if c.agentStopDone == nil {
+ c.agentStopDone = make(chan struct{})
+ }
+ c.Unlock()
+}
+
+// agentInitComplete notifies the successful completion of the Agent initialization
+func (c *controller) agentInitComplete() {
+ c.Lock()
+ if c.agentInitDone != nil {
+ close(c.agentInitDone)
+ c.agentInitDone = nil
+ }
+ c.Unlock()
+}
+
+// agentStopComplete notifies the successful completion of the Agent stop
+func (c *controller) agentStopComplete() {
+ c.Lock()
+ if c.agentStopDone != nil {
+ close(c.agentStopDone)
+ c.agentStopDone = nil
+ }
+ c.Unlock()
+}
+
+func (c *controller) makeDriverConfig(ntype string) map[string]interface{} {
+ if c.cfg == nil {
+ return nil
+ }
+
+ config := make(map[string]interface{})
+
+ for _, label := range c.cfg.Daemon.Labels {
+ if !strings.HasPrefix(netlabel.Key(label), netlabel.DriverPrefix+"."+ntype) {
+ continue
+ }
+
+ config[netlabel.Key(label)] = netlabel.Value(label)
+ }
+
+ drvCfg, ok := c.cfg.Daemon.DriverCfg[ntype]
+ if ok {
+ for k, v := range drvCfg.(map[string]interface{}) {
+ config[k] = v
+ }
+ }
+
+ for k, v := range c.cfg.Scopes {
+ if !v.IsValid() {
+ continue
+ }
+ config[netlabel.MakeKVClient(k)] = discoverapi.DatastoreConfigData{
+ Scope: k,
+ Provider: v.Client.Provider,
+ Address: v.Client.Address,
+ Config: v.Client.Config,
+ }
+ }
+
+ return config
+}
+
+var procReloadConfig = make(chan (bool), 1)
+
+func (c *controller) ReloadConfiguration(cfgOptions ...config.Option) error {
+ procReloadConfig <- true
+ defer func() { <-procReloadConfig }()
+
+ // For now we accept the configuration reload only as a mean to provide a global store config after boot.
+ // Refuse the configuration if it alters an existing datastore client configuration.
+ update := false
+ cfg := config.ParseConfigOptions(cfgOptions...)
+
+ for s := range c.cfg.Scopes {
+ if _, ok := cfg.Scopes[s]; !ok {
+ return types.ForbiddenErrorf("cannot accept new configuration because it removes an existing datastore client")
+ }
+ }
+ for s, nSCfg := range cfg.Scopes {
+ if eSCfg, ok := c.cfg.Scopes[s]; ok {
+ if eSCfg.Client.Provider != nSCfg.Client.Provider ||
+ eSCfg.Client.Address != nSCfg.Client.Address {
+ return types.ForbiddenErrorf("cannot accept new configuration because it modifies an existing datastore client")
+ }
+ } else {
+ if err := c.initScopedStore(s, nSCfg); err != nil {
+ return err
+ }
+ update = true
+ }
+ }
+ if !update {
+ return nil
+ }
+
+ c.Lock()
+ c.cfg = cfg
+ c.Unlock()
+
+ var dsConfig *discoverapi.DatastoreConfigData
+ for scope, sCfg := range cfg.Scopes {
+ if scope == datastore.LocalScope || !sCfg.IsValid() {
+ continue
+ }
+ dsConfig = &discoverapi.DatastoreConfigData{
+ Scope: scope,
+ Provider: sCfg.Client.Provider,
+ Address: sCfg.Client.Address,
+ Config: sCfg.Client.Config,
+ }
+ break
+ }
+ if dsConfig == nil {
+ return nil
+ }
+
+ c.drvRegistry.WalkIPAMs(func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool {
+ err := driver.DiscoverNew(discoverapi.DatastoreConfig, *dsConfig)
+ if err != nil {
+ logrus.Errorf("Failed to set datastore in driver %s: %v", name, err)
+ }
+ return false
+ })
+
+ c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
+ err := driver.DiscoverNew(discoverapi.DatastoreConfig, *dsConfig)
+ if err != nil {
+ logrus.Errorf("Failed to set datastore in driver %s: %v", name, err)
+ }
+ return false
+ })
+
+ if c.discovery == nil && c.cfg.Cluster.Watcher != nil {
+ if err := c.initDiscovery(c.cfg.Cluster.Watcher); err != nil {
+ logrus.Errorf("Failed to Initialize Discovery after configuration update: %v", err)
+ }
+ }
+
+ return nil
+}
+
+func (c *controller) ID() string {
+ return c.id
+}
+
+func (c *controller) BuiltinDrivers() []string {
+ drivers := []string{}
+ c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
+ if driver.IsBuiltIn() {
+ drivers = append(drivers, name)
+ }
+ return false
+ })
+ return drivers
+}
+
+func (c *controller) BuiltinIPAMDrivers() []string {
+ drivers := []string{}
+ c.drvRegistry.WalkIPAMs(func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool {
+ if driver.IsBuiltIn() {
+ drivers = append(drivers, name)
+ }
+ return false
+ })
+ return drivers
+}
+
+func (c *controller) validateHostDiscoveryConfig() bool {
+ if c.cfg == nil || c.cfg.Cluster.Discovery == "" || c.cfg.Cluster.Address == "" {
+ return false
+ }
+ return true
+}
+
+func (c *controller) clusterHostID() string {
+ c.Lock()
+ defer c.Unlock()
+ if c.cfg == nil || c.cfg.Cluster.Address == "" {
+ return ""
+ }
+ addr := strings.Split(c.cfg.Cluster.Address, ":")
+ return addr[0]
+}
+
+func (c *controller) isNodeAlive(node string) bool {
+ if c.discovery == nil {
+ return false
+ }
+
+ nodes := c.discovery.Fetch()
+ for _, n := range nodes {
+ if n.String() == node {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (c *controller) initDiscovery(watcher discovery.Watcher) error {
+ if c.cfg == nil {
+ return fmt.Errorf("discovery initialization requires a valid configuration")
+ }
+
+ c.discovery = hostdiscovery.NewHostDiscovery(watcher)
+ return c.discovery.Watch(c.activeCallback, c.hostJoinCallback, c.hostLeaveCallback)
+}
+
+func (c *controller) activeCallback() {
+ ds := c.getStore(datastore.GlobalScope)
+ if ds != nil && !ds.Active() {
+ ds.RestartWatch()
+ }
+}
+
+func (c *controller) hostJoinCallback(nodes []net.IP) {
+ c.processNodeDiscovery(nodes, true)
+}
+
+func (c *controller) hostLeaveCallback(nodes []net.IP) {
+ c.processNodeDiscovery(nodes, false)
+}
+
+func (c *controller) processNodeDiscovery(nodes []net.IP, add bool) {
+ c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
+ c.pushNodeDiscovery(driver, capability, nodes, add)
+ return false
+ })
+}
+
+func (c *controller) pushNodeDiscovery(d driverapi.Driver, cap driverapi.Capability, nodes []net.IP, add bool) {
+ var self net.IP
+ if c.cfg != nil {
+ addr := strings.Split(c.cfg.Cluster.Address, ":")
+ self = net.ParseIP(addr[0])
+ // if external kvstore is not configured, try swarm-mode config
+ if self == nil {
+ if agent := c.getAgent(); agent != nil {
+ self = net.ParseIP(agent.advertiseAddr)
+ }
+ }
+ }
+
+ if d == nil || cap.ConnectivityScope != datastore.GlobalScope || nodes == nil {
+ return
+ }
+
+ for _, node := range nodes {
+ nodeData := discoverapi.NodeDiscoveryData{Address: node.String(), Self: node.Equal(self)}
+ var err error
+ if add {
+ err = d.DiscoverNew(discoverapi.NodeDiscovery, nodeData)
+ } else {
+ err = d.DiscoverDelete(discoverapi.NodeDiscovery, nodeData)
+ }
+ if err != nil {
+ logrus.Debugf("discovery notification error: %v", err)
+ }
+ }
+}
+
+func (c *controller) Config() config.Config {
+ c.Lock()
+ defer c.Unlock()
+ if c.cfg == nil {
+ return config.Config{}
+ }
+ return *c.cfg
+}
+
+func (c *controller) isManager() bool {
+ c.Lock()
+ defer c.Unlock()
+ if c.cfg == nil || c.cfg.Daemon.ClusterProvider == nil {
+ return false
+ }
+ return c.cfg.Daemon.ClusterProvider.IsManager()
+}
+
+func (c *controller) isAgent() bool {
+ c.Lock()
+ defer c.Unlock()
+ if c.cfg == nil || c.cfg.Daemon.ClusterProvider == nil {
+ return false
+ }
+ return c.cfg.Daemon.ClusterProvider.IsAgent()
+}
+
+func (c *controller) isDistributedControl() bool {
+ return !c.isManager() && !c.isAgent()
+}
+
+func (c *controller) GetPluginGetter() plugingetter.PluginGetter {
+ return c.drvRegistry.GetPluginGetter()
+}
+
+func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver, capability driverapi.Capability) error {
+ c.Lock()
+ hd := c.discovery
+ c.Unlock()
+
+ if hd != nil {
+ c.pushNodeDiscovery(driver, capability, hd.Fetch(), true)
+ }
+
+ c.agentDriverNotify(driver)
+ return nil
+}
+
+// XXX This should be made driver agnostic. See comment below.
+const overlayDSROptionString = "dsr"
+
+// NewNetwork creates a new network of the specified network type. The options
+// are network specific and modeled in a generic way.
+func (c *controller) NewNetwork(networkType, name string, id string, options ...NetworkOption) (Network, error) {
+ var (
+ cap *driverapi.Capability
+ err error
+ t *network
+ )
+
+ if id != "" {
+ c.networkLocker.Lock(id)
+ defer c.networkLocker.Unlock(id)
+
+ if _, err = c.NetworkByID(id); err == nil {
+ return nil, NetworkNameError(id)
+ }
+ }
+
+ if !config.IsValidName(name) {
+ return nil, ErrInvalidName(name)
+ }
+
+ if id == "" {
+ id = stringid.GenerateRandomID()
+ }
+
+ defaultIpam := defaultIpamForNetworkType(networkType)
+ // Construct the network object
+ network := &network{
+ name: name,
+ networkType: networkType,
+ generic: map[string]interface{}{netlabel.GenericData: make(map[string]string)},
+ ipamType: defaultIpam,
+ id: id,
+ created: time.Now(),
+ ctrlr: c,
+ persist: true,
+ drvOnce: &sync.Once{},
+ loadBalancerMode: loadBalancerModeDefault,
+ }
+
+ network.processOptions(options...)
+ if err = network.validateConfiguration(); err != nil {
+ return nil, err
+ }
+
+ // Reset network types, force local scope and skip allocation and
+ // plumbing for configuration networks. Reset of the config-only
+ // network drivers is needed so that this special network is not
+ // usable by old engine versions.
+ if network.configOnly {
+ network.scope = datastore.LocalScope
+ network.networkType = "null"
+ goto addToStore
+ }
+
+ _, cap, err = network.resolveDriver(network.networkType, true)
+ if err != nil {
+ return nil, err
+ }
+
+ if network.scope == datastore.LocalScope && cap.DataScope == datastore.GlobalScope {
+ return nil, types.ForbiddenErrorf("cannot downgrade network scope for %s networks", networkType)
+
+ }
+ if network.ingress && cap.DataScope != datastore.GlobalScope {
+ return nil, types.ForbiddenErrorf("Ingress network can only be global scope network")
+ }
+
+ // At this point the network scope is still unknown if not set by user
+ if (cap.DataScope == datastore.GlobalScope || network.scope == datastore.SwarmScope) &&
+ !c.isDistributedControl() && !network.dynamic {
+ if c.isManager() {
+ // For non-distributed controlled environment, globalscoped non-dynamic networks are redirected to Manager
+ return nil, ManagerRedirectError(name)
+ }
+ return nil, types.ForbiddenErrorf("Cannot create a multi-host network from a worker node. Please create the network from a manager node.")
+ }
+
+ if network.scope == datastore.SwarmScope && c.isDistributedControl() {
+ return nil, types.ForbiddenErrorf("cannot create a swarm scoped network when swarm is not active")
+ }
+
+ // Make sure we have a driver available for this network type
+ // before we allocate anything.
+ if _, err := network.driver(true); err != nil {
+ return nil, err
+ }
+
+ // From this point on, we need the network specific configuration,
+ // which may come from a configuration-only network
+ if network.configFrom != "" {
+ t, err = c.getConfigNetwork(network.configFrom)
+ if err != nil {
+ return nil, types.NotFoundErrorf("configuration network %q does not exist", network.configFrom)
+ }
+ if err = t.applyConfigurationTo(network); err != nil {
+ return nil, types.InternalErrorf("Failed to apply configuration: %v", err)
+ }
+ defer func() {
+ if err == nil {
+ if err := t.getEpCnt().IncEndpointCnt(); err != nil {
+ logrus.Warnf("Failed to update reference count for configuration network %q on creation of network %q: %v",
+ t.Name(), network.Name(), err)
+ }
+ }
+ }()
+ }
+
+ err = network.ipamAllocate()
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err != nil {
+ network.ipamRelease()
+ }
+ }()
+
+ err = c.addNetwork(network)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err != nil {
+ if e := network.deleteNetwork(); e != nil {
+ logrus.Warnf("couldn't roll back driver network on network %s creation failure: %v", network.name, err)
+ }
+ }
+ }()
+
+ // XXX If the driver type is "overlay" check the options for DSR
+ // being set. If so, set the network's load balancing mode to DSR.
+ // This should really be done in a network option, but due to
+ // time pressure to get this in without adding changes to moby,
+ // swarm and CLI, it is being implemented as a driver-specific
+ // option. Unfortunately, drivers can't influence the core
+ // "libnetwork.network" data type. Hence we need this hack code
+ // to implement in this manner.
+ if gval, ok := network.generic[netlabel.GenericData]; ok && network.networkType == "overlay" {
+ optMap := gval.(map[string]string)
+ if _, ok := optMap[overlayDSROptionString]; ok {
+ network.loadBalancerMode = loadBalancerModeDSR
+ }
+ }
+
+addToStore:
+ // First store the endpoint count, then the network. To avoid to
+ // end up with a datastore containing a network and not an epCnt,
+ // in case of an ungraceful shutdown during this function call.
+ epCnt := &endpointCnt{n: network}
+ if err = c.updateToStore(epCnt); err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err != nil {
+ if e := c.deleteFromStore(epCnt); e != nil {
+ logrus.Warnf("could not rollback from store, epCnt %v on failure (%v): %v", epCnt, err, e)
+ }
+ }
+ }()
+
+ network.epCnt = epCnt
+ if err = c.updateToStore(network); err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err != nil {
+ if e := c.deleteFromStore(network); e != nil {
+ logrus.Warnf("could not rollback from store, network %v on failure (%v): %v", network, err, e)
+ }
+ }
+ }()
+
+ if network.configOnly {
+ return network, nil
+ }
+
+ joinCluster(network)
+ defer func() {
+ if err != nil {
+ network.cancelDriverWatches()
+ if e := network.leaveCluster(); e != nil {
+ logrus.Warnf("Failed to leave agent cluster on network %s on failure (%v): %v", network.name, err, e)
+ }
+ }
+ }()
+
+ if network.hasLoadBalancerEndpoint() {
+ if err = network.createLoadBalancerSandbox(); err != nil {
+ return nil, err
+ }
+ }
+
+ if !c.isDistributedControl() {
+ c.Lock()
+ arrangeIngressFilterRule()
+ c.Unlock()
+ }
+
+ c.arrangeUserFilterRule()
+
+ return network, nil
+}
+
+var joinCluster NetworkWalker = func(nw Network) bool {
+ n := nw.(*network)
+ if n.configOnly {
+ return false
+ }
+ if err := n.joinCluster(); err != nil {
+ logrus.Errorf("Failed to join network %s (%s) into agent cluster: %v", n.Name(), n.ID(), err)
+ }
+ n.addDriverWatches()
+ return false
+}
+
+func (c *controller) reservePools() {
+ networks, err := c.getNetworksForScope(datastore.LocalScope)
+ if err != nil {
+ logrus.Warnf("Could not retrieve networks from local store during ipam allocation for existing networks: %v", err)
+ return
+ }
+
+ for _, n := range networks {
+ if n.configOnly {
+ continue
+ }
+ if !doReplayPoolReserve(n) {
+ continue
+ }
+ // Construct pseudo configs for the auto IP case
+ autoIPv4 := (len(n.ipamV4Config) == 0 || (len(n.ipamV4Config) == 1 && n.ipamV4Config[0].PreferredPool == "")) && len(n.ipamV4Info) > 0
+ autoIPv6 := (len(n.ipamV6Config) == 0 || (len(n.ipamV6Config) == 1 && n.ipamV6Config[0].PreferredPool == "")) && len(n.ipamV6Info) > 0
+ if autoIPv4 {
+ n.ipamV4Config = []*IpamConf{{PreferredPool: n.ipamV4Info[0].Pool.String()}}
+ }
+ if n.enableIPv6 && autoIPv6 {
+ n.ipamV6Config = []*IpamConf{{PreferredPool: n.ipamV6Info[0].Pool.String()}}
+ }
+ // Account current network gateways
+ for i, c := range n.ipamV4Config {
+ if c.Gateway == "" && n.ipamV4Info[i].Gateway != nil {
+ c.Gateway = n.ipamV4Info[i].Gateway.IP.String()
+ }
+ }
+ if n.enableIPv6 {
+ for i, c := range n.ipamV6Config {
+ if c.Gateway == "" && n.ipamV6Info[i].Gateway != nil {
+ c.Gateway = n.ipamV6Info[i].Gateway.IP.String()
+ }
+ }
+ }
+ // Reserve pools
+ if err := n.ipamAllocate(); err != nil {
+ logrus.Warnf("Failed to allocate ipam pool(s) for network %q (%s): %v", n.Name(), n.ID(), err)
+ }
+ // Reserve existing endpoints' addresses
+ ipam, _, err := n.getController().getIPAMDriver(n.ipamType)
+ if err != nil {
+ logrus.Warnf("Failed to retrieve ipam driver for network %q (%s) during address reservation", n.Name(), n.ID())
+ continue
+ }
+ epl, err := n.getEndpointsFromStore()
+ if err != nil {
+ logrus.Warnf("Failed to retrieve list of current endpoints on network %q (%s)", n.Name(), n.ID())
+ continue
+ }
+ for _, ep := range epl {
+ if err := ep.assignAddress(ipam, true, ep.Iface().AddressIPv6() != nil); err != nil {
+ logrus.Warnf("Failed to reserve current address for endpoint %q (%s) on network %q (%s)",
+ ep.Name(), ep.ID(), n.Name(), n.ID())
+ }
+ }
+ }
+}
+
+func doReplayPoolReserve(n *network) bool {
+ _, caps, err := n.getController().getIPAMDriver(n.ipamType)
+ if err != nil {
+ logrus.Warnf("Failed to retrieve ipam driver for network %q (%s): %v", n.Name(), n.ID(), err)
+ return false
+ }
+ return caps.RequiresRequestReplay
+}
+
+func (c *controller) addNetwork(n *network) error {
+ d, err := n.driver(true)
+ if err != nil {
+ return err
+ }
+
+ // Create the network
+ if err := d.CreateNetwork(n.id, n.generic, n, n.getIPData(4), n.getIPData(6)); err != nil {
+ return err
+ }
+
+ n.startResolver()
+
+ return nil
+}
+
+func (c *controller) Networks() []Network {
+ var list []Network
+
+ networks, err := c.getNetworksFromStore()
+ if err != nil {
+ logrus.Error(err)
+ }
+
+ for _, n := range networks {
+ if n.inDelete {
+ continue
+ }
+ list = append(list, n)
+ }
+
+ return list
+}
+
+func (c *controller) WalkNetworks(walker NetworkWalker) {
+ for _, n := range c.Networks() {
+ if walker(n) {
+ return
+ }
+ }
+}
+
+func (c *controller) NetworkByName(name string) (Network, error) {
+ if name == "" {
+ return nil, ErrInvalidName(name)
+ }
+ var n Network
+
+ s := func(current Network) bool {
+ if current.Name() == name {
+ n = current
+ return true
+ }
+ return false
+ }
+
+ c.WalkNetworks(s)
+
+ if n == nil {
+ return nil, ErrNoSuchNetwork(name)
+ }
+
+ return n, nil
+}
+
+func (c *controller) NetworkByID(id string) (Network, error) {
+ if id == "" {
+ return nil, ErrInvalidID(id)
+ }
+
+ n, err := c.getNetworkFromStore(id)
+ if err != nil {
+ return nil, ErrNoSuchNetwork(id)
+ }
+
+ return n, nil
+}
+
+// NewSandbox creates a new sandbox for the passed container id
+func (c *controller) NewSandbox(containerID string, options ...SandboxOption) (Sandbox, error) {
+ if containerID == "" {
+ return nil, types.BadRequestErrorf("invalid container ID")
+ }
+
+ var sb *sandbox
+ c.Lock()
+ for _, s := range c.sandboxes {
+ if s.containerID == containerID {
+ // If not a stub, then we already have a complete sandbox.
+ if !s.isStub {
+ sbID := s.ID()
+ c.Unlock()
+ return nil, types.ForbiddenErrorf("container %s is already present in sandbox %s", containerID, sbID)
+ }
+
+ // We already have a stub sandbox from the
+ // store. Make use of it so that we don't lose
+ // the endpoints from store but reset the
+ // isStub flag.
+ sb = s
+ sb.isStub = false
+ break
+ }
+ }
+ c.Unlock()
+
+ sandboxID := stringid.GenerateRandomID()
+ if runtime.GOOS == "windows" {
+ sandboxID = containerID
+ }
+
+ // Create sandbox and process options first. Key generation depends on an option
+ if sb == nil {
+ sb = &sandbox{
+ id: sandboxID,
+ containerID: containerID,
+ endpoints: []*endpoint{},
+ epPriority: map[string]int{},
+ populatedEndpoints: map[string]struct{}{},
+ config: containerConfig{},
+ controller: c,
+ extDNS: []extDNSEntry{},
+ }
+ }
+
+ sb.processOptions(options...)
+
+ c.Lock()
+ if sb.ingress && c.ingressSandbox != nil {
+ c.Unlock()
+ return nil, types.ForbiddenErrorf("ingress sandbox already present")
+ }
+
+ if sb.ingress {
+ c.ingressSandbox = sb
+ sb.config.hostsPath = filepath.Join(c.cfg.Daemon.DataDir, "/network/files/hosts")
+ sb.config.resolvConfPath = filepath.Join(c.cfg.Daemon.DataDir, "/network/files/resolv.conf")
+ sb.id = "ingress_sbox"
+ } else if sb.loadBalancerNID != "" {
+ sb.id = "lb_" + sb.loadBalancerNID
+ }
+ c.Unlock()
+
+ var err error
+ defer func() {
+ if err != nil {
+ c.Lock()
+ if sb.ingress {
+ c.ingressSandbox = nil
+ }
+ c.Unlock()
+ }
+ }()
+
+ if err = sb.setupResolutionFiles(); err != nil {
+ return nil, err
+ }
+
+ if sb.config.useDefaultSandBox {
+ c.sboxOnce.Do(func() {
+ c.defOsSbox, err = osl.NewSandbox(sb.Key(), false, false)
+ })
+
+ if err != nil {
+ c.sboxOnce = sync.Once{}
+ return nil, fmt.Errorf("failed to create default sandbox: %v", err)
+ }
+
+ sb.osSbox = c.defOsSbox
+ }
+
+ if sb.osSbox == nil && !sb.config.useExternalKey {
+ if sb.osSbox, err = osl.NewSandbox(sb.Key(), !sb.config.useDefaultSandBox, false); err != nil {
+ return nil, fmt.Errorf("failed to create new osl sandbox: %v", err)
+ }
+ }
+
+ if sb.osSbox != nil {
+ // Apply operating specific knobs on the load balancer sandbox
+ sb.osSbox.ApplyOSTweaks(sb.oslTypes)
+ }
+
+ c.Lock()
+ c.sandboxes[sb.id] = sb
+ c.Unlock()
+ defer func() {
+ if err != nil {
+ c.Lock()
+ delete(c.sandboxes, sb.id)
+ c.Unlock()
+ }
+ }()
+
+ err = sb.storeUpdate()
+ if err != nil {
+ return nil, fmt.Errorf("failed to update the store state of sandbox: %v", err)
+ }
+
+ return sb, nil
+}
+
+func (c *controller) Sandboxes() []Sandbox {
+ c.Lock()
+ defer c.Unlock()
+
+ list := make([]Sandbox, 0, len(c.sandboxes))
+ for _, s := range c.sandboxes {
+ // Hide stub sandboxes from libnetwork users
+ if s.isStub {
+ continue
+ }
+
+ list = append(list, s)
+ }
+
+ return list
+}
+
+func (c *controller) WalkSandboxes(walker SandboxWalker) {
+ for _, sb := range c.Sandboxes() {
+ if walker(sb) {
+ return
+ }
+ }
+}
+
+func (c *controller) SandboxByID(id string) (Sandbox, error) {
+ if id == "" {
+ return nil, ErrInvalidID(id)
+ }
+ c.Lock()
+ s, ok := c.sandboxes[id]
+ c.Unlock()
+ if !ok {
+ return nil, types.NotFoundErrorf("sandbox %s not found", id)
+ }
+ return s, nil
+}
+
+// SandboxDestroy destroys a sandbox given a container ID
+func (c *controller) SandboxDestroy(id string) error {
+ var sb *sandbox
+ c.Lock()
+ for _, s := range c.sandboxes {
+ if s.containerID == id {
+ sb = s
+ break
+ }
+ }
+ c.Unlock()
+
+ // It is not an error if sandbox is not available
+ if sb == nil {
+ return nil
+ }
+
+ return sb.Delete()
+}
+
+// SandboxContainerWalker returns a Sandbox Walker function which looks for an existing Sandbox with the passed containerID
+func SandboxContainerWalker(out *Sandbox, containerID string) SandboxWalker {
+ return func(sb Sandbox) bool {
+ if sb.ContainerID() == containerID {
+ *out = sb
+ return true
+ }
+ return false
+ }
+}
+
+// SandboxKeyWalker returns a Sandbox Walker function which looks for an existing Sandbox with the passed key
+func SandboxKeyWalker(out *Sandbox, key string) SandboxWalker {
+ return func(sb Sandbox) bool {
+ if sb.Key() == key {
+ *out = sb
+ return true
+ }
+ return false
+ }
+}
+
+func (c *controller) loadDriver(networkType string) error {
+ var err error
+
+ if pg := c.GetPluginGetter(); pg != nil {
+ _, err = pg.Get(networkType, driverapi.NetworkPluginEndpointType, plugingetter.Lookup)
+ } else {
+ _, err = plugins.Get(networkType, driverapi.NetworkPluginEndpointType)
+ }
+
+ if err != nil {
+ if errors.Cause(err) == plugins.ErrNotFound {
+ return types.NotFoundErrorf(err.Error())
+ }
+ return err
+ }
+
+ return nil
+}
+
+func (c *controller) loadIPAMDriver(name string) error {
+ var err error
+
+ if pg := c.GetPluginGetter(); pg != nil {
+ _, err = pg.Get(name, ipamapi.PluginEndpointType, plugingetter.Lookup)
+ } else {
+ _, err = plugins.Get(name, ipamapi.PluginEndpointType)
+ }
+
+ if err != nil {
+ if err == plugins.ErrNotFound {
+ return types.NotFoundErrorf(err.Error())
+ }
+ return err
+ }
+
+ return nil
+}
+
+func (c *controller) getIPAMDriver(name string) (ipamapi.Ipam, *ipamapi.Capability, error) {
+ id, cap := c.drvRegistry.IPAM(name)
+ if id == nil {
+ // Might be a plugin name. Try loading it
+ if err := c.loadIPAMDriver(name); err != nil {
+ return nil, nil, err
+ }
+
+ // Now that we resolved the plugin, try again looking up the registry
+ id, cap = c.drvRegistry.IPAM(name)
+ if id == nil {
+ return nil, nil, types.BadRequestErrorf("invalid ipam driver: %q", name)
+ }
+ }
+
+ return id, cap, nil
+}
+
+func (c *controller) Stop() {
+ c.closeStores()
+ c.stopExternalKeyListener()
+ osl.GC()
+}
+
+// StartDiagnostic start the network dias mode
+func (c *controller) StartDiagnostic(port int) {
+ c.Lock()
+ if !c.DiagnosticServer.IsDiagnosticEnabled() {
+ c.DiagnosticServer.EnableDiagnostic("127.0.0.1", port)
+ }
+ c.Unlock()
+}
+
+// StopDiagnostic start the network dias mode
+func (c *controller) StopDiagnostic() {
+ c.Lock()
+ if c.DiagnosticServer.IsDiagnosticEnabled() {
+ c.DiagnosticServer.DisableDiagnostic()
+ }
+ c.Unlock()
+}
+
+// IsDiagnosticEnabled returns true if the dias is enabled
+func (c *controller) IsDiagnosticEnabled() bool {
+ c.Lock()
+ defer c.Unlock()
+ return c.DiagnosticServer.IsDiagnosticEnabled()
+}
--- /dev/null
+package datastore
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+
+ "github.com/docker/libkv/store"
+)
+
+type kvMap map[string]KVObject
+
+type cache struct {
+ sync.Mutex
+ kmm map[string]kvMap
+ ds *datastore
+}
+
+func newCache(ds *datastore) *cache {
+ return &cache{kmm: make(map[string]kvMap), ds: ds}
+}
+
+func (c *cache) kmap(kvObject KVObject) (kvMap, error) {
+ var err error
+
+ c.Lock()
+ keyPrefix := Key(kvObject.KeyPrefix()...)
+ kmap, ok := c.kmm[keyPrefix]
+ c.Unlock()
+
+ if ok {
+ return kmap, nil
+ }
+
+ kmap = kvMap{}
+
+ // Bail out right away if the kvObject does not implement KVConstructor
+ ctor, ok := kvObject.(KVConstructor)
+ if !ok {
+ return nil, errors.New("error while populating kmap, object does not implement KVConstructor interface")
+ }
+
+ kvList, err := c.ds.store.List(keyPrefix)
+ if err != nil {
+ if err == store.ErrKeyNotFound {
+ // If the store doesn't have anything then there is nothing to
+ // populate in the cache. Just bail out.
+ goto out
+ }
+
+ return nil, fmt.Errorf("error while populating kmap: %v", err)
+ }
+
+ for _, kvPair := range kvList {
+ // Ignore empty kvPair values
+ if len(kvPair.Value) == 0 {
+ continue
+ }
+
+ dstO := ctor.New()
+ err = dstO.SetValue(kvPair.Value)
+ if err != nil {
+ return nil, err
+ }
+
+ // Make sure the object has a correct view of the DB index in
+ // case we need to modify it and update the DB.
+ dstO.SetIndex(kvPair.LastIndex)
+
+ kmap[Key(dstO.Key()...)] = dstO
+ }
+
+out:
+ // There may multiple go routines racing to fill the
+ // cache. The one which places the kmap in c.kmm first
+ // wins. The others should just use what the first populated.
+ c.Lock()
+ kmapNew, ok := c.kmm[keyPrefix]
+ if ok {
+ c.Unlock()
+ return kmapNew, nil
+ }
+
+ c.kmm[keyPrefix] = kmap
+ c.Unlock()
+
+ return kmap, nil
+}
+
+func (c *cache) add(kvObject KVObject, atomic bool) error {
+ kmap, err := c.kmap(kvObject)
+ if err != nil {
+ return err
+ }
+
+ c.Lock()
+ // If atomic is true, cache needs to maintain its own index
+ // for atomicity and the add needs to be atomic.
+ if atomic {
+ if prev, ok := kmap[Key(kvObject.Key()...)]; ok {
+ if prev.Index() != kvObject.Index() {
+ c.Unlock()
+ return ErrKeyModified
+ }
+ }
+
+ // Increment index
+ index := kvObject.Index()
+ index++
+ kvObject.SetIndex(index)
+ }
+
+ kmap[Key(kvObject.Key()...)] = kvObject
+ c.Unlock()
+ return nil
+}
+
+func (c *cache) del(kvObject KVObject, atomic bool) error {
+ kmap, err := c.kmap(kvObject)
+ if err != nil {
+ return err
+ }
+
+ c.Lock()
+ // If atomic is true, cache needs to maintain its own index
+ // for atomicity and del needs to be atomic.
+ if atomic {
+ if prev, ok := kmap[Key(kvObject.Key()...)]; ok {
+ if prev.Index() != kvObject.Index() {
+ c.Unlock()
+ return ErrKeyModified
+ }
+ }
+ }
+
+ delete(kmap, Key(kvObject.Key()...))
+ c.Unlock()
+ return nil
+}
+
+func (c *cache) get(key string, kvObject KVObject) error {
+ kmap, err := c.kmap(kvObject)
+ if err != nil {
+ return err
+ }
+
+ c.Lock()
+ defer c.Unlock()
+
+ o, ok := kmap[Key(kvObject.Key()...)]
+ if !ok {
+ return ErrKeyNotFound
+ }
+
+ ctor, ok := o.(KVConstructor)
+ if !ok {
+ return errors.New("kvobject does not implement KVConstructor interface. could not get object")
+ }
+
+ return ctor.CopyTo(kvObject)
+}
+
+func (c *cache) list(kvObject KVObject) ([]KVObject, error) {
+ kmap, err := c.kmap(kvObject)
+ if err != nil {
+ return nil, err
+ }
+
+ c.Lock()
+ defer c.Unlock()
+
+ var kvol []KVObject
+ for _, v := range kmap {
+ kvol = append(kvol, v)
+ }
+
+ return kvol, nil
+}
--- /dev/null
+package datastore
+
+import (
+ "fmt"
+ "log"
+ "reflect"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/docker/libkv"
+ "github.com/docker/libkv/store"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/types"
+)
+
+//DataStore exported
+type DataStore interface {
+ // GetObject gets data from datastore and unmarshals to the specified object
+ GetObject(key string, o KVObject) error
+ // PutObject adds a new Record based on an object into the datastore
+ PutObject(kvObject KVObject) error
+ // PutObjectAtomic provides an atomic add and update operation for a Record
+ PutObjectAtomic(kvObject KVObject) error
+ // DeleteObject deletes a record
+ DeleteObject(kvObject KVObject) error
+ // DeleteObjectAtomic performs an atomic delete operation
+ DeleteObjectAtomic(kvObject KVObject) error
+ // DeleteTree deletes a record
+ DeleteTree(kvObject KVObject) error
+ // Watchable returns whether the store is watchable or not
+ Watchable() bool
+ // Watch for changes on a KVObject
+ Watch(kvObject KVObject, stopCh <-chan struct{}) (<-chan KVObject, error)
+ // RestartWatch retriggers stopped Watches
+ RestartWatch()
+ // Active returns if the store is active
+ Active() bool
+ // List returns of a list of KVObjects belonging to the parent
+ // key. The caller must pass a KVObject of the same type as
+ // the objects that need to be listed
+ List(string, KVObject) ([]KVObject, error)
+ // Map returns a Map of KVObjects
+ Map(key string, kvObject KVObject) (map[string]KVObject, error)
+ // Scope returns the scope of the store
+ Scope() string
+ // KVStore returns access to the KV Store
+ KVStore() store.Store
+ // Close closes the data store
+ Close()
+}
+
+// ErrKeyModified is raised for an atomic update when the update is working on a stale state
+var (
+ ErrKeyModified = store.ErrKeyModified
+ ErrKeyNotFound = store.ErrKeyNotFound
+)
+
+type datastore struct {
+ scope string
+ store store.Store
+ cache *cache
+ watchCh chan struct{}
+ active bool
+ sequential bool
+ sync.Mutex
+}
+
+// KVObject is Key/Value interface used by objects to be part of the DataStore
+type KVObject interface {
+ // Key method lets an object provide the Key to be used in KV Store
+ Key() []string
+ // KeyPrefix method lets an object return immediate parent key that can be used for tree walk
+ KeyPrefix() []string
+ // Value method lets an object marshal its content to be stored in the KV store
+ Value() []byte
+ // SetValue is used by the datastore to set the object's value when loaded from the data store.
+ SetValue([]byte) error
+ // Index method returns the latest DB Index as seen by the object
+ Index() uint64
+ // SetIndex method allows the datastore to store the latest DB Index into the object
+ SetIndex(uint64)
+ // True if the object exists in the datastore, false if it hasn't been stored yet.
+ // When SetIndex() is called, the object has been stored.
+ Exists() bool
+ // DataScope indicates the storage scope of the KV object
+ DataScope() string
+ // Skip provides a way for a KV Object to avoid persisting it in the KV Store
+ Skip() bool
+}
+
+// KVConstructor interface defines methods which can construct a KVObject from another.
+type KVConstructor interface {
+ // New returns a new object which is created based on the
+ // source object
+ New() KVObject
+ // CopyTo deep copies the contents of the implementing object
+ // to the passed destination object
+ CopyTo(KVObject) error
+}
+
+// ScopeCfg represents Datastore configuration.
+type ScopeCfg struct {
+ Client ScopeClientCfg
+}
+
+// ScopeClientCfg represents Datastore Client-only mode configuration
+type ScopeClientCfg struct {
+ Provider string
+ Address string
+ Config *store.Config
+}
+
+const (
+ // LocalScope indicates to store the KV object in local datastore such as boltdb
+ LocalScope = "local"
+ // GlobalScope indicates to store the KV object in global datastore such as consul/etcd/zookeeper
+ GlobalScope = "global"
+ // SwarmScope is not indicating a datastore location. It is defined here
+ // along with the other two scopes just for consistency.
+ SwarmScope = "swarm"
+ defaultPrefix = "/var/lib/docker/network/files"
+)
+
+const (
+ // NetworkKeyPrefix is the prefix for network key in the kv store
+ NetworkKeyPrefix = "network"
+ // EndpointKeyPrefix is the prefix for endpoint key in the kv store
+ EndpointKeyPrefix = "endpoint"
+)
+
+var (
+ defaultScopes = makeDefaultScopes()
+)
+
+func makeDefaultScopes() map[string]*ScopeCfg {
+ def := make(map[string]*ScopeCfg)
+ def[LocalScope] = &ScopeCfg{
+ Client: ScopeClientCfg{
+ Provider: string(store.BOLTDB),
+ Address: defaultPrefix + "/local-kv.db",
+ Config: &store.Config{
+ Bucket: "libnetwork",
+ ConnectionTimeout: time.Minute,
+ },
+ },
+ }
+
+ return def
+}
+
+var defaultRootChain = []string{"docker", "network", "v1.0"}
+var rootChain = defaultRootChain
+
+// DefaultScopes returns a map of default scopes and its config for clients to use.
+func DefaultScopes(dataDir string) map[string]*ScopeCfg {
+ if dataDir != "" {
+ defaultScopes[LocalScope].Client.Address = dataDir + "/network/files/local-kv.db"
+ return defaultScopes
+ }
+
+ defaultScopes[LocalScope].Client.Address = defaultPrefix + "/local-kv.db"
+ return defaultScopes
+}
+
+// IsValid checks if the scope config has valid configuration.
+func (cfg *ScopeCfg) IsValid() bool {
+ if cfg == nil ||
+ strings.TrimSpace(cfg.Client.Provider) == "" ||
+ strings.TrimSpace(cfg.Client.Address) == "" {
+ return false
+ }
+
+ return true
+}
+
+//Key provides convenient method to create a Key
+func Key(key ...string) string {
+ keychain := append(rootChain, key...)
+ str := strings.Join(keychain, "/")
+ return str + "/"
+}
+
+//ParseKey provides convenient method to unpack the key to complement the Key function
+func ParseKey(key string) ([]string, error) {
+ chain := strings.Split(strings.Trim(key, "/"), "/")
+
+ // The key must at least be equal to the rootChain in order to be considered as valid
+ if len(chain) <= len(rootChain) || !reflect.DeepEqual(chain[0:len(rootChain)], rootChain) {
+ return nil, types.BadRequestErrorf("invalid Key : %s", key)
+ }
+ return chain[len(rootChain):], nil
+}
+
+// newClient used to connect to KV Store
+func newClient(scope string, kv string, addr string, config *store.Config, cached bool) (DataStore, error) {
+
+ if cached && scope != LocalScope {
+ return nil, fmt.Errorf("caching supported only for scope %s", LocalScope)
+ }
+ sequential := false
+ if scope == LocalScope {
+ sequential = true
+ }
+
+ if config == nil {
+ config = &store.Config{}
+ }
+
+ var addrs []string
+
+ if kv == string(store.BOLTDB) {
+ // Parse file path
+ addrs = strings.Split(addr, ",")
+ } else {
+ // Parse URI
+ parts := strings.SplitN(addr, "/", 2)
+ addrs = strings.Split(parts[0], ",")
+
+ // Add the custom prefix to the root chain
+ if len(parts) == 2 {
+ rootChain = append([]string{parts[1]}, defaultRootChain...)
+ }
+ }
+
+ store, err := libkv.NewStore(store.Backend(kv), addrs, config)
+ if err != nil {
+ return nil, err
+ }
+
+ ds := &datastore{scope: scope, store: store, active: true, watchCh: make(chan struct{}), sequential: sequential}
+ if cached {
+ ds.cache = newCache(ds)
+ }
+
+ return ds, nil
+}
+
+// NewDataStore creates a new instance of LibKV data store
+func NewDataStore(scope string, cfg *ScopeCfg) (DataStore, error) {
+ if cfg == nil || cfg.Client.Provider == "" || cfg.Client.Address == "" {
+ c, ok := defaultScopes[scope]
+ if !ok || c.Client.Provider == "" || c.Client.Address == "" {
+ return nil, fmt.Errorf("unexpected scope %s without configuration passed", scope)
+ }
+
+ cfg = c
+ }
+
+ var cached bool
+ if scope == LocalScope {
+ cached = true
+ }
+
+ return newClient(scope, cfg.Client.Provider, cfg.Client.Address, cfg.Client.Config, cached)
+}
+
+// NewDataStoreFromConfig creates a new instance of LibKV data store starting from the datastore config data
+func NewDataStoreFromConfig(dsc discoverapi.DatastoreConfigData) (DataStore, error) {
+ var (
+ ok bool
+ sCfgP *store.Config
+ )
+
+ sCfgP, ok = dsc.Config.(*store.Config)
+ if !ok && dsc.Config != nil {
+ return nil, fmt.Errorf("cannot parse store configuration: %v", dsc.Config)
+ }
+
+ scopeCfg := &ScopeCfg{
+ Client: ScopeClientCfg{
+ Address: dsc.Address,
+ Provider: dsc.Provider,
+ Config: sCfgP,
+ },
+ }
+
+ ds, err := NewDataStore(dsc.Scope, scopeCfg)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct datastore client from datastore configuration %v: %v", dsc, err)
+ }
+
+ return ds, err
+}
+
+func (ds *datastore) Close() {
+ ds.store.Close()
+}
+
+func (ds *datastore) Scope() string {
+ return ds.scope
+}
+
+func (ds *datastore) Active() bool {
+ return ds.active
+}
+
+func (ds *datastore) Watchable() bool {
+ return ds.scope != LocalScope
+}
+
+func (ds *datastore) Watch(kvObject KVObject, stopCh <-chan struct{}) (<-chan KVObject, error) {
+ sCh := make(chan struct{})
+
+ ctor, ok := kvObject.(KVConstructor)
+ if !ok {
+ return nil, fmt.Errorf("error watching object type %T, object does not implement KVConstructor interface", kvObject)
+ }
+
+ kvpCh, err := ds.store.Watch(Key(kvObject.Key()...), sCh)
+ if err != nil {
+ return nil, err
+ }
+
+ kvoCh := make(chan KVObject)
+
+ go func() {
+ retry_watch:
+ var err error
+
+ // Make sure to get a new instance of watch channel
+ ds.Lock()
+ watchCh := ds.watchCh
+ ds.Unlock()
+
+ loop:
+ for {
+ select {
+ case <-stopCh:
+ close(sCh)
+ return
+ case kvPair := <-kvpCh:
+ // If the backend KV store gets reset libkv's go routine
+ // for the watch can exit resulting in a nil value in
+ // channel.
+ if kvPair == nil {
+ ds.Lock()
+ ds.active = false
+ ds.Unlock()
+ break loop
+ }
+
+ dstO := ctor.New()
+
+ if err = dstO.SetValue(kvPair.Value); err != nil {
+ log.Printf("Could not unmarshal kvpair value = %s", string(kvPair.Value))
+ break
+ }
+
+ dstO.SetIndex(kvPair.LastIndex)
+ kvoCh <- dstO
+ }
+ }
+
+ // Wait on watch channel for a re-trigger when datastore becomes active
+ <-watchCh
+
+ kvpCh, err = ds.store.Watch(Key(kvObject.Key()...), sCh)
+ if err != nil {
+ log.Printf("Could not watch the key %s in store: %v", Key(kvObject.Key()...), err)
+ }
+
+ goto retry_watch
+ }()
+
+ return kvoCh, nil
+}
+
+func (ds *datastore) RestartWatch() {
+ ds.Lock()
+ defer ds.Unlock()
+
+ ds.active = true
+ watchCh := ds.watchCh
+ ds.watchCh = make(chan struct{})
+ close(watchCh)
+}
+
+func (ds *datastore) KVStore() store.Store {
+ return ds.store
+}
+
+// PutObjectAtomic adds a new Record based on an object into the datastore
+func (ds *datastore) PutObjectAtomic(kvObject KVObject) error {
+ var (
+ previous *store.KVPair
+ pair *store.KVPair
+ err error
+ )
+ if ds.sequential {
+ ds.Lock()
+ defer ds.Unlock()
+ }
+
+ if kvObject == nil {
+ return types.BadRequestErrorf("invalid KV Object : nil")
+ }
+
+ kvObjValue := kvObject.Value()
+
+ if kvObjValue == nil {
+ return types.BadRequestErrorf("invalid KV Object with a nil Value for key %s", Key(kvObject.Key()...))
+ }
+
+ if kvObject.Skip() {
+ goto add_cache
+ }
+
+ if kvObject.Exists() {
+ previous = &store.KVPair{Key: Key(kvObject.Key()...), LastIndex: kvObject.Index()}
+ } else {
+ previous = nil
+ }
+
+ _, pair, err = ds.store.AtomicPut(Key(kvObject.Key()...), kvObjValue, previous, nil)
+ if err != nil {
+ if err == store.ErrKeyExists {
+ return ErrKeyModified
+ }
+ return err
+ }
+
+ kvObject.SetIndex(pair.LastIndex)
+
+add_cache:
+ if ds.cache != nil {
+ // If persistent store is skipped, sequencing needs to
+ // happen in cache.
+ return ds.cache.add(kvObject, kvObject.Skip())
+ }
+
+ return nil
+}
+
+// PutObject adds a new Record based on an object into the datastore
+func (ds *datastore) PutObject(kvObject KVObject) error {
+ if ds.sequential {
+ ds.Lock()
+ defer ds.Unlock()
+ }
+
+ if kvObject == nil {
+ return types.BadRequestErrorf("invalid KV Object : nil")
+ }
+
+ if kvObject.Skip() {
+ goto add_cache
+ }
+
+ if err := ds.putObjectWithKey(kvObject, kvObject.Key()...); err != nil {
+ return err
+ }
+
+add_cache:
+ if ds.cache != nil {
+ // If persistent store is skipped, sequencing needs to
+ // happen in cache.
+ return ds.cache.add(kvObject, kvObject.Skip())
+ }
+
+ return nil
+}
+
+func (ds *datastore) putObjectWithKey(kvObject KVObject, key ...string) error {
+ kvObjValue := kvObject.Value()
+
+ if kvObjValue == nil {
+ return types.BadRequestErrorf("invalid KV Object with a nil Value for key %s", Key(kvObject.Key()...))
+ }
+ return ds.store.Put(Key(key...), kvObjValue, nil)
+}
+
+// GetObject returns a record matching the key
+func (ds *datastore) GetObject(key string, o KVObject) error {
+ if ds.sequential {
+ ds.Lock()
+ defer ds.Unlock()
+ }
+
+ if ds.cache != nil {
+ return ds.cache.get(key, o)
+ }
+
+ kvPair, err := ds.store.Get(key)
+ if err != nil {
+ return err
+ }
+
+ if err := o.SetValue(kvPair.Value); err != nil {
+ return err
+ }
+
+ // Make sure the object has a correct view of the DB index in
+ // case we need to modify it and update the DB.
+ o.SetIndex(kvPair.LastIndex)
+ return nil
+}
+
+func (ds *datastore) ensureParent(parent string) error {
+ exists, err := ds.store.Exists(parent)
+ if err != nil {
+ return err
+ }
+ if exists {
+ return nil
+ }
+ return ds.store.Put(parent, []byte{}, &store.WriteOptions{IsDir: true})
+}
+
+func (ds *datastore) List(key string, kvObject KVObject) ([]KVObject, error) {
+ if ds.sequential {
+ ds.Lock()
+ defer ds.Unlock()
+ }
+
+ if ds.cache != nil {
+ return ds.cache.list(kvObject)
+ }
+
+ var kvol []KVObject
+ cb := func(key string, val KVObject) {
+ kvol = append(kvol, val)
+ }
+ err := ds.iterateKVPairsFromStore(key, kvObject, cb)
+ if err != nil {
+ return nil, err
+ }
+ return kvol, nil
+}
+
+func (ds *datastore) iterateKVPairsFromStore(key string, kvObject KVObject, callback func(string, KVObject)) error {
+ // Bail out right away if the kvObject does not implement KVConstructor
+ ctor, ok := kvObject.(KVConstructor)
+ if !ok {
+ return fmt.Errorf("error listing objects, object does not implement KVConstructor interface")
+ }
+
+ // Make sure the parent key exists
+ if err := ds.ensureParent(key); err != nil {
+ return err
+ }
+
+ kvList, err := ds.store.List(key)
+ if err != nil {
+ return err
+ }
+
+ for _, kvPair := range kvList {
+ if len(kvPair.Value) == 0 {
+ continue
+ }
+
+ dstO := ctor.New()
+ if err := dstO.SetValue(kvPair.Value); err != nil {
+ return err
+ }
+
+ // Make sure the object has a correct view of the DB index in
+ // case we need to modify it and update the DB.
+ dstO.SetIndex(kvPair.LastIndex)
+ callback(kvPair.Key, dstO)
+ }
+
+ return nil
+}
+
+func (ds *datastore) Map(key string, kvObject KVObject) (map[string]KVObject, error) {
+ if ds.sequential {
+ ds.Lock()
+ defer ds.Unlock()
+ }
+
+ kvol := make(map[string]KVObject)
+ cb := func(key string, val KVObject) {
+ // Trim the leading & trailing "/" to make it consistent across all stores
+ kvol[strings.Trim(key, "/")] = val
+ }
+ err := ds.iterateKVPairsFromStore(key, kvObject, cb)
+ if err != nil {
+ return nil, err
+ }
+ return kvol, nil
+}
+
+// DeleteObject unconditionally deletes a record from the store
+func (ds *datastore) DeleteObject(kvObject KVObject) error {
+ if ds.sequential {
+ ds.Lock()
+ defer ds.Unlock()
+ }
+
+ // cleanup the cache first
+ if ds.cache != nil {
+ // If persistent store is skipped, sequencing needs to
+ // happen in cache.
+ ds.cache.del(kvObject, kvObject.Skip())
+ }
+
+ if kvObject.Skip() {
+ return nil
+ }
+
+ return ds.store.Delete(Key(kvObject.Key()...))
+}
+
+// DeleteObjectAtomic performs atomic delete on a record
+func (ds *datastore) DeleteObjectAtomic(kvObject KVObject) error {
+ if ds.sequential {
+ ds.Lock()
+ defer ds.Unlock()
+ }
+
+ if kvObject == nil {
+ return types.BadRequestErrorf("invalid KV Object : nil")
+ }
+
+ previous := &store.KVPair{Key: Key(kvObject.Key()...), LastIndex: kvObject.Index()}
+
+ if kvObject.Skip() {
+ goto del_cache
+ }
+
+ if _, err := ds.store.AtomicDelete(Key(kvObject.Key()...), previous); err != nil {
+ if err == store.ErrKeyExists {
+ return ErrKeyModified
+ }
+ return err
+ }
+
+del_cache:
+ // cleanup the cache only if AtomicDelete went through successfully
+ if ds.cache != nil {
+ // If persistent store is skipped, sequencing needs to
+ // happen in cache.
+ return ds.cache.del(kvObject, kvObject.Skip())
+ }
+
+ return nil
+}
+
+// DeleteTree unconditionally deletes a record from the store
+func (ds *datastore) DeleteTree(kvObject KVObject) error {
+ if ds.sequential {
+ ds.Lock()
+ defer ds.Unlock()
+ }
+
+ // cleanup the cache first
+ if ds.cache != nil {
+ // If persistent store is skipped, sequencing needs to
+ // happen in cache.
+ ds.cache.del(kvObject, kvObject.Skip())
+ }
+
+ if kvObject.Skip() {
+ return nil
+ }
+
+ return ds.store.DeleteTree(Key(kvObject.KeyPrefix()...))
+}
--- /dev/null
+package datastore
+
+import (
+ "encoding/json"
+ "reflect"
+ "testing"
+
+ "github.com/docker/libnetwork/options"
+ _ "github.com/docker/libnetwork/testutils"
+ "gotest.tools/assert"
+)
+
+var dummyKey = "dummy"
+
+// NewCustomDataStore can be used by other Tests in order to use custom datastore
+func NewTestDataStore() DataStore {
+ return &datastore{scope: LocalScope, store: NewMockStore()}
+}
+
+func TestKey(t *testing.T) {
+ eKey := []string{"hello", "world"}
+ sKey := Key(eKey...)
+ if sKey != "docker/network/v1.0/hello/world/" {
+ t.Fatalf("unexpected key : %s", sKey)
+ }
+}
+
+func TestParseKey(t *testing.T) {
+ keySlice, err := ParseKey("/docker/network/v1.0/hello/world/")
+ if err != nil {
+ t.Fatal(err)
+ }
+ eKey := []string{"hello", "world"}
+ if len(keySlice) < 2 || !reflect.DeepEqual(eKey, keySlice) {
+ t.Fatalf("unexpected unkey : %s", keySlice)
+ }
+}
+
+func TestInvalidDataStore(t *testing.T) {
+ config := &ScopeCfg{}
+ config.Client.Provider = "invalid"
+ config.Client.Address = "localhost:8500"
+ _, err := NewDataStore(GlobalScope, config)
+ if err == nil {
+ t.Fatal("Invalid Datastore connection configuration must result in a failure")
+ }
+}
+
+func TestKVObjectFlatKey(t *testing.T) {
+ store := NewTestDataStore()
+ expected := dummyKVObject("1000", true)
+ err := store.PutObject(expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+ keychain := []string{dummyKey, "1000"}
+ data, err := store.KVStore().Get(Key(keychain...))
+ if err != nil {
+ t.Fatal(err)
+ }
+ var n dummyObject
+ json.Unmarshal(data.Value, &n)
+ if n.Name != expected.Name {
+ t.Fatal("Dummy object doesn't match the expected object")
+ }
+}
+
+func TestAtomicKVObjectFlatKey(t *testing.T) {
+ store := NewTestDataStore()
+ expected := dummyKVObject("1111", true)
+ assert.Check(t, !expected.Exists())
+ err := store.PutObjectAtomic(expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Check(t, expected.Exists())
+
+ // PutObjectAtomic automatically sets the Index again. Hence the following must pass.
+
+ err = store.PutObjectAtomic(expected)
+ if err != nil {
+ t.Fatal("Atomic update should succeed.")
+ }
+
+ // Get the latest index and try PutObjectAtomic again for the same Key
+ // This must succeed as well
+ data, err := store.KVStore().Get(Key(expected.Key()...))
+ if err != nil {
+ t.Fatal(err)
+ }
+ n := dummyObject{}
+ json.Unmarshal(data.Value, &n)
+ n.ID = "1111"
+ n.SetIndex(data.LastIndex)
+ n.ReturnValue = true
+ err = store.PutObjectAtomic(&n)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Get the Object using GetObject, then set again.
+ newObj := dummyObject{}
+ err = store.GetObject(Key(expected.Key()...), &newObj)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Check(t, newObj.Exists())
+ err = store.PutObjectAtomic(&n)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+}
+
+// dummy data used to test the datastore
+type dummyObject struct {
+ Name string `kv:"leaf"`
+ NetworkType string `kv:"leaf"`
+ EnableIPv6 bool `kv:"leaf"`
+ Rec *recStruct `kv:"recursive"`
+ Dict map[string]*recStruct `kv:"iterative"`
+ Generic options.Generic `kv:"iterative"`
+ ID string
+ DBIndex uint64
+ DBExists bool
+ SkipSave bool
+ ReturnValue bool
+}
+
+func (n *dummyObject) Key() []string {
+ return []string{dummyKey, n.ID}
+}
+
+func (n *dummyObject) KeyPrefix() []string {
+ return []string{dummyKey}
+}
+
+func (n *dummyObject) Value() []byte {
+ if !n.ReturnValue {
+ return nil
+ }
+
+ b, err := json.Marshal(n)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (n *dummyObject) SetValue(value []byte) error {
+ return json.Unmarshal(value, n)
+}
+
+func (n *dummyObject) Index() uint64 {
+ return n.DBIndex
+}
+
+func (n *dummyObject) SetIndex(index uint64) {
+ n.DBIndex = index
+ n.DBExists = true
+}
+
+func (n *dummyObject) Exists() bool {
+ return n.DBExists
+}
+
+func (n *dummyObject) Skip() bool {
+ return n.SkipSave
+}
+
+func (n *dummyObject) DataScope() string {
+ return LocalScope
+}
+
+func (n *dummyObject) MarshalJSON() ([]byte, error) {
+ netMap := make(map[string]interface{})
+ netMap["name"] = n.Name
+ netMap["networkType"] = n.NetworkType
+ netMap["enableIPv6"] = n.EnableIPv6
+ netMap["generic"] = n.Generic
+ return json.Marshal(netMap)
+}
+
+func (n *dummyObject) UnmarshalJSON(b []byte) (err error) {
+ var netMap map[string]interface{}
+ if err := json.Unmarshal(b, &netMap); err != nil {
+ return err
+ }
+ n.Name = netMap["name"].(string)
+ n.NetworkType = netMap["networkType"].(string)
+ n.EnableIPv6 = netMap["enableIPv6"].(bool)
+ n.Generic = netMap["generic"].(map[string]interface{})
+ return nil
+}
+
+// dummy structure to test "recursive" cases
+type recStruct struct {
+ Name string `kv:"leaf"`
+ Field1 int `kv:"leaf"`
+ Dict map[string]string `kv:"iterative"`
+ DBIndex uint64
+ DBExists bool
+ SkipSave bool
+}
+
+func (r *recStruct) Key() []string {
+ return []string{"recStruct"}
+}
+func (r *recStruct) Value() []byte {
+ b, err := json.Marshal(r)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (r *recStruct) SetValue(value []byte) error {
+ return json.Unmarshal(value, r)
+}
+
+func (r *recStruct) Index() uint64 {
+ return r.DBIndex
+}
+
+func (r *recStruct) SetIndex(index uint64) {
+ r.DBIndex = index
+ r.DBExists = true
+}
+
+func (r *recStruct) Exists() bool {
+ return r.DBExists
+}
+
+func (r *recStruct) Skip() bool {
+ return r.SkipSave
+}
+
+func dummyKVObject(id string, retValue bool) *dummyObject {
+ cDict := make(map[string]string)
+ cDict["foo"] = "bar"
+ cDict["hello"] = "world"
+ n := dummyObject{
+ Name: "testNw",
+ NetworkType: "bridge",
+ EnableIPv6: true,
+ Rec: &recStruct{"gen", 5, cDict, 0, false, false},
+ ID: id,
+ DBIndex: 0,
+ ReturnValue: retValue,
+ DBExists: false,
+ SkipSave: false}
+ generic := make(map[string]interface{})
+ generic["label1"] = &recStruct{"value1", 1, cDict, 0, false, false}
+ generic["label2"] = "subnet=10.1.1.0/16"
+ n.Generic = generic
+ return &n
+}
--- /dev/null
+package datastore
+
+import (
+ "errors"
+
+ "github.com/docker/libkv/store"
+ "github.com/docker/libnetwork/types"
+)
+
+var (
+ // ErrNotImplemented exported
+ ErrNotImplemented = errors.New("Functionality not implemented")
+)
+
+// MockData exported
+type MockData struct {
+ Data []byte
+ Index uint64
+}
+
+// MockStore exported
+type MockStore struct {
+ db map[string]*MockData
+}
+
+// NewMockStore creates a Map backed Datastore that is useful for mocking
+func NewMockStore() *MockStore {
+ db := make(map[string]*MockData)
+ return &MockStore{db}
+}
+
+// Get the value at "key", returns the last modified index
+// to use in conjunction to CAS calls
+func (s *MockStore) Get(key string) (*store.KVPair, error) {
+ mData := s.db[key]
+ if mData == nil {
+ return nil, nil
+ }
+ return &store.KVPair{Value: mData.Data, LastIndex: mData.Index}, nil
+
+}
+
+// Put a value at "key"
+func (s *MockStore) Put(key string, value []byte, options *store.WriteOptions) error {
+ mData := s.db[key]
+ if mData == nil {
+ mData = &MockData{value, 0}
+ }
+ mData.Index = mData.Index + 1
+ s.db[key] = mData
+ return nil
+}
+
+// Delete a value at "key"
+func (s *MockStore) Delete(key string) error {
+ delete(s.db, key)
+ return nil
+}
+
+// Exists checks that the key exists inside the store
+func (s *MockStore) Exists(key string) (bool, error) {
+ _, ok := s.db[key]
+ return ok, nil
+}
+
+// List gets a range of values at "directory"
+func (s *MockStore) List(prefix string) ([]*store.KVPair, error) {
+ return nil, ErrNotImplemented
+}
+
+// DeleteTree deletes a range of values at "directory"
+func (s *MockStore) DeleteTree(prefix string) error {
+ delete(s.db, prefix)
+ return nil
+}
+
+// Watch a single key for modifications
+func (s *MockStore) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair, error) {
+ return nil, ErrNotImplemented
+}
+
+// WatchTree triggers a watch on a range of values at "directory"
+func (s *MockStore) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
+ return nil, ErrNotImplemented
+}
+
+// NewLock exposed
+func (s *MockStore) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
+ return nil, ErrNotImplemented
+}
+
+// AtomicPut put a value at "key" if the key has not been
+// modified in the meantime, throws an error if this is the case
+func (s *MockStore) AtomicPut(key string, newValue []byte, previous *store.KVPair, options *store.WriteOptions) (bool, *store.KVPair, error) {
+ mData := s.db[key]
+
+ if previous == nil {
+ if mData != nil {
+ return false, nil, types.BadRequestErrorf("atomic put failed because key exists")
+ } // Else OK.
+ } else {
+ if mData == nil {
+ return false, nil, types.BadRequestErrorf("atomic put failed because key exists")
+ }
+ if mData != nil && mData.Index != previous.LastIndex {
+ return false, nil, types.BadRequestErrorf("atomic put failed due to mismatched Index")
+ } // Else OK.
+ }
+ err := s.Put(key, newValue, nil)
+ if err != nil {
+ return false, nil, err
+ }
+ return true, &store.KVPair{Key: key, Value: newValue, LastIndex: s.db[key].Index}, nil
+}
+
+// AtomicDelete deletes a value at "key" if the key has not
+// been modified in the meantime, throws an error if this is the case
+func (s *MockStore) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
+ mData := s.db[key]
+ if mData != nil && mData.Index != previous.LastIndex {
+ return false, types.BadRequestErrorf("atomic delete failed due to mismatched Index")
+ }
+ return true, s.Delete(key)
+}
+
+// Close closes the client connection
+func (s *MockStore) Close() {
+ return
+}
--- /dev/null
+package libnetwork
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ gwEPlen = 12
+)
+
+var procGwNetwork = make(chan (bool), 1)
+
+/*
+ libnetwork creates a bridge network "docker_gw_bridge" for providing
+ default gateway for the containers if none of the container's endpoints
+ have GW set by the driver. ICC is set to false for the GW_bridge network.
+
+ If a driver can't provide external connectivity it can choose to not set
+ the GW IP for the endpoint.
+
+ endpoint on the GW_bridge network is managed dynamically by libnetwork.
+ ie:
+ - its created when an endpoint without GW joins the container
+ - its deleted when an endpoint with GW joins the container
+*/
+
+func (sb *sandbox) setupDefaultGW() error {
+
+ // check if the container already has a GW endpoint
+ if ep := sb.getEndpointInGWNetwork(); ep != nil {
+ return nil
+ }
+
+ c := sb.controller
+
+ // Look for default gw network. In case of error (includes not found),
+ // retry and create it if needed in a serialized execution.
+ n, err := c.NetworkByName(libnGWNetwork)
+ if err != nil {
+ if n, err = c.defaultGwNetwork(); err != nil {
+ return err
+ }
+ }
+
+ createOptions := []EndpointOption{CreateOptionAnonymous()}
+
+ var gwName string
+ if len(sb.containerID) <= gwEPlen {
+ gwName = "gateway_" + sb.containerID
+ } else {
+ gwName = "gateway_" + sb.id[:gwEPlen]
+ }
+
+ sbLabels := sb.Labels()
+
+ if sbLabels[netlabel.PortMap] != nil {
+ createOptions = append(createOptions, CreateOptionPortMapping(sbLabels[netlabel.PortMap].([]types.PortBinding)))
+ }
+
+ if sbLabels[netlabel.ExposedPorts] != nil {
+ createOptions = append(createOptions, CreateOptionExposedPorts(sbLabels[netlabel.ExposedPorts].([]types.TransportPort)))
+ }
+
+ epOption := getPlatformOption()
+ if epOption != nil {
+ createOptions = append(createOptions, epOption)
+ }
+
+ newEp, err := n.CreateEndpoint(gwName, createOptions...)
+ if err != nil {
+ return fmt.Errorf("container %s: endpoint create on GW Network failed: %v", sb.containerID, err)
+ }
+
+ defer func() {
+ if err != nil {
+ if err2 := newEp.Delete(true); err2 != nil {
+ logrus.Warnf("Failed to remove gw endpoint for container %s after failing to join the gateway network: %v",
+ sb.containerID, err2)
+ }
+ }
+ }()
+
+ epLocal := newEp.(*endpoint)
+
+ if err = epLocal.sbJoin(sb); err != nil {
+ return fmt.Errorf("container %s: endpoint join on GW Network failed: %v", sb.containerID, err)
+ }
+
+ return nil
+}
+
+// If present, detach and remove the endpoint connecting the sandbox to the default gw network.
+func (sb *sandbox) clearDefaultGW() error {
+ var ep *endpoint
+
+ if ep = sb.getEndpointInGWNetwork(); ep == nil {
+ return nil
+ }
+ if err := ep.sbLeave(sb, false); err != nil {
+ return fmt.Errorf("container %s: endpoint leaving GW Network failed: %v", sb.containerID, err)
+ }
+ if err := ep.Delete(false); err != nil {
+ return fmt.Errorf("container %s: deleting endpoint on GW Network failed: %v", sb.containerID, err)
+ }
+ return nil
+}
+
+// Evaluate whether the sandbox requires a default gateway based
+// on the endpoints to which it is connected. It does not account
+// for the default gateway network endpoint.
+
+func (sb *sandbox) needDefaultGW() bool {
+ var needGW bool
+
+ for _, ep := range sb.getConnectedEndpoints() {
+ if ep.endpointInGWNetwork() {
+ continue
+ }
+ if ep.getNetwork().Type() == "null" || ep.getNetwork().Type() == "host" {
+ continue
+ }
+ if ep.getNetwork().Internal() {
+ continue
+ }
+ // During stale sandbox cleanup, joinInfo may be nil
+ if ep.joinInfo != nil && ep.joinInfo.disableGatewayService {
+ continue
+ }
+ // TODO v6 needs to be handled.
+ if len(ep.Gateway()) > 0 {
+ return false
+ }
+ for _, r := range ep.StaticRoutes() {
+ if r.Destination != nil && r.Destination.String() == "0.0.0.0/0" {
+ return false
+ }
+ }
+ needGW = true
+ }
+
+ return needGW
+}
+
+func (sb *sandbox) getEndpointInGWNetwork() *endpoint {
+ for _, ep := range sb.getConnectedEndpoints() {
+ if ep.getNetwork().name == libnGWNetwork && strings.HasPrefix(ep.Name(), "gateway_") {
+ return ep
+ }
+ }
+ return nil
+}
+
+func (ep *endpoint) endpointInGWNetwork() bool {
+ if ep.getNetwork().name == libnGWNetwork && strings.HasPrefix(ep.Name(), "gateway_") {
+ return true
+ }
+ return false
+}
+
+func (sb *sandbox) getEPwithoutGateway() *endpoint {
+ for _, ep := range sb.getConnectedEndpoints() {
+ if ep.getNetwork().Type() == "null" || ep.getNetwork().Type() == "host" {
+ continue
+ }
+ if len(ep.Gateway()) == 0 {
+ return ep
+ }
+ }
+ return nil
+}
+
+// Looks for the default gw network and creates it if not there.
+// Parallel executions are serialized.
+func (c *controller) defaultGwNetwork() (Network, error) {
+ procGwNetwork <- true
+ defer func() { <-procGwNetwork }()
+
+ n, err := c.NetworkByName(libnGWNetwork)
+ if _, ok := err.(types.NotFoundError); ok {
+ n, err = c.createGWNetwork()
+ }
+ return n, err
+}
+
+// Returns the endpoint which is providing external connectivity to the sandbox
+func (sb *sandbox) getGatewayEndpoint() *endpoint {
+ for _, ep := range sb.getConnectedEndpoints() {
+ if ep.getNetwork().Type() == "null" || ep.getNetwork().Type() == "host" {
+ continue
+ }
+ if len(ep.Gateway()) != 0 {
+ return ep
+ }
+ }
+ return nil
+}
--- /dev/null
+package libnetwork
+
+import "github.com/docker/libnetwork/types"
+
+const libnGWNetwork = "docker_gwbridge"
+
+func getPlatformOption() EndpointOption {
+ return nil
+}
+
+func (c *controller) createGWNetwork() (Network, error) {
+ return nil, types.NotImplementedErrorf("default gateway functionality is not implemented in freebsd")
+}
--- /dev/null
+package libnetwork
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/docker/libnetwork/drivers/bridge"
+)
+
+const libnGWNetwork = "docker_gwbridge"
+
+func getPlatformOption() EndpointOption {
+ return nil
+}
+
+func (c *controller) createGWNetwork() (Network, error) {
+ netOption := map[string]string{
+ bridge.BridgeName: libnGWNetwork,
+ bridge.EnableICC: strconv.FormatBool(false),
+ bridge.EnableIPMasquerade: strconv.FormatBool(true),
+ }
+
+ n, err := c.NewNetwork("bridge", libnGWNetwork, "",
+ NetworkOptionDriverOpts(netOption),
+ NetworkOptionEnableIPv6(false),
+ )
+
+ if err != nil {
+ return nil, fmt.Errorf("error creating external connectivity network: %v", err)
+ }
+ return n, err
+}
--- /dev/null
+package libnetwork
+
+import (
+ windriver "github.com/docker/libnetwork/drivers/windows"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/types"
+)
+
+const libnGWNetwork = "nat"
+
+func getPlatformOption() EndpointOption {
+
+ epOption := options.Generic{
+ windriver.DisableICC: true,
+ windriver.DisableDNS: true,
+ }
+ return EndpointOptionGeneric(epOption)
+}
+
+func (c *controller) createGWNetwork() (Network, error) {
+ return nil, types.NotImplementedErrorf("default gateway functionality is not implemented in windows")
+}
--- /dev/null
+package diagnostic
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "sync"
+ "sync/atomic"
+
+ stackdump "github.com/docker/docker/pkg/signal"
+ "github.com/docker/libnetwork/internal/caller"
+ "github.com/sirupsen/logrus"
+)
+
+// HTTPHandlerFunc TODO
+type HTTPHandlerFunc func(interface{}, http.ResponseWriter, *http.Request)
+
+type httpHandlerCustom struct {
+ ctx interface{}
+ F func(interface{}, http.ResponseWriter, *http.Request)
+}
+
+// ServeHTTP TODO
+func (h httpHandlerCustom) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ h.F(h.ctx, w, r)
+}
+
+var diagPaths2Func = map[string]HTTPHandlerFunc{
+ "/": notImplemented,
+ "/help": help,
+ "/ready": ready,
+ "/stackdump": stackTrace,
+}
+
+// Server when the debug is enabled exposes a
+// This data structure is protected by the Agent mutex so does not require and additional mutex here
+type Server struct {
+ enable int32
+ srv *http.Server
+ port int
+ mux *http.ServeMux
+ registeredHanders map[string]bool
+ sync.Mutex
+}
+
+// New creates a new diagnostic server
+func New() *Server {
+ return &Server{
+ registeredHanders: make(map[string]bool),
+ }
+}
+
+// Init initialize the mux for the http handling and register the base hooks
+func (s *Server) Init() {
+ s.mux = http.NewServeMux()
+
+ // Register local handlers
+ s.RegisterHandler(s, diagPaths2Func)
+}
+
+// RegisterHandler allows to register new handlers to the mux and to a specific path
+func (s *Server) RegisterHandler(ctx interface{}, hdlrs map[string]HTTPHandlerFunc) {
+ s.Lock()
+ defer s.Unlock()
+ for path, fun := range hdlrs {
+ if _, ok := s.registeredHanders[path]; ok {
+ continue
+ }
+ s.mux.Handle(path, httpHandlerCustom{ctx, fun})
+ s.registeredHanders[path] = true
+ }
+}
+
+// ServeHTTP this is the method called bu the ListenAndServe, and is needed to allow us to
+// use our custom mux
+func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ s.mux.ServeHTTP(w, r)
+}
+
+// EnableDiagnostic opens a TCP socket to debug the passed network DB
+func (s *Server) EnableDiagnostic(ip string, port int) {
+ s.Lock()
+ defer s.Unlock()
+
+ s.port = port
+
+ if s.enable == 1 {
+ logrus.Info("The server is already up and running")
+ return
+ }
+
+ logrus.Infof("Starting the diagnostic server listening on %d for commands", port)
+ srv := &http.Server{Addr: fmt.Sprintf("%s:%d", ip, port), Handler: s}
+ s.srv = srv
+ s.enable = 1
+ go func(n *Server) {
+ // Ingore ErrServerClosed that is returned on the Shutdown call
+ if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ logrus.Errorf("ListenAndServe error: %s", err)
+ atomic.SwapInt32(&n.enable, 0)
+ }
+ }(s)
+}
+
+// DisableDiagnostic stop the dubug and closes the tcp socket
+func (s *Server) DisableDiagnostic() {
+ s.Lock()
+ defer s.Unlock()
+
+ s.srv.Shutdown(context.Background())
+ s.srv = nil
+ s.enable = 0
+ logrus.Info("Disabling the diagnostic server")
+}
+
+// IsDiagnosticEnabled returns true when the debug is enabled
+func (s *Server) IsDiagnosticEnabled() bool {
+ s.Lock()
+ defer s.Unlock()
+ return s.enable == 1
+}
+
+func notImplemented(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ _, json := ParseHTTPFormOptions(r)
+ rsp := WrongCommand("not implemented", fmt.Sprintf("URL path: %s no method implemented check /help\n", r.URL.Path))
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("command not implemented done")
+
+ HTTPReply(w, rsp, json)
+}
+
+func help(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ _, json := ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("help done")
+
+ n, ok := ctx.(*Server)
+ var result string
+ if ok {
+ for path := range n.registeredHanders {
+ result += fmt.Sprintf("%s\n", path)
+ }
+ HTTPReply(w, CommandSucceed(&StringCmd{Info: result}), json)
+ }
+}
+
+func ready(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ _, json := ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("ready done")
+ HTTPReply(w, CommandSucceed(&StringCmd{Info: "OK"}), json)
+}
+
+func stackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ _, json := ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("stack trace")
+
+ path, err := stackdump.DumpStacks("/tmp/")
+ if err != nil {
+ log.WithError(err).Error("failed to write goroutines dump")
+ HTTPReply(w, FailCommand(err), json)
+ } else {
+ log.Info("stack trace done")
+ HTTPReply(w, CommandSucceed(&StringCmd{Info: fmt.Sprintf("goroutine stacks written to %s", path)}), json)
+ }
+}
+
+// DebugHTTPForm helper to print the form url parameters
+func DebugHTTPForm(r *http.Request) {
+ for k, v := range r.Form {
+ logrus.Debugf("Form[%q] = %q\n", k, v)
+ }
+}
+
+// JSONOutput contains details on JSON output printing
+type JSONOutput struct {
+ enable bool
+ prettyPrint bool
+}
+
+// ParseHTTPFormOptions easily parse the JSON printing options
+func ParseHTTPFormOptions(r *http.Request) (bool, *JSONOutput) {
+ _, unsafe := r.Form["unsafe"]
+ v, json := r.Form["json"]
+ var pretty bool
+ if len(v) > 0 {
+ pretty = v[0] == "pretty"
+ }
+ return unsafe, &JSONOutput{enable: json, prettyPrint: pretty}
+}
+
+// HTTPReply helper function that takes care of sending the message out
+func HTTPReply(w http.ResponseWriter, r *HTTPResult, j *JSONOutput) (int, error) {
+ var response []byte
+ if j.enable {
+ w.Header().Set("Content-Type", "application/json")
+ var err error
+ if j.prettyPrint {
+ response, err = json.MarshalIndent(r, "", " ")
+ if err != nil {
+ response, _ = json.MarshalIndent(FailCommand(err), "", " ")
+ }
+ } else {
+ response, err = json.Marshal(r)
+ if err != nil {
+ response, _ = json.Marshal(FailCommand(err))
+ }
+ }
+ } else {
+ response = []byte(r.String())
+ }
+ return fmt.Fprint(w, string(response))
+}
--- /dev/null
+package diagnostic
+
+import "fmt"
+
+// StringInterface interface that has to be implemented by messages
+type StringInterface interface {
+ String() string
+}
+
+// CommandSucceed creates a success message
+func CommandSucceed(result StringInterface) *HTTPResult {
+ return &HTTPResult{
+ Message: "OK",
+ Details: result,
+ }
+}
+
+// FailCommand creates a failure message with error
+func FailCommand(err error) *HTTPResult {
+ return &HTTPResult{
+ Message: "FAIL",
+ Details: &ErrorCmd{Error: err.Error()},
+ }
+}
+
+// WrongCommand creates a wrong command response
+func WrongCommand(message, usage string) *HTTPResult {
+ return &HTTPResult{
+ Message: message,
+ Details: &UsageCmd{Usage: usage},
+ }
+}
+
+// HTTPResult Diagnostic Server HTTP result operation
+type HTTPResult struct {
+ Message string `json:"message"`
+ Details StringInterface `json:"details"`
+}
+
+func (h *HTTPResult) String() string {
+ rsp := h.Message
+ if h.Details != nil {
+ rsp += "\n" + h.Details.String()
+ }
+ return rsp
+}
+
+// UsageCmd command with usage field
+type UsageCmd struct {
+ Usage string `json:"usage"`
+}
+
+func (u *UsageCmd) String() string {
+ return "Usage: " + u.Usage
+}
+
+// StringCmd command with info string
+type StringCmd struct {
+ Info string `json:"info"`
+}
+
+func (s *StringCmd) String() string {
+ return s.Info
+}
+
+// ErrorCmd command with error
+type ErrorCmd struct {
+ Error string `json:"error"`
+}
+
+func (e *ErrorCmd) String() string {
+ return "Error: " + e.Error
+}
+
+// TableObj network db table object
+type TableObj struct {
+ Length int `json:"size"`
+ Elements []StringInterface `json:"entries"`
+}
+
+func (t *TableObj) String() string {
+ output := fmt.Sprintf("total entries: %d\n", t.Length)
+ for _, e := range t.Elements {
+ output += e.String()
+ }
+ return output
+}
+
+// PeerEntryObj entry in the networkdb peer table
+type PeerEntryObj struct {
+ Index int `json:"-"`
+ Name string `json:"-=name"`
+ IP string `json:"ip"`
+}
+
+func (p *PeerEntryObj) String() string {
+ return fmt.Sprintf("%d) %s -> %s\n", p.Index, p.Name, p.IP)
+}
+
+// TableEntryObj network db table entry object
+type TableEntryObj struct {
+ Index int `json:"-"`
+ Key string `json:"key"`
+ Value string `json:"value"`
+ Owner string `json:"owner"`
+}
+
+func (t *TableEntryObj) String() string {
+ return fmt.Sprintf("%d) k:`%s` -> v:`%s` owner:`%s`\n", t.Index, t.Key, t.Value, t.Owner)
+}
+
+// TableEndpointsResult fully typed message for proper unmarshaling on the client side
+type TableEndpointsResult struct {
+ TableObj
+ Elements []TableEntryObj `json:"entries"`
+}
+
+// TablePeersResult fully typed message for proper unmarshaling on the client side
+type TablePeersResult struct {
+ TableObj
+ Elements []PeerEntryObj `json:"entries"`
+}
+
+// NetworkStatsResult network db stats related to entries and queue len for a network
+type NetworkStatsResult struct {
+ Entries int `json:"entries"`
+ QueueLen int `jsoin:"qlen"`
+}
+
+func (n *NetworkStatsResult) String() string {
+ return fmt.Sprintf("entries: %d, qlen: %d\n", n.Entries, n.QueueLen)
+}
--- /dev/null
+package discoverapi
+
+// Discover is an interface to be implemented by the component interested in receiving discover events
+// like new node joining the cluster or datastore updates
+type Discover interface {
+ // DiscoverNew is a notification for a new discovery event, Example:a new node joining a cluster
+ DiscoverNew(dType DiscoveryType, data interface{}) error
+
+ // DiscoverDelete is a notification for a discovery delete event, Example:a node leaving a cluster
+ DiscoverDelete(dType DiscoveryType, data interface{}) error
+}
+
+// DiscoveryType represents the type of discovery element the DiscoverNew function is invoked on
+type DiscoveryType int
+
+const (
+ // NodeDiscovery represents Node join/leave events provided by discovery
+ NodeDiscovery = iota + 1
+ // DatastoreConfig represents an add/remove datastore event
+ DatastoreConfig
+ // EncryptionKeysConfig represents the initial key(s) for performing datapath encryption
+ EncryptionKeysConfig
+ // EncryptionKeysUpdate represents an update to the datapath encryption key(s)
+ EncryptionKeysUpdate
+)
+
+// NodeDiscoveryData represents the structure backing the node discovery data json string
+type NodeDiscoveryData struct {
+ Address string
+ BindAddress string
+ Self bool
+}
+
+// DatastoreConfigData is the data for the datastore update event message
+type DatastoreConfigData struct {
+ Scope string
+ Provider string
+ Address string
+ Config interface{}
+}
+
+// DriverEncryptionConfig contains the initial datapath encryption key(s)
+// Key in first position is the primary key, the one to be used in tx.
+// Original key and tag types are []byte and uint64
+type DriverEncryptionConfig struct {
+ Keys [][]byte
+ Tags []uint64
+}
+
+// DriverEncryptionUpdate carries an update to the encryption key(s) as:
+// a new key and/or set a primary key and/or a removal of an existing key.
+// Original key and tag types are []byte and uint64
+type DriverEncryptionUpdate struct {
+ Key []byte
+ Tag uint64
+ Primary []byte
+ PrimaryTag uint64
+ Prune []byte
+ PruneTag uint64
+}
--- /dev/null
+Bridge Driver
+=============
+
+The bridge driver is an implementation that uses Linux Bridging and iptables to provide connectivity for containers
+It creates a single bridge, called `docker0` by default, and attaches a `veth pair` between the bridge and every endpoint.
+
+## Configuration
+
+The bridge driver supports configuration through the Docker Daemon flags.
+
+## Usage
+
+This driver is supported for the default "bridge" network only and it cannot be used for any other networks.
--- /dev/null
+Design
+======
+
+The vision and goals of libnetwork are highlighted in [roadmap](../ROADMAP.md).
+This document describes how libnetwork has been designed in order to achieve this.
+Requirements for individual releases can be found on the [Project Page](https://github.com/docker/libnetwork/wiki).
+
+Many of the design decisions are inspired by the learnings from the Docker networking design as of Docker v1.6.
+Please refer to this [Docker v1.6 Design](legacy.md) document for more information on networking design as of Docker v1.6.
+
+## Goal
+
+libnetwork project will follow Docker and Linux philosophy of developing small, highly modular and composable tools that work well independently.
+Libnetwork aims to satisfy that composable need for Networking in Containers.
+
+## The Container Network Model
+
+Libnetwork implements Container Network Model (CNM) which formalizes the steps required to provide networking for containers while providing an abstraction that can be used to support multiple network drivers. The CNM is built on 3 main components (shown below)
+
+
+
+**Sandbox**
+
+A Sandbox contains the configuration of a container's network stack.
+This includes management of the container's interfaces, routing table and DNS settings.
+An implementation of a Sandbox could be a Linux Network Namespace, a FreeBSD Jail or other similar concept.
+A Sandbox may contain *many* endpoints from *multiple* networks.
+
+**Endpoint**
+
+An Endpoint joins a Sandbox to a Network.
+An implementation of an Endpoint could be a `veth` pair, an Open vSwitch internal port or similar.
+An Endpoint can belong to *only one* network but may only belong to *one* Sandbox.
+
+**Network**
+
+A Network is a group of Endpoints that are able to communicate with each-other directly.
+An implementation of a Network could be a Linux bridge, a VLAN, etc.
+Networks consist of *many* endpoints.
+
+## CNM Objects
+
+**NetworkController**
+`NetworkController` object provides the entry-point into libnetwork that exposes simple APIs for the users (such as Docker Engine) to allocate and manage Networks. libnetwork supports multiple active drivers (both inbuilt and remote). `NetworkController` allows user to bind a particular driver to a given network.
+
+**Driver**
+`Driver` is not a user visible object, but drivers provides the actual implementation that makes network work. `NetworkController` however provides an API to configure any specific driver with driver-specific options/labels that is transparent to libnetwork, but can be handled by the drivers directly. Drivers can be both inbuilt (such as Bridge, Host, None & overlay) and remote (from plugin providers) to satisfy various usecases & deployment scenarios. At this point, the Driver owns a network and is responsible for managing the network (including IPAM, etc.). This can be improved in the future by having multiple drivers participating in handling various network management functionalities.
+
+**Network**
+`Network` object is an implementation of the `CNM : Network` as defined above. `NetworkController` provides APIs to create and manage `Network` object. Whenever a `Network` is created or updated, the corresponding `Driver` will be notified of the event. LibNetwork treats `Network` object at an abstract level to provide connectivity between a group of end-points that belong to the same network and isolate from the rest. The Driver performs the actual work of providing the required connectivity and isolation. The connectivity can be within the same host or across multiple-hosts. Hence `Network` has a global scope within a cluster.
+
+**Endpoint**
+`Endpoint` represents a Service Endpoint. It provides the connectivity for services exposed by a container in a network with other services provided by other containers in the network. `Network` object provides APIs to create and manage endpoint. An endpoint can be attached to only one network. `Endpoint` creation calls are made to the corresponding `Driver` which is responsible for allocating resources for the corresponding `Sandbox`. Since Endpoint represents a Service and not necessarily a particular container, `Endpoint` has a global scope within a cluster as well.
+
+**Sandbox**
+`Sandbox` object represents container's network configuration such as ip-address, mac-address, routes, DNS entries. A `Sandbox` object is created when the user requests to create an endpoint on a network. The `Driver` that handles the `Network` is responsible to allocate the required network resources (such as ip-address) and pass the info called `SandboxInfo` back to libnetwork. libnetwork will make use of OS specific constructs (example: netns for Linux) to populate the network configuration into the containers that is represented by the `Sandbox`. A `Sandbox` can have multiple endpoints attached to different networks. Since `Sandbox` is associated with a particular container in a given host, it has a local scope that represents the Host that the Container belong to.
+
+**CNM Attributes**
+
+***Options***
+`Options` provides a generic and flexible mechanism to pass `Driver` specific configuration option from the user to the `Driver` directly. `Options` are just key-value pairs of data with `key` represented by a string and `value` represented by a generic object (such as golang `interface{}`). Libnetwork will operate on the `Options` ONLY if the `key` matches any of the well-known `Label` defined in the `net-labels` package. `Options` also encompasses `Labels` as explained below. `Options` are generally NOT end-user visible (in UI), while `Labels` are.
+
+***Labels***
+`Labels` are very similar to `Options` & in fact they are just a subset of `Options`. `Labels` are typically end-user visible and are represented in the UI explicitly using the `--labels` option. They are passed from the UI to the `Driver` so that `Driver` can make use of it and perform any `Driver` specific operation (such as a subnet to allocate IP-Addresses from in a Network).
+
+## CNM Lifecycle
+
+Consumers of the CNM, like Docker for example, interact through the CNM Objects and its APIs to network the containers that they manage.
+
+1. `Drivers` registers with `NetworkController`. Build-in drivers registers inside of LibNetwork, while remote Drivers registers with LibNetwork via Plugin mechanism. (*plugin-mechanism is WIP*). Each `driver` handles a particular `networkType`.
+
+2. `NetworkController` object is created using `libnetwork.New()` API to manage the allocation of Networks and optionally configure a `Driver` with driver specific `Options`.
+
+3. `Network` is created using the controller's `NewNetwork()` API by providing a `name` and `networkType`. `networkType` parameter helps to choose a corresponding `Driver` and binds the created `Network` to that `Driver`. From this point, any operation on `Network` will be handled by that `Driver`.
+
+4. `controller.NewNetwork()` API also takes in optional `options` parameter which carries Driver-specific options and `Labels`, which the Drivers can make use of for its purpose.
+
+5. `network.CreateEndpoint()` can be called to create a new Endpoint in a given network. This API also accepts optional `options` parameter which drivers can make use of. These 'options' carry both well-known labels and driver-specific labels. Drivers will in turn be called with `driver.CreateEndpoint` and it can choose to reserve IPv4/IPv6 addresses when an `Endpoint` is created in a `Network`. The `Driver` will assign these addresses using `InterfaceInfo` interface defined in the `driverapi`. The IP/IPv6 are needed to complete the endpoint as service definition along with the ports the endpoint exposes since essentially a service endpoint is nothing but a network address and the port number that the application container is listening on.
+
+6. `endpoint.Join()` can be used to attach a container to an `Endpoint`. The Join operation will create a `Sandbox` if it doesn't exist already for that container. The Drivers can make use of the Sandbox Key to identify multiple endpoints attached to a same container. This API also accepts optional `options` parameter which drivers can make use of.
+ * Though it is not a direct design issue of LibNetwork, it is highly encouraged to have users like `Docker` to call the endpoint.Join() during Container's `Start()` lifecycle that is invoked *before* the container is made operational. As part of Docker integration, this will be taken care of.
+ * One of a FAQ on endpoint join() API is that, why do we need an API to create an Endpoint and another to join the endpoint.
+ - The answer is based on the fact that Endpoint represents a Service which may or may not be backed by a Container. When an Endpoint is created, it will have its resources reserved so that any container can get attached to the endpoint later and get a consistent networking behaviour.
+
+7. `endpoint.Leave()` can be invoked when a container is stopped. The `Driver` can cleanup the states that it allocated during the `Join()` call. LibNetwork will delete the `Sandbox` when the last referencing endpoint leaves the network. But LibNetwork keeps hold of the IP addresses as long as the endpoint is still present and will be reused when the container(or any container) joins again. This ensures that the container's resources are reused when they are Stopped and Started again.
+
+8. `endpoint.Delete()` is used to delete an endpoint from a network. This results in deleting an endpoint and cleaning up the cached `sandbox.Info`.
+
+9. `network.Delete()` is used to delete a network. LibNetwork will not allow the delete to proceed if there are any existing endpoints attached to the Network.
+
+
+## Implementation Details
+
+### Networks & Endpoints
+
+LibNetwork's Network and Endpoint APIs are primarily for managing the corresponding Objects and book-keeping them to provide a level of abstraction as required by the CNM. It delegates the actual implementation to the drivers which realize the functionality as promised in the CNM. For more information on these details, please see [the drivers section](#drivers)
+
+### Sandbox
+
+Libnetwork provides a framework to implement of a Sandbox in multiple operating systems. Currently we have implemented Sandbox for Linux using `namespace_linux.go` and `configure_linux.go` in `sandbox` package.
+This creates a Network Namespace for each sandbox which is uniquely identified by a path on the host filesystem.
+Netlink calls are used to move interfaces from the global namespace to the Sandbox namespace.
+Netlink is also used to manage the routing table in the namespace.
+
+## Drivers
+
+## API
+
+Drivers are essentially an extension of libnetwork and provide the actual implementation for all of the LibNetwork APIs defined above. Hence there is an 1-1 correspondence for all the `Network` and `Endpoint` APIs, which includes :
+* `driver.Config`
+* `driver.CreateNetwork`
+* `driver.DeleteNetwork`
+* `driver.CreateEndpoint`
+* `driver.DeleteEndpoint`
+* `driver.Join`
+* `driver.Leave`
+
+These Driver facing APIs make use of unique identifiers (`networkid`,`endpointid`,...) instead of names (as seen in user-facing APIs).
+
+The APIs are still work in progress and there can be changes to these based on the driver requirements especially when it comes to Multi-host networking.
+
+### Driver semantics
+
+ * `Driver.CreateEndpoint`
+
+This method is passed an interface `EndpointInfo`, with methods `Interface` and `AddInterface`.
+
+If the value returned by `Interface` is non-nil, the driver is expected to make use of the interface information therein (e.g., treating the address or addresses as statically supplied), and must return an error if it cannot. If the value is `nil`, the driver should allocate exactly one _fresh_ interface, and use `AddInterface` to record them; or return an error if it cannot.
+
+It is forbidden to use `AddInterface` if `Interface` is non-nil.
+
+## Implementations
+
+Libnetwork includes the following driver packages:
+
+- null
+- bridge
+- overlay
+- remote
+
+### Null
+
+The null driver is a `noop` implementation of the driver API, used only in cases where no networking is desired. This is to provide backward compatibility to the Docker's `--net=none` option.
+
+### Bridge
+
+The `bridge` driver provides a Linux-specific bridging implementation based on the Linux Bridge.
+For more details, please [see the Bridge Driver documentation](bridge.md).
+
+### Overlay
+
+The `overlay` driver implements networking that can span multiple hosts using overlay network encapsulations such as VXLAN.
+For more details on its design, please see the [Overlay Driver Design](overlay.md).
+
+### Remote
+
+The `remote` package does not provide a driver, but provides a means of supporting drivers over a remote transport.
+This allows a driver to be written in a language of your choice.
+For further details, please see the [Remote Driver Design](remote.md).
--- /dev/null
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#ffffff","width":328,"height":292,"nodeIndex":215,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":16,"y":21.51999694824218},"max":{"x":328,"y":291.5}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":241.0,"y":36.0,"rotation":0.0,"id":199,"width":73.00000000000003,"height":40.150000000000006,"uid":"com.gliffy.shape.network.network_v4.business.router","order":42,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.router","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":85.0,"y":50.0,"rotation":0.0,"id":150,"width":211.0,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.1159999999999997,6.359996948242184],[85.55799999999999,6.359996948242184],[85.55799999999999,62.0],[84.0,62.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":22.803646598905374,"y":21.51999694824218,"rotation":0.0,"id":134,"width":64.31235340109463,"height":90.0,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":44,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":89.0,"y":22.199996948242188,"rotation":0.0,"id":187,"width":105.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"\">eth1 172.16.86.0/24</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":147.0,"y":50.0,"rotation":0.0,"id":196,"width":211.0,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":41,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":199,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-82.00001598011289,6.075000000000003],[94.0,6.075000000000003]],"lockSegments":{"1":true},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":210.0,"y":80.19999694824219,"rotation":0.0,"id":207,"width":120.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":43,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">Network Router</span></p><p style=\"text-align:center;\"><span style=\"\">172.16.86.1</span><span style=\"\">/24</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":27.38636363636374,"y":108.14285409109937,"rotation":0.0,"id":129,"width":262.0,"height":124.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#929292","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":33.0,"y":157.96785409109907,"rotation":0.0,"id":114,"width":150.0,"height":60.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":1,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.9951060358893704,"rotation":0.0,"id":95,"width":62.0,"height":36.17618270799329,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":6,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":3.2300163132136848,"rotation":0.0,"id":96,"width":3.719999999999998,"height":29.7161500815659,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":15,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":99,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":99,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8599999999999994,-1.2920065252854727],[1.8599999999999994,31.0081566068514]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":51.46,"y":3.2300163132136848,"rotation":0.0,"id":97,"width":1.2156862745098034,"height":31.008156606851365,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.292006525285804],[-1.4193795664340882,31.008156606851536]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":9.919999999999993,"y":1.5073409461663854,"rotation":0.0,"id":98,"width":1.239999999999999,"height":31.008156606851365,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":9,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.4306688417619762],[2.0393795664339223,32.73083197389853]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":1.9380097879282103,"rotation":0.0,"id":99,"width":62.0,"height":32.300163132136866,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":4,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":38.326264274062034,"rotation":0.0,"id":112,"width":150.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":17,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-family:Arial;font-size:12px;\"><span style=\"\">container1</span></span></p><p style=\"text-align:center;\"><span style=\"font-family:Arial;font-size:12px;\"><span style=\"\">172.16.86.2/24</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":124.0,"y":157.96785409109907,"rotation":0.0,"id":115,"width":150.0,"height":58.99999999999999,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":34,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.94518760195788,"rotation":0.0,"id":116,"width":62.0,"height":35.573246329526725,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":22,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":3.1761827079934557,"rotation":0.0,"id":117,"width":3.719999999999998,"height":29.220880913539798,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":31,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":120,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":120,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.2704730831974018],[1.8600000000000136,30.49135399673719]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":51.46,"y":3.1761827079934557,"rotation":0.0,"id":118,"width":1.2156862745098034,"height":30.49135399673717,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":28,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.2704730831977067],[-1.4193795664340882,30.491353996737335]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":9.919999999999993,"y":1.482218597063612,"rotation":0.0,"id":119,"width":1.239999999999999,"height":30.49135399673717,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.42349102773260977],[2.0393795664339223,32.185318107666895]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":1.9057096247960732,"rotation":0.0,"id":120,"width":62.0,"height":31.76182707993458,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":37.45415986949433,"rotation":0.0,"id":121,"width":150.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":33,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-family:Arial;font-size:12px;\"><span style=\"\">container2</span></span></p><p style=\"text-align:center;\"><span style=\"\">172.16.86.3/24</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":102.0,"y":130.1999969482422,"rotation":0.0,"id":130,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">pub_net (eth0)</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":93.0,"y":92.69999694824219,"rotation":0.0,"id":140,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":36,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\"><br /></span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":14.0,"y":114.19999694824219,"rotation":0.0,"id":142,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":37,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-family:Arial;font-size:12px;\"><span style=\"\">Docker Host</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":71.0,"y":235.5,"rotation":0.0,"id":184,"width":196.0,"height":56.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":39,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"\">docker network create -d macvlan \\</span></p><p style=\"text-align:left;\"><span style=\"\"> --subnet=172.16.86.0/24 \\</span></p><p style=\"text-align:left;\"><span style=\"\"> --gateway=172.16.86.1 \\</span></p><p style=\"text-align:left;\"><span style=\"\"> -o parent=eth1 pub_net</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":45}],"shapeStyles":{},"lineStyles":{"global":{"stroke":"#999999","strokeWidth":6,"orthoMode":1}},"textStyles":{"global":{"bold":true,"face":"Arial","size":"12px","color":"#000000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.network.network_v4.home","com.gliffy.libraries.network.network_v4.business","com.gliffy.libraries.network.network_v4.rack","com.gliffy.libraries.network.network_v3.home","com.gliffy.libraries.network.network_v3.business","com.gliffy.libraries.network.network_v3.rack"],"lastSerialized":1457586216662,"analyticsProduct":"Confluence"},"embeddedResources":{"index":0,"resources":[]}}
\ No newline at end of file
--- /dev/null
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="348" height="311.5"><style xmlns="http://www.w3.org/1999/xhtml"></style><defs><linearGradient id="ddeLbvFrpPPd" x1="0px" x2="0px" y1="100px" y2="-50px" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#3d85c6"/><stop offset="1" stop-color="#FFFFFF"/></linearGradient><linearGradient id="dLREXEIeFVSa" x1="0px" x2="0px" y1="100px" y2="-50px" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#3d85c6"/><stop offset="1" stop-color="#FFFFFF"/></linearGradient></defs><g transform="translate(0,0)"><g><rect fill="#ffffff" stroke="none" x="0" y="0" width="348" height="311.5"/></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,27.38636363636374,108.14285409109937)"><g><g transform="translate(0,0) scale(3.742857142857143,2.8181818181818183)"><g><g><g><path fill="#929292" stroke="rgb(0,0,0)" d="M 58.97 19.094 C 58.977 18.895 59 18.7 59 18.5 C 59 8.283 50.717 0 40.5 0 C 33.11 0 26.751 4.344 23.787 10.607 C 22.275 9.593 20.458 9 18.5 9 C 13.5 9 9.41 12.866 9.037 17.771 C 3.778 19.616 0 24.61 0 30.5 C 0 37.787 5.778 43.71 13 43.975 L 13 44 L 58 44 L 58 43.975 C 64.671 43.71 70 38.235 70 31.5 C 70 25.095 65.18 19.822 58.97 19.094 Z M 58 41.975 L 58 42 L 13 42 L 13 41.975 C 6.883 41.711 2 36.683 2 30.5 C 2 24.994 5.872 20.398 11.039 19.271 C 11.013 19.017 11 18.76 11 18.5 C 11 14.357 14.358 11 18.5 11 C 21.017 11 23.239 12.244 24.6 14.146 C 26.512 7.15 32.897 2 40.5 2 C 49.613 2 57 9.388 57 18.5 C 57 19.353 56.914 20.183 56.79 21 L 58 21 L 58 21.025 C 63.565 21.288 68 25.87 68 31.5 C 68 37.13 63.565 41.712 58 41.975 Z" stroke-opacity="0" stroke-miterlimit="10"/></g></g></g></g></g></g><g transform="translate(0,0) matrix(1,0,0,1,77,162.90096991491666)"><g><g transform="translate(0,0) scale(0.62,0.32300163132136867)"><g><path fill="url(#ddeLbvFrpPPd)" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(1.6129032258064517,3.0959595959596116)"><path fill="none" stroke="none" d="M 0 0 L 62 0 Q 62 0 62 0 L 62 32.300163132136866 Q 62 32.300163132136866 62 32.300163132136866 L 0 32.300163132136866 Q 0 32.300163132136866 0 32.300163132136866 L 0 0 Q 0 0 0 0 Z"/><path fill="url(#ddeLbvFrpPPd)" stroke="#6fa8dc" d="M 0 0 M 0 0 L 62 0 Q 62 0 62 0 L 62 32.300163132136866 Q 62 32.300163132136866 62 32.300163132136866 L 0 32.300163132136866 Q 0 32.300163132136866 0 32.300163132136866 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g></g><g transform="matrix(1,0,0,1,84.45937956643391,158.4009699149168)"><g transform="translate(0,0)"><g transform="translate(-86.91999999999999,-162.47030107315481) translate(2.4606204335660777,4.069331158238015) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 88.95937956643391 162.9009699149168 L 88.95937956643391 195.20113304705336" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,122.54062043356592,158.40096991491632)"><g transform="translate(0,0)"><g transform="translate(-128.46,-164.19297644020213) translate(5.919379566434088,5.792006525285814) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 127.04062043356592 162.90096991491632 L 127.04062043356592 195.20113304705367" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,103.5,158.40096991491666)"><g transform="translate(0,0)"><g transform="translate(-106.14,-164.19297644020213) translate(2.6400000000000006,5.792006525285473) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 108 162.90096991491666 L 108 195.20113304705353" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,35,196)"><g transform="translate(156,42) matrix(1,0,0,1,0,0) translate(-156,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="147" height="28" fill-opacity="0"/></g></g><g transform="translate(156,42) matrix(1,0,0,1,0,0) translate(-156,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="147" height="14" fill-opacity="0"/></g></g><g transform="translate(156,42) matrix(1,0,0,1,0,0) translate(-156,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="45" y="0" width="57" height="14" fill-opacity="0"/></g></g><g transform="translate(156,42) matrix(1,0,0,1,0,0) translate(-156,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="45" y="0" width="57" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="45" y="12">container1</text></g><g transform="translate(156,42) matrix(1,0,0,1,0,0) translate(-156,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="147" height="14" fill-opacity="0"/></g></g><g transform="translate(156,42) matrix(1,0,0,1,0,0) translate(-156,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="33" y="14" width="81" height="14" fill-opacity="0"/></g></g><g transform="translate(156,42) matrix(1,0,0,1,0,0) translate(-156,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="33" y="14" width="81" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="33" y="26">172</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="53" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="57" y="26">16</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="70" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="74" y="26">86</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="87" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="90" y="26">2</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="97" y="26">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="100" y="26">24</text></g></g><g transform="translate(0,0) matrix(1,0,0,1,168,162.818751317853)"><g><g transform="translate(0,0) scale(0.62,0.3176182707993458)"><g><path fill="url(#dLREXEIeFVSa)" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(1.6129032258064517,3.148433487416555)"><path fill="none" stroke="none" d="M 0 0 L 62 0 Q 62 0 62 0 L 62 31.76182707993458 Q 62 31.76182707993458 62 31.76182707993458 L 0 31.76182707993458 Q 0 31.76182707993458 0 31.76182707993458 L 0 0 Q 0 0 0 0 Z"/><path fill="url(#dLREXEIeFVSa)" stroke="#6fa8dc" d="M 0 0 M 0 0 L 62 0 Q 62 0 62 0 L 62 31.76182707993458 Q 62 31.76182707993458 62 31.76182707993458 L 0 31.76182707993458 Q 0 31.76182707993458 0 31.76182707993458 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g></g><g transform="matrix(1,0,0,1,175.4593795664339,158.31875131785318)"><g transform="translate(0,0)"><g transform="translate(-177.92,-162.39526029012058) translate(2.4606204335660777,4.076508972267391) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 179.9593795664339 162.81875131785318 L 179.9593795664339 194.58057839778746" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,213.54062043356592,158.3187513178527)"><g transform="translate(0,0)"><g transform="translate(-219.46,-164.08922440105042) translate(5.919379566434088,5.7704730831977145) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 218.04062043356592 162.8187513178527 L 218.04062043356592 194.58057839778775" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,194.5,158.318751317853)"><g transform="translate(0,0)"><g transform="translate(-197.14,-164.08922440105042) translate(2.6399999999999864,5.770473083197402) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 199 162.818751317853 L 199 194.5805783977876" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,126,195)"><g transform="translate(123,28) matrix(1,0,0,1,0,0) translate(-123,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="147" height="28" fill-opacity="0"/></g></g><g transform="translate(123,28) matrix(1,0,0,1,0,0) translate(-123,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="147" height="14" fill-opacity="0"/></g></g><g transform="translate(123,28) matrix(1,0,0,1,0,0) translate(-123,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="45" y="0" width="57" height="14" fill-opacity="0"/></g></g><g transform="translate(123,28) matrix(1,0,0,1,0,0) translate(-123,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="45" y="0" width="57" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="45" y="12">container2</text></g><g transform="translate(123,28) matrix(1,0,0,1,0,0) translate(-123,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="147" height="14" fill-opacity="0"/></g></g><g transform="translate(123,28) matrix(1,0,0,1,0,0) translate(-123,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="33" y="14" width="81" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="33" y="26">172</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="53" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="57" y="26">16</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="70" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="74" y="26">86</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="87" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="90" y="26">3</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="97" y="26">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="100" y="26">24</text></g></g><g transform="matrix(1,0,0,1,104,130)"><g transform="translate(34,0) matrix(1,0,0,1,0,0) translate(-34,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="147" height="14" fill-opacity="0"/></g></g><g transform="translate(34,0) matrix(1,0,0,1,0,0) translate(-34,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="147" height="14" fill-opacity="0"/></g></g><g transform="translate(34,0) matrix(1,0,0,1,0,0) translate(-34,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="34" y="0" width="79" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="34" y="12">pub_net</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="78" y="12"> (</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="85" y="12">eth0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="109" y="12">)</text></g></g><g transform="matrix(1,0,0,1,95,100)"><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="147" height="14" fill-opacity="0"/></g></g><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g/></g><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g><rect fill="rgb(0,0,0)" stroke="none" x="74" y="-7" width="1" height="14" fill-opacity="0"/></g></g><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g><rect fill="rgb(0,0,0)" stroke="none" x="74" y="-7" width="1" height="14" fill-opacity="0"/></g></g><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="1" height="14" fill-opacity="0"/></g></g></g><g transform="matrix(1,0,0,1,16,114)"><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="75" height="14" fill-opacity="0"/></g></g><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="75" height="14" fill-opacity="0"/></g></g><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="4" y="0" width="67" height="14" fill-opacity="0"/></g></g><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="4" y="0" width="67" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="4" y="12">Docker</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="46" y="12">Host</text></g></g><g transform="matrix(1,0,0,1,81.616,49.859996948242184)"><g transform="translate(0,0)"><g transform="translate(-85,-50) translate(3.3840000000000003,0.1400030517578159) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#999999" d="M 88.116 56.359996948242184 L 160.558 56.359996948242184 Q 170.558 56.359996948242184 170.558 66.35999694824218 L 170.558 102 Q 170.558 112 169.779 112 L 169 112" stroke-miterlimit="10" stroke-width="6"/></g></g></g></g><g transform="matrix(1,0,0,1,73,236)"><g transform="translate(0,168) matrix(1,0,0,1,0,0) translate(0,-168)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="193" height="56" fill-opacity="0"/></g></g><g transform="translate(0,168) matrix(1,0,0,1,0,0) translate(0,-168)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="193" height="14" fill-opacity="0"/></g></g><g transform="translate(0,168) matrix(1,0,0,1,0,0) translate(0,-168)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="187" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="0" y="12">docker</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="39" y="12">network</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="85" y="12">create</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="118" y="12"> -</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="125" y="12">d</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="135" y="12">macvlan</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="180" y="12"> \</text></g><g transform="translate(0,168) matrix(1,0,0,1,0,0) translate(0,-168)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="193" height="14" fill-opacity="0"/></g></g><g transform="translate(0,168) matrix(1,0,0,1,0,0) translate(0,-168)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="152" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="13" y="26">--</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="21" y="26">subnet</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="57" y="26">=</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="64" y="26">172</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="84" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="88" y="26">16</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="101" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="104" y="26">86</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="118" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="121" y="26">0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="128" y="26">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="131" y="26">24</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="144" y="26"> \</text></g><g transform="translate(0,168) matrix(1,0,0,1,0,0) translate(0,-168)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="28" width="193" height="14" fill-opacity="0"/></g></g><g transform="translate(0,168) matrix(1,0,0,1,0,0) translate(0,-168)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="28" width="147" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="13" y="40">--</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="21" y="40">gateway</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="66" y="40">=</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="73" y="40">172</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="93" y="40">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="96" y="40">16</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="110" y="40">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="113" y="40">86</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="126" y="40">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="130" y="40">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="136" y="40">  \</text></g><g transform="translate(0,168) matrix(1,0,0,1,0,0) translate(0,-168)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="42" width="193" height="14" fill-opacity="0"/></g></g><g transform="translate(0,168) matrix(1,0,0,1,0,0) translate(0,-168)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="42" width="139" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="13" y="54">-</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="17" y="54">o</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="27" y="54">parent</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="61" y="54">=</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="68" y="54">eth1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="95" y="54">pub_net</text></g></g><g transform="matrix(1,0,0,1,91,22)"><g transform="translate(0,0) matrix(1,0,0,1,0,0) translate(0,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="102" height="28" fill-opacity="0"/></g></g><g transform="translate(0,0) matrix(1,0,0,1,0,0) translate(0,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="102" height="28" fill-opacity="0"/></g></g><g transform="translate(0,0) matrix(1,0,0,1,0,0) translate(0,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="81" height="28" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="0" y="12">eth1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="0" y="26">172</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="20" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="23" y="26">16</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="37" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="40" y="26">86</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="53" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="57" y="26">0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="63" y="26">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="67" y="26">24</text></g></g><g transform="matrix(1,0,0,1,58.49998401988711,49.575)"><g transform="translate(0,0)"><g transform="translate(-147,-50) translate(88.50001598011289,0.42499999999999716) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#999999" d="M 64.99998401988711 56.075 L 241 56.075" stroke-miterlimit="10" stroke-width="6"/></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,241,36)"><g><g transform="translate(0,0) scale(0.6083333333333336,0.6104050109462419)"><g><g><path fill="#3966A0" stroke="rgb(0,0,0)" d="M 102.635 0 C 93.815 0 86.371 6.797 85.395 15.43 L 4.554 15.43 C 2.043 15.431 0 17.473 0 19.984 L 0 53.338 C 0 55.849 2.043 57.892 4.554 57.892 L 8.016 57.892 L 8.016 65.776 L 96.471 65.776 L 96.471 57.892 L 99.672 57.892 C 102.184 57.892 104.227 55.849 104.227 53.338 L 104.227 34.655 C 107.211 34.382 109.971 33.334 112.331 31.735 L 112.331 31.735 C 112.785 31.428 113.223 31.1 113.643 30.753 C 113.655 30.744 113.665 30.734 113.676 30.725 C 114.093 30.38 114.492 30.017 114.875 29.635 C 114.875 29.635 114.887 29.623 114.893 29.617 C 118.039 26.471 120 22.143 120 17.365 C 120 7.79 112.21 0 102.635 0 Z" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 102.688 3.191 C 94.836 3.191 88.45 9.579 88.45 17.43 C 88.45 25.281 94.838 31.669 102.688 31.669 C 110.54 31.669 116.926 25.281 116.926 17.43 C 116.926 9.579 110.539 3.191 102.688 3.191 Z M 97.377 20.901 C 97.118 20.901 96.859 20.802 96.662 20.605 C 96.267 20.21 96.267 19.569 96.662 19.174 L 97.412 18.424 L 91.228 18.424 C 90.669 18.424 90.216 17.971 90.216 17.412 C 90.216 16.853 90.67 16.4 91.229 16.4 L 97.414 16.4 L 96.699 15.686 C 96.303 15.291 96.303 14.65 96.699 14.255 C 97.095 13.86 97.735 13.86 98.13 14.255 L 100.571 16.696 C 100.665 16.79 100.739 16.902 100.791 17.026 C 100.894 17.273 100.894 17.552 100.791 17.799 C 100.739 17.924 100.665 18.036 100.571 18.129 L 98.093 20.605 C 97.895 20.802 97.637 20.901 97.377 20.901 Z M 105.863 25.522 L 103.422 27.963 C 103.225 28.161 102.965 28.259 102.707 28.259 C 102.698 28.259 102.689 28.254 102.68 28.254 C 102.671 28.254 102.663 28.259 102.655 28.259 C 102.305 28.259 102.012 28.07 101.83 27.801 L 99.515 25.484 C 99.119 25.089 99.119 24.448 99.515 24.053 C 99.91 23.658 100.55 23.658 100.945 24.053 L 101.644 24.752 L 101.644 9.174 L 100.945 9.873 C 100.748 10.071 100.488 10.169 100.23 10.169 C 99.97 10.169 99.711 10.07 99.515 9.873 C 99.119 9.478 99.119 8.837 99.515 8.442 L 101.954 6 C 102.35 5.605 102.988 5.605 103.385 6 L 105.864 8.478 C 106.26 8.873 106.26 9.514 105.864 9.909 C 105.667 10.107 105.408 10.205 105.149 10.205 C 104.889 10.205 104.63 10.106 104.434 9.909 L 103.667 9.143 L 103.667 24.857 L 104.434 24.091 C 104.829 23.696 105.468 23.696 105.864 24.091 C 106.258 24.486 106.258 25.126 105.863 25.522 Z M 114.148 18.46 L 107.964 18.46 L 108.677 19.174 C 109.073 19.569 109.073 20.21 108.677 20.605 C 108.48 20.803 108.22 20.901 107.961 20.901 C 107.702 20.901 107.442 20.802 107.246 20.605 L 104.808 18.165 C 104.713 18.072 104.64 17.959 104.587 17.835 C 104.485 17.588 104.485 17.309 104.587 17.062 C 104.64 16.937 104.713 16.825 104.808 16.732 L 107.284 14.255 C 107.68 13.86 108.32 13.86 108.715 14.255 C 109.11 14.65 109.11 15.291 108.715 15.686 L 107.964 16.437 L 114.149 16.437 C 114.708 16.437 115.161 16.89 115.161 17.449 C 115.16 18.008 114.707 18.46 114.148 18.46 Z" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 80.533 36.476 A 3.247 3.247 0 1 0 80.53299837650013 36.47924699945883 Z" opacity="0.5" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 9.278 35.006 L 20.39 35.006 L 20.39 24 L 9.278 24 L 9.278 35.006 Z M 10.929 25.567 L 18.819 25.567 L 18.819 31.173 L 16.536 31.173 L 16.536 33.456 L 13.213 33.456 L 13.213 31.173 L 10.93 31.173 L 10.929 25.567 L 10.929 25.567 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 24.253 35.015 L 35.365 35.015 L 35.365 24.009 L 24.253 24.009 L 24.253 35.015 Z M 25.904 25.576 L 33.794 25.576 L 33.794 31.182 L 31.51 31.182 L 31.51 33.465 L 28.187 33.465 L 28.187 31.182 L 25.904 31.182 L 25.904 25.576 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 50.3 24.009 L 39.188 24.009 L 39.188 35.014 L 50.3 35.014 L 50.3 24.009 Z M 48.729 31.173 L 46.446 31.173 L 46.446 33.456 L 43.123 33.456 L 43.123 31.173 L 40.84 31.173 L 40.84 25.567 L 48.73 25.567 L 48.729 31.173 L 48.729 31.173 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 54.123 35.023 L 65.234 35.023 L 65.234 24.018 L 54.123 24.018 L 54.123 35.023 Z M 55.774 25.567 L 63.664 25.567 L 63.664 31.173 L 61.38 31.173 L 61.38 33.456 L 58.057 33.456 L 58.057 31.173 L 55.774 31.173 L 55.774 25.567 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 9.278 49.95 L 20.39 49.95 L 20.39 38.944 L 9.278 38.944 L 9.278 49.95 Z M 10.929 40.502 L 18.819 40.502 L 18.819 46.108 L 16.536 46.108 L 16.536 48.391 L 13.213 48.391 L 13.213 46.108 L 10.93 46.108 L 10.929 40.502 L 10.929 40.502 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 24.253 49.958 L 35.365 49.958 L 35.365 38.953 L 24.253 38.953 L 24.253 49.958 Z M 25.904 40.52 L 33.794 40.52 L 33.794 46.126 L 31.51 46.126 L 31.51 48.408 L 28.187 48.408 L 28.187 46.126 L 25.904 46.126 L 25.904 40.52 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 39.228 49.966 L 50.34 49.966 L 50.34 38.962 L 39.228 38.962 L 39.228 49.966 Z M 40.839 40.511 L 48.729 40.511 L 48.729 46.117 L 46.446 46.117 L 46.446 48.4 L 43.123 48.4 L 43.123 46.117 L 40.84 46.117 L 40.839 40.511 L 40.839 40.511 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 54.123 49.966 L 65.234 49.966 L 65.234 38.962 L 54.123 38.962 L 54.123 49.966 Z M 55.774 40.511 L 63.664 40.511 L 63.664 46.117 L 61.38 46.117 L 61.38 48.4 L 58.057 48.4 L 58.057 46.117 L 55.774 46.117 L 55.774 40.511 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 11.504 57.898 L 92.919 57.898 Q 92.919 57.898 92.919 57.898 L 92.919 62.69 Q 92.919 62.69 92.919 62.69 L 11.504 62.69 Q 11.504 62.69 11.504 62.69 L 11.504 57.898 Q 11.504 57.898 11.504 57.898 Z" opacity="0.7" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 85.525 18.936 L 5.082 18.936 C 4.255 18.936 3.582 19.609 3.582 20.436 L 3.582 53.397 C 3.582 54.224 4.255 54.897 5.082 54.897 L 11.504 54.897 L 92.919 54.897 L 99.082 54.897 C 99.909 54.897 100.582 54.224 100.582 53.397 L 100.582 34.526 C 92.561 33.543 86.232 27.039 85.525 18.936 Z M 20.389 49.95 L 9.278 49.95 L 9.278 38.944 L 20.39 38.944 L 20.389 49.95 L 20.389 49.95 Z M 20.389 35.006 L 9.278 35.006 L 9.278 24 L 20.39 24 L 20.389 35.006 L 20.389 35.006 Z M 35.365 49.958 L 24.253 49.958 L 24.253 38.953 L 35.365 38.953 L 35.365 49.958 Z M 35.365 35.015 L 24.253 35.015 L 24.253 24.009 L 35.365 24.009 L 35.365 35.015 Z M 39.188 24.009 L 50.3 24.009 L 50.3 35.014 L 39.188 35.014 L 39.188 24.009 Z M 50.34 49.966 L 39.228 49.966 L 39.228 38.962 L 50.34 38.962 L 50.34 49.966 Z M 65.234 49.966 L 54.123 49.966 L 54.123 38.962 L 65.234 38.962 L 65.234 49.966 Z M 65.234 35.023 L 54.123 35.023 L 54.123 24.018 L 65.234 24.018 L 65.234 35.023 Z M 77.286 39.723 C 75.493 39.723 74.039 38.269 74.039 36.476 C 74.039 34.683 75.493 33.229 77.286 33.229 C 79.079 33.229 80.533 34.683 80.533 36.476 C 80.533 38.269 79.08 39.723 77.286 39.723 Z M 89.541 39.723 C 87.748 39.723 86.294 38.269 86.294 36.476 C 86.294 34.683 87.748 33.229 89.541 33.229 C 91.334 33.229 92.788 34.683 92.788 36.476 C 92.787 38.269 91.334 39.723 89.541 39.723 Z" opacity="0.93" stroke-opacity="0" stroke-miterlimit="10"/></g></g></g></g></g><g transform="matrix(1,0,0,1,212,80)"><g transform="translate(117,42) matrix(1,0,0,1,0,0) translate(-117,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="117" height="28" fill-opacity="0"/></g></g><g transform="translate(117,42) matrix(1,0,0,1,0,0) translate(-117,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="117" height="14" fill-opacity="0"/></g></g><g transform="translate(117,42) matrix(1,0,0,1,0,0) translate(-117,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="17" y="0" width="85" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="17" y="12">Network</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="64" y="12">Router</text></g><g transform="translate(117,42) matrix(1,0,0,1,0,0) translate(-117,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="117" height="14" fill-opacity="0"/></g></g><g transform="translate(117,42) matrix(1,0,0,1,0,0) translate(-117,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="18" y="14" width="64" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="18" y="26">172</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="38" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="42" y="26">16</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="55" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="59" y="26">86</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="72" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="75" y="26">1</text></g><g transform="translate(117,42) matrix(1,0,0,1,0,0) translate(-117,-42)"><g><rect fill="rgb(0,0,0)" stroke="none" x="82" y="14" width="18" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="82" y="26">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="85" y="26">24</text></g></g><g transform="translate(0,0) matrix(1,0,0,1,22.803646598905374,21.51999694824218)"><g><g transform="translate(0,0) scale(0.9127109745695561,0.9)"><g><g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 52.371 16.22 L -1.393 16.22 L -1.393 99.52 L 52.371 99.52" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 52.371 99.52 L 69.07 82.82 L 69.07 -0.48 L 19.98 -0.48 L -1.393 16.22 L 52.371 16.22 L 52.371 99.52 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="rgb(0,0,0)" stroke="#FFFFFF" d="M 52.371 16.22 L 69.07 -0.48" fill-opacity="0" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="rgb(0,0,0)" stroke="#FFFFFF" d="M 25.3 16.22 L 25.3 99.52" fill-opacity="0" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="rgb(0,0,0)" stroke="#FFFFFF" d="M 26.149 16.22 L 44.316 -0.48" fill-opacity="0" stroke-miterlimit="10" stroke-width="1.3594"/></g></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 3.263 23.823 L 21.317 23.823 Q 21.317 23.823 21.317 23.823 L 21.317 34.868 Q 21.317 34.868 21.317 34.868 L 3.263 34.868 Q 3.263 34.868 3.263 34.868 L 3.263 23.823 Q 3.263 23.823 3.263 23.823 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 29.156 23.823 L 47.207 23.823 Q 47.207 23.823 47.207 23.823 L 47.207 34.868 Q 47.207 34.868 47.207 34.868 L 29.156 34.868 Q 29.156 34.868 29.156 34.868 L 29.156 23.823 Q 29.156 23.823 29.156 23.823 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 3.263 64.604 L 21.317 64.604 Q 21.317 64.604 21.317 64.604 L 21.317 75.648 Q 21.317 75.648 21.317 75.648 L 3.263 75.648 Q 3.263 75.648 3.263 75.648 L 3.263 64.604 Q 3.263 64.604 3.263 64.604 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 29.156 64.604 L 47.207 64.604 Q 47.207 64.604 47.207 64.604 L 47.207 75.648 Q 47.207 75.648 47.207 75.648 L 29.156 75.648 Q 29.156 75.648 29.156 75.648 L 29.156 64.604 Q 29.156 64.604 29.156 64.604 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 3.263 82.445 L 21.317 82.445 Q 21.317 82.445 21.317 82.445 L 21.317 93.49 Q 21.317 93.49 21.317 93.49 L 3.263 93.49 Q 3.263 93.49 3.263 93.49 L 3.263 82.445 Q 3.263 82.445 3.263 82.445 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 29.156 82.445 L 47.207 82.445 Q 47.207 82.445 47.207 82.445 L 47.207 93.49 Q 47.207 93.49 47.207 93.49 L 29.156 93.49 Q 29.156 93.49 29.156 93.49 L 29.156 82.445 Q 29.156 82.445 29.156 82.445 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g></g></g></g></g></g></svg>
\ No newline at end of file
--- /dev/null
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#ffffff","width":389,"height":213,"nodeIndex":276,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":5,"y":6.6999969482421875},"max":{"x":389,"y":212.14285409109937}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":64.0,"y":36.0,"rotation":0.0,"id":216,"width":211.0,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":5.0,"strokeColor":"#e69138","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-12.0,33.0],[84.0,33.0],[84.0,86.0],[120.0,86.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":190.0,"y":32.0,"rotation":0.0,"id":254,"width":211.0,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":11,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":5.0,"strokeColor":"#f1c232","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-142.0,16.0],[54.0,16.0],[54.0,115.0],[87.0,115.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":133.38636363636374,"y":108.14285409109937,"rotation":0.0,"id":226,"width":123.00000000000001,"height":104.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#999999","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":15.147567221510933,"y":139.96785409109907,"rotation":0.0,"id":115,"width":107.40845070422536,"height":49.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":29,"lockAspectRatio":false,"lockShape":false,"children":[{"x":31.506478873239438,"y":2.4460032626429853,"rotation":0.0,"id":116,"width":44.395492957746484,"height":29.54388254486117,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":17,"lockAspectRatio":false,"lockShape":false,"children":[{"x":20.86588169014084,"y":2.637846655791175,"rotation":0.0,"id":117,"width":2.663729577464789,"height":24.268189233278818,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":26,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":120,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":120,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.3318647887324033,-1.055138662316466],[1.3318647887324033,25.3233278955953]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":36.84825915492961,"y":2.637846655791175,"rotation":0.0,"id":118,"width":1.0000000000000002,"height":25.323327895595277,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.8875219090985048,-1.0551386623167391],[-0.8875219090985048,25.323327895595412]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":7.103278873239435,"y":1.230995106035881,"rotation":0.0,"id":119,"width":1.0000000000000002,"height":25.323327895595277,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.2752008616871728,0.3517128874389471],[1.2752008616871728,26.73017944535047]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":1.5827079934747048,"rotation":0.0,"id":120,"width":44.395492957746484,"height":26.378466557911768,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":37.199347471451986,"rotation":0.0,"id":121,"width":107.40845070422536,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":28,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-family:Arial;font-size:12px;\"><span style=\"\">container1 - vlan10</span></span></p><p style=\"text-align:center;\"><span style=\"\">192.168.1.2/24</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":68.0,"y":82.69999694824219,"rotation":0.0,"id":140,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":30,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\"><br /></span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":71.0,"y":4.1999969482421875,"rotation":0.0,"id":187,"width":108.99999999999999,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">eth0 - 802.1q trunk</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":282.0,"y":8.0,"rotation":0.0,"id":199,"width":73.00000000000003,"height":40.150000000000006,"uid":"com.gliffy.shape.network.network_v4.business.router","order":32,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.router","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":62.0,"y":55.0,"rotation":0.0,"id":210,"width":211.0,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":34,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":5.0,"strokeColor":"#e06666","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-8.0,11.0],[-8.0,34.0],[26.0,34.0],[26.0,57.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":12.805718530101615,"y":11.940280333547719,"rotation":0.0,"id":134,"width":59.31028146989837,"height":83.0,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":35,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":64.0,"y":73.19999694824219,"rotation":0.0,"id":211,"width":60.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":36,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">eth0.10</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":65.0,"y":52.19999694824219,"rotation":0.0,"id":212,"width":60.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":37,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">eth0.20</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":7.386363636363733,"y":108.14285409109937,"rotation":0.0,"id":219,"width":123.00000000000001,"height":104.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#999999","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":139.1475672215109,"y":139.96785409109907,"rotation":0.0,"id":227,"width":107.40845070422536,"height":49.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":55,"lockAspectRatio":false,"lockShape":false,"children":[{"x":31.506478873239438,"y":2.4460032626429853,"rotation":0.0,"id":228,"width":44.395492957746484,"height":29.54388254486117,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":43,"lockAspectRatio":false,"lockShape":false,"children":[{"x":20.86588169014084,"y":2.637846655791175,"rotation":0.0,"id":229,"width":2.663729577464789,"height":24.268189233278818,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":52,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":232,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":232,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.3318647887323891,-1.055138662316466],[1.3318647887323891,25.3233278955953]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":36.84825915492961,"y":2.637846655791175,"rotation":0.0,"id":230,"width":1.0000000000000002,"height":25.323327895595277,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":49,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.8875219090985048,-1.0551386623167391],[-0.8875219090985048,25.323327895595412]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":7.103278873239435,"y":1.230995106035881,"rotation":0.0,"id":231,"width":1.0000000000000002,"height":25.323327895595277,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.2752008616871728,0.3517128874389471],[1.2752008616871728,26.73017944535047]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":1.5827079934747048,"rotation":0.0,"id":232,"width":44.395492957746484,"height":26.378466557911768,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":41,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":37.199347471451986,"rotation":0.0,"id":233,"width":107.40845070422536,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":54,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-family:Arial;font-size:12px;\"><span style=\"\">container2 - vlan20</span></span></p><p style=\"text-align:center;\"><span style=\"\">172.16.1.2/24</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":259.38636363636374,"y":108.14285409109937,"rotation":0.0,"id":248,"width":123.00000000000001,"height":104.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#999999","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":265.14756722151094,"y":139.96785409109907,"rotation":0.0,"id":241,"width":107.40845070422536,"height":49.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":73,"lockAspectRatio":false,"lockShape":false,"children":[{"x":31.506478873239438,"y":2.4460032626429853,"rotation":0.0,"id":242,"width":44.395492957746484,"height":29.54388254486117,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":61,"lockAspectRatio":false,"lockShape":false,"children":[{"x":20.86588169014084,"y":2.637846655791175,"rotation":0.0,"id":243,"width":2.663729577464789,"height":24.268189233278818,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":70,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":246,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":246,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.3318647887323891,-1.055138662316466],[1.3318647887323891,25.3233278955953]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":36.84825915492961,"y":2.637846655791175,"rotation":0.0,"id":244,"width":1.0000000000000002,"height":25.323327895595277,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":67,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.8875219090985048,-1.0551386623167391],[-0.8875219090985048,25.323327895595412]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":7.103278873239435,"y":1.230995106035881,"rotation":0.0,"id":245,"width":1.0000000000000002,"height":25.323327895595277,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":64,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.2752008616871728,0.3517128874389471],[1.2752008616871728,26.73017944535047]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":1.5827079934747048,"rotation":0.0,"id":246,"width":44.395492957746484,"height":26.378466557911768,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":59,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":37.199347471451986,"rotation":0.0,"id":247,"width":107.40845070422536,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":72,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-family:Arial;font-size:12px;\"><span style=\"\">container3 - vlan30</span></span></p><p style=\"text-align:center;\"><span style=\"\">10.1.1.2/16</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":65.0,"y":31.199996948242188,"rotation":0.0,"id":253,"width":60.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":74,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">eth0.30</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":44.49612211422149,"y":17.874999999999943,"rotation":0.0,"id":266,"width":275.00609168449375,"height":15.70000000000006,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":75,"lockAspectRatio":false,"lockShape":false,"children":[{"x":68.50387788577851,"y":43.12500000000006,"rotation":0.0,"id":258,"width":211.0,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":9,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-64.00387788577851,-31.924999999999997],[197.00221379871527,-31.925000000000153]],"lockSegments":{"1":true},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":68.50387788577851,"y":38.55333333333314,"rotation":0.0,"id":262,"width":211.0,"height":33.06666666666631,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-64.00387788577851,-34.053333333332965],[197.00221379871527,-34.05333333333314]],"lockSegments":{"1":true},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":70.50387788577851,"y":40.7533333333331,"rotation":0.0,"id":261,"width":211.0,"height":33.06666666666631,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":5,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#e06666","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-64.00387788577851,-34.053333333332965],[197.00221379871527,-34.05333333333314]],"lockSegments":{"1":true},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":70.50387788577851,"y":42.88666666666643,"rotation":0.0,"id":260,"width":211.0,"height":33.06666666666631,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#e69138","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-64.00387788577851,-34.053333333332965],[197.00221379871527,-34.05333333333314]],"lockSegments":{"1":true},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":73.50387788577851,"y":43.95333333333309,"rotation":0.0,"id":259,"width":211.0,"height":33.06666666666631,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":1,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#ffe599","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-64.00387788577851,-34.053333333332965],[197.00221379871527,-34.05333333333314]],"lockSegments":{"1":true},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":248.0,"y":51.19999694824219,"rotation":0.0,"id":207,"width":143.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":33,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"\">Network Router (gateway)</span></p><p style=\"text-align:left;\"><span style=\"\">vlan10 - 192.168.1.1/24</span></p><p style=\"text-align:left;\"><span style=\"\">vlan20 - </span><span style=\"\">172.16.1.1/24</span></p><p style=\"text-align:left;\"><span style=\"\">vlan30 - </span><span style=\"\">10.1.1.1/16</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":3.0,"y":88.19999694824219,"rotation":0.0,"id":272,"width":77.99999999999999,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">Docker Host</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":80}],"shapeStyles":{},"lineStyles":{"global":{"stroke":"#e06666","strokeWidth":2,"orthoMode":1}},"textStyles":{"global":{"bold":true,"face":"Arial","size":"12px","color":"#000000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.network.network_v4.home","com.gliffy.libraries.network.network_v4.business","com.gliffy.libraries.network.network_v4.rack","com.gliffy.libraries.network.network_v3.home","com.gliffy.libraries.network.network_v3.business","com.gliffy.libraries.network.network_v3.rack"],"lastSerialized":1457586821719,"analyticsProduct":"Confluence"},"embeddedResources":{"index":0,"resources":[]}}
\ No newline at end of file
--- /dev/null
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="409" height="232.14285409109937"><style xmlns="http://www.w3.org/1999/xhtml"></style><defs><linearGradient id="RKkzpzQhZTCk" x1="0px" x2="0px" y1="100px" y2="-50px" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#3d85c6"/><stop offset="1" stop-color="#FFFFFF"/></linearGradient><linearGradient id="SeALyqvahCFZ" x1="0px" x2="0px" y1="100px" y2="-50px" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#3d85c6"/><stop offset="1" stop-color="#FFFFFF"/></linearGradient><linearGradient id="KIKSmddbxdCC" x1="0px" x2="0px" y1="100px" y2="-50px" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#3d85c6"/><stop offset="1" stop-color="#FFFFFF"/></linearGradient></defs><g transform="translate(0,0)"><g><rect fill="#ffffff" stroke="none" x="0" y="0" width="409" height="232.14285409109937"/></g><g transform="matrix(1,0,0,1,49.49612211422149,23.274999999999892)"><g transform="translate(0,0)"><g transform="translate(-118,-61.828333333333035) translate(68.50387788577851,38.55333333333314) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#ffe599" d="M 53.99612211422149 27.77500000000007 L 315.0022137987153 27.774999999999892" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,46.49612211422149,22.20833333333323)"><g transform="translate(0,0)"><g transform="translate(-115,-60.76166666666637) translate(68.50387788577851,38.55333333333314) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#e69138" d="M 50.99612211422149 26.708333333333407 L 312.0022137987153 26.70833333333323" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,46.49612211422149,20.074999999999903)"><g transform="translate(0,0)"><g transform="translate(-115,-58.628333333333046) translate(68.50387788577851,38.55333333333314) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#e06666" d="M 50.99612211422149 24.57500000000008 L 312.0022137987153 24.574999999999903" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,44.49612211422149,17.874999999999943)"><g transform="translate(0,0)"><g transform="translate(-113,-56.428333333333086) translate(68.50387788577851,38.55333333333314) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#999999" d="M 48.99612211422149 22.37500000000012 L 310.0022137987153 22.374999999999943" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,44.49612211422149,24.574999999999847)"><g transform="translate(0,0)"><g transform="translate(-113,-61) translate(68.50387788577851,36.42500000000015) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#999999" d="M 48.99612211422149 29.075000000000003 L 310.0022137987153 29.074999999999847" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,46,63)"><g transform="translate(0,0)"><g transform="translate(-64,-36) translate(18,-27) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#e69138" d="M 52 69 L 138 69 Q 148 69 148 79 L 148 112 Q 148 122 158 122 L 184 122" stroke-miterlimit="10" stroke-width="5"/></g></g></g></g><g transform="matrix(1,0,0,1,42,42)"><g transform="translate(0,0)"><g transform="translate(-190,-32) translate(148,-10) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#f1c232" d="M 48 48 L 234 48 Q 244 48 244 58 L 244 137 Q 244 147 254 147 L 277 147" stroke-miterlimit="10" stroke-width="5"/></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,133.38636363636374,108.14285409109937)"><g><g transform="translate(0,0) scale(1.7571428571428573,2.3636363636363638)"><g><g><g><path fill="#999999" stroke="rgb(0,0,0)" d="M 58.97 19.094 C 58.977 18.895 59 18.7 59 18.5 C 59 8.283 50.717 0 40.5 0 C 33.11 0 26.751 4.344 23.787 10.607 C 22.275 9.593 20.458 9 18.5 9 C 13.5 9 9.41 12.866 9.037 17.771 C 3.778 19.616 0 24.61 0 30.5 C 0 37.787 5.778 43.71 13 43.975 L 13 44 L 58 44 L 58 43.975 C 64.671 43.71 70 38.235 70 31.5 C 70 25.095 65.18 19.822 58.97 19.094 Z M 58 41.975 L 58 42 L 13 42 L 13 41.975 C 6.883 41.711 2 36.683 2 30.5 C 2 24.994 5.872 20.398 11.039 19.271 C 11.013 19.017 11 18.76 11 18.5 C 11 14.357 14.358 11 18.5 11 C 21.017 11 23.239 12.244 24.6 14.146 C 26.512 7.15 32.897 2 40.5 2 C 49.613 2 57 9.388 57 18.5 C 57 19.353 56.914 20.183 56.79 21 L 58 21 L 58 21.025 C 63.565 21.288 68 25.87 68 31.5 C 68 37.13 63.565 41.712 58 41.975 Z" stroke-opacity="0" stroke-miterlimit="10"/></g></g></g></g></g></g><g transform="translate(0,0) matrix(1,0,0,1,46.65404609475037,143.99656534721677)"><g><g transform="translate(0,0) scale(0.44395492957746485,0.26378466557911767)"><g><path fill="url(#RKkzpzQhZTCk)" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.2524809014999616,3.790970933828097)"><path fill="none" stroke="none" d="M 0 0 L 44.395492957746484 0 Q 44.395492957746484 0 44.395492957746484 0 L 44.395492957746484 26.378466557911768 Q 44.395492957746484 26.378466557911768 44.395492957746484 26.378466557911768 L 0 26.378466557911768 Q 0 26.378466557911768 0 26.378466557911768 L 0 0 Q 0 0 0 0 Z"/><path fill="url(#RKkzpzQhZTCk)" stroke="#6fa8dc" d="M 0 0 M 0 0 L 44.395492957746484 0 Q 44.395492957746484 0 44.395492957746484 0 L 44.395492957746484 26.378466557911768 Q 44.395492957746484 26.378466557911768 44.395492957746484 26.378466557911768 L 0 26.378466557911768 Q 0 26.378466557911768 0 26.378466557911768 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g></g><g transform="matrix(1,0,0,1,50.53252582967698,139.4965653472169)"><g transform="translate(0,0)"><g transform="translate(-53.7573249679898,-143.64485245977795) translate(3.224799138312825,4.148287112561064) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 55.03252582967698 143.9965653472169 L 55.03252582967698 170.37503190512842" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,78.11478334058147,139.4965653472165)"><g transform="translate(0,0)"><g transform="translate(-83.50230524967998,-145.05170400953324) translate(5.38752190909851,5.55513866231675) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 82.61478334058147 143.9965653472165 L 82.61478334058147 170.37503190512865" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,64.35179257362361,139.49656534721677)"><g transform="translate(0,0)"><g transform="translate(-67.51992778489121,-145.05170400953324) translate(3.1681352112675967,5.555138662316466) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 68.85179257362361 143.99656534721677 L 68.85179257362361 170.37503190512854" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,17,177)"><g transform="translate(14,28) matrix(1,0,0,1,0,0) translate(-14,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="104" height="28" fill-opacity="0"/></g></g><g transform="translate(14,28) matrix(1,0,0,1,0,0) translate(-14,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="104" height="14" fill-opacity="0"/></g></g><g transform="translate(14,28) matrix(1,0,0,1,0,0) translate(-14,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="1" y="0" width="103" height="14" fill-opacity="0"/></g></g><g transform="translate(14,28) matrix(1,0,0,1,0,0) translate(-14,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="1" y="0" width="103" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="1" y="12">container1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="57" y="12"> -</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="68" y="12">vlan10</text></g><g transform="translate(14,28) matrix(1,0,0,1,0,0) translate(-14,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="104" height="14" fill-opacity="0"/></g></g><g transform="translate(14,28) matrix(1,0,0,1,0,0) translate(-14,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="12" y="14" width="81" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="12" y="26">192</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="32" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="36" y="26">168</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="56" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="59" y="26">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="66" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="69" y="26">2</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="76" y="26">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="79" y="26">24</text></g></g><g transform="matrix(1,0,0,1,70,90)"><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="147" height="14" fill-opacity="0"/></g></g><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g/></g><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g><rect fill="rgb(0,0,0)" stroke="none" x="74" y="-7" width="1" height="14" fill-opacity="0"/></g></g><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g><rect fill="rgb(0,0,0)" stroke="none" x="74" y="-7" width="1" height="14" fill-opacity="0"/></g></g><g transform="translate(148,-14) matrix(1,0,0,1,0,0) translate(-148,14)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="1" height="14" fill-opacity="0"/></g></g></g><g transform="matrix(1,0,0,1,73,7)"><g transform="translate(3,0) matrix(1,0,0,1,0,0) translate(-3,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="106" height="19" fill-opacity="0"/></g></g><g transform="translate(3,0) matrix(1,0,0,1,0,0) translate(-3,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="106" height="14" fill-opacity="0"/></g></g><g transform="translate(3,0) matrix(1,0,0,1,0,0) translate(-3,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="3" y="0" width="102" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="3" y="12">eth0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="26" y="12"> -</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="37" y="12">802</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="57" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="60" y="12">1q</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="77" y="12">trunk</text></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,282,8)"><g><g transform="translate(0,0) scale(0.6083333333333336,0.6104050109462419)"><g><g><path fill="#3966A0" stroke="rgb(0,0,0)" d="M 102.635 0 C 93.815 0 86.371 6.797 85.395 15.43 L 4.554 15.43 C 2.043 15.431 0 17.473 0 19.984 L 0 53.338 C 0 55.849 2.043 57.892 4.554 57.892 L 8.016 57.892 L 8.016 65.776 L 96.471 65.776 L 96.471 57.892 L 99.672 57.892 C 102.184 57.892 104.227 55.849 104.227 53.338 L 104.227 34.655 C 107.211 34.382 109.971 33.334 112.331 31.735 L 112.331 31.735 C 112.785 31.428 113.223 31.1 113.643 30.753 C 113.655 30.744 113.665 30.734 113.676 30.725 C 114.093 30.38 114.492 30.017 114.875 29.635 C 114.875 29.635 114.887 29.623 114.893 29.617 C 118.039 26.471 120 22.143 120 17.365 C 120 7.79 112.21 0 102.635 0 Z" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 102.688 3.191 C 94.836 3.191 88.45 9.579 88.45 17.43 C 88.45 25.281 94.838 31.669 102.688 31.669 C 110.54 31.669 116.926 25.281 116.926 17.43 C 116.926 9.579 110.539 3.191 102.688 3.191 Z M 97.377 20.901 C 97.118 20.901 96.859 20.802 96.662 20.605 C 96.267 20.21 96.267 19.569 96.662 19.174 L 97.412 18.424 L 91.228 18.424 C 90.669 18.424 90.216 17.971 90.216 17.412 C 90.216 16.853 90.67 16.4 91.229 16.4 L 97.414 16.4 L 96.699 15.686 C 96.303 15.291 96.303 14.65 96.699 14.255 C 97.095 13.86 97.735 13.86 98.13 14.255 L 100.571 16.696 C 100.665 16.79 100.739 16.902 100.791 17.026 C 100.894 17.273 100.894 17.552 100.791 17.799 C 100.739 17.924 100.665 18.036 100.571 18.129 L 98.093 20.605 C 97.895 20.802 97.637 20.901 97.377 20.901 Z M 105.863 25.522 L 103.422 27.963 C 103.225 28.161 102.965 28.259 102.707 28.259 C 102.698 28.259 102.689 28.254 102.68 28.254 C 102.671 28.254 102.663 28.259 102.655 28.259 C 102.305 28.259 102.012 28.07 101.83 27.801 L 99.515 25.484 C 99.119 25.089 99.119 24.448 99.515 24.053 C 99.91 23.658 100.55 23.658 100.945 24.053 L 101.644 24.752 L 101.644 9.174 L 100.945 9.873 C 100.748 10.071 100.488 10.169 100.23 10.169 C 99.97 10.169 99.711 10.07 99.515 9.873 C 99.119 9.478 99.119 8.837 99.515 8.442 L 101.954 6 C 102.35 5.605 102.988 5.605 103.385 6 L 105.864 8.478 C 106.26 8.873 106.26 9.514 105.864 9.909 C 105.667 10.107 105.408 10.205 105.149 10.205 C 104.889 10.205 104.63 10.106 104.434 9.909 L 103.667 9.143 L 103.667 24.857 L 104.434 24.091 C 104.829 23.696 105.468 23.696 105.864 24.091 C 106.258 24.486 106.258 25.126 105.863 25.522 Z M 114.148 18.46 L 107.964 18.46 L 108.677 19.174 C 109.073 19.569 109.073 20.21 108.677 20.605 C 108.48 20.803 108.22 20.901 107.961 20.901 C 107.702 20.901 107.442 20.802 107.246 20.605 L 104.808 18.165 C 104.713 18.072 104.64 17.959 104.587 17.835 C 104.485 17.588 104.485 17.309 104.587 17.062 C 104.64 16.937 104.713 16.825 104.808 16.732 L 107.284 14.255 C 107.68 13.86 108.32 13.86 108.715 14.255 C 109.11 14.65 109.11 15.291 108.715 15.686 L 107.964 16.437 L 114.149 16.437 C 114.708 16.437 115.161 16.89 115.161 17.449 C 115.16 18.008 114.707 18.46 114.148 18.46 Z" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 80.533 36.476 A 3.247 3.247 0 1 0 80.53299837650013 36.47924699945883 Z" opacity="0.5" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 9.278 35.006 L 20.39 35.006 L 20.39 24 L 9.278 24 L 9.278 35.006 Z M 10.929 25.567 L 18.819 25.567 L 18.819 31.173 L 16.536 31.173 L 16.536 33.456 L 13.213 33.456 L 13.213 31.173 L 10.93 31.173 L 10.929 25.567 L 10.929 25.567 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 24.253 35.015 L 35.365 35.015 L 35.365 24.009 L 24.253 24.009 L 24.253 35.015 Z M 25.904 25.576 L 33.794 25.576 L 33.794 31.182 L 31.51 31.182 L 31.51 33.465 L 28.187 33.465 L 28.187 31.182 L 25.904 31.182 L 25.904 25.576 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 50.3 24.009 L 39.188 24.009 L 39.188 35.014 L 50.3 35.014 L 50.3 24.009 Z M 48.729 31.173 L 46.446 31.173 L 46.446 33.456 L 43.123 33.456 L 43.123 31.173 L 40.84 31.173 L 40.84 25.567 L 48.73 25.567 L 48.729 31.173 L 48.729 31.173 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 54.123 35.023 L 65.234 35.023 L 65.234 24.018 L 54.123 24.018 L 54.123 35.023 Z M 55.774 25.567 L 63.664 25.567 L 63.664 31.173 L 61.38 31.173 L 61.38 33.456 L 58.057 33.456 L 58.057 31.173 L 55.774 31.173 L 55.774 25.567 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 9.278 49.95 L 20.39 49.95 L 20.39 38.944 L 9.278 38.944 L 9.278 49.95 Z M 10.929 40.502 L 18.819 40.502 L 18.819 46.108 L 16.536 46.108 L 16.536 48.391 L 13.213 48.391 L 13.213 46.108 L 10.93 46.108 L 10.929 40.502 L 10.929 40.502 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 24.253 49.958 L 35.365 49.958 L 35.365 38.953 L 24.253 38.953 L 24.253 49.958 Z M 25.904 40.52 L 33.794 40.52 L 33.794 46.126 L 31.51 46.126 L 31.51 48.408 L 28.187 48.408 L 28.187 46.126 L 25.904 46.126 L 25.904 40.52 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 39.228 49.966 L 50.34 49.966 L 50.34 38.962 L 39.228 38.962 L 39.228 49.966 Z M 40.839 40.511 L 48.729 40.511 L 48.729 46.117 L 46.446 46.117 L 46.446 48.4 L 43.123 48.4 L 43.123 46.117 L 40.84 46.117 L 40.839 40.511 L 40.839 40.511 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 54.123 49.966 L 65.234 49.966 L 65.234 38.962 L 54.123 38.962 L 54.123 49.966 Z M 55.774 40.511 L 63.664 40.511 L 63.664 46.117 L 61.38 46.117 L 61.38 48.4 L 58.057 48.4 L 58.057 46.117 L 55.774 46.117 L 55.774 40.511 Z" opacity="0.65" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 11.504 57.898 L 92.919 57.898 Q 92.919 57.898 92.919 57.898 L 92.919 62.69 Q 92.919 62.69 92.919 62.69 L 11.504 62.69 Q 11.504 62.69 11.504 62.69 L 11.504 57.898 Q 11.504 57.898 11.504 57.898 Z" opacity="0.7" stroke-opacity="0" stroke-miterlimit="10"/></g><g><path fill="#FFFFFF" stroke="rgb(0,0,0)" d="M 85.525 18.936 L 5.082 18.936 C 4.255 18.936 3.582 19.609 3.582 20.436 L 3.582 53.397 C 3.582 54.224 4.255 54.897 5.082 54.897 L 11.504 54.897 L 92.919 54.897 L 99.082 54.897 C 99.909 54.897 100.582 54.224 100.582 53.397 L 100.582 34.526 C 92.561 33.543 86.232 27.039 85.525 18.936 Z M 20.389 49.95 L 9.278 49.95 L 9.278 38.944 L 20.39 38.944 L 20.389 49.95 L 20.389 49.95 Z M 20.389 35.006 L 9.278 35.006 L 9.278 24 L 20.39 24 L 20.389 35.006 L 20.389 35.006 Z M 35.365 49.958 L 24.253 49.958 L 24.253 38.953 L 35.365 38.953 L 35.365 49.958 Z M 35.365 35.015 L 24.253 35.015 L 24.253 24.009 L 35.365 24.009 L 35.365 35.015 Z M 39.188 24.009 L 50.3 24.009 L 50.3 35.014 L 39.188 35.014 L 39.188 24.009 Z M 50.34 49.966 L 39.228 49.966 L 39.228 38.962 L 50.34 38.962 L 50.34 49.966 Z M 65.234 49.966 L 54.123 49.966 L 54.123 38.962 L 65.234 38.962 L 65.234 49.966 Z M 65.234 35.023 L 54.123 35.023 L 54.123 24.018 L 65.234 24.018 L 65.234 35.023 Z M 77.286 39.723 C 75.493 39.723 74.039 38.269 74.039 36.476 C 74.039 34.683 75.493 33.229 77.286 33.229 C 79.079 33.229 80.533 34.683 80.533 36.476 C 80.533 38.269 79.08 39.723 77.286 39.723 Z M 89.541 39.723 C 87.748 39.723 86.294 38.269 86.294 36.476 C 86.294 34.683 87.748 33.229 89.541 33.229 C 91.334 33.229 92.788 34.683 92.788 36.476 C 92.787 38.269 91.334 39.723 89.541 39.723 Z" opacity="0.93" stroke-opacity="0" stroke-miterlimit="10"/></g></g></g></g></g><g transform="matrix(1,0,0,1,250,51)"><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="140" height="70" fill-opacity="0"/></g></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="140" height="14" fill-opacity="0"/></g></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="140" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="0" y="12">Network</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="47" y="12">Router</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="83" y="12"> (</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="91" y="12">gateway</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="135" y="12">)</text></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="140" height="14" fill-opacity="0"/></g></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="127" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="0" y="26">vlan10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="35" y="26"> -</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="46" y="26">192</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="66" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="69" y="26">168</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="89" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="93" y="26">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="99" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="103" y="26">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="109" y="26">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="113" y="26">24</text></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="28" width="140" height="14" fill-opacity="0"/></g></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="28" width="47" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="0" y="40">vlan20</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="35" y="40"> - </text></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="46" y="28" width="74" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="46" y="40">172</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="66" y="40">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="69" y="40">16</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="83" y="40">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="86" y="40">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="93" y="40">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="96" y="40">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="103" y="40">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="106" y="40">24</text></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="42" width="140" height="14" fill-opacity="0"/></g></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="42" width="47" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="0" y="54">vlan30</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="35" y="54"> - </text></g><g transform="translate(92,238) matrix(1,0,0,1,0,0) translate(-92,-238)"><g><rect fill="rgb(0,0,0)" stroke="none" x="46" y="42" width="61" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="46" y="54">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="59" y="54">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="63" y="54">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="69" y="54">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="73" y="54">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="79" y="54">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="83" y="54">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="89" y="54">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="93" y="54">16</text></g></g><g transform="matrix(1,0,0,1,48,60)"><g transform="translate(0,0)"><g transform="translate(-62,-55) translate(14,-5) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#e06666" d="M 54 66 L 54 79 Q 54 89 64 89 L 78 89 Q 88 89 88 99 L 88 112" stroke-miterlimit="10" stroke-width="5"/></g></g></g></g><g transform="translate(0,0) matrix(1,0,0,1,12.805718530101615,11.940280333547719)"><g><g transform="translate(0,0) scale(0.8417223432141461,0.83)"><g><g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 52.371 16.22 L -1.393 16.22 L -1.393 99.52 L 52.371 99.52" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 52.371 99.52 L 69.07 82.82 L 69.07 -0.48 L 19.98 -0.48 L -1.393 16.22 L 52.371 16.22 L 52.371 99.52 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="rgb(0,0,0)" stroke="#FFFFFF" d="M 52.371 16.22 L 69.07 -0.48" fill-opacity="0" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="rgb(0,0,0)" stroke="#FFFFFF" d="M 25.3 16.22 L 25.3 99.52" fill-opacity="0" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="rgb(0,0,0)" stroke="#FFFFFF" d="M 26.149 16.22 L 44.316 -0.48" fill-opacity="0" stroke-miterlimit="10" stroke-width="1.3594"/></g></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 3.263 23.823 L 21.317 23.823 Q 21.317 23.823 21.317 23.823 L 21.317 34.868 Q 21.317 34.868 21.317 34.868 L 3.263 34.868 Q 3.263 34.868 3.263 34.868 L 3.263 23.823 Q 3.263 23.823 3.263 23.823 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 29.156 23.823 L 47.207 23.823 Q 47.207 23.823 47.207 23.823 L 47.207 34.868 Q 47.207 34.868 47.207 34.868 L 29.156 34.868 Q 29.156 34.868 29.156 34.868 L 29.156 23.823 Q 29.156 23.823 29.156 23.823 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 3.263 64.604 L 21.317 64.604 Q 21.317 64.604 21.317 64.604 L 21.317 75.648 Q 21.317 75.648 21.317 75.648 L 3.263 75.648 Q 3.263 75.648 3.263 75.648 L 3.263 64.604 Q 3.263 64.604 3.263 64.604 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 29.156 64.604 L 47.207 64.604 Q 47.207 64.604 47.207 64.604 L 47.207 75.648 Q 47.207 75.648 47.207 75.648 L 29.156 75.648 Q 29.156 75.648 29.156 75.648 L 29.156 64.604 Q 29.156 64.604 29.156 64.604 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 3.263 82.445 L 21.317 82.445 Q 21.317 82.445 21.317 82.445 L 21.317 93.49 Q 21.317 93.49 21.317 93.49 L 3.263 93.49 Q 3.263 93.49 3.263 93.49 L 3.263 82.445 Q 3.263 82.445 3.263 82.445 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g><g><path fill="#3d85c6" stroke="#FFFFFF" d="M 29.156 82.445 L 47.207 82.445 Q 47.207 82.445 47.207 82.445 L 47.207 93.49 Q 47.207 93.49 47.207 93.49 L 29.156 93.49 Q 29.156 93.49 29.156 93.49 L 29.156 82.445 Q 29.156 82.445 29.156 82.445 Z" stroke-miterlimit="10" stroke-width="1.3594"/></g></g></g></g></g><g transform="matrix(1,0,0,1,66,73)"><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="57" height="14" fill-opacity="0"/></g></g><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="57" height="14" fill-opacity="0"/></g></g><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="8" y="0" width="41" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="8" y="12">eth0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="32" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="35" y="12">10</text></g></g><g transform="matrix(1,0,0,1,67,52)"><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="57" height="14" fill-opacity="0"/></g></g><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="57" height="14" fill-opacity="0"/></g></g><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="8" y="0" width="41" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="8" y="12">eth0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="32" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="35" y="12">20</text></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,7.386363636363733,108.14285409109937)"><g><g transform="translate(0,0) scale(1.7571428571428573,2.3636363636363638)"><g><g><g><path fill="#999999" stroke="rgb(0,0,0)" d="M 58.97 19.094 C 58.977 18.895 59 18.7 59 18.5 C 59 8.283 50.717 0 40.5 0 C 33.11 0 26.751 4.344 23.787 10.607 C 22.275 9.593 20.458 9 18.5 9 C 13.5 9 9.41 12.866 9.037 17.771 C 3.778 19.616 0 24.61 0 30.5 C 0 37.787 5.778 43.71 13 43.975 L 13 44 L 58 44 L 58 43.975 C 64.671 43.71 70 38.235 70 31.5 C 70 25.095 65.18 19.822 58.97 19.094 Z M 58 41.975 L 58 42 L 13 42 L 13 41.975 C 6.883 41.711 2 36.683 2 30.5 C 2 24.994 5.872 20.398 11.039 19.271 C 11.013 19.017 11 18.76 11 18.5 C 11 14.357 14.358 11 18.5 11 C 21.017 11 23.239 12.244 24.6 14.146 C 26.512 7.15 32.897 2 40.5 2 C 49.613 2 57 9.388 57 18.5 C 57 19.353 56.914 20.183 56.79 21 L 58 21 L 58 21.025 C 63.565 21.288 68 25.87 68 31.5 C 68 37.13 63.565 41.712 58 41.975 Z" stroke-opacity="0" stroke-miterlimit="10"/></g></g></g></g></g></g><g transform="translate(0,0) matrix(1,0,0,1,170.65404609475036,143.99656534721677)"><g><g transform="translate(0,0) scale(0.44395492957746485,0.26378466557911767)"><g><path fill="url(#SeALyqvahCFZ)" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.2524809014999616,3.790970933828097)"><path fill="none" stroke="none" d="M 0 0 L 44.395492957746484 0 Q 44.395492957746484 0 44.395492957746484 0 L 44.395492957746484 26.378466557911768 Q 44.395492957746484 26.378466557911768 44.395492957746484 26.378466557911768 L 0 26.378466557911768 Q 0 26.378466557911768 0 26.378466557911768 L 0 0 Q 0 0 0 0 Z"/><path fill="url(#SeALyqvahCFZ)" stroke="#6fa8dc" d="M 0 0 M 0 0 L 44.395492957746484 0 Q 44.395492957746484 0 44.395492957746484 0 L 44.395492957746484 26.378466557911768 Q 44.395492957746484 26.378466557911768 44.395492957746484 26.378466557911768 L 0 26.378466557911768 Q 0 26.378466557911768 0 26.378466557911768 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g></g><g transform="matrix(1,0,0,1,174.53252582967698,139.4965653472169)"><g transform="translate(0,0)"><g transform="translate(-177.7573249679898,-143.64485245977795) translate(3.224799138312818,4.148287112561064) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 179.03252582967698 143.9965653472169 L 179.03252582967698 170.37503190512842" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,202.11478334058148,139.4965653472165)"><g transform="translate(0,0)"><g transform="translate(-207.50230524967998,-145.05170400953324) translate(5.387521909098496,5.55513866231675) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 206.61478334058148 143.9965653472165 L 206.61478334058148 170.37503190512865" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,188.35179257362358,139.49656534721677)"><g transform="translate(0,0)"><g transform="translate(-191.5199277848912,-145.05170400953324) translate(3.168135211267611,5.555138662316466) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 192.85179257362358 143.99656534721677 L 192.85179257362358 170.37503190512854" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,141,177)"><g transform="translate(18,28) matrix(1,0,0,1,0,0) translate(-18,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="104" height="28" fill-opacity="0"/></g></g><g transform="translate(18,28) matrix(1,0,0,1,0,0) translate(-18,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="104" height="14" fill-opacity="0"/></g></g><g transform="translate(18,28) matrix(1,0,0,1,0,0) translate(-18,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="1" y="0" width="103" height="14" fill-opacity="0"/></g></g><g transform="translate(18,28) matrix(1,0,0,1,0,0) translate(-18,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="1" y="0" width="103" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="1" y="12">container2</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="57" y="12"> -</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="68" y="12">vlan20</text></g><g transform="translate(18,28) matrix(1,0,0,1,0,0) translate(-18,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="104" height="14" fill-opacity="0"/></g></g><g transform="translate(18,28) matrix(1,0,0,1,0,0) translate(-18,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="16" y="14" width="74" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="16" y="26">172</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="36" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="39" y="26">16</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="52" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="56" y="26">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="62" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="66" y="26">2</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="72" y="26">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="76" y="26">24</text></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,259.38636363636374,108.14285409109937)"><g><g transform="translate(0,0) scale(1.7571428571428573,2.3636363636363638)"><g><g><g><path fill="#999999" stroke="rgb(0,0,0)" d="M 58.97 19.094 C 58.977 18.895 59 18.7 59 18.5 C 59 8.283 50.717 0 40.5 0 C 33.11 0 26.751 4.344 23.787 10.607 C 22.275 9.593 20.458 9 18.5 9 C 13.5 9 9.41 12.866 9.037 17.771 C 3.778 19.616 0 24.61 0 30.5 C 0 37.787 5.778 43.71 13 43.975 L 13 44 L 58 44 L 58 43.975 C 64.671 43.71 70 38.235 70 31.5 C 70 25.095 65.18 19.822 58.97 19.094 Z M 58 41.975 L 58 42 L 13 42 L 13 41.975 C 6.883 41.711 2 36.683 2 30.5 C 2 24.994 5.872 20.398 11.039 19.271 C 11.013 19.017 11 18.76 11 18.5 C 11 14.357 14.358 11 18.5 11 C 21.017 11 23.239 12.244 24.6 14.146 C 26.512 7.15 32.897 2 40.5 2 C 49.613 2 57 9.388 57 18.5 C 57 19.353 56.914 20.183 56.79 21 L 58 21 L 58 21.025 C 63.565 21.288 68 25.87 68 31.5 C 68 37.13 63.565 41.712 58 41.975 Z" stroke-opacity="0" stroke-miterlimit="10"/></g></g></g></g></g></g><g transform="translate(0,0) matrix(1,0,0,1,296.65404609475036,143.99656534721677)"><g><g transform="translate(0,0) scale(0.44395492957746485,0.26378466557911767)"><g><path fill="url(#KIKSmddbxdCC)" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.2524809014999616,3.790970933828097)"><path fill="none" stroke="none" d="M 0 0 L 44.395492957746484 0 Q 44.395492957746484 0 44.395492957746484 0 L 44.395492957746484 26.378466557911768 Q 44.395492957746484 26.378466557911768 44.395492957746484 26.378466557911768 L 0 26.378466557911768 Q 0 26.378466557911768 0 26.378466557911768 L 0 0 Q 0 0 0 0 Z"/><path fill="url(#KIKSmddbxdCC)" stroke="#6fa8dc" d="M 0 0 M 0 0 L 44.395492957746484 0 Q 44.395492957746484 0 44.395492957746484 0 L 44.395492957746484 26.378466557911768 Q 44.395492957746484 26.378466557911768 44.395492957746484 26.378466557911768 L 0 26.378466557911768 Q 0 26.378466557911768 0 26.378466557911768 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g></g><g transform="matrix(1,0,0,1,300.53252582967696,139.4965653472169)"><g transform="translate(0,0)"><g transform="translate(-303.7573249679898,-143.64485245977795) translate(3.2247991383128465,4.148287112561064) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 305.03252582967696 143.9965653472169 L 305.03252582967696 170.37503190512842" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,328.1147833405815,139.4965653472165)"><g transform="translate(0,0)"><g transform="translate(-333.50230524968,-145.05170400953324) translate(5.387521909098496,5.55513866231675) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 332.6147833405815 143.9965653472165 L 332.6147833405815 170.37503190512865" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,314.3517925736236,139.49656534721677)"><g transform="translate(0,0)"><g transform="translate(-317.5199277848912,-145.05170400953324) translate(3.168135211267611,5.555138662316466) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#0b5394" d="M 318.8517925736236 143.99656534721677 L 318.8517925736236 170.37503190512854" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g><g transform="matrix(1,0,0,1,267,177)"><g transform="translate(24,28) matrix(1,0,0,1,0,0) translate(-24,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="104" height="28" fill-opacity="0"/></g></g><g transform="translate(24,28) matrix(1,0,0,1,0,0) translate(-24,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="104" height="14" fill-opacity="0"/></g></g><g transform="translate(24,28) matrix(1,0,0,1,0,0) translate(-24,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="1" y="0" width="103" height="14" fill-opacity="0"/></g></g><g transform="translate(24,28) matrix(1,0,0,1,0,0) translate(-24,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="1" y="0" width="103" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="1" y="12">container3</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="57" y="12"> -</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="68" y="12">vlan30</text></g><g transform="translate(24,28) matrix(1,0,0,1,0,0) translate(-24,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="104" height="14" fill-opacity="0"/></g></g><g transform="translate(24,28) matrix(1,0,0,1,0,0) translate(-24,-28)"><g><rect fill="rgb(0,0,0)" stroke="none" x="22" y="14" width="61" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="22" y="26">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="36" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="39" y="26">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="46" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="49" y="26">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="56" y="26">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="59" y="26">2</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="66" y="26">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="69" y="26">16</text></g></g><g transform="matrix(1,0,0,1,67,31)"><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="57" height="14" fill-opacity="0"/></g></g><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="57" height="14" fill-opacity="0"/></g></g><g transform="translate(8,0) matrix(1,0,0,1,0,0) translate(-8,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="8" y="0" width="41" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="8" y="12">eth0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="32" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="35" y="12">30</text></g></g><g transform="matrix(1,0,0,1,5,95)"><g transform="translate(4,0) matrix(1,0,0,1,0,0) translate(-4,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="75" height="28" fill-opacity="0"/></g></g><g transform="translate(4,0) matrix(1,0,0,1,0,0) translate(-4,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="75" height="14" fill-opacity="0"/></g></g><g transform="translate(4,0) matrix(1,0,0,1,0,0) translate(-4,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="4" y="0" width="67" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="4" y="12">Docker</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="46" y="12">Host</text></g></g></g></svg>
\ No newline at end of file
--- /dev/null
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":566,"height":581,"nodeIndex":500,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":{"uid":"com.gliffy.theme.beach_day","name":"Beach Day","shape":{"primary":{"strokeWidth":2,"strokeColor":"#00A4DA","fillColor":"#AEE4F4","gradient":false,"dropShadow":false,"opacity":1,"text":{"color":"#004257"}},"secondary":{"strokeWidth":2,"strokeColor":"#CDB25E","fillColor":"#EACF81","gradient":false,"dropShadow":false,"opacity":1,"text":{"color":"#332D1A"}},"tertiary":{"strokeWidth":2,"strokeColor":"#FFBE00","fillColor":"#FFF1CB","gradient":false,"dropShadow":false,"opacity":1,"text":{"color":"#000000"}},"highlight":{"strokeWidth":2,"strokeColor":"#00A4DA","fillColor":"#00A4DA","gradient":false,"dropShadow":false,"opacity":1,"text":{"color":"#ffffff"}}},"line":{"strokeWidth":2,"strokeColor":"#00A4DA","fillColor":"none","arrowType":2,"interpolationType":"quadratic","cornerRadius":0,"text":{"color":"#002248"}},"text":{"color":"#002248"},"stage":{"color":"#FFFFFF"}},"viewportType":"default","fitBB":{"min":{"x":-3,"y":-1.0100878848684474},"max":{"x":566,"y":581}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":-5.0,"y":-1.0100878848684474,"rotation":0.0,"id":499,"width":569.0,"height":582.0100878848684,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":103,"lockAspectRatio":false,"lockShape":false,"children":[{"x":374.0,"y":44.510087884868476,"rotation":0.0,"id":497,"width":145.0,"height":32.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":101,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:14px;\"><span style=\"font-weight:bold;\">Network & </span></span><span style=\"font-size:14px;\"><span style=\"font-weight:bold;\">other </span></span></p><p style=\"text-align:center;\"><span style=\"font-size:14px;\"><span style=\"font-weight:bold;\">Docker Hosts</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":157.40277777777783,"y":108.18042331083174,"rotation":0.0,"id":492,"width":121.19444444444446,"height":256.03113588084784,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":99,"lockAspectRatio":false,"lockShape":false,"children":[{"x":-126.13675213675185,"y":31.971494223140525,"rotation":180.0,"id":453,"width":11.1452323717951,"height":61.19357171974171,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":57,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#38761d","fillColor":"#38761d","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-121.4915197649562,-156.36606993796556],[-121.49151976495622,-99.52846483047983],[-229.68596420939843,-99.52846483047591],[-229.68596420939843,-34.22088765589871]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":289.82598824786317,"y":137.23816896148608,"rotation":180.0,"id":454,"width":11.1452323717951,"height":61.19357171974171,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":55,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#38761d","fillColor":"#38761d","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[291.05455395299924,191.93174068122784],[291.05455395299924,106.06051735724502],[186.27677617521402,106.06051735724502],[186.27677617521402,69.78655839914467]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":372.0,"y":332.0100878848684,"rotation":0.0,"id":490,"width":144.0,"height":60.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":97,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":9.5,"rotation":0.0,"id":365,"width":141.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":98,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:18px;font-family:Arial;\"> Parent: <span style=\"font-weight:bold;\">eth0.30</span></span></p><p style=\"text-align:center;\"><span style=\"font-size:18px;font-family:Arial;\">VLAN: <span style=\"font-weight:bold;\">30</span></span></p><p style=\"text-align:center;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":0.0,"rotation":0.0,"id":342,"width":144.0,"height":60.0,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":96,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#eb6c6c","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.99,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":52.0,"y":332.0100878848684,"rotation":0.0,"id":489,"width":144.0,"height":60.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":92,"lockAspectRatio":false,"lockShape":false,"children":[{"x":1.0,"y":10.5,"rotation":0.0,"id":367,"width":138.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":93,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:18px;font-family:Arial;\"><span style=\"\">Parent</span>: <span style=\"font-weight:bold;\">eth0.10</span></span></p><p style=\"text-align:center;\"><span style=\"font-size:18px;font-family:Arial;\">VLAN ID</span><span style=\"font-size:18px;font-family:Arial;\">:</span><span style=\"font-size:18px;font-weight:bold;font-family:Arial;\"> <span style=\"\">10</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":0.0,"rotation":0.0,"id":340,"width":144.0,"height":60.0,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":91,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#5fcc5a","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.99,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":289.40277777777794,"y":126.43727235088903,"rotation":0.0,"id":486,"width":121.19444444444446,"height":250.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":88,"lockAspectRatio":false,"lockShape":false,"children":[{"x":236.18596420940128,"y":158.89044937932732,"rotation":0.0,"id":449,"width":11.1452323717951,"height":59.50782702798556,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":53,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#cc0000","fillColor":"#cc0000","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-121.49151976495682,-152.05853787273531],[-121.49151976495682,-81.64750068755309],[-229.68596420940125,-81.64750068755139],[-229.68596420940125,-33.27817949077674]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":-179.77677617521388,"y":56.523633779319084,"rotation":0.0,"id":450,"width":11.1452323717951,"height":59.50782702798556,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":51,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#cc0000","fillColor":"#cc0000","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[291.0545539529992,186.6444547140887],[291.0545539529992,117.79470574474337],[186.276776175214,117.79470574474337],[186.276776175214,67.8640963321146]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":447.0,"y":150.01008788486848,"rotation":0.0,"id":472,"width":46.99999999999994,"height":27.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":87,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":473,"width":37.09803921568625,"height":18.000000000000004,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":86,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#666666","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":5.485490196078445,"y":5.153846153846132,"rotation":0.0,"id":474,"width":37.09803921568625,"height":18.000000000000004,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":84,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#666666","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":9.901960784313701,"y":9.0,"rotation":0.0,"id":475,"width":37.09803921568625,"height":18.000000000000004,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":82,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#666666","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":368.0,"y":101.71008483311067,"rotation":0.0,"id":477,"width":140.0,"height":56.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":80,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:12px;font-family:Arial;\">Gateway </span><span style=\"font-weight:bold;\">10.1.30.1</span></p><p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;\"> </span><span style=\"\"> and other </span><span style=\"\">containers on the same VLAN/</span><span style=\"\">subnet</span></p><p style=\"text-align:center;\"><span style=\"text-decoration:none;\"> </span></p><p style=\"text-align:center;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":350.51767083236393,"y":87.47159983339776,"rotation":0.0,"id":478,"width":175.20345848455912,"height":73.0,"uid":"com.gliffy.shape.cisco.cisco_v1.storage.cloud","order":79,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.storage.cloud","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#cc0000","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":94.0,"y":155.01008788486848,"rotation":0.0,"id":463,"width":46.99999999999994,"height":27.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":78,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":464,"width":37.09803921568625,"height":18.000000000000004,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":77,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#666666","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":5.485490196078445,"y":5.153846153846132,"rotation":0.0,"id":465,"width":37.09803921568625,"height":18.000000000000004,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":75,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#666666","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":9.901960784313701,"y":9.0,"rotation":0.0,"id":466,"width":37.09803921568625,"height":18.000000000000004,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":73,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#666666","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":80.0,"y":109.71008483311067,"rotation":0.0,"id":468,"width":140.0,"height":56.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":71,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:12px;font-family:Arial;\">Gateway </span><span style=\"font-weight:bold;\">10.1.10.1</span></p><p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;\"> </span><span style=\"\"> and other </span><span style=\"\">containers on the same VLAN/</span><span style=\"\">subnet</span></p><p style=\"text-align:center;\"><span style=\"text-decoration:none;\"> </span></p><p style=\"text-align:center;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":62.51767083236396,"y":95.47159983339776,"rotation":0.0,"id":469,"width":175.20345848455912,"height":73.0,"uid":"com.gliffy.shape.cisco.cisco_v1.storage.cloud","order":70,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.storage.cloud","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#38761d","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":341.0,"y":40.010087884868476,"rotation":0.0,"id":460,"width":46.99999999999994,"height":27.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":69,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":417,"width":37.09803921568625,"height":18.000000000000004,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":68,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#666666","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":5.485490196078445,"y":5.153846153846132,"rotation":0.0,"id":418,"width":37.09803921568625,"height":18.000000000000004,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":66,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#666666","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":9.901960784313701,"y":9.0,"rotation":0.0,"id":419,"width":37.09803921568625,"height":18.000000000000004,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":64,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#666666","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":198.51767083236396,"y":41.471599833397754,"rotation":0.0,"id":459,"width":175.20345848455912,"height":79.73848499971291,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":62,"lockAspectRatio":false,"lockShape":false,"children":[{"x":17.482329167636067,"y":14.23848499971291,"rotation":0.0,"id":458,"width":140.0,"height":56.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":61,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:12px;font-family:Arial;\">Gateway </span><span style=\"font-weight:bold;\">10.1.20.1</span></p><p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;\"> </span><span style=\"\"> and other </span><span style=\"\">containers on the same VLAN/</span><span style=\"\">subnet</span></p><p style=\"text-align:center;\"><span style=\"text-decoration:none;\"> </span></p><p style=\"text-align:center;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":0.0,"rotation":0.0,"id":330,"width":175.20345848455912,"height":73.0,"uid":"com.gliffy.shape.cisco.cisco_v1.storage.cloud","order":59,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.storage.cloud","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#ff9900","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":279.0,"y":129.01008788486848,"rotation":0.0,"id":440,"width":5.0,"height":227.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":49,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#ff9900","fillColor":"#ff9900","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[4.000000000000057,-25.08952732449731],[4.000000000000114,176.01117206537933]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":56.0,"y":503.0913886978766,"rotation":0.0,"id":386,"width":135.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:18px;font-family:Arial;\">Frontend</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":62.0,"y":420.0100878848684,"rotation":0.0,"id":381,"width":120.0,"height":74.18803418803415,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":41,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":382,"width":102.08955223880598,"height":54.91841491841488,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.97,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0417910447761187,"y":0.0,"rotation":0.0,"id":383,"width":98.00597014925374,"height":44.0,"uid":null,"order":47,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">Container(s)</span></p><p style=\"text-align:center;\"><span style=\"font-size:13px;text-decoration:none;font-family:Arial;\"><span style=\"text-decoration:none;\">Eth0 <span style=\"font-weight:bold;\">10.1.10.0/24</span></span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":8.955223880597016,"y":9.634809634809635,"rotation":0.0,"id":384,"width":102.08955223880598,"height":54.91841491841488,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.97,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":17.910447761194032,"y":19.26961926961927,"rotation":0.0,"id":385,"width":102.08955223880598,"height":54.91841491841488,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.97,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":382.0,"y":420.0100878848684,"rotation":0.0,"id":376,"width":120.0,"height":74.18803418803415,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":31,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":377,"width":102.08955223880598,"height":54.91841491841488,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":34,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.97,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0417910447761187,"y":0.0,"rotation":0.0,"id":378,"width":98.00597014925374,"height":44.0,"uid":null,"order":37,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">Container(s)</span></p><p style=\"text-align:center;\"><span style=\"font-size:13px;text-decoration:none;font-family:Arial;\"><span style=\"text-decoration:none;\">Eth0 <span style=\"font-weight:bold;\">10.1.30.0/24</span></span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":8.955223880597016,"y":9.634809634809635,"rotation":0.0,"id":379,"width":102.08955223880598,"height":54.91841491841488,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.97,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":17.910447761194032,"y":19.26961926961927,"rotation":0.0,"id":380,"width":102.08955223880598,"height":54.91841491841488,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":30,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.97,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":214.0,"y":503.0100878848685,"rotation":0.0,"id":374,"width":135.0,"height":20.162601626016258,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":27,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:18px;font-family:Arial;\">Backend</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":376.0,"y":502.0100878848684,"rotation":0.0,"id":373,"width":135.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":26,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:18px;font-family:Arial;\">Credit Cards</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":627.0,"y":99.94304076572786,"rotation":0.0,"id":364,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.uml.uml_v2.sequence.anchor_line","order":25,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":363,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":342,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-183.0,310.0670471191406],[-183.0,292.0670471191406]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":372.0,"y":410.0100878848684,"rotation":0.0,"id":363,"width":144.0,"height":117.0,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":24,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#eb6c6c","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.99,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":218.0,"y":341.5100878848684,"rotation":0.0,"id":366,"width":132.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:18px;font-family:Arial;\">Parent: <span style=\"font-weight:bold;\">eth0.20</span></span></p><p style=\"text-align:center;\"><span style=\"font-size:18px;font-family:Arial;\">VLAN ID: <span style=\"font-weight:bold;\">20</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":297.0,"y":89.94304076572786,"rotation":0.0,"id":356,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.uml.uml_v2.sequence.anchor_line","order":22,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":353,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":343,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-13.0,320.0670471191406],[-13.0,302.0670471191406]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":222.0,"y":420.0100878848684,"rotation":0.0,"id":348,"width":120.0,"height":74.18803418803415,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":21,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":349,"width":102.08955223880598,"height":54.91841491841488,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":17,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.97,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0417910447761187,"y":0.0,"rotation":0.0,"id":350,"width":98.00597014925374,"height":44.0,"uid":null,"order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"\">Container(s)</span></p><p style=\"text-align:center;\"><span style=\"font-size:13px;text-decoration:none;font-family:Arial;\"><span style=\"text-decoration:none;\">Eth0 <span style=\"font-weight:bold;\">10.1.20.0/24</span></span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":8.955223880597016,"y":9.634809634809635,"rotation":0.0,"id":351,"width":102.08955223880598,"height":54.91841491841488,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.97,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":17.910447761194032,"y":19.26961926961927,"rotation":0.0,"id":352,"width":102.08955223880598,"height":54.91841491841488,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#4cacf5","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.97,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":212.0,"y":410.0100878848684,"rotation":0.0,"id":353,"width":144.0,"height":119.0,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":11,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#fca13f","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.99,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":212.0,"y":332.0100878848684,"rotation":0.0,"id":343,"width":144.0,"height":60.0,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#fca13f","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.99,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":203.0,"y":307.5100878848684,"rotation":0.0,"id":333,"width":160.0,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":9,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:20px;font-family:Arial;\"><span style=\"font-weight:bold;\">eth0</span> Interface</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":303.0,"y":240.51008788486845,"rotation":0.0,"id":323,"width":261.0,"height":48.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":8,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:14px;font-weight:bold;font-family:Arial;\">802.1Q Trunk - </span><span style=\"font-size:14px;font-family:Arial;\"><span style=\"font-style:italic;\">can be a single Ethernet link or Multiple Bonded Ethernet links</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":36.0,"y":291.0100878848684,"rotation":0.0,"id":290,"width":497.0,"height":80.0,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":7,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#cccccc","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":543.5100878848684,"rotation":0.0,"id":282,"width":569.0,"height":32.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-size:14px;\"><span style=\"font-weight:bold;\">Docker Host</span></span><span style=\"font-size:14px;font-style:italic;\">: Frontend, Backend & Credit Card App Tiers are <span style=\"font-weight:bold;\">Isolated</span></span><span style=\"font-size:14px;\"> but can still communicate inside <span style=\"font-weight:bold;\">parent</span> interface or any other Docker hosts using the <span style=\"font-weight:bold;\">VLAN ID</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":-33.0,"y":79.94304076572786,"rotation":0.0,"id":269,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.uml.uml_v2.sequence.anchor_line","order":5,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":345,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":340,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[157.0,330.0670471191406],[157.0,312.0670471191406]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":52.0,"y":410.0100878848684,"rotation":0.0,"id":345,"width":144.0,"height":119.0,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":4,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#434343","fillColor":"#5fcc5a","gradient":false,"dashStyle":null,"dropShadow":true,"state":0,"opacity":0.99,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":20.0,"y":323.0100878848684,"rotation":0.0,"id":276,"width":531.0,"height":259.0,"uid":"com.gliffy.shape.basic.basic_v1.default.round_rectangle","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#434343","fillColor":"#c5e4fc","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":0.93,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":19.609892022503004,"y":20.27621073737908,"rotation":355.62347411485274,"id":246,"width":540.0106597126834,"height":225.00000000000003,"uid":"com.gliffy.shape.cisco.cisco_v1.storage.cloud","order":2,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.storage.cloud","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#999999","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":1.0,"y":99.94304076572786,"rotation":0.0,"id":394,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.uml.uml_v2.sequence.anchor_line","order":1,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":3.0,"strokeColor":"#666666","fillColor":"#999999","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[261.0,233.5670471191406],[261.0,108.05111187584177]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":44.0,"y":90.94304076572786,"rotation":0.0,"id":481,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.uml.uml_v2.sequence.anchor_line","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":3.0,"strokeColor":"#666666","fillColor":"#999999","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[261.0,233.56704711914062],[261.0,108.05111187584174]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":104}],"shapeStyles":{},"lineStyles":{"global":{"fill":"#999999","stroke":"#38761d","strokeWidth":3,"dashStyle":"1.0,1.0","orthoMode":2}},"textStyles":{"global":{"bold":true,"face":"Arial","size":"14px","color":"#000000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.images","com.gliffy.libraries.network.network_v4.home","com.gliffy.libraries.network.network_v4.business","com.gliffy.libraries.network.network_v4.rack","com.gliffy.libraries.network.network_v3.home","com.gliffy.libraries.network.network_v3.business","com.gliffy.libraries.network.network_v3.rack"],"lastSerialized":1458117295143,"analyticsProduct":"Confluence"},"embeddedResources":{"index":0,"resources":[]}}
\ No newline at end of file
--- /dev/null
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="586" height="601"><style xmlns="http://www.w3.org/1999/xhtml"></style><defs/><g transform="translate(0,0)"><g><rect fill="#FFFFFF" stroke="none" x="0" y="0" width="586" height="601"/></g><g transform="matrix(1,0,0,1,295,192.98406475670114)"><g transform="translate(0,0)"><g transform="translate(-39,-89.9329528808594) translate(-256,-103.05111187584174) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#666666" d="M 300 323.5 L 300 197.98406475670114" stroke-miterlimit="10" stroke-width="3"/></g></g></g></g><g transform="matrix(1,0,0,1,295,192.98406475670114)"><g transform="translate(0,0)"><g transform="translate(-39,-89.9329528808594) translate(-256,-103.05111187584174) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#999999" d="M 300 323.5 L 300 197.98406475670114" stroke-miterlimit="10" stroke-width="3"/></g></g></g></g><g transform="matrix(1,0,0,1,252,201.98406475670117)"><g transform="translate(0,0)"><g transform="translate(4,-98.9329528808594) translate(-256,-103.05111187584177) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#666666" d="M 257 332.5 L 257 206.98406475670117" stroke-miterlimit="10" stroke-width="3"/></g></g></g></g><g transform="matrix(1,0,0,1,252,201.98406475670117)"><g transform="translate(0,0)"><g transform="translate(4,-98.9329528808594) translate(-256,-103.05111187584177) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#999999" d="M 257 332.5 L 257 206.98406475670117" stroke-miterlimit="10" stroke-width="3"/></g></g></g></g><g transform="translate(0,0) matrix(0.9970841003774397,-0.07631052859541608,0.07631052859541608,0.9970841003774397,6.812265994936054,40.19841100496578)"><g><g transform="translate(0,0) scale(5.40007959672885,3.87815826436388) translate(0.00001348378,-0.001498589)"><g><g><path fill="#FFFFFF" stroke="#999999" d="M 33.84 5.912 C 16.263 2.403 6.986 11.405 8.451 19.186 L 8.57 19.61 C -5.128 21.942 -1.537 38.106 5.033 38.106 L 5.679 38.085 C 4.026 45.68 20.032 53.41 28.958 49.548 L 29.531 48.92 C 32.713 55.752 44.02 56.893 55.201 57.177 C 64.89 57.425 70.546 56.658 74.702 52.411 L 75.341 52.599 C 90.852 53.845 97.915 43.133 94.526 35.442 L 95.36 35.207 C 100.648 33.808 101.258 21.602 92.187 19.797 L 92.361 19.325 C 96.299 11.385 87.011 3.896 74.124 5.303 L 73.23 4.837 C 64.003 -3.517 37.449 -2.157 34.373 6.108 L 33.84 5.912 Z" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g></g><g transform="translate(0,0) matrix(1,0,0,1,15,322)"><g><g transform="translate(0,0) scale(5.31,2.59)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.18832391713747648,0.3861003861003861)"><path fill="#c5e4fc" stroke="none" d="M 10 0 L 521 0 Q 531 0 531 10 L 531 249 Q 531 259 521 259 L 10 259 Q 0 259 0 249 L 0 10 Q 0 0 10 0 Z" opacity="0.93"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 521 0 Q 531 0 531 10 L 531 249 Q 531 259 521 259 L 10 259 Q 0 259 0 249 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" stroke-width="2" opacity="0.93"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,47,409)"><g transform="translate(4,4) scale(1.0034722222222223,1.004201680672269)"><g><g transform="translate(0,0) scale(1.44,1.19)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,0.8403361344537815)"><path fill="#000000" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 109 Q 144 119 134 119 L 10 119 Q 0 119 0 109 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 109 Q 144 119 134 119 L 10 119 Q 0 119 0 109 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.44,1.19)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,0.8403361344537815)"><path fill="#5fcc5a" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 109 Q 144 119 134 119 L 10 119 Q 0 119 0 109 L 0 10 Q 0 0 10 0 Z" opacity="0.99"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 109 Q 144 119 134 119 L 10 119 Q 0 119 0 109 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.99"/></g></g></g></g></g><g transform="matrix(1,0,0,1,115,387)"><g transform="translate(0,0)"><g transform="translate(38,-78.9329528808594) translate(-153,-308.0670471191406) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#000000" d="M 119 409 L 119 391" stroke-miterlimit="10"/></g></g></g></g><g transform="matrix(1,0,0,1,-3,543)"><g transform="translate(1676,32) matrix(1,0,0,1,0,0) translate(-1676,-32)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="566" height="32" fill-opacity="0"/></g></g><g transform="translate(1676,32) matrix(1,0,0,1,0,0) translate(-1676,-32)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="566" height="32" fill-opacity="0"/></g></g><g transform="translate(1676,32) matrix(1,0,0,1,0,0) translate(-1676,-32)"><g><rect fill="rgb(0,0,0)" stroke="none" x="25" y="0" width="83" height="16" fill-opacity="0"/></g></g><g transform="translate(1676,32) matrix(1,0,0,1,0,0) translate(-1676,-32)"><g><rect fill="rgb(0,0,0)" stroke="none" x="25" y="0" width="83" height="16" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="25" y="14">Docker</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="76" y="14">Host</text></g><g transform="translate(1676,32) matrix(1,0,0,1,0,0) translate(-1676,-32)"><g><rect fill="rgb(0,0,0)" stroke="none" x="108" y="0" width="360" height="16" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="108" y="14">:</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="115" y="14">Frontend</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="171" y="14">,</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="179" y="14">Backend</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="234" y="14"> &</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="251" y="14">Credit</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="292" y="14">Card</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="326" y="14">App</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="355" y="14">Tiers</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="390" y="14">are</text></g><g transform="translate(1676,32) matrix(1,0,0,1,0,0) translate(-1676,-32)"><g><rect fill="rgb(0,0,0)" stroke="none" x="414" y="0" width="53" height="16" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="bold" text-decoration="" line-height="16.5px" x="414" y="14">Isolated</text></g><g transform="translate(1676,32) matrix(1,0,0,1,0,0) translate(-1676,-32)"><g><rect fill="rgb(0,0,0)" stroke="none" x="467" y="0" width="515" height="32" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="471" y="14">but</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="494" y="14">can</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="521" y="14">still</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="27" y="30">communicate</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="114" y="30">inside</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="201" y="30">interface</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="258" y="30">or</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="275" y="30">any</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="301" y="30">other</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="337" y="30">Docker</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="385" y="30">hosts</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="423" y="30">using</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="normal" text-decoration="" line-height="16.5px" x="460" y="30">the</text></g><g transform="translate(1676,32) matrix(1,0,0,1,0,0) translate(-1676,-32)"><g><rect fill="rgb(0,0,0)" stroke="none" x="154" y="16" width="44" height="16" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="154" y="30">parent</text></g><g transform="translate(1676,32) matrix(1,0,0,1,0,0) translate(-1676,-32)"><g><rect fill="rgb(0,0,0)" stroke="none" x="483" y="16" width="57" height="16" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="483" y="30">VLAN</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="525" y="30">ID</text></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,31,290)"><g transform="translate(4,4) scale(1.0010060362173039,1.00625)"><g><g transform="translate(0,0) scale(4.97,0.8)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.2012072434607646,1.25)"><path fill="#000000" stroke="none" d="M 10 0 L 487 0 Q 497 0 497 10 L 497 70 Q 497 80 487 80 L 10 80 Q 0 80 0 70 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 487 0 Q 497 0 497 10 L 497 70 Q 497 80 487 80 L 10 80 Q 0 80 0 70 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(4.97,0.8)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.2012072434607646,1.25)"><path fill="#cccccc" stroke="none" d="M 10 0 L 487 0 Q 497 0 497 10 L 497 70 Q 497 80 487 80 L 10 80 Q 0 80 0 70 L 0 10 Q 0 0 10 0 Z"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 487 0 Q 497 0 497 10 L 497 70 Q 497 80 487 80 L 10 80 Q 0 80 0 70 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="matrix(1,0,0,1,300,248)"><g transform="translate(211,0) matrix(1,0,0,1,0,0) translate(-211,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="258" height="48" fill-opacity="0"/></g></g><g transform="translate(211,0) matrix(1,0,0,1,0,0) translate(-211,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="258" height="32" fill-opacity="0"/></g></g><g transform="translate(211,0) matrix(1,0,0,1,0,0) translate(-211,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="3" y="0" width="101" height="16" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="3" y="14">802</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="27" y="14">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="31" y="14">1Q</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="53" y="14">Trunk</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="91" y="14"> -</text></g><g transform="translate(211,0) matrix(1,0,0,1,0,0) translate(-211,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="104" y="0" width="242" height="32" fill-opacity="0"/></g></g><g transform="translate(211,0) matrix(1,0,0,1,0,0) translate(-211,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="104" y="0" width="242" height="32" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="104" y="14">can</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="130" y="14">be</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="150" y="14">a</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="161" y="14">single</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="202" y="14">Ethernet</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="14" y="30">link</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="39" y="30">or</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="55" y="30">Multiple</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="107" y="30">Bonded</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="159" y="30">Ethernet</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="italic" font-weight="normal" text-decoration="" line-height="16.5px" x="216" y="30">links</text></g></g><g transform="matrix(1,0,0,1,200,307)"><g transform="translate(32,0) matrix(1,0,0,1,0,0) translate(-32,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="157" height="22" fill-opacity="0"/></g></g><g transform="translate(32,0) matrix(1,0,0,1,0,0) translate(-32,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="157" height="22" fill-opacity="0"/></g></g><g transform="translate(32,0) matrix(1,0,0,1,0,0) translate(-32,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="16" y="0" width="125" height="22" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="20px" font-style="normal" font-weight="normal" text-decoration="" line-height="22.75px" x="63" y="19">Interface</text></g><g transform="translate(32,0) matrix(1,0,0,1,0,0) translate(-32,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="16" y="0" width="42" height="22" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="20px" font-style="normal" font-weight="bold" text-decoration="" line-height="22.75px" x="16" y="19">eth0</text></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,207,331)"><g transform="translate(4,4) scale(1.0034722222222223,1.0083333333333333)"><g><g transform="translate(0,0) scale(1.44,0.6)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,1.6666666666666667)"><path fill="#000000" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.44,0.6)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,1.6666666666666667)"><path fill="#fca13f" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" opacity="0.99"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.99"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,207,409)"><g transform="translate(4,4) scale(1.0034722222222223,1.004201680672269)"><g><g transform="translate(0,0) scale(1.44,1.19)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,0.8403361344537815)"><path fill="#000000" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 109 Q 144 119 134 119 L 10 119 Q 0 119 0 109 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 109 Q 144 119 134 119 L 10 119 Q 0 119 0 109 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.44,1.19)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,0.8403361344537815)"><path fill="#fca13f" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 109 Q 144 119 134 119 L 10 119 Q 0 119 0 109 L 0 10 Q 0 0 10 0 Z" opacity="0.99"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 109 Q 144 119 134 119 L 10 119 Q 0 119 0 109 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.99"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,234.91044776119404,438.2696192696193)"><g transform="translate(4,4) scale(1.0048976608187135,1.00910441426146)"><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#000000" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#4cacf5" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.97"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.97"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,225.955223880597,428.63480963480964)"><g transform="translate(4,4) scale(1.0048976608187135,1.00910441426146)"><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#000000" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#4cacf5" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.97"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.97"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,217,419)"><g transform="translate(4,4) scale(1.0048976608187135,1.00910441426146)"><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#000000" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#4cacf5" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.97"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.97"/></g></g></g></g></g><g transform="matrix(1,0,0,1,227,424)"><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="83" height="44" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="83" height="14" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="8" y="0" width="67" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="8" y="12">Container</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="61" y="12">(</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="65" y="12">s</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="71" y="12">)</text></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="83" height="30" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="28" y="14" width="73" height="30" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="28" y="14" width="73" height="30" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="" line-height="15.5px" x="28" y="27">Eth0</text></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="5" y="29" width="73" height="15" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="5" y="42">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="20" y="42">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="23" y="42">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="31" y="42">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="34" y="42">20</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="49" y="42">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="52" y="42">0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="60" y="42">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="63" y="42">24</text></g></g><g transform="matrix(1,0,0,1,275,387)"><g transform="translate(0,0)"><g transform="translate(-292,-88.9329528808594) translate(17,-298.0670471191406) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#000000" d="M 279 409 L 279 391" stroke-miterlimit="10"/></g></g></g></g><g transform="matrix(1,0,0,1,215,341)"><g transform="translate(176,60) matrix(1,0,0,1,0,0) translate(-176,-60)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="129" height="40" fill-opacity="0"/></g></g><g transform="translate(176,60) matrix(1,0,0,1,0,0) translate(-176,-60)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="129" height="20" fill-opacity="0"/></g></g><g transform="translate(176,60) matrix(1,0,0,1,0,0) translate(-176,-60)"><g><rect fill="rgb(0,0,0)" stroke="none" x="2" y="0" width="127" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="2" y="17">Parent</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="55" y="17">:</text></g><g transform="translate(176,60) matrix(1,0,0,1,0,0) translate(-176,-60)"><g><rect fill="rgb(0,0,0)" stroke="none" x="65" y="0" width="63" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="65" y="17">eth0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="102" y="17">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="107" y="17">20</text></g><g transform="translate(176,60) matrix(1,0,0,1,0,0) translate(-176,-60)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="20" width="129" height="20" fill-opacity="0"/></g></g><g transform="translate(176,60) matrix(1,0,0,1,0,0) translate(-176,-60)"><g><rect fill="rgb(0,0,0)" stroke="none" x="14" y="20" width="101" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="14" y="37">VLAN</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="66" y="37">ID</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="84" y="37">:</text></g><g transform="translate(176,60) matrix(1,0,0,1,0,0) translate(-176,-60)"><g><rect fill="rgb(0,0,0)" stroke="none" x="95" y="20" width="21" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="95" y="37">20</text></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,367,409)"><g transform="translate(4,4) scale(1.0034722222222223,1.0042735042735043)"><g><g transform="translate(0,0) scale(1.44,1.17)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,0.8547008547008548)"><path fill="#000000" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 107 Q 144 117 134 117 L 10 117 Q 0 117 0 107 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 107 Q 144 117 134 117 L 10 117 Q 0 117 0 107 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.44,1.17)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,0.8547008547008548)"><path fill="#eb6c6c" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 107 Q 144 117 134 117 L 10 117 Q 0 117 0 107 L 0 10 Q 0 0 10 0 Z" opacity="0.99"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 107 Q 144 117 134 117 L 10 117 Q 0 117 0 107 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.99"/></g></g></g></g></g><g transform="matrix(1,0,0,1,435,387)"><g transform="translate(0,0)"><g transform="translate(-622,-98.9329528808594) translate(187,-288.0670471191406) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#000000" d="M 439 409 L 439 391" stroke-miterlimit="10"/></g></g></g></g><g transform="matrix(1,0,0,1,373,501)"><g transform="translate(15,0) matrix(1,0,0,1,0,0) translate(-15,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="132" height="20" fill-opacity="0"/></g></g><g transform="translate(15,0) matrix(1,0,0,1,0,0) translate(-15,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="132" height="20" fill-opacity="0"/></g></g><g transform="translate(15,0) matrix(1,0,0,1,0,0) translate(-15,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="15" y="0" width="102" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="15" y="17">Credit</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="69" y="17">Cards</text></g></g><g transform="matrix(1,0,0,1,211,502)"><g transform="translate(31,0) matrix(1,0,0,1,0,0) translate(-31,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="132" height="20" fill-opacity="0"/></g></g><g transform="translate(31,0) matrix(1,0,0,1,0,0) translate(-31,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="132" height="20" fill-opacity="0"/></g></g><g transform="translate(31,0) matrix(1,0,0,1,0,0) translate(-31,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="31" y="0" width="72" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="31" y="17">Backend</text></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,394.910447761194,438.2696192696193)"><g transform="translate(4,4) scale(1.0048976608187135,1.00910441426146)"><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#000000" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#4cacf5" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.97"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.97"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,385.95522388059703,428.63480963480964)"><g transform="translate(4,4) scale(1.0048976608187135,1.00910441426146)"><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#000000" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#4cacf5" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.97"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.97"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,377,419)"><g transform="translate(4,4) scale(1.0048976608187135,1.00910441426146)"><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#000000" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#4cacf5" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.97"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.97"/></g></g></g></g></g><g transform="matrix(1,0,0,1,387,424)"><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="83" height="44" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="83" height="14" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="8" y="0" width="67" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="8" y="12">Container</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="61" y="12">(</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="65" y="12">s</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="71" y="12">)</text></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="83" height="30" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="28" y="14" width="73" height="30" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="28" y="14" width="73" height="30" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="" line-height="15.5px" x="28" y="27">Eth0</text></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="5" y="29" width="73" height="15" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="5" y="42">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="20" y="42">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="23" y="42">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="31" y="42">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="34" y="42">30</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="49" y="42">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="52" y="42">0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="60" y="42">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="63" y="42">24</text></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,74.91044776119404,438.2696192696193)"><g transform="translate(4,4) scale(1.0048976608187135,1.00910441426146)"><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#000000" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#4cacf5" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.97"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.97"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,65.95522388059702,428.63480963480964)"><g transform="translate(4,4) scale(1.0048976608187135,1.00910441426146)"><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#000000" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#4cacf5" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.97"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.97"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,57,419)"><g transform="translate(4,4) scale(1.0048976608187135,1.00910441426146)"><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#000000" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.0208955223880598,0.5491841491841488)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.9795321637426899,1.8208828522920215)"><path fill="#4cacf5" stroke="none" d="M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" opacity="0.97"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 92.08955223880598 0 Q 102.08955223880598 0 102.08955223880598 10 L 102.08955223880598 44.91841491841488 Q 102.08955223880598 54.91841491841488 92.08955223880598 54.91841491841488 L 10 54.91841491841488 Q 0 54.91841491841488 0 44.91841491841488 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.97"/></g></g></g></g></g><g transform="matrix(1,0,0,1,67,424)"><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="83" height="44" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="83" height="14" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="8" y="0" width="67" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="8" y="12">Container</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="61" y="12">(</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="65" y="12">s</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="71" y="12">)</text></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="83" height="30" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="28" y="14" width="73" height="30" fill-opacity="0"/></g></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="28" y="14" width="73" height="30" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="" line-height="15.5px" x="28" y="27">Eth0</text></g><g transform="translate(69,71) matrix(1,0,0,1,0,0) translate(-69,-71)"><g><rect fill="rgb(0,0,0)" stroke="none" x="5" y="29" width="73" height="15" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="5" y="42">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="20" y="42">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="23" y="42">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="31" y="42">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="34" y="42">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="49" y="42">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="52" y="42">0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="60" y="42">/</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="" line-height="15.5px" x="63" y="42">24</text></g></g><g transform="matrix(1,0,0,1,53,502)"><g transform="translate(30,0) matrix(1,0,0,1,0,0) translate(-30,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="132" height="20" fill-opacity="0"/></g></g><g transform="translate(30,0) matrix(1,0,0,1,0,0) translate(-30,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="132" height="20" fill-opacity="0"/></g></g><g transform="translate(30,0) matrix(1,0,0,1,0,0) translate(-30,0)"><g><rect fill="rgb(0,0,0)" stroke="none" x="30" y="0" width="74" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="30" y="17">Frontend</text></g></g><g transform="matrix(1,0,0,1,271.50000000000006,96.41047267550272)"><g transform="translate(0,0)"><g transform="translate(-274,-128.00000000000003) translate(2.499999999999943,31.58952732449731) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#ff9900" d="M 278.00000000000006 102.91047267550272 M 278.00000000000006 102.91047267550272 L 278.00000000000006 108.91047267550272 M 278.00000000000006 108.91047267550272 M 278.00000000000006 114.91047267550272 L 278.00000000000006 120.91047267550272 M 278.00000000000006 120.91047267550272 M 278.00000000000006 126.91047267550272 L 278.00000000000006 132.91047267550272 M 278.00000000000006 132.91047267550272 M 278.00000000000006 138.91047267550272 L 278.00000000000006 144.91047267550272 M 278.00000000000006 144.91047267550272 M 278.00000000000006 150.91047267550272 L 278.00000000000006 156.91047267550272 M 278.00000000000006 156.91047267550272 M 278.00000000000006 162.91047267550272 L 278.00000000000006 168.91047267550272 M 278.00000000000006 168.91047267550272 M 278.00000000000006 174.91047267550272 L 278.00000000000006 180.91047267550272 M 278.00000000000006 180.91047267550272 M 278.00000000000006 186.91047267550272 L 278.00000000000006 192.91047267550272 M 278.00000000000006 192.91047267550272 M 278.00000000000006 198.91047267550272 L 278.00000000000006 204.91047267550272 M 278.00000000000006 204.91047267550272 M 278.00000000000006 210.91047267550272 L 278.00000000000006 216.91047267550272 M 278.00000000000006 216.91047267550272 M 278.00000000000006 222.91047267550272 L 278.00000000000006 228.91047267550272 M 278.00000000000006 228.91047267550272 M 278.00000000000006 234.91047267550272 L 278.00000000000006 240.91047267550272 M 278.00000000000006 240.91047267550272 M 278.00000000000006 246.91047267550272 L 278.00000000000006 252.91047267550272 M 278.00000000000006 252.91047267550272 M 278.00000000000006 258.9104726755027 L 278.00000000000006 264.9104726755027 M 278.00000000000006 264.9104726755027 M 278.00000000000006 270.9104726755027 L 278.00000000000006 276.9104726755027 M 278.00000000000006 276.9104726755027 M 278.00000000000006 282.9104726755027 L 278.00000000000006 288.9104726755027 M 278.00000000000006 288.9104726755027 M 278.00000000000006 294.9104726755027 L 278.0000000000001 300.9104726755027 M 278.0000000000001 300.9104726755027" stroke-miterlimit="10" stroke-width="6"/></g></g></g></g><g transform="matrix(1,0,0,1,284.40277777777806,243.31491457745426)"><g transform="translate(0,0)"><g transform="translate(-104.62600160256406,-181.95081824533966) translate(-179.776776175214,-61.3640963321146) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#cc0000" d="M 395.6805555555633 368.59527295942837 M 395.6805555555633 368.59527295942837 L 395.6805555555633 362.59527295942837 M 395.6805555555633 362.59527295942837 M 395.6805555555633 356.59527295942837 L 395.6805555555633 350.59527295942837 M 395.6805555555633 350.59527295942837 M 395.6805555555633 344.59527295942837 L 395.6805555555633 338.59527295942837 M 395.6805555555633 338.59527295942837 M 395.6805555555633 332.59527295942837 L 395.6805555555633 326.59527295942837 M 395.6805555555633 326.59527295942837 M 395.6805555555633 320.59527295942837 L 395.6805555555633 314.59527295942837 M 395.6805555555633 314.59527295942837 M 395.6454730957975 308.5959974887863 Q 395.42473204507803 305.08992137071624 393.81507437340014 302.97276187360467 M 393.81507437340014 302.97276187360467 M 388.7448234749301 300.0250475217899 Q 387.3524512811001 299.74552399008303 385.6805555555633 299.74552399008303 L 382.7625569263178 299.74552399008303 M 382.7625569263178 299.74552399008303 M 376.7625569263178 299.74552399008303 L 370.7625569263178 299.74552399008303 M 370.7625569263178 299.74552399008303 M 364.7625569263178 299.74552399008303 L 358.7625569263178 299.74552399008303 M 358.7625569263178 299.74552399008303 M 352.7625569263178 299.74552399008303 L 346.7625569263178 299.74552399008303 M 346.7625569263178 299.74552399008303 M 340.7625569263178 299.74552399008303 L 334.7625569263178 299.74552399008303 M 334.7625569263178 299.74552399008303 M 328.7625569263178 299.74552399008303 L 322.7625569263178 299.74552399008303 M 322.7625569263178 299.74552399008303 M 316.7625569263178 299.74552399008303 L 310.7625569263178 299.74552399008303 M 310.7625569263178 299.74552399008303 M 304.7625569263178 299.74552399008303 L 300.90277777777806 299.74552399008303 Q 299.7712873305603 299.74552399008303 298.7678239465571 299.6174969268685 M 298.7678239465571 299.6174969268685 M 293.3857693473039 297.228457504614 Q 291.4531834876415 295.28265607285664 291.0247864262038 291.83266584287463 M 291.0247864262038 291.83266584287463 M 290.90277777777806 285.8375555400254 L 290.90277777777806 279.8375555400254 M 290.90277777777806 279.8375555400254 M 290.90277777777806 273.8375555400254 L 290.90277777777806 267.8375555400254 M 290.90277777777806 267.8375555400254 M 290.90277777777806 261.8375555400254 L 290.90277777777806 255.8375555400254 M 290.90277777777806 255.8375555400254 M 290.90277777777806 249.8375555400254 L 290.90277777777806 249.81491457745426" stroke-miterlimit="10" stroke-width="6"/></g></g></g></g><g transform="matrix(1,0,0,1,284.40277777777794,125.75909597261261)"><g transform="translate(0,0)"><g transform="translate(-520.5887419871792,-284.3176338453479) translate(236.18596420940128,158.55853787273531) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#cc0000" d="M 399.0972222222224 132.2590959726126 M 399.0972222222224 132.2590959726126 L 399.0972222222224 138.2590959726126 M 399.0972222222224 138.2590959726126 M 399.0972222222224 144.2590959726126 L 399.0972222222224 150.2590959726126 M 399.0972222222224 150.2590959726126 M 399.0972222222224 156.2590959726126 L 399.0972222222224 162.2590959726126 M 399.0972222222224 162.2590959726126 M 399.0972222222224 168.2590959726126 L 399.0972222222224 174.2590959726126 M 399.0972222222224 174.2590959726126 M 399.0972222222224 180.2590959726126 L 399.0972222222224 186.2590959726126 M 399.0972222222224 186.2590959726126 M 399.0972222222224 192.2590959726126 L 399.0972222222224 192.67013315779485 Q 399.0972222222224 195.91836389834842 398.0421219278347 198.11149434451426 M 398.0421219278347 198.11149434451426 M 393.6659405128273 201.97829744802294 Q 391.72749922241087 202.67013315779496 389.0972222222224 202.67013315779502 L 387.7405118088071 202.67013315779505 M 387.7405118088071 202.67013315779505 M 381.7405118088071 202.67013315779514 L 375.7405118088071 202.67013315779522 M 375.7405118088071 202.67013315779522 M 369.7405118088071 202.6701331577953 L 363.7405118088071 202.6701331577954 M 363.7405118088071 202.6701331577954 M 357.7405118088071 202.67013315779548 L 351.7405118088071 202.67013315779556 M 351.7405118088071 202.67013315779556 M 345.7405118088071 202.67013315779565 L 339.7405118088071 202.67013315779573 M 339.7405118088071 202.67013315779573 M 333.7405118088071 202.67013315779585 L 327.7405118088071 202.67013315779593 M 327.7405118088071 202.67013315779593 M 321.7405118088071 202.67013315779604 L 315.7405118088071 202.67013315779613 M 315.7405118088071 202.67013315779613 M 309.7405118088071 202.67013315779624 L 303.7405118088071 202.67013315779633 M 303.7405118088071 202.67013315779633 M 297.7598678856485 202.9657135423712 Q 294.4328042761472 203.65647640532174 292.72003362291935 205.9615322245457 M 292.72003362291935 205.9615322245457 M 290.93300792210687 211.60072437358687 Q 290.90277777777794 212.12031369352724 290.90277777777794 212.67013315779656 L 290.90277777777794 217.600146725365 M 290.90277777777794 217.600146725365 M 290.90277777777794 223.600146725365 L 290.90277777777794 229.600146725365 M 290.90277777777794 229.600146725365 M 290.90277777777794 235.600146725365 L 290.90277777777794 241.600146725365 M 290.90277777777794 241.600146725365 M 290.90277777777794 247.600146725365 L 290.90277777777794 251.03945435457118" stroke-miterlimit="10" stroke-width="6"/></g></g></g></g><g transform="matrix(1,0,0,1,155.81944444443684,107.17033542596326)"><g transform="translate(0,0)"><g transform="translate(-453.3739983974361,-305.60207610719107) translate(297.55455395299924,198.4317406812278) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#38761d" d="M 162.31944444443684 113.67033542596326 M 162.31944444443684 113.67033542596326 L 162.31944444443684 119.67033542596326 M 162.31944444443684 119.67033542596326 M 162.31944444443684 125.67033542596326 L 162.31944444443684 131.67033542596326 M 162.31944444443684 131.67033542596326 M 162.31944444443684 137.67033542596326 L 162.31944444443684 143.67033542596326 M 162.31944444443684 143.67033542596326 M 162.31944444443684 149.67033542596326 L 162.31944444443684 155.67033542596326 M 162.31944444443684 155.67033542596326 M 162.31944444443684 161.67033542596326 L 162.31944444443684 167.67033542596326 M 162.31944444443684 167.67033542596326 M 162.31944444443684 173.67033542596326 L 162.31944444443684 179.67033542596326 M 162.31944444443684 179.67033542596326 M 162.31944444443684 185.67033542596326 L 162.31944444443684 189.5415587499461 Q 162.31944444443684 190.66665091919444 162.44602768336722 191.66515984951238 M 162.44602768336722 191.66515984951238 M 164.82839767826374 197.05049598033753 Q 166.77191548654997 198.987072120429 170.22094619965992 199.41813535569676 M 170.22094619965992 199.41813535569676 M 176.2159687190514 199.5415587499461 L 182.2159687190514 199.5415587499461 M 182.2159687190514 199.5415587499461 M 188.2159687190514 199.5415587499461 L 194.2159687190514 199.5415587499461 M 194.2159687190514 199.5415587499461 M 200.2159687190514 199.5415587499461 L 206.2159687190514 199.5415587499461 M 206.2159687190514 199.5415587499461 M 212.2159687190514 199.5415587499461 L 218.2159687190514 199.5415587499461 M 218.2159687190514 199.5415587499461 M 224.2159687190514 199.5415587499461 L 230.2159687190514 199.5415587499461 M 230.2159687190514 199.5415587499461 M 236.2159687190514 199.5415587499461 L 242.2159687190514 199.5415587499461 M 242.2159687190514 199.5415587499461 M 248.2159687190514 199.5415587499461 L 254.2159687190514 199.5415587499461 M 254.2159687190514 199.5415587499461 M 260.1975055563821 199.82836878769723 Q 263.5309416038837 200.5080005580397 265.2539063203237 202.79810365944377 M 265.2539063203237 202.79810365944377 M 267.0644085030223 208.4287072615088 Q 267.09722222222206 208.96872614612755 267.09722222222206 209.5415587499461 L 267.09722222222206 214.4280528453731 M 267.09722222222206 214.4280528453731 M 267.09722222222206 220.4280528453731 L 267.09722222222206 226.4280528453731 M 267.09722222222206 226.4280528453731 M 267.09722222222206 232.4280528453731 L 267.09722222222206 235.81551770804643" stroke-miterlimit="10" stroke-width="6"/></g></g></g></g><g transform="matrix(1,0,0,1,152.4027777777773,228.0562890247442)"><g transform="translate(0,0)"><g transform="translate(-37.41125801282108,-200.3354013688455) translate(-114.99151976495621,-27.72088765589868) matrix(1,0,0,1,0,0)"><g><path fill="none" stroke="#38761d" d="M 158.9027777777773 356.7014713068111 M 158.9027777777773 356.7014713068111 L 158.9027777777773 350.7014713068111 M 158.9027777777773 350.7014713068111 M 158.9027777777773 344.7014713068111 L 158.9027777777773 338.7014713068111 M 158.9027777777773 338.7014713068111 M 158.9027777777773 332.7014713068111 L 158.9027777777773 326.7014713068111 M 158.9027777777773 326.7014713068111 M 158.9027777777773 320.7014713068111 L 158.90277777777732 314.7014713068111 M 158.90277777777732 314.7014713068111 M 158.9386276948351 308.7022201112491 Q 159.16189382506047 305.19660315095655 160.77561686556083 303.081443100939 M 160.77561686556083 303.081443100939 M 165.85041734767782 300.14100539293156 Q 167.23802796592435 299.86386619932506 168.90277777777732 299.863866199325 L 171.83292026888248 299.8638661993249 M 171.83292026888248 299.8638661993249 M 177.83292026888248 299.86386619932466 L 183.83292026888248 299.86386619932443 M 183.83292026888248 299.86386619932443 M 189.83292026888248 299.8638661993242 L 195.83292026888248 299.863866199324 M 195.83292026888248 299.863866199324 M 201.83292026888248 299.86386619932375 L 207.83292026888248 299.8638661993235 M 207.83292026888248 299.8638661993235 M 213.83292026888248 299.8638661993233 L 219.83292026888248 299.86386619932307 M 219.83292026888248 299.86386619932307 M 225.83292026888248 299.86386619932284 L 231.83292026888248 299.8638661993226 M 231.83292026888248 299.8638661993226 M 237.83292026888248 299.8638661993224 L 243.83292026888248 299.86386619932216 M 243.83292026888248 299.86386619932216 M 249.83292026888248 299.86386619932193 L 255.83292026888248 299.86386619932176 M 255.83292026888248 299.86386619932176 M 261.7528202251644 299.1405476054279 Q 264.7675844859315 298.0314529459795 266.0817275073145 295.22173704714703 M 266.0817275073145 295.22173704714703 M 267.0972222222195 289.3604205540609 L 267.0972222222195 283.3604205540609 M 267.0972222222195 283.3604205540609 M 267.0972222222195 277.3604205540609 L 267.0972222222195 271.3604205540609 M 267.0972222222195 271.3604205540609 M 267.0972222222195 265.3604205540609 L 267.0972222222195 259.3604205540609 M 267.0972222222195 259.3604205540609 M 267.0972222222195 253.3604205540609 L 267.0972222222195 247.3604205540609 M 267.0972222222195 247.3604205540609 M 267.0972222222195 241.3604205540609 L 267.0972222222195 235.3604205540609 M 267.0972222222195 235.3604205540609" stroke-miterlimit="10" stroke-width="6"/></g></g></g></g><g transform="translate(0,0) matrix(1,0,0,1,193.51767083236396,40.461511948529306)"><g><g transform="translate(0,0) scale(1.7520258247164675,1.25824690354917) translate(0.00001348378,-0.001498589)"><g><g><path fill="#FFFFFF" stroke="#ff9900" d="M 33.84 5.912 C 16.263 2.403 6.986 11.405 8.451 19.186 L 8.57 19.61 C -5.128 21.942 -1.537 38.106 5.033 38.106 L 5.679 38.085 C 4.026 45.68 20.032 53.41 28.958 49.548 L 29.531 48.92 C 32.713 55.752 44.02 56.893 55.201 57.177 C 64.89 57.425 70.546 56.658 74.702 52.411 L 75.341 52.599 C 90.852 53.845 97.915 43.133 94.526 35.442 L 95.36 35.207 C 100.648 33.808 101.258 21.602 92.187 19.797 L 92.361 19.325 C 96.299 11.385 87.011 3.896 74.124 5.303 L 73.23 4.837 C 64.003 -3.517 37.449 -2.157 34.373 6.108 L 33.84 5.912 Z" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g></g><g transform="matrix(1,0,0,1,213,55)"><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="137" height="56" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="137" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="18" y="0" width="51" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="18" y="12">Gateway</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="69" y="0" width="51" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="69" y="12">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="82" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="86" y="12">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="92" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="96" y="12">20</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="109" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="112" y="12">1</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="137" height="28" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="2" y="14" width="4" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="5" y="14" width="58" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="9" y="26">and</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="32" y="26">other</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="63" y="14" width="129" height="28" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="63" y="26">containers</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="122" y="26">on</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="7" y="40">the</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="27" y="40">same</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="59" y="40">VLAN</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="91" y="40">/</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="94" y="28" width="37" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="94" y="40">subnet</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="42" width="137" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="67" y="42" width="5" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g/></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,345.9019607843137,48.00000000000003)"><g><g transform="translate(0,0) scale(0.3709803921568625,0.18000000000000005)"><g><path fill="#4cacf5" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.6955602536997905,5.5555555555555545)"><path fill="none" stroke="none" d="M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z"/><path fill="none" stroke="#666666" d="M 0 0 M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,341.48549019607844,44.15384615384616)"><g><g transform="translate(0,0) scale(0.3709803921568625,0.18000000000000005)"><g><path fill="#4cacf5" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.6955602536997905,5.5555555555555545)"><path fill="none" stroke="none" d="M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z"/><path fill="none" stroke="#666666" d="M 0 0 M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,336,39.00000000000003)"><g><g transform="translate(0,0) scale(0.3709803921568625,0.18000000000000005)"><g><path fill="#4cacf5" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.6955602536997905,5.5555555555555545)"><path fill="none" stroke="none" d="M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z"/><path fill="none" stroke="#666666" d="M 0 0 M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="translate(0,0) matrix(1,0,0,1,57.51767083236396,94.4615119485293)"><g><g transform="translate(0,0) scale(1.7520258247164675,1.25824690354917) translate(0.00001348378,-0.001498589)"><g><g><path fill="#FFFFFF" stroke="#38761d" d="M 33.84 5.912 C 16.263 2.403 6.986 11.405 8.451 19.186 L 8.57 19.61 C -5.128 21.942 -1.537 38.106 5.033 38.106 L 5.679 38.085 C 4.026 45.68 20.032 53.41 28.958 49.548 L 29.531 48.92 C 32.713 55.752 44.02 56.893 55.201 57.177 C 64.89 57.425 70.546 56.658 74.702 52.411 L 75.341 52.599 C 90.852 53.845 97.915 43.133 94.526 35.442 L 95.36 35.207 C 100.648 33.808 101.258 21.602 92.187 19.797 L 92.361 19.325 C 96.299 11.385 87.011 3.896 74.124 5.303 L 73.23 4.837 C 64.003 -3.517 37.449 -2.157 34.373 6.108 L 33.84 5.912 Z" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g></g><g transform="matrix(1,0,0,1,77,109)"><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="137" height="56" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="137" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="18" y="0" width="51" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="18" y="12">Gateway</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="69" y="0" width="51" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="69" y="12">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="82" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="86" y="12">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="92" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="96" y="12">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="109" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="112" y="12">1</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="137" height="28" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="2" y="14" width="4" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="5" y="14" width="58" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="9" y="26">and</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="32" y="26">other</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="63" y="14" width="129" height="28" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="63" y="26">containers</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="122" y="26">on</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="7" y="40">the</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="27" y="40">same</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="59" y="40">VLAN</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="91" y="40">/</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="94" y="28" width="37" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="94" y="40">subnet</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="42" width="137" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="67" y="42" width="5" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g/></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,98.9019607843137,163.00000000000003)"><g><g transform="translate(0,0) scale(0.3709803921568625,0.18000000000000005)"><g><path fill="#4cacf5" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.6955602536997905,5.5555555555555545)"><path fill="none" stroke="none" d="M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z"/><path fill="none" stroke="#666666" d="M 0 0 M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,94.48549019607844,159.15384615384616)"><g><g transform="translate(0,0) scale(0.3709803921568625,0.18000000000000005)"><g><path fill="#4cacf5" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.6955602536997905,5.5555555555555545)"><path fill="none" stroke="none" d="M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z"/><path fill="none" stroke="#666666" d="M 0 0 M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,89,154.00000000000003)"><g><g transform="translate(0,0) scale(0.3709803921568625,0.18000000000000005)"><g><path fill="#4cacf5" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.6955602536997905,5.5555555555555545)"><path fill="none" stroke="none" d="M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z"/><path fill="none" stroke="#666666" d="M 0 0 M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="translate(0,0) matrix(1,0,0,1,345.51767083236393,86.4615119485293)"><g><g transform="translate(0,0) scale(1.7520258247164675,1.25824690354917) translate(0.00001348378,-0.001498589)"><g><g><path fill="#FFFFFF" stroke="#cc0000" d="M 33.84 5.912 C 16.263 2.403 6.986 11.405 8.451 19.186 L 8.57 19.61 C -5.128 21.942 -1.537 38.106 5.033 38.106 L 5.679 38.085 C 4.026 45.68 20.032 53.41 28.958 49.548 L 29.531 48.92 C 32.713 55.752 44.02 56.893 55.201 57.177 C 64.89 57.425 70.546 56.658 74.702 52.411 L 75.341 52.599 C 90.852 53.845 97.915 43.133 94.526 35.442 L 95.36 35.207 C 100.648 33.808 101.258 21.602 92.187 19.797 L 92.361 19.325 C 96.299 11.385 87.011 3.896 74.124 5.303 L 73.23 4.837 C 64.003 -3.517 37.449 -2.157 34.373 6.108 L 33.84 5.912 Z" stroke-miterlimit="10" stroke-width="2"/></g></g></g></g></g><g transform="matrix(1,0,0,1,365,101)"><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="137" height="56" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="137" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="18" y="0" width="51" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="18" y="12">Gateway</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="69" y="0" width="51" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="69" y="12">10</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="82" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="86" y="12">1</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="92" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="96" y="12">30</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="109" y="12">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="bold" text-decoration="" line-height="14px" x="112" y="12">1</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="14" width="137" height="28" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="2" y="14" width="4" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="5" y="14" width="58" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="9" y="26">and</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="32" y="26">other</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="63" y="14" width="129" height="28" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="63" y="26">containers</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="122" y="26">on</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="7" y="40">the</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="27" y="40">same</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="59" y="40">VLAN</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="91" y="40">/</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="94" y="28" width="37" height="14" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="12px" font-style="normal" font-weight="normal" text-decoration="" line-height="14px" x="94" y="40">subnet</text></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="42" width="137" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g><rect fill="rgb(0,0,0)" stroke="none" x="67" y="42" width="5" height="14" fill-opacity="0"/></g></g><g transform="translate(318,224) matrix(1,0,0,1,0,0) translate(-318,-224)"><g/></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,451.9019607843137,158.00000000000003)"><g><g transform="translate(0,0) scale(0.3709803921568625,0.18000000000000005)"><g><path fill="#4cacf5" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.6955602536997905,5.5555555555555545)"><path fill="none" stroke="none" d="M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z"/><path fill="none" stroke="#666666" d="M 0 0 M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,447.48549019607844,154.15384615384616)"><g><g transform="translate(0,0) scale(0.3709803921568625,0.18000000000000005)"><g><path fill="#4cacf5" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.6955602536997905,5.5555555555555545)"><path fill="none" stroke="none" d="M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z"/><path fill="none" stroke="#666666" d="M 0 0 M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,442,149.00000000000003)"><g><g transform="translate(0,0) scale(0.3709803921568625,0.18000000000000005)"><g><path fill="#4cacf5" stroke="none" d="M 0 0 L 100 0 Q 100 0 100 0 L 100 100 Q 100 100 100 100 L 0 100 Q 0 100 0 100 L 0 0 Q 0 0 0 0 Z"/><g transform="scale(2.6955602536997905,5.5555555555555545)"><path fill="none" stroke="none" d="M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z"/><path fill="none" stroke="#666666" d="M 0 0 M 0 0 L 37.09803921568625 0 Q 37.09803921568625 0 37.09803921568625 0 L 37.09803921568625 18.000000000000004 Q 37.09803921568625 18.000000000000004 37.09803921568625 18.000000000000004 L 0 18.000000000000004 Q 0 18.000000000000004 0 18.000000000000004 L 0 0 Q 0 0 0 0 Z" stroke-miterlimit="10"/></g></g></g></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,47,331)"><g transform="translate(4,4) scale(1.0034722222222223,1.0083333333333333)"><g><g transform="translate(0,0) scale(1.44,0.6)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,1.6666666666666667)"><path fill="#000000" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.44,0.6)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,1.6666666666666667)"><path fill="#5fcc5a" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" opacity="0.99"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.99"/></g></g></g></g></g><g transform="matrix(1,0,0,1,50,342)"><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="135" height="40" fill-opacity="0"/></g></g><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="135" height="20" fill-opacity="0"/></g></g><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="5" y="0" width="127" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="58" y="17">:</text></g><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="5" y="0" width="54" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="5" y="17">Parent</text></g><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="68" y="0" width="63" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="68" y="17">eth0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="105" y="17">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="110" y="17">10</text></g><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="20" width="135" height="20" fill-opacity="0"/></g></g><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="17" y="20" width="71" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="17" y="37">VLAN</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="69" y="37">ID</text></g><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="87" y="20" width="6" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="87" y="37">:</text></g><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="93" y="20" width="26" height="20" fill-opacity="0"/></g></g><g transform="translate(373,100) matrix(1,0,0,1,0,0) translate(-373,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="98" y="20" width="21" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="98" y="37">10</text></g></g><g transform="translate(0.5,0.5) matrix(1,0,0,1,367,331)"><g transform="translate(4,4) scale(1.0034722222222223,1.0083333333333333)"><g><g transform="translate(0,0) scale(1.44,0.6)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,1.6666666666666667)"><path fill="#000000" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" opacity="0.294117647"/><path fill="none" stroke="rgb(0,0,0)" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" stroke-opacity="0" stroke-miterlimit="10" opacity="0.294117647"/></g></g></g></g></g><g><g transform="translate(0,0) scale(1.44,0.6)"><g><path fill="none" stroke="none" d="M 10 0 L 90 0 Q 100 0 100 10 L 100 90 Q 100 100 90 100 L 10 100 Q 0 100 0 90 L 0 10 Q 0 0 10 0 Z"/><g transform="scale(0.6944444444444444,1.6666666666666667)"><path fill="#eb6c6c" stroke="none" d="M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" opacity="0.99"/><path fill="none" stroke="#434343" d="M 10 0 M 10 0 L 134 0 Q 144 0 144 10 L 144 50 Q 144 60 134 60 L 10 60 Q 0 60 0 50 L 0 10 Q 0 0 10 0 Z" stroke-miterlimit="10" opacity="0.99"/></g></g></g></g></g><g transform="matrix(1,0,0,1,369,341)"><g transform="translate(194,100) matrix(1,0,0,1,0,0) translate(-194,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="138" height="40" fill-opacity="0"/></g></g><g transform="translate(194,100) matrix(1,0,0,1,0,0) translate(-194,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="138" height="20" fill-opacity="0"/></g></g><g transform="translate(194,100) matrix(1,0,0,1,0,0) translate(-194,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="4" y="0" width="132" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="9" y="17">Parent</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="62" y="17">:</text></g><g transform="translate(194,100) matrix(1,0,0,1,0,0) translate(-194,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="72" y="0" width="63" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="72" y="17">eth0</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="109" y="17">.</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="114" y="17">30</text></g><g transform="translate(194,100) matrix(1,0,0,1,0,0) translate(-194,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="20" width="138" height="20" fill-opacity="0"/></g></g><g transform="translate(194,100) matrix(1,0,0,1,0,0) translate(-194,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="30" y="20" width="78" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="30" y="37">VLAN</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="normal" text-decoration="" line-height="20.5px" x="77" y="37">:</text></g><g transform="translate(194,100) matrix(1,0,0,1,0,0) translate(-194,-100)"><g><rect fill="rgb(0,0,0)" stroke="none" x="88" y="20" width="21" height="20" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="18px" font-style="normal" font-weight="bold" text-decoration="" line-height="20.5px" x="88" y="37">30</text></g><g transform="translate(194,100) matrix(1,0,0,1,0,0) translate(-194,-100)"><g/></g></g><g transform="matrix(1,0,0,1,371,44)"><g transform="translate(266,48) matrix(1,0,0,1,0,0) translate(-266,-48)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="142" height="32" fill-opacity="0"/></g></g><g transform="translate(266,48) matrix(1,0,0,1,0,0) translate(-266,-48)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="0" width="142" height="16" fill-opacity="0"/></g></g><g transform="translate(266,48) matrix(1,0,0,1,0,0) translate(-266,-48)"><g><rect fill="rgb(0,0,0)" stroke="none" x="17" y="0" width="75" height="16" fill-opacity="0"/></g></g><g transform="translate(266,48) matrix(1,0,0,1,0,0) translate(-266,-48)"><g><rect fill="rgb(0,0,0)" stroke="none" x="17" y="0" width="75" height="16" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="17" y="14">Network</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="72" y="14"> & </text></g><g transform="translate(266,48) matrix(1,0,0,1,0,0) translate(-266,-48)"><g><rect fill="rgb(0,0,0)" stroke="none" x="90" y="0" width="36" height="16" fill-opacity="0"/></g></g><g transform="translate(266,48) matrix(1,0,0,1,0,0) translate(-266,-48)"><g><rect fill="rgb(0,0,0)" stroke="none" x="90" y="0" width="36" height="16" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="90" y="14">other</text></g><g transform="translate(266,48) matrix(1,0,0,1,0,0) translate(-266,-48)"><g><rect fill="rgb(0,0,0)" stroke="none" x="0" y="16" width="142" height="16" fill-opacity="0"/></g></g><g transform="translate(266,48) matrix(1,0,0,1,0,0) translate(-266,-48)"><g><rect fill="rgb(0,0,0)" stroke="none" x="26" y="16" width="92" height="16" fill-opacity="0"/></g></g><g transform="translate(266,48) matrix(1,0,0,1,0,0) translate(-266,-48)"><g><rect fill="rgb(0,0,0)" stroke="none" x="26" y="16" width="92" height="16" fill-opacity="0"/></g><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="26" y="30">Docker</text><text fill="rgb(0, 0, 0)" stroke="none" font-family="Arial" font-size="14px" font-style="normal" font-weight="bold" text-decoration="" line-height="16.5px" x="77" y="30">Hosts</text></g></g></g></svg>
\ No newline at end of file
--- /dev/null
+# IPAM Driver
+
+During the Network and Endpoints lifecycle, the CNM model controls the IP address assignment for network and endpoint interfaces via the IPAM driver(s).
+Libnetwork has a default, built-in IPAM driver and allows third party IPAM drivers to be dynamically plugged. On network creation, the user can specify which IPAM driver libnetwork needs to use for the network's IP address management. This document explains the APIs with which the IPAM driver needs to comply, and the corresponding HTTPS request/response body relevant for remote drivers.
+
+
+## Remote IPAM driver
+
+On the same line of remote network driver registration (see [remote.md](./remote.md) for more details), libnetwork initializes the `ipams.remote` package with the `Init()` function. It passes a `ipamapi.Callback` as a parameter, which implements `RegisterIpamDriver()`. The remote driver package uses this interface to register remote drivers with libnetwork's `NetworkController`, by supplying it in a `plugins.Handle` callback. The remote drivers register and communicate with libnetwork via the Docker plugin package. The `ipams.remote` provides the proxy for the remote driver processes.
+
+
+## Protocol
+
+Communication protocol is the same as the remote network driver.
+
+## Handshake
+
+During driver registration, libnetwork will query the remote driver about the default local and global address spaces strings, and about the driver capabilities.
+More detailed information can be found in the respective section in this document.
+
+## Datastore Requirements
+
+It is the remote driver's responsibility to manage its database.
+
+## Ipam Contract
+
+The remote IPAM driver must serve the following requests:
+
+- **GetDefaultAddressSpaces**
+
+- **RequestPool**
+
+- **ReleasePool**
+
+- **Request address**
+
+- **Release address**
+
+
+The following sections explain each of the above requests' semantic, when they are called during network/endpoint lifecycle, and the corresponding payload for remote driver HTTP request/responses.
+
+
+## IPAM Configuration and flow
+
+A libnetwork user can provide IPAM related configuration when creating a network, via the `NetworkOptionIpam` setter function.
+
+```go
+func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf, opts map[string]string) NetworkOption
+```
+
+The caller has to provide the IPAM driver name and may provide the address space and a list of `IpamConf` structures for IPv4 and a list for IPv6. The IPAM driver name is the only mandatory field. If not provided, network creation will fail.
+
+In the list of configurations, each element has the following form:
+
+```go
+// IpamConf contains all the ipam related configurations for a network
+type IpamConf struct {
+ // The master address pool for containers and network interfaces
+ PreferredPool string
+ // A subset of the master pool. If specified,
+ // this becomes the container pool
+ SubPool string
+ // Input options for IPAM Driver (optional)
+ Options map[string]string
+ // Preferred Network Gateway address (optional)
+ Gateway string
+ // Auxiliary addresses for network driver. Must be within the master pool.
+ // libnetwork will reserve them if they fall into the container pool
+ AuxAddresses map[string]string
+}
+```
+
+On network creation, libnetwork will iterate the list and perform the following requests to the IPAM driver:
+
+1. Request the address pool and pass the options along via `RequestPool()`.
+2. Request the network gateway address if specified. Otherwise request any address from the pool to be used as network gateway. This is done via `RequestAddress()`.
+3. Request each of the specified auxiliary addresses via `RequestAddress()`.
+
+If the list of IPv4 configurations is empty, libnetwork will automatically add one empty `IpamConf` structure. This will cause libnetwork to request IPAM driver an IPv4 address pool of the driver's choice on the configured address space, if specified, or on the IPAM driver default address space otherwise. If the IPAM driver is not able to provide an address pool, network creation will fail.
+If the list of IPv6 configurations is empty, libnetwork will not take any action.
+The data retrieved from the IPAM driver during the execution of point 1) to 3) will be stored in the network structure as a list of `IpamInfo` structures for IPv4 and a list for IPv6.
+
+On endpoint creation, libnetwork will iterate over the list of configs and perform the following operation:
+
+1. Request an IPv4 address from the IPv4 pool and assign it to the endpoint interface IPv4 address. If successful, stop iterating.
+2. Request an IPv6 address from the IPv6 pool (if exists) and assign it to the endpoint interface IPv6 address. If successful, stop iterating.
+
+Endpoint creation will fail if any of the above operation does not succeed
+
+On endpoint deletion, libnetwork will perform the following operations:
+
+1. Release the endpoint interface IPv4 address
+2. Release the endpoint interface IPv6 address if present
+
+On network deletion, libnetwork will iterate the list of `IpamData` structures and perform the following requests to ipam driver:
+
+1. Release the network gateway address via `ReleaseAddress()`
+2. Release each of the auxiliary addresses via `ReleaseAddress()`
+3. Release the pool via `ReleasePool()`
+
+### GetDefaultAddressSpaces
+
+GetDefaultAddressSpaces returns the default local and global address space names for this IPAM. An address space is a set of non-overlapping address pools isolated from other address spaces' pools. In other words, same pool can exist on N different address spaces. An address space naturally maps to a tenant name.
+In libnetwork, the meaning associated to `local` or `global` address space is that a local address space doesn't need to get synchronized across the
+cluster whereas the global address spaces does. Unless specified otherwise in the IPAM configuration, libnetwork will request address pools from the default local or default global address space based on the scope of the network being created. For example, if not specified otherwise in the configuration, libnetwork will request address pool from the default local address space for a bridge network, whereas from the default global address space for an overlay network.
+
+During registration, the remote driver will receive a POST message to the URL `/IpamDriver.GetDefaultAddressSpaces` with no payload. The driver's response should have the form:
+
+
+ {
+ "LocalDefaultAddressSpace": string
+ "GlobalDefaultAddressSpace": string
+ }
+
+
+
+### RequestPool
+
+This API is for registering an address pool with the IPAM driver. Multiple identical calls must return the same result.
+It is the IPAM driver's responsibility to keep a reference count for the pool.
+
+```go
+RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error)
+```
+
+
+For this API, the remote driver will receive a POST message to the URL `/IpamDriver.RequestPool` with the following payload:
+
+ {
+ "AddressSpace": string
+ "Pool": string
+ "SubPool": string
+ "Options": map[string]string
+ "V6": bool
+ }
+
+
+Where:
+
+ * `AddressSpace` the IP address space. It denotes a set of non-overlapping pools.
+ * `Pool` The IPv4 or IPv6 address pool in CIDR format
+ * `SubPool` An optional subset of the address pool, an ip range in CIDR format
+ * `Options` A map of IPAM driver specific options
+ * `V6` Whether an IPAM self-chosen pool should be IPv6
+
+AddressSpace is the only mandatory field. If no `Pool` is specified IPAM driver may choose to return a self chosen address pool. In such case, `V6` flag must be set if caller wants an IPAM-chosen IPv6 pool. A request with empty `Pool` and non-empty `SubPool` should be rejected as invalid.
+If a `Pool` is not specified IPAM will allocate one of the default pools. When `Pool` is not specified, the `V6` flag should be set if the network needs IPv6 addresses to be allocated.
+
+A successful response is in the form:
+
+
+ {
+ "PoolID": string
+ "Pool": string
+ "Data": map[string]string
+ }
+
+
+Where:
+
+* `PoolID` is an identifier for this pool. Same pools must have same pool id.
+* `Pool` is the pool in CIDR format
+* `Data` is the IPAM driver supplied metadata for this pool
+
+
+### ReleasePool
+
+This API is for releasing a previously registered address pool.
+
+```go
+ReleasePool(poolID string) error
+```
+
+For this API, the remote driver will receive a POST message to the URL `/IpamDriver.ReleasePool` with the following payload:
+
+ {
+ "PoolID": string
+ }
+
+Where:
+
+* `PoolID` is the pool identifier
+
+A successful response is empty:
+
+ {}
+
+### RequestAddress
+
+This API is for reserving an ip address.
+
+```go
+RequestAddress(string, net.IP, map[string]string) (*net.IPNet, map[string]string, error)
+```
+
+For this API, the remote driver will receive a POST message to the URL `/IpamDriver.RequestAddress` with the following payload:
+
+ {
+ "PoolID": string
+ "Address": string
+ "Options": map[string]string
+ }
+
+Where:
+
+* `PoolID` is the pool identifier
+* `Address` is the required address in regular IP form (A.B.C.D). If this address cannot be satisfied, the request fails. If empty, the IPAM driver chooses any available address on the pool
+* `Options` are IPAM driver specific options
+
+
+A successful response is in the form:
+
+
+ {
+ "Address": string
+ "Data": map[string]string
+ }
+
+
+Where:
+
+* `Address` is the allocated address in CIDR format (A.B.C.D/MM)
+* `Data` is some IPAM driver specific metadata
+
+### ReleaseAddress
+
+This API is for releasing an IP address.
+
+For this API, the remote driver will receive a POST message to the URL `/IpamDriver.ReleaseAddress` with the following payload:
+
+ {
+ "PoolID": string
+ "Address": string
+ }
+
+Where:
+
+* `PoolID` is the pool identifier
+* `Address` is the IP address to release
+
+
+
+### GetCapabilities
+
+During the driver registration, libnetwork will query the driver about its capabilities. It is not mandatory for the driver to support this URL endpoint. If driver does not support it, registration will succeed with empty capabilities automatically added to the internal driver handle.
+
+During registration, the remote driver will receive a POST message to the URL `/IpamDriver.GetCapabilities` with no payload. The driver's response should have the form:
+
+
+ {
+ "RequiresMACAddress": bool
+ "RequiresRequestReplay": bool
+ }
+
+
+
+## Capabilities
+
+Capabilities are requirements, features the remote ipam driver can express during registration with libnetwork.
+As of now libnetwork accepts the following capabilities:
+
+### RequiresMACAddress
+
+It is a boolean value which tells libnetwork whether the ipam driver needs to know the interface MAC address in order to properly process the `RequestAddress()` call.
+If true, on `CreateEndpoint()` request, libnetwork will generate a random MAC address for the endpoint (if an explicit MAC address was not already provided by the user) and pass it to `RequestAddress()` when requesting the IP address inside the options map. The key will be the `netlabel.MacAddress` constant: `"com.docker.network.endpoint.macaddress"`.
+
+### RequiresRequestReplay
+
+It is a boolean value which tells libnetwork whether the ipam driver needs to receive the replay of the `RequestPool()` and `RequestAddress()` requests on daemon reload. When libnetwork controller is initializing, it retrieves from local store the list of current local scope networks and, if this capability flag is set, it allows the IPAM driver to reconstruct the database of pools by replaying the `RequestPool()` requests for each pool and the `RequestAddress()` for each network gateway owned by the local networks. This can be useful to ipam drivers which decide not to persist the pools allocated to local scope networks.
+
+
+## Appendix
+
+A Go extension for the IPAM remote API is available at [docker/go-plugins-helpers/ipam](https://github.com/docker/go-plugins-helpers/tree/master/ipam)
--- /dev/null
+
+This document provides a TLD&R version of https://docs.docker.com/v1.6/articles/networking/.
+If more interested in detailed operational design, please refer to this link.
+
+## Docker Networking design as of Docker v1.6
+
+Prior to libnetwork, Docker Networking was handled in both Docker Engine and libcontainer.
+Docker Engine makes use of the Bridge Driver to provide single-host networking solution with the help of linux bridge and IPTables.
+Docker Engine provides simple configurations such as `--link`, `--expose`,... to enable container connectivity within the same host by abstracting away networking configuration completely from the Containers.
+For external connectivity, it relied upon NAT & Port-mapping
+
+Docker Engine was responsible for providing the configuration for the container's networking stack.
+
+Libcontainer would then use this information to create the necessary networking devices and move them in to a network namespace.
+This namespace would then be used when the container is started.
--- /dev/null
+
+# Macvlan Driver
+
+### Overview
+
+The Macvlan driver provides operators the ability to integrate Docker networking in a simple and lightweight fashion into the underlying network. Macvlan is supported by the Linux kernel and is a well known Linux network type. The Macvlan built-in driver does not require any port mapping and supports VLAN trunking (Virtual Local Area Network). VLANs are a traditional method of network virtualization and layer 2 datapath isolation that is prevalent in some form or fashion in most data centers.
+
+The Linux implementation is considered lightweight because it eliminates the need for using a Linux bridge for isolating containers on the Docker host. The VLAN driver requires full access to the underlying host making it suitable for Enterprise data centers that have administrative access to the host.
+
+Instead of attaching container network interfaces to a Docker host Linux bridge for a network, the driver simply connects the container interface to the Docker Host Ethernet interface (or sub-interface). Each network is attached to a unique parent interface. Containers in a network share a common broadcast domain and intra-network connectivity is permitted. Two separate networks will each have a unique parent interface and that parent is what enforces datapath isolation between two networks. In order for inter-network communications to occur, an IP router, external to the Docker host, is required to route between the two networks by hair-pining into the physical network and then back to the Docker host. While hairpinning traffic can be less efficient then east/west traffic staying local to the host, there is often more complexity associated with disaggregating services to the host. It can be practical for some users to leverage existing network services, such firewalls and load balancers that already exist in a data center architecture.
+
+When using traditional Linux bridges there are two common techniques to get traffic out of a container and into the physical network and vice versa. The first method to connect containers to the underlying network is to use Iptable rules which perform a NAT translation from a bridge that represents the Docker network to the physical Ethernet connection such as `eth0`. The upside of Iptables using the Docker built-in bridge driver is that the NIC does not have to be in promiscuous mode. The second bridge driver method is to move a host's external Ethernet connection into the bridge. Moving the host Ethernet connection can at times be unforgiving. Common mistakes such as cutting oneself off from the host, or worse, creating bridging loops that can cripple a VLAN throughout a data center can open a network design up to potential risks as the infrastructure grows.
+
+Connecting containers without any NATing is where the VLAN drivers accel. Rather than having to manage a bridge for each Docker network containers are connected directly to a `parent` interface such as `eth0` that attaches the container to the same broadcast domain as the parent interface. A simple example is if a host's `eth0` is on the network `192.168.1.0/24` with a gateway of `192.168.1.1` then a Macvlan Docker network can start containers on the addresses `192.168.1.2 - 192.168.1.254`. Containers use the same network as the parent `-o parent` that is specified in the `docker network create` command.
+
+There are positive performance implication as a result of bypassing the Linux bridge, along with the simplicity of less moving parts, which is also attractive. Macvlan containers are easy to troubleshoot. The actual MAC and IP address of the container is bridged into the upstream network making a problematic application easy for operators to trace from the network. Existing underlay network management and monitoring tools remain relevant.
+
+### Pre-Requisites
+
+- The examples on this page are all single host and require Docker v1.12 or greater running on Linux.
+
+- Any examples using a sub-interface like `eth0.10` can be replaced with `eth0` or any other valid parent interface on the Docker host. Sub-interfaces with a `.` are dynamically created. The parent `-o parent` interface parameter can also be left out of the `docker network create` all together and the driver will create a `dummy` Linux type interface that will enable local host connectivity to perform the examples.
+
+- Kernel requirements:
+
+ - To check your current kernel version, use `uname -r` to display your kernel version.
+ - Macvlan Linux kernel v3.9–3.19 and 4.0+.
+
+### MacVlan Bridge Mode Example Usage
+
+- Macvlan driver networks are attached to a parent Docker host interface. Examples are a physical interface such as `eth0`, a sub-interface for 802.1q VLAN tagging like `eth0.10` (`.10` representing VLAN `10`) or even bonded `bond0` host adapters which bundle two Ethernet interfaces into a single logical interface and provide diversity in the server connection.
+
+- The specified gateway is external to the host that is expected to be provided by the network infrastructure. If a gateway is not specified using the `--gateway` parameter, then Libnetwork will infer the first usable address of a subnet. For example, if a network's subnet is `--subnet 10.1.100.0/24` and no gateway is specified, Libnetwork will assign a gateway of `10.1.100.1` to the container. A second example would be a subnet of `--subnet 10.1.100.128/25` would receive a gateway of `10.1.100.129`.
+
+- Containers on separate networks cannot reach one another without an external process routing between the two networks/subnets.
+
+- Each Macvlan Bridge mode Docker network is isolated from one another and there can be only one network attached to a parent interface at a time. There is a theoretical limit of 4,094 sub-interfaces per host adapter that a Docker network could be attached to.
+
+- The driver limits one network per parent interface. The driver does however accommodate secondary subnets to be allocated in a single Docker network for a multi-subnet requirement. The upstream router is responsible for proxy-arping between the two subnets.
+
+- Any Macvlan container sharing the same subnet can communicate via IP to any other container in the same subnet without a gateway. It is important to note, that the parent will go into promiscuous mode when a container is attached to the parent since each container has a unique MAC address. Alternatively, Ipvlan which is currently an experimental driver uses the same MAC address as the parent interface and thus precluding the need for the parent being promiscuous.
+
+In the following example, `eth0` on the docker host has an IP on the `172.16.86.0/24` network and a default gateway of `172.16.86.1`. The gateway is an external router with an address of `172.16.86.1`. An IP address is not required on the Docker host interface `eth0` in `bridge` mode, it merely needs to be on the proper upstream network to get forwarded by a network switch or network router.
+
+
+
+**Note** The Docker network subnet specified needs to match the network that parent interface of the Docker host for external communications. For example, use the same subnet and gateway of the Docker host ethernet interface specified by the `-o parent=` option. The parent interface is not required to have a IP address assigned to it, since this is simply L2 flooding and learning.
+
+- The parent interface used in this example is `eth0` and it is on the subnet `172.16.86.0/24`. The containers in the `docker network` will also need to be on this same subnet as the parent `-o parent=`. The gateway is an external router on the network.
+
+- Libnetwork driver types are specified with the `-d <driver_name>` option. In this case `-d macvlan`
+
+- The parent interface `-o parent=eth0` is configured as followed:
+
+```
+ip addr show eth0
+3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
+ inet 172.16.86.250/24 brd 172.16.86.255 scope global eth0
+```
+
+Create the macvlan network and run a couple of containers attached to it:
+
+```
+# Macvlan (-o macvlan_mode= Defaults to Bridge mode if not specified)
+docker network create -d macvlan \
+ --subnet=172.16.86.0/24 \
+ --gateway=172.16.86.1 \
+ -o parent=eth0 pub_net
+
+# Run a container on the new network specifying the --ip address.
+docker run --net=pub_net --ip=172.16.86.10 -itd alpine /bin/sh
+
+# Start a second container and ping the first
+docker run --net=pub_net -it --rm alpine /bin/sh
+ping -c 4 172.16.86.10
+
+```
+
+ Take a look at the containers ip and routing table:
+
+```
+
+ip a show eth0
+ eth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UNKNOWN
+ link/ether 46:b2:6b:26:2f:69 brd ff:ff:ff:ff:ff:ff
+ inet 172.16.86.2/24 scope global eth0
+
+ip route
+ default via 172.16.86.1 dev eth0
+ 172.16.86.0/24 dev eth0 src 172.16.86.2
+
+# NOTE: the containers can NOT ping the underlying host interfaces as
+# they are intentionally filtered by Linux for additional isolation.
+# In this case the containers cannot ping the -o parent=172.16.86.250
+```
+
+
+Users can explicitly specify the `bridge` mode option `-o macvlan_mode=bridge` or leave the mode option out since the most common mode of `bridge` is the driver default.
+
+While the `eth0` interface does not need to have an IP address, it is not uncommon to have an IP address on the interface. Addresses can be excluded from getting an address from the default built in IPAM by using the `--aux-address=x.x.x.x` argument. This will blacklist the specified address from being handed out to containers from the built-in Libnetwork IPAM.
+
+- The following is the same network example as above, but blacklisting the `-o parent=eth0` address from being handed out to a container.
+
+```
+docker network create -d macvlan \
+ --subnet=172.16.86.0/24 \
+ --gateway=172.16.86.1 \
+ --aux-address="exclude_host=172.16.86.250" \
+ -o parent=eth0 pub_net
+```
+
+Another option for specifying what subpool or range of usable addresses is used by the default Docker IPAM driver is to use the argument `--ip-range=`. This instructs the driver to allocate container addresses from the specific range, rather then the broader range from the `--subnet=` argument.
+
+- The network create in the following example, allocates addresses beginning at `192.168.32.128` and increments n+1 upwards from there.
+
+```
+docker network create -d macvlan \
+ --subnet=192.168.32.0/24 \
+ --ip-range=192.168.32.128/25 \
+ --gateway=192.168.32.254 \
+ -o parent=eth0 macnet32
+
+# Start a container and verify the address is 192.168.32.128
+docker run --net=macnet32 -it --rm alpine /bin/sh
+```
+
+The network can then be deleted with:
+
+```
+docker network rm <network_name or id>
+```
+
+- **Note:** Linux Macvlan interface types are not able to ping or communicate with the default namespace IP address. For example, if you create a container and try to ping the Docker host's `eth0` it will **not** work. That traffic is explicitly filtered by the kernel to offer additional provider isolation and security. This is a common gotcha when a user first uses those Linux interface types since it is natural to ping local addresses when testing.
+
+For more on Docker networking commands see: [Working with Docker network commands](https://docs.docker.com/engine/userguide/networking/work-with-networks/)
+
+### Macvlan 802.1q Trunk Bridge Mode Example Usage
+
+VLANs have long been a primary means of virtualizing data center networks and are still in virtually all existing networks today. VLANs work by tagging a Layer-2 isolation domain with a 12-bit identifier ranging from 1-4094. The VLAN tag is inserted into a packet header that enables a logical grouping of a single subnet or multiple subnets of IPv4 and/or IPv6. It is very common for network operators to separate traffic using VLANs based on a subnet(s) function or security profile such as `web`, `db` or any other isolation requirements.
+
+It is very common to have a compute host requirement of running multiple virtual networks concurrently on a host. Linux networking has long supported VLAN tagging, also known by its standard 802.1Q, for maintaining datapath isolation between networks. The Ethernet link connected to a Docker host can be configured to support the 802.1q VLAN IDs by creating Linux sub-interfaces, each sub-interface being allocated a unique VLAN ID.
+
+
+
+Trunking 802.1q to a Linux host is notoriously painful for operations. It requires configuration file changes in order to be persistent through a reboot. If a bridge is involved, a physical NIC needs to be moved into the bridge and the bridge then gets the IP address. This has lead to many a stranded servers since the risk of cutting off access or misconfiguration is relatively high.
+
+Like all of the Docker network drivers, the overarching goal is to alleviate the operational pains of managing network resources. To that end, when a network receives a sub-interface as the parent that does not exist, the drivers create the VLAN tagged interfaces while creating the network. If the sub-interface already exists it is simply used as is.
+
+In the case of a host reboot, instead of needing to modify often complex network configuration files the driver will recreate all network links when the Docker daemon restarts. The driver tracks if it created the VLAN tagged sub-interface originally with the network create and will **only** recreate the sub-interface after a restart if it created the link in the first place.
+
+The same holds true if the network is deleted `docker network rm`. If driver created the sub-interface with `docker network create` it will remove the sub-interface link for the operator.
+
+If the user doesn't want Docker to create and delete the `-o parent` sub-interface, then you simply pass an interface that already exists as the parent link. Parent interfaces such as `eth0` are not deleted, only interfaces that are slave links.
+
+For the driver to add/delete the vlan sub-interfaces the format needs to be `-o parent interface_name.vlan_tag`.
+
+For example: `-o parent eth0.50` denotes a parent interface of `eth0` with a slave of `eth0.50` tagged with vlan id `50`. The equivalent `ip link` command would be `ip link add link eth0 name eth0.50 type vlan id 50`.
+
+Replace the `macvlan` with `ipvlan` in the `-d` driver argument to create macvlan 802.1q trunks.
+
+**Vlan ID 50**
+
+In the next example, the network is tagged and isolated by the Docker host. A parent of `eth0.50` will tag the Ethernet traffic with the vlan id `50` specified by the parent nomenclature `-o parent=eth0.50`. Other naming formats can be used, but the links need to be added and deleted manually using `ip link` or Linux configuration files. As long as the `-o parent` exists, anything can be used if compliant with Linux netlink.
+
+```
+# now add networks and hosts as you would normally by attaching to the master (sub)interface that is tagged
+docker network create -d macvlan \
+ --subnet=192.168.50.0/24 \
+ --gateway=192.168.50.1 \
+ -o parent=eth0.50 macvlan50
+
+# In two separate terminals, start a Docker container and the containers can now ping one another.
+docker run --net=macvlan50 -it --name macvlan_test5 --rm alpine /bin/sh
+docker run --net=macvlan50 -it --name macvlan_test6 --rm alpine /bin/sh
+```
+
+**Vlan ID 60**
+
+In the second network, tagged and isolated by the Docker host, `eth0.60` is the parent interface tagged with vlan id `60` specified with `-o parent=eth0.60`. The `macvlan_mode=` defaults to `macvlan_mode=bridge`. It can also be explicitly set with the same result, as shown in the next example.
+
+```
+# now add networks and hosts as you would normally by attaching to the master (sub)interface that is tagged.
+docker network create -d macvlan \
+ --subnet=192.168.60.0/24 \
+ --gateway=192.168.60.1 \
+ -o parent=eth0.60 -o \
+ -o macvlan_mode=bridge macvlan60
+
+# In two separate terminals, start a Docker container and the containers can now ping one another.
+docker run --net=macvlan60 -it --name macvlan_test7 --rm alpine /bin/sh
+docker run --net=macvlan60 -it --name macvlan_test8 --rm alpine /bin/sh
+```
+
+**Example:** Multi-Subnet Macvlan 802.1q Trunking
+
+The same as the example before except there is an additional subnet bound to the network that the user can choose to provision containers on. In MacVlan/Bridge mode, containers can only ping one another if they are on the same subnet/broadcast domain unless there is an external router that routes the traffic (answers ARP etc) between the two subnets. Multiple subnets assigned to a network require a gateway external to the host that falls within the subnet range to hairpin the traffic back to the host.
+
+
+```
+docker network create -d macvlan \
+ --subnet=10.1.20.0/24 --subnet=10.1.10.0/24 \
+ --gateway=10.1.20.1 --gateway=10.1.10.1 \
+ -o parent=eth0.101 mcv101
+
+# View Links after to network create `ip link`
+$ ip link
+
+# Test 10.1.20.10.0/24 connectivity
+docker run --net=mcv101 --ip=10.1.20.9 -itd alpine /bin/sh
+docker run --net=mcv101 --ip=10.1.20.10 -it --rm alpine ping -c 4 10.1.20.10
+
+# Test 10.1.10.10.0/24 connectivity
+docker run --net=mcv101 --ip=10.1.10.10 -itd alpine /bin/sh
+docker run --net=mcv101 --ip=10.1.10.9 -it --rm alpine ping -c 4 10.1.10.10
+
+# Delete All Containers
+docker rm -f `docker ps -qa`
+
+# Delete all Networks
+docker network rm $(docker network ls -q)
+
+# Run ip links again and verify the links are cleaned up
+ip link
+```
+
+Hosts on the same VLAN are typically on the same subnet and almost always are grouped together based on their security policy. In most scenarios, a multi-tier application is tiered into different subnets because the security profile of each process requires some form of isolation. For example, hosting your credit card processing on the same virtual network as the front-end web-server would be a regulatory compliance issue, along with circumventing the long standing best practice of layered defense in depth architectures. VLANs or the equivalent VNI (Virtual Network Identifier) when using the built-in Overlay driver, are the first step in isolating tenant traffic.
+
+
+
+
+### Dual Stack IPv4 IPv6 Macvlan Bridge Mode
+
+The following specifies both v4 and v6 addresses. An address from each family will be assigned to each container. You can specify either family type explicitly or allow the Libnetwork IPAM to assign them from the subnet pool.
+
+*Note on IPv6:* When declaring a v6 subnet with a `docker network create`, the flag `--ipv6` is required along with the subnet (in the following example `--subnet=2001:db8:abc8::/64`). Similar to IPv4 functionality, if a IPv6 `--gateway` is not specified, the first usable address in the v6 subnet is inferred and assigned as the gateway for the broadcast domain.
+
+The following example creates a network with multiple IPv4 and IPv6 subnets. The network is attached to a sub-interface of `eth0.218`. By specifying `eth0.218` as the parent, the driver will create the sub-interface (if it does not already exist) and tag all traffic for containers in the network with a VLAN ID of 218. The physical switch port on the ToR (top of rack) network port needs to have 802.1Q trunking enabled for communications in and out of the host to work.
+
+```
+# Create multiple subnets w/ dual stacks:
+docker network create -d macvlan \
+ --subnet=192.168.216.0/24 --subnet=192.168.218.0/24 \
+ --gateway=192.168.216.1 --gateway=192.168.218.1 \
+ --ipv6 --subnet=2001:db8:abc8::/64 --gateway=2001:db8:abc8::10 \
+ -o parent=eth0.218 \
+ -o macvlan_mode=bridge macvlan216
+
+# Start a container on the first subnet 192.168.216.0/24
+docker run --net=macvlan216 --name=macnet216_test --ip=192.168.216.10 -itd alpine /bin/sh
+
+# Start a container on the second subnet 192.168.218.0/24
+docker run --net=macvlan216 --name=macnet218_test --ip=192.168.218.10 -itd alpine /bin/sh
+
+# Ping the first container started on the 192.168.216.0/24 subnet
+docker run --net=macvlan216 --ip=192.168.216.11 -it --rm alpine /bin/sh
+
+# From inside the container shell ping the other host on the same subnet and then exit
+$ ping -c4 192.168.216.10
+$ exit
+
+# Ping the first container started on the 192.168.218.0/24 subnet
+docker run --net=macvlan216 --ip=192.168.218.11 -it --rm alpine /bin/sh
+
+# From inside the container shell ping the other host on the same subnet and then exit
+$ ping -c4 192.168.218.10
+$ exit
+
+# Start a container in the back explicitly declaring the v6 address
+docker run --net=macvlan216 --ip6=2001:db8:abc8::20 -itd alpine /bin/sh
+
+# Start another container pinging the v6 address of the previous container started in the background
+docker run --net=macvlan216 -it --rm alpine /bin/sh
+$ ping6 -c4 2001:db8:abc8::20
+$ exit
+# Or, run the ping as a explicit process
+docker run --net=macvlan216 -it --rm alpine ping6 -c4 2001:db8:abc8::20
+```
+
+View the details of one of the containers:
+
+```
+docker run --net=macvlan216 --ip=192.168.216.11 -it --rm alpine /bin/sh
+
+root@526f3060d759:/# ip a show eth0
+25: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UNKNOWN
+ link/ether 02:42:c0:a8:d8:0b brd ff:ff:ff:ff:ff:ff
+ inet 192.168.216.11/24 scope global eth0
+ valid_lft forever preferred_lft forever
+ inet6 2001:db8:abc8::1/64 scope link
+ valid_lft forever preferred_lft forever
+
+# The default gateway is a network gateway external to the Docker host
+$ ip route
+default via 192.168.216.1 dev eth0
+192.168.216.0/24 dev eth0 src 192.168.216.11
+
+# Specified v6 gateway of 2001:db8:abc8::10
+$ ip -6 route
+ 2001:db8:abc4::/64 dev eth0 proto kernel metric 256
+ 2001:db8:abc8::/64 dev eth0 proto kernel metric 256
+ default via 2001:db8:abc8::10 dev eth0 metric 1024
+
+#Containers can have both v4 and v6 addresses assigned to their interfaces or
+# Both v4 and v6 addresses can be assigned to the container's interface
+docker run --net=macvlan216 --ip=192.168.216.50 --ip6=2001:db8:abc8::50 -it --rm alpine /bin/sh
+
+# View the details of the dual stack eth0 interface from inside of the container
+$ ip a show eth0
+95: eth0@if91: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UNKNOWN
+ link/ether 02:42:c0:a8:d8:32 brd ff:ff:ff:ff:ff:ff
+ inet 192.168.216.50/24 scope global eth0
+ valid_lft forever preferred_lft forever
+ inet6 2001:db8:abc8::50/64 scope global flags 02
+ valid_lft forever preferred_lft forever
+```
+
+The next example demonstrates how default gateways are inferred if the `--gateway` option is not specified for a subnet in the `docker network create ...` command. If the gateway is not specified, the first usable address in the subnet is selected. It also demonstrates how `--ip-range` and `--aux-address` are used in conjunction to exclude address assignments within a network and reserve sub-pools of usable addresses within a network's subnet. All traffic is untagged since `eth0` is used rather then a sub-interface.
+
+```
+docker network create -d macvlan \
+ --subnet=192.168.136.0/24 \
+ --subnet=192.168.138.0/24 \
+ --ipv6 --subnet=fd11::/64 \
+ --ip-range=192.168.136.0/25 \
+ --ip-range=192.168.138.0/25 \
+ --aux-address="reserved1=fd11::2" \
+ --aux-address="reserved2=192.168.136.2" \
+ --aux-address="reserved3=192.168.138.2" \
+ -o parent=eth0 mcv0
+
+docker run --net=mcv0 -it --rm alpine /bin/sh
+```
+
+Next is the output from a running container provisioned on the example network named `mcv0`.
+
+```
+# Container eth0 output (the fe80::42:c0ff:fea8:8803/64 address is the local link addr)
+ip address show eth0
+100: eth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UNKNOWN
+ link/ether 02:42:c0:a8:88:03 brd ff:ff:ff:ff:ff:ff
+ inet 192.168.136.3/24 scope global eth0
+ valid_lft forever preferred_lft forever
+ inet6 fd11::3/64 scope global flags 02
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:c0ff:fea8:8803/64 scope link
+ valid_lft forever preferred_lft forever
+
+# IPv4 routing table from within the container
+$ ip route
+default via 192.168.136.1 dev eth0
+192.168.136.0/24 dev eth0 src 192.168.136.3
+
+# IPv6 routing table from within the container (the second v6 addresses is the local link addr)
+$ ip -6 route
+fd11::/64 dev eth0 metric 256
+fe80::/64 dev eth0 metric 256
+default via fd11::1 dev eth0 metric 1024
+```
+
+- After the examples, `docker rm -f `docker ps -qa`` can be used to remove all existing containers on the host, both running and stopped.
+
+A key takeaway is, operators have the ability to map their physical network into their virtual network for integrating containers into their environment with no operational overhauls required. NetOps simply drops an 802.1q trunk into the Docker host. That virtual link would be the `-o parent=` passed in the network creation. For untagged (non-VLAN) links, it is as simple as `-o parent=eth0` or for 802.1q trunks with VLAN IDs each network gets mapped to the corresponding VLAN/Subnet from the network.
+
+An example being, NetOps provides VLAN ID and the associated subnets for VLANs being passed on the Ethernet link to the Docker host server. Those values are simply plugged into the `docker network create` commands when provisioning the Docker networks. These are persistent configurations that are applied every time the Docker engine starts which alleviates having to manage often complex configuration files. The network interfaces can also be managed manually by being pre-created and docker networking will never modify them, simply use them as parent interfaces. Example mappings from NetOps to Docker network commands are as follows:
+
+- VLAN: 10, Subnet: 172.16.80.0/24, Gateway: 172.16.80.1
+
+ - `--subnet=172.16.80.0/24 --gateway=172.16.80.1 -o parent=eth0.10`
+
+- VLAN: 20, IP subnet: 172.16.50.0/22, Gateway: 172.16.50.1
+
+ - `--subnet=172.16.50.0/22 --gateway=172.16.50.1 -o parent=eth0.20`
+
+- VLAN: 30, Subnet: 10.1.100.0/16, Gateway: 10.1.100.1
+
+ - `--subnet=10.1.100.0/16 --gateway=10.1.100.1 -o parent=eth0.30`
+
+### Manually Creating 802.1q Links
+
+If a user does not want the driver to create the vlan sub-interface it simply needs to exist prior to the `docker network create`. If you have sub-interface naming that is not `interface.vlan_id` it is honored in the `-o parent=` option again as long as the interface exists and us up.
+
+Links if manually created can be named anything you want. As long as the exist when the network is created that is all that matters. Manually created links do not get deleted regardless of the name when the network is deleted with `docker network rm`.
+
+```
+# create a new sub-interface tied to dot1q vlan 40
+ip link add link eth0 name eth0.40 type vlan id 40
+
+# enable the new sub-interface
+ip link set eth0.40 up
+
+# now add networks and hosts as you would normally by attaching to the master (sub)interface that is tagged
+docker network create -d macvlan \
+ --subnet=192.168.40.0/24 \
+ --gateway=192.168.40.1 \
+ -o parent=eth0.40 macvlan40
+
+# in two separate terminals, start a Docker container and the containers can now ping one another.
+docker run --net=macvlan40 -it --name mcv_test5 --rm alpine /bin/sh
+docker run --net=macvlan40 -it --name mcv_test6 --rm alpine /bin/sh
+```
+
+**Example:** Vlan sub-interface manually created with any name:
+
+```
+# create a new sub interface tied to dot1q vlan 40
+ip link add link eth0 name foo type vlan id 40
+
+# enable the new sub-interface
+ip link set foo up
+
+# now add networks and hosts as you would normally by attaching to the master (sub)interface that is tagged
+docker network create -d macvlan \
+ --subnet=192.168.40.0/24 --gateway=192.168.40.1 \
+ -o parent=foo macvlan40
+
+# in two separate terminals, start a Docker container and the containers can now ping one another.
+docker run --net=macvlan40 -it --name mcv_test5 --rm alpine /bin/sh
+docker run --net=macvlan40 -it --name mcv_test6 --rm alpine /bin/sh
+```
+
+Manually created links can be cleaned up with:
+
+```
+ip link del foo
+```
+
+As with all of the Libnetwork drivers, networks of various driver types can be mixed and matched. This even applies to 3rd party ecosystem drivers that can be run in parallel with built-in drivers for maximum flexibility to the user.
--- /dev/null
+# Overlay Driver
+
+### Design
+TODO
+
+### Multi-Host Overlay Driver Quick Start
+
+This example is to provision two Docker Hosts with the **experimental** Libnetwork overlay network driver.
+
+### Pre-Requisites
+
+- Kernel >= 3.16
+- Experimental Docker client
+
+### Install Docker Experimental
+
+Follow Docker experimental installation instructions at: [https://github.com/docker/docker/tree/master/experimental](https://github.com/docker/docker/tree/master/experimental)
+
+To ensure you are running the experimental Docker branch, check the version and look for the experimental tag:
+
+```
+$ docker -v
+Docker version 1.8.0-dev, build f39b9a0, experimental
+```
+
+### Install and Bootstrap K/V Store
+
+
+Multi-host networking uses a pluggable Key-Value store backend to distribute states using `libkv`.
+`libkv` supports multiple pluggable backends such as `consul`, `etcd` & `zookeeper` (more to come).
+
+In this example we will use `consul`
+
+Install:
+
+```
+$ curl -OL https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip
+$ unzip 0.5.2_linux_amd64.zip
+$ mv consul /usr/local/bin/
+```
+
+**host-1** Start Consul as a server in bootstrap mode:
+
+```
+$ consul agent -server -bootstrap -data-dir /tmp/consul -bind=<host-1-ip-address>
+```
+
+**host-2** Start the Consul agent:
+
+```
+$ consul agent -data-dir /tmp/consul -bind=<host-2-ip-address>
+$ consul join <host-1-ip-address>
+```
+
+
+### Start the Docker Daemon with the Network Driver Daemon Flags
+
+**host-1** Docker daemon:
+
+```
+$ docker -d --kv-store=consul:localhost:8500 --label=com.docker.network.driver.overlay.bind_interface=eth0
+```
+
+**host-2** Start the Docker Daemon with the neighbor ID configuration:
+
+```
+$ docker -d --kv-store=consul:localhost:8500 --label=com.docker.network.driver.overlay.bind_interface=eth0 --label=com.docker.network.driver.overlay.neighbor_ip=<host-1-ip-address>
+```
+
+### QuickStart Containers Attached to a Network
+
+**host-1** Start a container that publishes a service svc1 in the network dev that is managed by overlay driver.
+
+```
+$ docker run -i -t --publish-service=svc1.dev.overlay debian
+root@21578ff721a9:/# ip add show eth0
+34: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:ec:41:35:bf brd ff:ff:ff:ff:ff:ff
+ inet 172.21.0.16/16 scope global eth0
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:ecff:fe41:35bf/64 scope link
+ valid_lft forever preferred_lft forever
+```
+
+**host-2** Start a container that publishes a service svc2 in the network dev that is managed by overlay driver.
+
+```
+$ docker run -i -t --publish-service=svc2.dev.overlay debian
+root@d217828eb876:/# ping svc1
+PING svc1 (172.21.0.16): 56 data bytes
+64 bytes from 172.21.0.16: icmp_seq=0 ttl=64 time=0.706 ms
+64 bytes from 172.21.0.16: icmp_seq=1 ttl=64 time=0.687 ms
+64 bytes from 172.21.0.16: icmp_seq=2 ttl=64 time=0.841 ms
+```
+### Detailed Setup
+
+You can also setup networks and services and then attach a running container to them.
+
+**host-1**:
+
+```
+docker network create -d overlay prod
+docker network ls
+docker network info prod
+docker service publish db1.prod
+cid=$(docker run -itd -p 8000:8000 ubuntu)
+docker service attach $cid db1.prod
+```
+
+**host-2**:
+
+```
+docker network ls
+docker network info prod
+docker service publish db2.prod
+cid=$(docker run -itd -p 8000:8000 ubuntu)
+docker service attach $cid db2.prod
+```
+
+Once a container is started, a container on `host-1` and `host-2` both containers should be able to ping one another via IP, service name, \<service name>.\<network name>
+
+
+View information about the networks and services using `ls` and `info` subcommands like so:
+
+```
+$ docker service ls
+SERVICE ID NAME NETWORK CONTAINER
+0771deb5f84b db2 prod 0e54a527f22c
+aea23b224acf db1 prod 4b0a309ca311
+
+$ docker network info prod
+Network Id: 5ac68be2518959b48ad102e9ec3d8f42fb2ec72056aa9592eb5abd0252203012
+ Name: prod
+ Type: overlay
+
+$ docker service info db1.prod
+Service Id: aea23b224acfd2da9b893870e0d632499188a1a4b3881515ba042928a9d3f465
+ Name: db1
+ Network: prod
+```
+
+To detach and unpublish a service:
+
+```
+$ docker service detach $cid <service>.<network>
+$ docker service unpublish <service>.<network>
+
+# Example:
+$ docker service detach $cid db2.prod
+$ docker service unpublish db2.prod
+```
+
+To reiterate, this is experimental, and will be under active development.
--- /dev/null
+Remote Drivers
+==============
+
+The `drivers.remote` package provides the integration point for dynamically-registered drivers. Unlike the other driver packages, it does not provide a single implementation of a driver; rather, it provides a proxy for remote driver processes, which are registered and communicate with LibNetwork via the Docker plugin package.
+
+For the semantics of driver methods, which correspond to the protocol below, please see the [overall design](design.md).
+
+## LibNetwork integration with the Docker `plugins` package
+
+When LibNetwork initializes the `drivers.remote` package with the `Init()` function, it passes a `DriverCallback` as a parameter, which implements `RegisterDriver()`. The remote driver package uses this interface to register remote drivers with LibNetwork's `NetworkController`, by supplying it in a `plugins.Handle` callback.
+
+The callback is invoked when a driver is loaded with the `plugins.Get` API call. How that comes about is out of scope here (but it might be, for instance, when that driver is mentioned by the user).
+
+This design ensures that the details of driver registration mechanism are owned by the remote driver package, and it doesn't expose any of the driver layer to the North of LibNetwork.
+
+## Implementation
+
+The remote driver implementation uses a `plugins.Client` to communicate with the remote driver process. The `driverapi.Driver` methods are implemented as RPCs over the plugin client.
+
+The payloads of these RPCs are mostly direct translations into JSON of the arguments given to the method. There are some exceptions to account for the use of the interfaces `InterfaceInfo` and `JoinInfo`, and data types that do not serialise to JSON well (e.g., `net.IPNet`). The protocol is detailed below under "Protocol".
+
+## Usage
+
+A remote driver proxy follows all the rules of any other in-built driver and has exactly the same `Driver` interface exposed. LibNetwork will also support driver-specific `options` and user-supplied `labels` which may influence the behaviour of a remote driver process.
+
+## Protocol
+
+The remote driver protocol is a set of RPCs, issued as HTTP POSTs with JSON payloads. The proxy issues requests, and the remote driver process is expected to respond usually with a JSON payload of its own, although in some cases these are empty maps.
+
+### Errors
+
+If the remote process cannot decode, or otherwise detects a syntactic problem with the HTTP request or payload, it must respond with an HTTP error status (4xx or 5xx).
+
+If the remote process http server receives a request for an unknown URI, it should respond with the HTTP StatusCode `404 Not Found`. This allows LibNetwork to detect when a remote driver does not implement yet a newly added method, therefore not to deem the request as failed.
+
+If the remote process can decode the request, but cannot complete the operation, it must send a response in the form
+
+ {
+ "Err": string
+ }
+
+The string value supplied may appear in logs, so should not include confidential information.
+
+### Handshake
+
+When loaded, a remote driver process receives an HTTP POST on the URL `/Plugin.Activate` with no payload. It must respond with a manifest of the form
+
+ {
+ "Implements": ["NetworkDriver"]
+ }
+
+Other entries in the list value are allowed; `"NetworkDriver"` indicates that the plugin should be registered with LibNetwork as a driver.
+
+### Set capability
+
+After Handshake, the remote driver will receive another POST message to the URL `/NetworkDriver.GetCapabilities` with no payload. The driver's response should have the form:
+
+ {
+ "Scope": "local"
+ "ConnectivityScope": "global"
+ }
+
+Value of "Scope" should be either "local" or "global" which indicates whether the resource allocations for this driver's network can be done only locally to the node or globally across the cluster of nodes. Any other value will fail driver's registration and return an error to the caller.
+Similarly, value of "ConnectivityScope" should be either "local" or "global" which indicates whether the driver's network can provide connectivity only locally to this node or globally across the cluster of nodes. If the value is missing, libnetwork will set it to the value of "Scope". should be either "local" or "global" which indicates
+
+### Create network
+
+When the proxy is asked to create a network, the remote process shall receive a POST to the URL `/NetworkDriver.CreateNetwork` of the form
+
+ {
+ "NetworkID": string,
+ "IPv4Data" : [
+ {
+ "AddressSpace": string,
+ "Pool": ipv4-cidr-string,
+ "Gateway" : ipv4-cidr-string,
+ "AuxAddresses": {
+ "<identifier1>" : "<ipv4-address1>",
+ "<identifier2>" : "<ipv4-address2>",
+ ...
+ }
+ },
+ ],
+ "IPv6Data" : [
+ {
+ "AddressSpace": string,
+ "Pool": ipv6-cidr-string,
+ "Gateway" : ipv6-cidr-string,
+ "AuxAddresses": {
+ "<identifier1>" : "<ipv6-address1>",
+ "<identifier2>" : "<ipv6-address2>",
+ ...
+ }
+ },
+ ],
+ "Options": {
+ ...
+ }
+ }
+
+* `NetworkID` value is generated by LibNetwork which represents a unique network.
+* `Options` value is the arbitrary map given to the proxy by LibNetwork.
+* `IPv4Data` and `IPv6Data` are the ip-addressing data configured by the user and managed by IPAM driver. The network driver is expected to honor the ip-addressing data supplied by IPAM driver. The data include,
+* `AddressSpace` : A unique string represents an isolated space for IP Addressing
+* `Pool` : A range of IP Addresses represented in CIDR format address/mask. Since, the IPAM driver is responsible for allocating container ip-addresses, the network driver can make use of this information for the network plumbing purposes.
+* `Gateway` : Optionally, the IPAM driver may provide a Gateway IP address in CIDR format for the subnet represented by the Pool. The network driver can make use of this information for the network plumbing purposes.
+* `AuxAddresses` : A list of pre-allocated ip-addresses with an associated identifier as provided by the user to assist network driver if it requires specific ip-addresses for its operation.
+
+The response indicating success is empty:
+
+ {}
+
+### Delete network
+
+When a network owned by the remote driver is deleted, the remote process shall receive a POST to the URL `/NetworkDriver.DeleteNetwork` of the form
+
+ {
+ "NetworkID": string
+ }
+
+The success response is empty:
+
+ {}
+
+### Create endpoint
+
+When the proxy is asked to create an endpoint, the remote process shall receive a POST to the URL `/NetworkDriver.CreateEndpoint` of the form
+
+ {
+ "NetworkID": string,
+ "EndpointID": string,
+ "Options": {
+ ...
+ },
+ "Interface": {
+ "Address": string,
+ "AddressIPv6": string,
+ "MacAddress": string
+ }
+ }
+
+The `NetworkID` is the generated identifier for the network to which the endpoint belongs; the `EndpointID` is a generated identifier for the endpoint.
+
+`Options` is an arbitrary map as supplied to the proxy.
+
+The `Interface` value is of the form given. The fields in the `Interface` may be empty; and the `Interface` itself may be empty. If supplied, `Address` is an IPv4 address and subnet in CIDR notation; e.g., `"192.168.34.12/16"`. If supplied, `AddressIPv6` is an IPv6 address and subnet in CIDR notation. `MacAddress` is a MAC address as a string; e.g., `"6e:75:32:60:44:c9"`.
+
+A success response is of the form
+
+ {
+ "Interface": {
+ "Address": string,
+ "AddressIPv6": string,
+ "MacAddress": string
+ }
+ }
+
+with values in the `Interface` as above. As far as the value of `Interface` is concerned, `MacAddress` and either or both of `Address` and `AddressIPv6` must be given.
+
+If the remote process was supplied a non-empty value in `Interface`, it must respond with an empty `Interface` value. LibNetwork will treat it as an error if it supplies a non-empty value and receives a non-empty value back, and roll back the operation.
+
+### Endpoint operational info
+
+The proxy may be asked for "operational info" on an endpoint. When this happens, the remote process shall receive a POST to `/NetworkDriver.EndpointOperInfo` of the form
+
+ {
+ "NetworkID": string,
+ "EndpointID": string
+ }
+
+where `NetworkID` and `EndpointID` have meanings as above. It must send a response of the form
+
+ {
+ "Value": { ... }
+ }
+
+where the value of the `Value` field is an arbitrary (possibly empty) map.
+
+### Delete endpoint
+
+When an endpoint is deleted, the remote process shall receive a POST to the URL `/NetworkDriver.DeleteEndpoint` with a body of the form
+
+ {
+ "NetworkID": string,
+ "EndpointID": string
+ }
+
+where `NetworkID` and `EndpointID` have meanings as above. A success response is empty:
+
+ {}
+
+### Join
+
+When a sandbox is given an endpoint, the remote process shall receive a POST to the URL `NetworkDriver.Join` of the form
+
+ {
+ "NetworkID": string,
+ "EndpointID": string,
+ "SandboxKey": string,
+ "Options": { ... }
+ }
+
+The `NetworkID` and `EndpointID` have meanings as above. The `SandboxKey` identifies the sandbox. `Options` is an arbitrary map as supplied to the proxy.
+
+The response must have the form
+
+ {
+ "InterfaceName": {
+ SrcName: string,
+ DstPrefix: string
+ },
+ "Gateway": string,
+ "GatewayIPv6": string,
+ "StaticRoutes": [{
+ "Destination": string,
+ "RouteType": int,
+ "NextHop": string,
+ }, ...]
+ }
+
+`Gateway` is optional and if supplied is an IP address as a string; e.g., `"192.168.0.1"`. `GatewayIPv6` is optional and if supplied is an IPv6 address as a string; e.g., `"fe80::7809:baff:fec6:7744"`.
+
+The entries in `InterfaceName` represent actual OS level interfaces that should be moved by LibNetwork into the sandbox; the `SrcName` is the name of the OS level interface that the remote process created, and the `DstPrefix` is a prefix for the name the OS level interface should have after it has been moved into the sandbox (LibNetwork will append an index to make sure the actual name does not collide with others).
+
+The entries in `"StaticRoutes"` represent routes that should be added to an interface once it has been moved into the sandbox. Since there may be zero or more routes for an interface, unlike the interface name they can be supplied in any order.
+
+Routes are either given a `RouteType` of `0` and a value for `NextHop`; or, a `RouteType` of `1` and no value for `NextHop`, meaning a connected route.
+
+If no gateway and no default static route is set by the driver in the Join response, LibNetwork will add an additional interface to the sandbox connecting to a default gateway network (a bridge network named *docker_gwbridge*) and program the default gateway into the sandbox accordingly, pointing to the interface address of the bridge *docker_gwbridge*.
+
+### Leave
+
+If the proxy is asked to remove an endpoint from a sandbox, the remote process shall receive a POST to the URL `/NetworkDriver.Leave` of the form
+
+ {
+ "NetworkID": string,
+ "EndpointID": string
+ }
+
+where `NetworkID` and `EndpointID` have meanings as above. The success response is empty:
+
+ {}
+
+### DiscoverNew Notification
+
+LibNetwork listens to inbuilt docker discovery notifications and passes it along to the interested drivers.
+
+When the proxy receives a DiscoverNew notification, the remote process shall receive a POST to the URL `/NetworkDriver.DiscoverNew` of the form
+
+ {
+ "DiscoveryType": int,
+ "DiscoveryData": {
+ ...
+ }
+ }
+
+`DiscoveryType` represents the discovery type. Each Discovery Type is represented by a number.
+`DiscoveryData` carries discovery data the structure of which is determined by the DiscoveryType
+
+The response indicating success is empty:
+
+ {}
+
+* Node Discovery
+
+Node Discovery is represented by a `DiscoveryType` value of `1` and the corresponding `DiscoveryData` will carry Node discovery data.
+
+ {
+ "DiscoveryType": int,
+ "DiscoveryData": {
+ "Address" : string
+ "self" : bool
+ }
+ }
+
+### DiscoverDelete Notification
+
+When the proxy receives a DiscoverDelete notification, the remote process shall receive a POST to the URL `/NetworkDriver.DiscoverDelete` of the form
+
+ {
+ "DiscoveryType": int,
+ "DiscoveryData": {
+ ...
+ }
+ }
+
+`DiscoveryType` represents the discovery type. Each Discovery Type is represented by a number.
+`DiscoveryData` carries discovery data the structure of which is determined by the DiscoveryType
+
+The response indicating success is empty:
+
+ {}
+
+* Node Discovery
+
+Similar to the DiscoverNew call, Node Discovery is represented by a `DiscoveryType` value of `1` and the corresponding `DiscoveryData` will carry Node discovery data to be deleted.
+
+ {
+ "DiscoveryType": int,
+ "DiscoveryData": {
+ "Address" : string
+ "self" : bool
+ }
+ }
--- /dev/null
+[Unit]
+Description=Docker Application Container Engine
+Documentation=https://docs.docker.com
+After=network.target docker.socket
+Requires=docker.socket
+
+[Service]
+EnvironmentFile=-/etc/default/docker
+ExecStart=/usr/bin/docker daemon -H fd:// $DOCKER_OPTS
+MountFlags=slave
+LimitNOFILE=1048576
+LimitNPROC=1048576
+LimitCORE=infinity
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+# Vagrant Setup to Test the Overlay Driver
+
+This documentation highlights how to use Vagrant to start a three nodes setup to test Docker network.
+
+## Pre-requisites
+
+This was tested on:
+
+- Vagrant 1.7.2
+- VirtualBox 4.3.26
+
+## Machine Setup
+
+The Vagrantfile provided will start three virtual machines. One will act as a consul server, and the other two will act as Docker host.
+The experimental version of Docker is installed.
+
+- `consul-server` is the Consul server node, based on Ubuntu 14.04, this has IP 192.168.33.10
+- `net-1` is the first Docker host based on Ubuntu 14.10, this has IP 192.168.33.11
+- `net-2` is the second Docker host based on Ubuntu 14.10, this has IP 192.168.33.12
+
+## Getting Started
+
+Clone this repo, change to the `docs` directory and let Vagrant do the work.
+
+ $ vagrant up
+ $ vagrant status
+ Current machine states:
+
+ consul-server running (virtualbox)
+ net-1 running (virtualbox)
+ net-2 running (virtualbox)
+
+You are now ready to SSH to the Docker hosts and start containers.
+
+ $ vagrant ssh net-1
+ vagrant@net-1:~$ docker version
+ Client version: 1.8.0-dev
+ ...<snip>...
+
+Check that Docker network is functional by listing the default networks:
+
+ vagrant@net-1:~$ docker network ls
+ NETWORK ID NAME TYPE
+ 4275f8b3a821 none null
+ 80eba28ed4a7 host host
+ 64322973b4aa bridge bridge
+
+No services has been published so far, so the `docker service ls` will return an empty list:
+
+ $ docker service ls
+ SERVICE ID NAME NETWORK CONTAINER
+
+Start a container and check the content of `/etc/hosts`.
+
+ $ docker run -it --rm ubuntu:14.04 bash
+ root@df479e660658:/# cat /etc/hosts
+ 172.21.0.3 df479e660658
+ 127.0.0.1 localhost
+ ::1 localhost ip6-localhost ip6-loopback
+ fe00::0 ip6-localnet
+ ff00::0 ip6-mcastprefix
+ ff02::1 ip6-allnodes
+ ff02::2 ip6-allrouters
+ 172.21.0.3 distracted_bohr
+ 172.21.0.3 distracted_bohr.multihost
+
+In a separate terminal on `net-1` list the networks again. You will see that the _multihost_ overlay now appears.
+The overlay network _multihost_ is your default network. This was setup by the Docker daemon during the Vagrant provisioning. Check `/etc/default/docker` to see the options that were set.
+
+ vagrant@net-1:~$ docker network ls
+ NETWORK ID NAME TYPE
+ 4275f8b3a821 none null
+ 80eba28ed4a7 host host
+ 64322973b4aa bridge bridge
+ b5c9f05f1f8f multihost overlay
+
+Now in a separate terminal, SSH to `net-2`, check the network and services. The networks will be the same, and the default network will also be _multihost_ of type overlay. But the service will show the container started on `net-1`:
+
+ $ vagrant ssh net-2
+ vagrant@net-2:~$ docker service ls
+ SERVICE ID NAME NETWORK CONTAINER
+ b00f2bfd81ac distracted_bohr multihost df479e660658
+
+Start a container on `net-2` and check the `/etc/hosts`.
+
+ vagrant@net-2:~$ docker run -ti --rm ubuntu:14.04 bash
+ root@2ac726b4ce60:/# cat /etc/hosts
+ 172.21.0.4 2ac726b4ce60
+ 127.0.0.1 localhost
+ ::1 localhost ip6-localhost ip6-loopback
+ fe00::0 ip6-localnet
+ ff00::0 ip6-mcastprefix
+ ff02::1 ip6-allnodes
+ ff02::2 ip6-allrouters
+ 172.21.0.3 distracted_bohr
+ 172.21.0.3 distracted_bohr.multihost
+ 172.21.0.4 modest_curie
+ 172.21.0.4 modest_curie.multihost
+
+You will see not only the container that you just started on `net-2` but also the container that you started earlier on `net-1`.
+And of course you will be able to ping each container.
+
+## Creating a Non Default Overlay Network
+
+In the previous test we started containers with regular options `-ti --rm` and these containers got placed automatically in the default network which was set to be the _multihost_ network of type overlay.
+
+But you could create your own overlay network and start containers in it. Let's create a new overlay network.
+On one of your Docker hosts, `net-1` or `net-2` do:
+
+ $ docker network create -d overlay foobar
+ 8805e22ad6e29cd7abb95597c91420fdcac54f33fcdd6fbca6dd4ec9710dd6a4
+ $ docker network ls
+ NETWORK ID NAME TYPE
+ a77e16a1e394 host host
+ 684a4bb4c471 bridge bridge
+ 8805e22ad6e2 foobar overlay
+ b5c9f05f1f8f multihost overlay
+ 67d5a33a2e54 none null
+
+Automatically, the second host will also see this network. To start a container on this new network, simply use the `--publish-service` option of `docker run` like so:
+
+ $ docker run -it --rm --publish-service=bar.foobar.overlay ubuntu:14.04 bash
+
+Note, that you could directly start a container with a new overlay using the `--publish-service` option and it will create the network automatically.
+
+Check the docker services now:
+
+ $ docker service ls
+ SERVICE ID NAME NETWORK CONTAINER
+ b1ffdbfb1ac6 bar foobar 6635a3822135
+
+Repeat the getting started steps, by starting another container in this new overlay on the other host, check the `/etc/hosts` file and try to ping each container.
+
+## A look at the interfaces
+
+This new Docker multihost networking is made possible via VXLAN tunnels and the use of network namespaces.
+Check the [design](design.md) documentation for all the details. But to explore these concepts a bit, nothing beats an example.
+
+With a running container in one overlay, check the network namespace:
+
+ $ docker inspect -f '{{ .NetworkSettings.SandboxKey}}' 6635a3822135
+ /var/run/docker/netns/6635a3822135
+
+This is a none default location for network namespaces which might confuse things a bit. So let's become root, head over to this directory that contains the network namespaces of the containers and check the interfaces:
+
+ $ sudo su
+ root@net-2:/home/vagrant# cd /var/run/docker/
+ root@net-2:/var/run/docker# ls netns
+ 6635a3822135
+ 8805e22ad6e2
+
+To be able to check the interfaces in those network namespace using `ip` command, just create a symlink for `netns` that points to `/var/run/docker/netns`:
+
+ root@net-2:/var/run# ln -s /var/run/docker/netns netns
+ root@net-2:/var/run# ip netns show
+ 6635a3822135
+ 8805e22ad6e2
+
+The two namespace ID return are the ones of the running container on that host and the one of the actual overlay network the container is in.
+Let's check the interfaces in the container:
+
+ root@net-2:/var/run/docker# ip netns exec 6635a3822135 ip addr show eth0
+ 15: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:b3:91:22:c3 brd ff:ff:ff:ff:ff:ff
+ inet 172.21.0.5/16 scope global eth0
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:b3ff:fe91:22c3/64 scope link
+ valid_lft forever preferred_lft forever
+
+Indeed we get back the network interface of our running container, same MAC address, same IP.
+If we check the links of the overlay namespace we see our vxlan interface and the VLAN ID being used.
+
+ root@net-2:/var/run/docker# ip netns exec 8805e22ad6e2 ip -d link show
+ ...<snip>...
+ 14: vxlan1: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default
+ link/ether 7a:af:20:ee:e3:81 brd ff:ff:ff:ff:ff:ff promiscuity 1
+ vxlan id 256 srcport 32768 61000 dstport 8472 proxy l2miss l3miss ageing 300
+ bridge_slave
+ 16: veth2: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br0 state UP mode DEFAULT group default qlen 1000
+ link/ether 46:b1:e2:5c:48:a8 brd ff:ff:ff:ff:ff:ff promiscuity 1
+ veth
+ bridge_slave
+
+If you sniff packets on these interfaces you will see the traffic between your containers.
+
--- /dev/null
+package driverapi
+
+import (
+ "net"
+
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/libnetwork/discoverapi"
+)
+
+// NetworkPluginEndpointType represents the Endpoint Type used by Plugin system
+const NetworkPluginEndpointType = "NetworkDriver"
+
+// Driver is an interface that every plugin driver needs to implement.
+type Driver interface {
+ discoverapi.Discover
+
+ // NetworkAllocate invokes the driver method to allocate network
+ // specific resources passing network id and network specific config.
+ // It returns a key,value pair of network specific driver allocations
+ // to the caller.
+ NetworkAllocate(nid string, options map[string]string, ipV4Data, ipV6Data []IPAMData) (map[string]string, error)
+
+ // NetworkFree invokes the driver method to free network specific resources
+ // associated with a given network id.
+ NetworkFree(nid string) error
+
+ // CreateNetwork invokes the driver method to create a network
+ // passing the network id and network specific config. The
+ // config mechanism will eventually be replaced with labels
+ // which are yet to be introduced. The driver can return a
+ // list of table names for which it is interested in receiving
+ // notification when a CRUD operation is performed on any
+ // entry in that table. This will be ignored for local scope
+ // drivers.
+ CreateNetwork(nid string, options map[string]interface{}, nInfo NetworkInfo, ipV4Data, ipV6Data []IPAMData) error
+
+ // DeleteNetwork invokes the driver method to delete network passing
+ // the network id.
+ DeleteNetwork(nid string) error
+
+ // CreateEndpoint invokes the driver method to create an endpoint
+ // passing the network id, endpoint id endpoint information and driver
+ // specific config. The endpoint information can be either consumed by
+ // the driver or populated by the driver. The config mechanism will
+ // eventually be replaced with labels which are yet to be introduced.
+ CreateEndpoint(nid, eid string, ifInfo InterfaceInfo, options map[string]interface{}) error
+
+ // DeleteEndpoint invokes the driver method to delete an endpoint
+ // passing the network id and endpoint id.
+ DeleteEndpoint(nid, eid string) error
+
+ // EndpointOperInfo retrieves from the driver the operational data related to the specified endpoint
+ EndpointOperInfo(nid, eid string) (map[string]interface{}, error)
+
+ // Join method is invoked when a Sandbox is attached to an endpoint.
+ Join(nid, eid string, sboxKey string, jinfo JoinInfo, options map[string]interface{}) error
+
+ // Leave method is invoked when a Sandbox detaches from an endpoint.
+ Leave(nid, eid string) error
+
+ // ProgramExternalConnectivity invokes the driver method which does the necessary
+ // programming to allow the external connectivity dictated by the passed options
+ ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error
+
+ // RevokeExternalConnectivity asks the driver to remove any external connectivity
+ // programming that was done so far
+ RevokeExternalConnectivity(nid, eid string) error
+
+ // EventNotify notifies the driver when a CRUD operation has
+ // happened on a table of its interest as soon as this node
+ // receives such an event in the gossip layer. This method is
+ // only invoked for the global scope driver.
+ EventNotify(event EventType, nid string, tableName string, key string, value []byte)
+
+ // DecodeTableEntry passes the driver a key, value pair from table it registered
+ // with libnetwork. Driver should return {object ID, map[string]string} tuple.
+ // If DecodeTableEntry is called for a table associated with NetworkObject or
+ // EndpointObject the return object ID should be the network id or endpoint id
+ // associated with that entry. map should have information about the object that
+ // can be presented to the user.
+ // For example: overlay driver returns the VTEP IP of the host that has the endpoint
+ // which is shown in 'network inspect --verbose'
+ DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string)
+
+ // Type returns the type of this driver, the network type this driver manages
+ Type() string
+
+ // IsBuiltIn returns true if it is a built-in driver
+ IsBuiltIn() bool
+}
+
+// NetworkInfo provides a go interface for drivers to provide network
+// specific information to libnetwork.
+type NetworkInfo interface {
+ // TableEventRegister registers driver interest in a given
+ // table name.
+ TableEventRegister(tableName string, objType ObjectType) error
+}
+
+// InterfaceInfo provides a go interface for drivers to retrieve
+// network information to interface resources.
+type InterfaceInfo interface {
+ // SetMacAddress allows the driver to set the mac address to the endpoint interface
+ // during the call to CreateEndpoint, if the mac address is not already set.
+ SetMacAddress(mac net.HardwareAddr) error
+
+ // SetIPAddress allows the driver to set the ip address to the endpoint interface
+ // during the call to CreateEndpoint, if the address is not already set.
+ // The API is to be used to assign both the IPv4 and IPv6 address types.
+ SetIPAddress(ip *net.IPNet) error
+
+ // MacAddress returns the MAC address.
+ MacAddress() net.HardwareAddr
+
+ // Address returns the IPv4 address.
+ Address() *net.IPNet
+
+ // AddressIPv6 returns the IPv6 address.
+ AddressIPv6() *net.IPNet
+}
+
+// InterfaceNameInfo provides a go interface for the drivers to assign names
+// to interfaces.
+type InterfaceNameInfo interface {
+ // SetNames method assigns the srcName and dstPrefix for the interface.
+ SetNames(srcName, dstPrefix string) error
+}
+
+// JoinInfo represents a set of resources that the driver has the ability to provide during
+// join time.
+type JoinInfo interface {
+ // InterfaceName returns an InterfaceNameInfo go interface to facilitate
+ // setting the names for the interface.
+ InterfaceName() InterfaceNameInfo
+
+ // SetGateway sets the default IPv4 gateway when a container joins the endpoint.
+ SetGateway(net.IP) error
+
+ // SetGatewayIPv6 sets the default IPv6 gateway when a container joins the endpoint.
+ SetGatewayIPv6(net.IP) error
+
+ // AddStaticRoute adds a route to the sandbox.
+ // It may be used in addition to or instead of a default gateway (as above).
+ AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP) error
+
+ // DisableGatewayService tells libnetwork not to provide Default GW for the container
+ DisableGatewayService()
+
+ // AddTableEntry adds a table entry to the gossip layer
+ // passing the table name, key and an opaque value.
+ AddTableEntry(tableName string, key string, value []byte) error
+}
+
+// DriverCallback provides a Callback interface for Drivers into LibNetwork
+type DriverCallback interface {
+ // GetPluginGetter returns the pluginv2 getter.
+ GetPluginGetter() plugingetter.PluginGetter
+ // RegisterDriver provides a way for Remote drivers to dynamically register new NetworkType and associate with a driver instance
+ RegisterDriver(name string, driver Driver, capability Capability) error
+}
+
+// Capability represents the high level capabilities of the drivers which libnetwork can make use of
+type Capability struct {
+ DataScope string
+ ConnectivityScope string
+}
+
+// IPAMData represents the per-network ip related
+// operational information libnetwork will send
+// to the network driver during CreateNetwork()
+type IPAMData struct {
+ AddressSpace string
+ Pool *net.IPNet
+ Gateway *net.IPNet
+ AuxAddresses map[string]*net.IPNet
+}
+
+// EventType defines a type for the CRUD event
+type EventType uint8
+
+const (
+ // Create event is generated when a table entry is created,
+ Create EventType = 1 + iota
+ // Update event is generated when a table entry is updated.
+ Update
+ // Delete event is generated when a table entry is deleted.
+ Delete
+)
+
+// ObjectType represents the type of object driver wants to store in libnetwork's networkDB
+type ObjectType int
+
+const (
+ // EndpointObject should be set for libnetwork endpoint object related data
+ EndpointObject ObjectType = 1 + iota
+ // NetworkObject should be set for libnetwork network object related data
+ NetworkObject
+ // OpaqueObject is for driver specific data with no corresponding libnetwork object
+ OpaqueObject
+)
+
+// IsValidType validates the passed in type against the valid object types
+func IsValidType(objType ObjectType) bool {
+ switch objType {
+ case EndpointObject:
+ fallthrough
+ case NetworkObject:
+ fallthrough
+ case OpaqueObject:
+ return true
+ }
+ return false
+}
--- /dev/null
+package driverapi
+
+import (
+ "encoding/json"
+ "net"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+func TestIPDataMarshalling(t *testing.T) {
+ i := &IPAMData{
+ AddressSpace: "giallo",
+ Pool: &net.IPNet{IP: net.IP{10, 10, 10, 8}, Mask: net.IPMask{255, 255, 255, 0}},
+ Gateway: &net.IPNet{IP: net.IP{10, 10, 10, 254}, Mask: net.IPMask{255, 255, 255, 0}},
+ AuxAddresses: map[string]*net.IPNet{
+ "ip1": {IP: net.IP{10, 10, 10, 1}, Mask: net.IPMask{255, 255, 255, 0}},
+ "ip2": {IP: net.IP{10, 10, 10, 2}, Mask: net.IPMask{255, 255, 255, 0}},
+ },
+ }
+
+ b, err := json.Marshal(i)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ii := &IPAMData{}
+ err = json.Unmarshal(b, &ii)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if i.AddressSpace != ii.AddressSpace || !types.CompareIPNet(i.Pool, ii.Pool) ||
+ !types.CompareIPNet(i.Gateway, ii.Gateway) ||
+ !compareAddresses(i.AuxAddresses, ii.AuxAddresses) {
+ t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%s\nDecoded:\n%s", i, ii)
+ }
+}
+
+func compareAddresses(a, b map[string]*net.IPNet) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ if len(a) > 0 {
+ for k := range a {
+ if !types.CompareIPNet(a[k], b[k]) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+func TestValidateAndIsV6(t *testing.T) {
+ var err error
+
+ i := &IPAMData{
+ Pool: &net.IPNet{IP: net.IP{10, 10, 10, 8}, Mask: net.IPMask{255, 255, 255, 0}},
+ Gateway: &net.IPNet{IP: net.IP{10, 10, 10, 254}, Mask: net.IPMask{255, 255, 255, 0}},
+ AuxAddresses: map[string]*net.IPNet{
+ "ip1": {IP: net.IP{10, 10, 10, 1}, Mask: net.IPMask{255, 255, 255, 0}},
+ "ip2": {IP: net.IP{10, 10, 10, 2}, Mask: net.IPMask{255, 255, 255, 0}},
+ },
+ }
+
+ // Check ip version
+ if i.IsV6() {
+ t.Fatal("incorrect ip version returned")
+ }
+ orig := i.Pool
+ if i.Pool, err = types.ParseCIDR("2001:db8::33/64"); err != nil {
+ t.Fatal(err)
+ }
+ if !i.IsV6() {
+ t.Fatal("incorrect ip version returned")
+ }
+ i.Pool = orig
+
+ // valid ip data
+ if err = i.Validate(); err != nil {
+ t.Fatal(err)
+ }
+
+ // incongruent gw ver
+ if i.Gateway, err = types.ParseCIDR("2001:db8::45/65"); err != nil {
+ t.Fatal(err)
+ }
+ if err = i.Validate(); err == nil {
+ t.Fatal("expected error but succeeded")
+ }
+ i.Gateway = nil
+
+ // incongruent secondary ip ver
+ if i.AuxAddresses["ip2"], err = types.ParseCIDR("2001:db8::44/80"); err != nil {
+ t.Fatal(err)
+ }
+ if err = i.Validate(); err == nil {
+ t.Fatal("expected error but succeeded")
+ }
+ delete(i.AuxAddresses, "ip2")
+
+ // gw outside pool
+ if i.Gateway, err = types.ParseCIDR("10.10.15.254/24"); err != nil {
+ t.Fatal(err)
+ }
+ if err = i.Validate(); err == nil {
+ t.Fatal("expected error but succeeded")
+ }
+ i.Gateway = nil
+
+ // sec ip outside of pool
+ if i.AuxAddresses["ip1"], err = types.ParseCIDR("10.10.2.1/24"); err != nil {
+ t.Fatal(err)
+ }
+ if err = i.Validate(); err == nil {
+ t.Fatal("expected error but succeeded")
+ }
+}
--- /dev/null
+package driverapi
+
+import (
+ "fmt"
+)
+
+// ErrNoNetwork is returned if no network with the specified id exists
+type ErrNoNetwork string
+
+func (enn ErrNoNetwork) Error() string {
+ return fmt.Sprintf("No network (%s) exists", string(enn))
+}
+
+// NotFound denotes the type of this error
+func (enn ErrNoNetwork) NotFound() {}
+
+// ErrEndpointExists is returned if more than one endpoint is added to the network
+type ErrEndpointExists string
+
+func (ee ErrEndpointExists) Error() string {
+ return fmt.Sprintf("Endpoint (%s) already exists (Only one endpoint allowed)", string(ee))
+}
+
+// Forbidden denotes the type of this error
+func (ee ErrEndpointExists) Forbidden() {}
+
+// ErrNotImplemented is returned when a Driver has not implemented an API yet
+type ErrNotImplemented struct{}
+
+func (eni *ErrNotImplemented) Error() string {
+ return "The API is not implemented yet"
+}
+
+// NotImplemented denotes the type of this error
+func (eni *ErrNotImplemented) NotImplemented() {}
+
+// ErrNoEndpoint is returned if no endpoint with the specified id exists
+type ErrNoEndpoint string
+
+func (ene ErrNoEndpoint) Error() string {
+ return fmt.Sprintf("No endpoint (%s) exists", string(ene))
+}
+
+// NotFound denotes the type of this error
+func (ene ErrNoEndpoint) NotFound() {}
+
+// ErrActiveRegistration represents an error when a driver is registered to a networkType that is previously registered
+type ErrActiveRegistration string
+
+// Error interface for ErrActiveRegistration
+func (ar ErrActiveRegistration) Error() string {
+ return fmt.Sprintf("Driver already registered for type %q", string(ar))
+}
+
+// Forbidden denotes the type of this error
+func (ar ErrActiveRegistration) Forbidden() {}
--- /dev/null
+package driverapi
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/types"
+)
+
+// MarshalJSON encodes IPAMData into json message
+func (i *IPAMData) MarshalJSON() ([]byte, error) {
+ m := map[string]interface{}{}
+ m["AddressSpace"] = i.AddressSpace
+ if i.Pool != nil {
+ m["Pool"] = i.Pool.String()
+ }
+ if i.Gateway != nil {
+ m["Gateway"] = i.Gateway.String()
+ }
+ if i.AuxAddresses != nil {
+ am := make(map[string]string, len(i.AuxAddresses))
+ for k, v := range i.AuxAddresses {
+ am[k] = v.String()
+ }
+ m["AuxAddresses"] = am
+ }
+ return json.Marshal(m)
+}
+
+// UnmarshalJSON decodes a json message into IPAMData
+func (i *IPAMData) UnmarshalJSON(data []byte) error {
+ var (
+ m map[string]interface{}
+ err error
+ )
+ if err := json.Unmarshal(data, &m); err != nil {
+ return err
+ }
+ i.AddressSpace = m["AddressSpace"].(string)
+ if v, ok := m["Pool"]; ok {
+ if i.Pool, err = types.ParseCIDR(v.(string)); err != nil {
+ return err
+ }
+ }
+ if v, ok := m["Gateway"]; ok {
+ if i.Gateway, err = types.ParseCIDR(v.(string)); err != nil {
+ return err
+ }
+ }
+ if v, ok := m["AuxAddresses"]; ok {
+ b, _ := json.Marshal(v)
+ var am map[string]string
+ if err = json.Unmarshal(b, &am); err != nil {
+ return err
+ }
+ i.AuxAddresses = make(map[string]*net.IPNet, len(am))
+ for k, v := range am {
+ if i.AuxAddresses[k], err = types.ParseCIDR(v); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// Validate checks whether the IPAMData structure contains congruent data
+func (i *IPAMData) Validate() error {
+ var isV6 bool
+ if i.Pool == nil {
+ return types.BadRequestErrorf("invalid pool")
+ }
+ if i.Gateway == nil {
+ return types.BadRequestErrorf("invalid gateway address")
+ }
+ isV6 = i.IsV6()
+ if isV6 && i.Gateway.IP.To4() != nil || !isV6 && i.Gateway.IP.To4() == nil {
+ return types.BadRequestErrorf("incongruent ip versions for pool and gateway")
+ }
+ for k, sip := range i.AuxAddresses {
+ if isV6 && sip.IP.To4() != nil || !isV6 && sip.IP.To4() == nil {
+ return types.BadRequestErrorf("incongruent ip versions for pool and secondary ip address %s", k)
+ }
+ }
+ if !i.Pool.Contains(i.Gateway.IP) {
+ return types.BadRequestErrorf("invalid gateway address (%s) does not belong to the pool (%s)", i.Gateway, i.Pool)
+ }
+ for k, sip := range i.AuxAddresses {
+ if !i.Pool.Contains(sip.IP) {
+ return types.BadRequestErrorf("invalid secondary address %s (%s) does not belong to the pool (%s)", k, i.Gateway, i.Pool)
+ }
+ }
+ return nil
+}
+
+// IsV6 returns whether this is an IPv6 IPAMData structure
+func (i *IPAMData) IsV6() bool {
+ return nil == i.Pool.IP.To4()
+}
+
+func (i *IPAMData) String() string {
+ return fmt.Sprintf("AddressSpace: %s\nPool: %v\nGateway: %v\nAddresses: %v", i.AddressSpace, i.Pool, i.Gateway, i.AuxAddresses)
+}
--- /dev/null
+package bridge
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "sync"
+ "syscall"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/iptables"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/portmapper"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+const (
+ networkType = "bridge"
+ vethPrefix = "veth"
+ vethLen = 7
+ defaultContainerVethPrefix = "eth"
+ maxAllocatePortAttempts = 10
+)
+
+const (
+ // DefaultGatewayV4AuxKey represents the default-gateway configured by the user
+ DefaultGatewayV4AuxKey = "DefaultGatewayIPv4"
+ // DefaultGatewayV6AuxKey represents the ipv6 default-gateway configured by the user
+ DefaultGatewayV6AuxKey = "DefaultGatewayIPv6"
+)
+
+type defaultBridgeNetworkConflict struct {
+ ID string
+}
+
+func (d defaultBridgeNetworkConflict) Error() string {
+ return fmt.Sprintf("Stale default bridge network %s", d.ID)
+}
+
+type iptableCleanFunc func() error
+type iptablesCleanFuncs []iptableCleanFunc
+
+// configuration info for the "bridge" driver.
+type configuration struct {
+ EnableIPForwarding bool
+ EnableIPTables bool
+ EnableUserlandProxy bool
+ UserlandProxyPath string
+}
+
+// networkConfiguration for network specific configuration
+type networkConfiguration struct {
+ ID string
+ BridgeName string
+ EnableIPv6 bool
+ EnableIPMasquerade bool
+ EnableICC bool
+ Mtu int
+ DefaultBindingIP net.IP
+ DefaultBridge bool
+ ContainerIfacePrefix string
+ // Internal fields set after ipam data parsing
+ AddressIPv4 *net.IPNet
+ AddressIPv6 *net.IPNet
+ DefaultGatewayIPv4 net.IP
+ DefaultGatewayIPv6 net.IP
+ dbIndex uint64
+ dbExists bool
+ Internal bool
+
+ BridgeIfaceCreator ifaceCreator
+}
+
+// ifaceCreator represents how the bridge interface was created
+type ifaceCreator int8
+
+const (
+ ifaceCreatorUnknown ifaceCreator = iota
+ ifaceCreatedByLibnetwork
+ ifaceCreatedByUser
+)
+
+// endpointConfiguration represents the user specified configuration for the sandbox endpoint
+type endpointConfiguration struct {
+ MacAddress net.HardwareAddr
+}
+
+// containerConfiguration represents the user specified configuration for a container
+type containerConfiguration struct {
+ ParentEndpoints []string
+ ChildEndpoints []string
+}
+
+// connectivityConfiguration represents the user specified configuration regarding the external connectivity
+type connectivityConfiguration struct {
+ PortBindings []types.PortBinding
+ ExposedPorts []types.TransportPort
+}
+
+type bridgeEndpoint struct {
+ id string
+ nid string
+ srcName string
+ addr *net.IPNet
+ addrv6 *net.IPNet
+ macAddress net.HardwareAddr
+ config *endpointConfiguration // User specified parameters
+ containerConfig *containerConfiguration
+ extConnConfig *connectivityConfiguration
+ portMapping []types.PortBinding // Operation port bindings
+ dbIndex uint64
+ dbExists bool
+}
+
+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
+ iptCleanFuncs iptablesCleanFuncs
+ sync.Mutex
+}
+
+type driver struct {
+ config *configuration
+ network *bridgeNetwork
+ natChain *iptables.ChainInfo
+ filterChain *iptables.ChainInfo
+ isolationChain1 *iptables.ChainInfo
+ isolationChain2 *iptables.ChainInfo
+ networks map[string]*bridgeNetwork
+ store datastore.DataStore
+ nlh *netlink.Handle
+ configNetwork sync.Mutex
+ sync.Mutex
+}
+
+// New constructs a new bridge driver
+func newDriver() *driver {
+ return &driver{networks: map[string]*bridgeNetwork{}, config: &configuration{}}
+}
+
+// Init registers a new instance of bridge driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ d := newDriver()
+ if err := d.configure(config); err != nil {
+ return err
+ }
+
+ c := driverapi.Capability{
+ DataScope: datastore.LocalScope,
+ ConnectivityScope: datastore.LocalScope,
+ }
+ return dc.RegisterDriver(networkType, d, c)
+}
+
+// Validate performs a static validation on the network configuration parameters.
+// Whatever can be assessed a priori before attempting any programming.
+func (c *networkConfiguration) Validate() error {
+ if c.Mtu < 0 {
+ return ErrInvalidMtu(c.Mtu)
+ }
+
+ // If bridge v4 subnet is specified
+ if c.AddressIPv4 != nil {
+ // If default gw is specified, it must be part of bridge subnet
+ if c.DefaultGatewayIPv4 != nil {
+ if !c.AddressIPv4.Contains(c.DefaultGatewayIPv4) {
+ return &ErrInvalidGateway{}
+ }
+ }
+ }
+
+ // If default v6 gw is specified, AddressIPv6 must be specified and gw must belong to AddressIPv6 subnet
+ if c.EnableIPv6 && c.DefaultGatewayIPv6 != nil {
+ if c.AddressIPv6 == nil || !c.AddressIPv6.Contains(c.DefaultGatewayIPv6) {
+ return &ErrInvalidGateway{}
+ }
+ }
+ return nil
+}
+
+// Conflicts check if two NetworkConfiguration objects overlap
+func (c *networkConfiguration) Conflicts(o *networkConfiguration) error {
+ if o == nil {
+ return errors.New("same configuration")
+ }
+
+ // Also empty, because only one network with empty name is allowed
+ if c.BridgeName == o.BridgeName {
+ return errors.New("networks have same bridge name")
+ }
+
+ // They must be in different subnets
+ if (c.AddressIPv4 != nil && o.AddressIPv4 != nil) &&
+ (c.AddressIPv4.Contains(o.AddressIPv4.IP) || o.AddressIPv4.Contains(c.AddressIPv4.IP)) {
+ return errors.New("networks have overlapping IPv4")
+ }
+
+ // They must be in different v6 subnets
+ if (c.AddressIPv6 != nil && o.AddressIPv6 != nil) &&
+ (c.AddressIPv6.Contains(o.AddressIPv6.IP) || o.AddressIPv6.Contains(c.AddressIPv6.IP)) {
+ return errors.New("networks have overlapping IPv6")
+ }
+
+ return nil
+}
+
+func (c *networkConfiguration) fromLabels(labels map[string]string) error {
+ var err error
+ for label, value := range labels {
+ switch label {
+ case BridgeName:
+ c.BridgeName = value
+ case netlabel.DriverMTU:
+ if c.Mtu, err = strconv.Atoi(value); err != nil {
+ return parseErr(label, value, err.Error())
+ }
+ case netlabel.EnableIPv6:
+ if c.EnableIPv6, err = strconv.ParseBool(value); err != nil {
+ return parseErr(label, value, err.Error())
+ }
+ case EnableIPMasquerade:
+ if c.EnableIPMasquerade, err = strconv.ParseBool(value); err != nil {
+ return parseErr(label, value, err.Error())
+ }
+ case EnableICC:
+ if c.EnableICC, err = strconv.ParseBool(value); err != nil {
+ return parseErr(label, value, err.Error())
+ }
+ case DefaultBridge:
+ if c.DefaultBridge, err = strconv.ParseBool(value); err != nil {
+ return parseErr(label, value, err.Error())
+ }
+ case DefaultBindingIP:
+ if c.DefaultBindingIP = net.ParseIP(value); c.DefaultBindingIP == nil {
+ return parseErr(label, value, "nil ip")
+ }
+ case netlabel.ContainerIfacePrefix:
+ c.ContainerIfacePrefix = value
+ }
+ }
+
+ return nil
+}
+
+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, *iptables.ChainInfo, *iptables.ChainInfo, error) {
+ n.Lock()
+ defer n.Unlock()
+
+ if n.driver == nil {
+ return nil, nil, nil, nil, types.BadRequestErrorf("no driver found")
+ }
+
+ return n.driver.natChain, n.driver.filterChain, n.driver.isolationChain1, n.driver.isolationChain2, nil
+}
+
+func (n *bridgeNetwork) getNetworkBridgeName() string {
+ n.Lock()
+ config := n.config
+ n.Unlock()
+
+ return config.BridgeName
+}
+
+func (n *bridgeNetwork) getEndpoint(eid string) (*bridgeEndpoint, error) {
+ n.Lock()
+ defer n.Unlock()
+
+ if eid == "" {
+ return nil, InvalidEndpointIDError(eid)
+ }
+
+ if ep, ok := n.endpoints[eid]; ok {
+ return ep, nil
+ }
+
+ return nil, nil
+}
+
+// Install/Removes the iptables rules needed to isolate this network
+// from each of the other networks
+func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) error {
+ n.Lock()
+ thisConfig := n.config
+ n.Unlock()
+
+ if thisConfig.Internal {
+ return nil
+ }
+
+ // Install the rules to isolate this network against each of the other networks
+ return setINC(thisConfig.BridgeName, enable)
+}
+
+func (d *driver) configure(option map[string]interface{}) error {
+ var (
+ config *configuration
+ err error
+ natChain *iptables.ChainInfo
+ filterChain *iptables.ChainInfo
+ isolationChain1 *iptables.ChainInfo
+ isolationChain2 *iptables.ChainInfo
+ )
+
+ genericData, ok := option[netlabel.GenericData]
+ if !ok || genericData == nil {
+ return nil
+ }
+
+ switch opt := genericData.(type) {
+ case options.Generic:
+ opaqueConfig, err := options.GenerateFromModel(opt, &configuration{})
+ if err != nil {
+ return err
+ }
+ config = opaqueConfig.(*configuration)
+ case *configuration:
+ config = opt
+ default:
+ return &ErrInvalidDriverConfig{}
+ }
+
+ if config.EnableIPTables {
+ if _, err := os.Stat("/proc/sys/net/bridge"); err != nil {
+ if out, err := exec.Command("modprobe", "-va", "bridge", "br_netfilter").CombinedOutput(); err != nil {
+ logrus.Warnf("Running modprobe bridge br_netfilter failed with message: %s, error: %v", out, err)
+ }
+ }
+ removeIPChains()
+ natChain, filterChain, isolationChain1, isolationChain2, err = setupIPChains(config)
+ if err != nil {
+ return err
+ }
+ // Make sure on firewall reload, first thing being re-played is chains creation
+ iptables.OnReloaded(func() { logrus.Debugf("Recreating iptables chains on firewall reload"); setupIPChains(config) })
+ }
+
+ if config.EnableIPForwarding {
+ err = setupIPForwarding(config.EnableIPTables)
+ if err != nil {
+ logrus.Warn(err)
+ return err
+ }
+ }
+
+ d.Lock()
+ d.natChain = natChain
+ d.filterChain = filterChain
+ d.isolationChain1 = isolationChain1
+ d.isolationChain2 = isolationChain2
+ d.config = config
+ d.Unlock()
+
+ err = d.initStore(option)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (d *driver) getNetwork(id string) (*bridgeNetwork, error) {
+ d.Lock()
+ defer d.Unlock()
+
+ if id == "" {
+ return nil, types.BadRequestErrorf("invalid network id: %s", id)
+ }
+
+ if nw, ok := d.networks[id]; ok {
+ return nw, nil
+ }
+
+ return nil, types.NotFoundErrorf("network not found: %s", id)
+}
+
+func parseNetworkGenericOptions(data interface{}) (*networkConfiguration, error) {
+ var (
+ err error
+ config *networkConfiguration
+ )
+
+ switch opt := data.(type) {
+ case *networkConfiguration:
+ config = opt
+ case map[string]string:
+ config = &networkConfiguration{
+ EnableICC: true,
+ EnableIPMasquerade: true,
+ }
+ err = config.fromLabels(opt)
+ case options.Generic:
+ var opaqueConfig interface{}
+ if opaqueConfig, err = options.GenerateFromModel(opt, config); err == nil {
+ config = opaqueConfig.(*networkConfiguration)
+ }
+ default:
+ err = types.BadRequestErrorf("do not recognize network configuration format: %T", opt)
+ }
+
+ return config, err
+}
+
+func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []driverapi.IPAMData) error {
+ if len(ipamV4Data) > 1 || len(ipamV6Data) > 1 {
+ return types.ForbiddenErrorf("bridge driver doesn't support multiple subnets")
+ }
+
+ if len(ipamV4Data) == 0 {
+ return types.BadRequestErrorf("bridge network %s requires ipv4 configuration", id)
+ }
+
+ if ipamV4Data[0].Gateway != nil {
+ c.AddressIPv4 = types.GetIPNetCopy(ipamV4Data[0].Gateway)
+ }
+
+ if gw, ok := ipamV4Data[0].AuxAddresses[DefaultGatewayV4AuxKey]; ok {
+ c.DefaultGatewayIPv4 = gw.IP
+ }
+
+ if len(ipamV6Data) > 0 {
+ c.AddressIPv6 = ipamV6Data[0].Pool
+
+ if ipamV6Data[0].Gateway != nil {
+ c.AddressIPv6 = types.GetIPNetCopy(ipamV6Data[0].Gateway)
+ }
+
+ if gw, ok := ipamV6Data[0].AuxAddresses[DefaultGatewayV6AuxKey]; ok {
+ c.DefaultGatewayIPv6 = gw.IP
+ }
+ }
+
+ return nil
+}
+
+func parseNetworkOptions(id string, option options.Generic) (*networkConfiguration, error) {
+ var (
+ err error
+ config = &networkConfiguration{}
+ )
+
+ // Parse generic label first, config will be re-assigned
+ if genData, ok := option[netlabel.GenericData]; ok && genData != nil {
+ if config, err = parseNetworkGenericOptions(genData); err != nil {
+ return nil, err
+ }
+ }
+
+ // Process well-known labels next
+ if val, ok := option[netlabel.EnableIPv6]; ok {
+ config.EnableIPv6 = val.(bool)
+ }
+
+ if val, ok := option[netlabel.Internal]; ok {
+ if internal, ok := val.(bool); ok && internal {
+ config.Internal = true
+ }
+ }
+
+ // Finally validate the configuration
+ if err = config.Validate(); err != nil {
+ return nil, err
+ }
+
+ if config.BridgeName == "" && config.DefaultBridge == false {
+ config.BridgeName = "br-" + id[:12]
+ }
+
+ exists, err := bridgeInterfaceExists(config.BridgeName)
+ if err != nil {
+ return nil, err
+ }
+
+ if !exists {
+ config.BridgeIfaceCreator = ifaceCreatedByLibnetwork
+ } else {
+ config.BridgeIfaceCreator = ifaceCreatedByUser
+ }
+
+ config.ID = id
+ return config, nil
+}
+
+// Returns the non link-local IPv6 subnet for the containers attached to this bridge if found, nil otherwise
+func getV6Network(config *networkConfiguration, i *bridgeInterface) *net.IPNet {
+ if config.AddressIPv6 != nil {
+ return config.AddressIPv6
+ }
+ if i.bridgeIPv6 != nil && i.bridgeIPv6.IP != nil && !i.bridgeIPv6.IP.IsLinkLocalUnicast() {
+ return i.bridgeIPv6
+ }
+
+ return nil
+}
+
+// Return a slice of networks over which caller can iterate safely
+func (d *driver) getNetworks() []*bridgeNetwork {
+ d.Lock()
+ defer d.Unlock()
+
+ ls := make([]*bridgeNetwork, 0, len(d.networks))
+ for _, nw := range d.networks {
+ ls = append(ls, nw)
+ }
+ return ls
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+// Create a new network using bridge plugin
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {
+ return types.BadRequestErrorf("ipv4 pool is empty")
+ }
+ // Sanity checks
+ d.Lock()
+ if _, ok := d.networks[id]; ok {
+ d.Unlock()
+ return types.ForbiddenErrorf("network %s exists", id)
+ }
+ d.Unlock()
+
+ // Parse and validate the config. It should not be conflict with existing networks' config
+ config, err := parseNetworkOptions(id, option)
+ if err != nil {
+ return err
+ }
+
+ if err = config.processIPAM(id, ipV4Data, ipV6Data); err != nil {
+ return err
+ }
+
+ // start the critical section, from this point onward we are dealing with the list of networks
+ // so to be consistent we cannot allow that the list changes
+ d.configNetwork.Lock()
+ defer d.configNetwork.Unlock()
+
+ // check network conflicts
+ if err = d.checkConflict(config); err != nil {
+ nerr, ok := err.(defaultBridgeNetworkConflict)
+ if !ok {
+ return err
+ }
+ // Got a conflict with a stale default network, clean that up and continue
+ logrus.Warn(nerr)
+ d.deleteNetwork(nerr.ID)
+ }
+
+ // there is no conflict, now create the network
+ if err = d.createNetwork(config); err != nil {
+ return err
+ }
+
+ return d.storeUpdate(config)
+}
+
+func (d *driver) checkConflict(config *networkConfiguration) error {
+ networkList := d.getNetworks()
+ for _, nw := range networkList {
+ nw.Lock()
+ nwConfig := nw.config
+ nw.Unlock()
+ if err := nwConfig.Conflicts(config); err != nil {
+ if config.DefaultBridge {
+ // We encountered and identified a stale default network
+ // We must delete it as libnetwork is the source of truth
+ // The default network being created must be the only one
+ // This can happen only from docker 1.12 on ward
+ logrus.Infof("Found stale default bridge network %s (%s)", nwConfig.ID, nwConfig.BridgeName)
+ return defaultBridgeNetworkConflict{nwConfig.ID}
+ }
+
+ return types.ForbiddenErrorf("cannot create network %s (%s): conflicts with network %s (%s): %s",
+ config.ID, config.BridgeName, nwConfig.ID, nwConfig.BridgeName, err.Error())
+ }
+ }
+ return nil
+}
+
+func (d *driver) createNetwork(config *networkConfiguration) (err error) {
+ defer osl.InitOSContext()()
+
+ networkList := d.getNetworks()
+
+ // Initialize handle when needed
+ d.Lock()
+ if d.nlh == nil {
+ d.nlh = ns.NlHandle()
+ }
+ d.Unlock()
+
+ // Create or retrieve the bridge L3 interface
+ bridgeIface, err := newInterface(d.nlh, config)
+ if err != nil {
+ return err
+ }
+
+ // Create and set network handler in driver
+ network := &bridgeNetwork{
+ id: config.ID,
+ endpoints: make(map[string]*bridgeEndpoint),
+ config: config,
+ portMapper: portmapper.New(d.config.UserlandProxyPath),
+ bridge: bridgeIface,
+ driver: d,
+ }
+
+ d.Lock()
+ d.networks[config.ID] = network
+ d.Unlock()
+
+ // On failure make sure to reset driver network handler to nil
+ defer func() {
+ if err != nil {
+ d.Lock()
+ delete(d.networks, config.ID)
+ d.Unlock()
+ }
+ }()
+
+ // Add inter-network communication rules.
+ setupNetworkIsolationRules := func(config *networkConfiguration, i *bridgeInterface) error {
+ if err := network.isolateNetwork(networkList, true); err != nil {
+ if err = network.isolateNetwork(networkList, false); err != nil {
+ logrus.Warnf("Failed on removing the inter-network iptables rules on cleanup: %v", err)
+ }
+ return err
+ }
+ // register the cleanup function
+ network.registerIptCleanFunc(func() error {
+ nwList := d.getNetworks()
+ return network.isolateNetwork(nwList, false)
+ })
+ return nil
+ }
+
+ // Prepare the bridge setup configuration
+ bridgeSetup := newBridgeSetup(config, bridgeIface)
+
+ // If the bridge interface doesn't exist, we need to start the setup steps
+ // by creating a new device and assigning it an IPv4 address.
+ bridgeAlreadyExists := bridgeIface.exists()
+ if !bridgeAlreadyExists {
+ bridgeSetup.queueStep(setupDevice)
+ }
+
+ // Even if a bridge exists try to setup IPv4.
+ bridgeSetup.queueStep(setupBridgeIPv4)
+
+ enableIPv6Forwarding := d.config.EnableIPForwarding && config.AddressIPv6 != nil
+
+ // Conditionally queue setup steps depending on configuration values.
+ for _, step := range []struct {
+ Condition bool
+ Fn setupStep
+ }{
+ // Enable IPv6 on the bridge if required. We do this even for a
+ // previously existing bridge, as it may be here from a previous
+ // installation where IPv6 wasn't supported yet and needs to be
+ // assigned an IPv6 link-local address.
+ {config.EnableIPv6, setupBridgeIPv6},
+
+ // We ensure that the bridge has the expectedIPv4 and IPv6 addresses in
+ // the case of a previously existing device.
+ {bridgeAlreadyExists, setupVerifyAndReconcile},
+
+ // Enable IPv6 Forwarding
+ {enableIPv6Forwarding, setupIPv6Forwarding},
+
+ // Setup Loopback Addresses Routing
+ {!d.config.EnableUserlandProxy, setupLoopbackAddressesRouting},
+
+ // Setup IPTables.
+ {d.config.EnableIPTables, network.setupIPTables},
+
+ //We want to track firewalld configuration so that
+ //if it is started/reloaded, the rules can be applied correctly
+ {d.config.EnableIPTables, network.setupFirewalld},
+
+ // Setup DefaultGatewayIPv4
+ {config.DefaultGatewayIPv4 != nil, setupGatewayIPv4},
+
+ // Setup DefaultGatewayIPv6
+ {config.DefaultGatewayIPv6 != nil, setupGatewayIPv6},
+
+ // Add inter-network communication rules.
+ {d.config.EnableIPTables, setupNetworkIsolationRules},
+
+ //Configure bridge networking filtering if ICC is off and IP tables are enabled
+ {!config.EnableICC && d.config.EnableIPTables, setupBridgeNetFiltering},
+ } {
+ if step.Condition {
+ bridgeSetup.queueStep(step.Fn)
+ }
+ }
+
+ // Apply the prepared list of steps, and abort at the first error.
+ bridgeSetup.queueStep(setupDeviceUp)
+ return bridgeSetup.apply()
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+
+ d.configNetwork.Lock()
+ defer d.configNetwork.Unlock()
+
+ return d.deleteNetwork(nid)
+}
+
+func (d *driver) deleteNetwork(nid string) error {
+ var err error
+
+ defer osl.InitOSContext()()
+ // Get network handler and remove it from driver
+ d.Lock()
+ n, ok := d.networks[nid]
+ d.Unlock()
+
+ if !ok {
+ return types.InternalMaskableErrorf("network %s does not exist", nid)
+ }
+
+ n.Lock()
+ config := n.config
+ n.Unlock()
+
+ // delele endpoints belong to this network
+ for _, ep := range n.endpoints {
+ if err := n.releasePorts(ep); err != nil {
+ logrus.Warn(err)
+ }
+ if link, err := d.nlh.LinkByName(ep.srcName); err == nil {
+ if err := d.nlh.LinkDel(link); err != nil {
+ logrus.WithError(err).Errorf("Failed to delete interface (%s)'s link on endpoint (%s) delete", ep.srcName, ep.id)
+ }
+ }
+
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Warnf("Failed to remove bridge endpoint %.7s from store: %v", ep.id, err)
+ }
+ }
+
+ d.Lock()
+ delete(d.networks, nid)
+ d.Unlock()
+
+ // On failure set network handler back in driver, but
+ // only if is not already taken over by some other thread
+ defer func() {
+ if err != nil {
+ d.Lock()
+ if _, ok := d.networks[nid]; !ok {
+ d.networks[nid] = n
+ }
+ d.Unlock()
+ }
+ }()
+
+ switch config.BridgeIfaceCreator {
+ case ifaceCreatedByLibnetwork, ifaceCreatorUnknown:
+ // We only delete the bridge if it was created by the bridge driver and
+ // it is not the default one (to keep the backward compatible behavior.)
+ if !config.DefaultBridge {
+ if err := d.nlh.LinkDel(n.bridge.Link); err != nil {
+ logrus.Warnf("Failed to remove bridge interface %s on network %s delete: %v", config.BridgeName, nid, err)
+ }
+ }
+ case ifaceCreatedByUser:
+ // Don't delete the bridge interface if it was not created by libnetwork.
+ }
+
+ // 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)
+}
+
+func addToBridge(nlh *netlink.Handle, ifaceName, bridgeName string) error {
+ link, err := nlh.LinkByName(ifaceName)
+ if err != nil {
+ return fmt.Errorf("could not find interface %s: %v", ifaceName, err)
+ }
+ if err = nlh.LinkSetMaster(link,
+ &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: bridgeName}}); err != nil {
+ logrus.Debugf("Failed to add %s to bridge via netlink.Trying ioctl: %v", ifaceName, err)
+ iface, err := net.InterfaceByName(ifaceName)
+ if err != nil {
+ return fmt.Errorf("could not find network interface %s: %v", ifaceName, err)
+ }
+
+ master, err := net.InterfaceByName(bridgeName)
+ if err != nil {
+ return fmt.Errorf("could not find bridge %s: %v", bridgeName, err)
+ }
+
+ return ioctlAddToBridge(iface, master)
+ }
+ return nil
+}
+
+func setHairpinMode(nlh *netlink.Handle, link netlink.Link, enable bool) error {
+ err := nlh.LinkSetHairpin(link, enable)
+ if err != nil && err != syscall.EINVAL {
+ // If error is not EINVAL something else went wrong, bail out right away
+ return fmt.Errorf("unable to set hairpin mode on %s via netlink: %v",
+ link.Attrs().Name, err)
+ }
+
+ // Hairpin mode successfully set up
+ if err == nil {
+ return nil
+ }
+
+ // The netlink method failed with EINVAL which is probably because of an older
+ // kernel. Try one more time via the sysfs method.
+ path := filepath.Join("/sys/class/net", link.Attrs().Name, "brport/hairpin_mode")
+
+ var val []byte
+ if enable {
+ val = []byte{'1', '\n'}
+ } else {
+ val = []byte{'0', '\n'}
+ }
+
+ if err := ioutil.WriteFile(path, val, 0644); err != nil {
+ return fmt.Errorf("unable to set hairpin mode on %s via sysfs: %v", link.Attrs().Name, err)
+ }
+
+ return nil
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+ defer osl.InitOSContext()()
+
+ if ifInfo == nil {
+ return errors.New("invalid interface info passed")
+ }
+
+ // Get the network handler and make sure it exists
+ d.Lock()
+ n, ok := d.networks[nid]
+ dconfig := d.config
+ d.Unlock()
+
+ if !ok {
+ return types.NotFoundErrorf("network %s does not exist", nid)
+ }
+ if n == nil {
+ return driverapi.ErrNoNetwork(nid)
+ }
+
+ // Sanity check
+ n.Lock()
+ if n.id != nid {
+ n.Unlock()
+ return InvalidNetworkIDError(nid)
+ }
+ n.Unlock()
+
+ // Check if endpoint id is good and retrieve correspondent endpoint
+ ep, err := n.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+
+ // Endpoint with that id exists either on desired or other sandbox
+ if ep != nil {
+ return driverapi.ErrEndpointExists(eid)
+ }
+
+ // Try to convert the options to endpoint configuration
+ epConfig, err := parseEndpointOptions(epOptions)
+ if err != nil {
+ return err
+ }
+
+ // Create and add the endpoint
+ n.Lock()
+ endpoint := &bridgeEndpoint{id: eid, nid: nid, config: epConfig}
+ n.endpoints[eid] = endpoint
+ n.Unlock()
+
+ // On failure make sure to remove the endpoint
+ defer func() {
+ if err != nil {
+ n.Lock()
+ delete(n.endpoints, eid)
+ n.Unlock()
+ }
+ }()
+
+ // Generate a name for what will be the host side pipe interface
+ hostIfName, err := netutils.GenerateIfaceName(d.nlh, vethPrefix, vethLen)
+ if err != nil {
+ return err
+ }
+
+ // Generate a name for what will be the sandbox side pipe interface
+ containerIfName, err := netutils.GenerateIfaceName(d.nlh, vethPrefix, vethLen)
+ if err != nil {
+ return err
+ }
+
+ // Generate and add the interface pipe host <-> sandbox
+ veth := &netlink.Veth{
+ LinkAttrs: netlink.LinkAttrs{Name: hostIfName, TxQLen: 0},
+ PeerName: containerIfName}
+ if err = d.nlh.LinkAdd(veth); err != nil {
+ return types.InternalErrorf("failed to add the host (%s) <=> sandbox (%s) pair interfaces: %v", hostIfName, containerIfName, err)
+ }
+
+ // Get the host side pipe interface handler
+ host, err := d.nlh.LinkByName(hostIfName)
+ if err != nil {
+ return types.InternalErrorf("failed to find host side interface %s: %v", hostIfName, err)
+ }
+ defer func() {
+ if err != nil {
+ if err := d.nlh.LinkDel(host); err != nil {
+ logrus.WithError(err).Warnf("Failed to delete host side interface (%s)'s link", hostIfName)
+ }
+ }
+ }()
+
+ // Get the sandbox side pipe interface handler
+ sbox, err := d.nlh.LinkByName(containerIfName)
+ if err != nil {
+ return types.InternalErrorf("failed to find sandbox side interface %s: %v", containerIfName, err)
+ }
+ defer func() {
+ if err != nil {
+ if err := d.nlh.LinkDel(sbox); err != nil {
+ logrus.WithError(err).Warnf("Failed to delete sandbox side interface (%s)'s link", containerIfName)
+ }
+ }
+ }()
+
+ n.Lock()
+ config := n.config
+ n.Unlock()
+
+ // Add bridge inherited attributes to pipe interfaces
+ if config.Mtu != 0 {
+ err = d.nlh.LinkSetMTU(host, config.Mtu)
+ if err != nil {
+ return types.InternalErrorf("failed to set MTU on host interface %s: %v", hostIfName, err)
+ }
+ err = d.nlh.LinkSetMTU(sbox, config.Mtu)
+ if err != nil {
+ return types.InternalErrorf("failed to set MTU on sandbox interface %s: %v", containerIfName, err)
+ }
+ }
+
+ // Attach host side pipe interface into the bridge
+ if err = addToBridge(d.nlh, hostIfName, config.BridgeName); err != nil {
+ return fmt.Errorf("adding interface %s to bridge %s failed: %v", hostIfName, config.BridgeName, err)
+ }
+
+ if !dconfig.EnableUserlandProxy {
+ err = setHairpinMode(d.nlh, host, true)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Store the sandbox side pipe interface parameters
+ endpoint.srcName = containerIfName
+ endpoint.macAddress = ifInfo.MacAddress()
+ endpoint.addr = ifInfo.Address()
+ endpoint.addrv6 = ifInfo.AddressIPv6()
+
+ // Set the sbox's MAC if not provided. If specified, use the one configured by user, otherwise generate one based on IP.
+ if endpoint.macAddress == nil {
+ endpoint.macAddress = electMacAddress(epConfig, endpoint.addr.IP)
+ if err = ifInfo.SetMacAddress(endpoint.macAddress); err != nil {
+ return err
+ }
+ }
+
+ // Up the host interface after finishing all netlink configuration
+ if err = d.nlh.LinkSetUp(host); err != nil {
+ return fmt.Errorf("could not set link up for host interface %s: %v", hostIfName, err)
+ }
+
+ if endpoint.addrv6 == nil && config.EnableIPv6 {
+ var ip6 net.IP
+ network := n.bridge.bridgeIPv6
+ if config.AddressIPv6 != nil {
+ network = config.AddressIPv6
+ }
+
+ ones, _ := network.Mask.Size()
+ if ones > 80 {
+ err = types.ForbiddenErrorf("Cannot self generate an IPv6 address on network %v: At least 48 host bits are needed.", network)
+ return err
+ }
+
+ ip6 = make(net.IP, len(network.IP))
+ copy(ip6, network.IP)
+ for i, h := range endpoint.macAddress {
+ ip6[i+10] = h
+ }
+
+ endpoint.addrv6 = &net.IPNet{IP: ip6, Mask: network.Mask}
+ if err = ifInfo.SetIPAddress(endpoint.addrv6); err != nil {
+ return err
+ }
+ }
+
+ if err = d.storeUpdate(endpoint); err != nil {
+ return fmt.Errorf("failed to save bridge endpoint %.7s to store: %v", endpoint.id, err)
+ }
+
+ return nil
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ var err error
+
+ defer osl.InitOSContext()()
+
+ // Get the network handler and make sure it exists
+ d.Lock()
+ n, ok := d.networks[nid]
+ d.Unlock()
+
+ if !ok {
+ return types.InternalMaskableErrorf("network %s does not exist", nid)
+ }
+ if n == nil {
+ return driverapi.ErrNoNetwork(nid)
+ }
+
+ // Sanity Check
+ n.Lock()
+ if n.id != nid {
+ n.Unlock()
+ return InvalidNetworkIDError(nid)
+ }
+ n.Unlock()
+
+ // Check endpoint id and if an endpoint is actually there
+ ep, err := n.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+ if ep == nil {
+ return EndpointNotFoundError(eid)
+ }
+
+ // Remove it
+ n.Lock()
+ delete(n.endpoints, eid)
+ n.Unlock()
+
+ // On failure make sure to set back ep in n.endpoints, but only
+ // if it hasn't been taken over already by some other thread.
+ defer func() {
+ if err != nil {
+ n.Lock()
+ if _, ok := n.endpoints[eid]; !ok {
+ n.endpoints[eid] = ep
+ }
+ n.Unlock()
+ }
+ }()
+
+ // Try removal of link. Discard error: it is a best effort.
+ // Also make sure defer does not see this error either.
+ if link, err := d.nlh.LinkByName(ep.srcName); err == nil {
+ if err := d.nlh.LinkDel(link); err != nil {
+ logrus.WithError(err).Errorf("Failed to delete interface (%s)'s link on endpoint (%s) delete", ep.srcName, ep.id)
+ }
+ }
+
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Warnf("Failed to remove bridge endpoint %.7s from store: %v", ep.id, err)
+ }
+
+ return nil
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ // Get the network handler and make sure it exists
+ d.Lock()
+ n, ok := d.networks[nid]
+ d.Unlock()
+ if !ok {
+ return nil, types.NotFoundErrorf("network %s does not exist", nid)
+ }
+ if n == nil {
+ return nil, driverapi.ErrNoNetwork(nid)
+ }
+
+ // Sanity check
+ n.Lock()
+ if n.id != nid {
+ n.Unlock()
+ return nil, InvalidNetworkIDError(nid)
+ }
+ n.Unlock()
+
+ // Check if endpoint id is good and retrieve correspondent endpoint
+ ep, err := n.getEndpoint(eid)
+ if err != nil {
+ return nil, err
+ }
+ if ep == nil {
+ return nil, driverapi.ErrNoEndpoint(eid)
+ }
+
+ m := make(map[string]interface{})
+
+ if ep.extConnConfig != nil && ep.extConnConfig.ExposedPorts != nil {
+ // Return a copy of the config data
+ epc := make([]types.TransportPort, 0, len(ep.extConnConfig.ExposedPorts))
+ for _, tp := range ep.extConnConfig.ExposedPorts {
+ epc = append(epc, tp.GetCopy())
+ }
+ m[netlabel.ExposedPorts] = epc
+ }
+
+ if ep.portMapping != nil {
+ // Return a copy of the operational data
+ pmc := make([]types.PortBinding, 0, len(ep.portMapping))
+ for _, pm := range ep.portMapping {
+ pmc = append(pmc, pm.GetCopy())
+ }
+ m[netlabel.PortMap] = pmc
+ }
+
+ if len(ep.macAddress) != 0 {
+ m[netlabel.MacAddress] = ep.macAddress
+ }
+
+ return m, nil
+}
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ defer osl.InitOSContext()()
+
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+ }
+
+ endpoint, err := network.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+
+ if endpoint == nil {
+ return EndpointNotFoundError(eid)
+ }
+
+ endpoint.containerConfig, err = parseContainerOptions(options)
+ if err != nil {
+ return err
+ }
+
+ iNames := jinfo.InterfaceName()
+ containerVethPrefix := defaultContainerVethPrefix
+ if network.config.ContainerIfacePrefix != "" {
+ containerVethPrefix = network.config.ContainerIfacePrefix
+ }
+ err = iNames.SetNames(endpoint.srcName, containerVethPrefix)
+ if err != nil {
+ return err
+ }
+
+ err = jinfo.SetGateway(network.bridge.gatewayIPv4)
+ if err != nil {
+ return err
+ }
+
+ err = jinfo.SetGatewayIPv6(network.bridge.gatewayIPv6)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ defer osl.InitOSContext()()
+
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return types.InternalMaskableErrorf("%s", err)
+ }
+
+ endpoint, err := network.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+
+ if endpoint == nil {
+ return EndpointNotFoundError(eid)
+ }
+
+ if !network.config.EnableICC {
+ if err = d.link(network, endpoint, false); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ defer osl.InitOSContext()()
+
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+ }
+
+ endpoint, err := network.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+
+ if endpoint == nil {
+ return EndpointNotFoundError(eid)
+ }
+
+ endpoint.extConnConfig, err = parseConnectivityOptions(options)
+ if err != nil {
+ return err
+ }
+
+ // Program any required port mapping and store them in the endpoint
+ endpoint.portMapping, err = network.allocatePorts(endpoint, network.config.DefaultBindingIP, d.config.EnableUserlandProxy)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil {
+ if e := network.releasePorts(endpoint); e != nil {
+ logrus.Errorf("Failed to release ports allocated for the bridge endpoint %s on failure %v because of %v",
+ eid, err, e)
+ }
+ endpoint.portMapping = nil
+ }
+ }()
+
+ if err = d.storeUpdate(endpoint); err != nil {
+ return fmt.Errorf("failed to update bridge endpoint %.7s to store: %v", endpoint.id, err)
+ }
+
+ if !network.config.EnableICC {
+ return d.link(network, endpoint, true)
+ }
+
+ return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ defer osl.InitOSContext()()
+
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+ }
+
+ endpoint, err := network.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+
+ if endpoint == nil {
+ return EndpointNotFoundError(eid)
+ }
+
+ err = network.releasePorts(endpoint)
+ if err != nil {
+ logrus.Warn(err)
+ }
+
+ endpoint.portMapping = nil
+
+ // Clean the connection tracker state of the host for the specific endpoint
+ // The host kernel keeps track of the connections (TCP and UDP), so if a new endpoint gets the same IP of
+ // this one (that is going down), is possible that some of the packets would not be routed correctly inside
+ // the new endpoint
+ // Deeper details: https://github.com/docker/docker/issues/8795
+ clearEndpointConnections(d.nlh, endpoint)
+
+ if err = d.storeUpdate(endpoint); err != nil {
+ return fmt.Errorf("failed to update bridge endpoint %.7s to store: %v", endpoint.id, err)
+ }
+
+ return nil
+}
+
+func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, enable bool) error {
+ var err error
+
+ cc := endpoint.containerConfig
+ if cc == nil {
+ return nil
+ }
+ ec := endpoint.extConnConfig
+ if ec == nil {
+ return nil
+ }
+
+ if ec.ExposedPorts != nil {
+ for _, p := range cc.ParentEndpoints {
+ var parentEndpoint *bridgeEndpoint
+ parentEndpoint, err = network.getEndpoint(p)
+ if err != nil {
+ return err
+ }
+ if parentEndpoint == nil {
+ err = InvalidEndpointIDError(p)
+ return err
+ }
+
+ l := newLink(parentEndpoint.addr.IP.String(),
+ endpoint.addr.IP.String(),
+ ec.ExposedPorts, network.config.BridgeName)
+ if enable {
+ err = l.Enable()
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ l.Disable()
+ }
+ }()
+ } else {
+ l.Disable()
+ }
+ }
+ }
+
+ for _, c := range cc.ChildEndpoints {
+ var childEndpoint *bridgeEndpoint
+ childEndpoint, err = network.getEndpoint(c)
+ if err != nil {
+ return err
+ }
+ if childEndpoint == nil {
+ err = InvalidEndpointIDError(c)
+ return err
+ }
+ if childEndpoint.extConnConfig == nil || childEndpoint.extConnConfig.ExposedPorts == nil {
+ continue
+ }
+
+ l := newLink(endpoint.addr.IP.String(),
+ childEndpoint.addr.IP.String(),
+ childEndpoint.extConnConfig.ExposedPorts, network.config.BridgeName)
+ if enable {
+ err = l.Enable()
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ l.Disable()
+ }
+ }()
+ } else {
+ l.Disable()
+ }
+ }
+
+ return nil
+}
+
+func (d *driver) Type() string {
+ return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+func parseEndpointOptions(epOptions map[string]interface{}) (*endpointConfiguration, error) {
+ if epOptions == nil {
+ return nil, nil
+ }
+
+ ec := &endpointConfiguration{}
+
+ if opt, ok := epOptions[netlabel.MacAddress]; ok {
+ if mac, ok := opt.(net.HardwareAddr); ok {
+ ec.MacAddress = mac
+ } else {
+ return nil, &ErrInvalidEndpointConfig{}
+ }
+ }
+
+ return ec, nil
+}
+
+func parseContainerOptions(cOptions map[string]interface{}) (*containerConfiguration, error) {
+ if cOptions == nil {
+ return nil, nil
+ }
+ genericData := cOptions[netlabel.GenericData]
+ if genericData == nil {
+ return nil, nil
+ }
+ switch opt := genericData.(type) {
+ case options.Generic:
+ opaqueConfig, err := options.GenerateFromModel(opt, &containerConfiguration{})
+ if err != nil {
+ return nil, err
+ }
+ return opaqueConfig.(*containerConfiguration), nil
+ case *containerConfiguration:
+ return opt, nil
+ default:
+ return nil, nil
+ }
+}
+
+func parseConnectivityOptions(cOptions map[string]interface{}) (*connectivityConfiguration, error) {
+ if cOptions == nil {
+ return nil, nil
+ }
+
+ cc := &connectivityConfiguration{}
+
+ if opt, ok := cOptions[netlabel.PortMap]; ok {
+ if pb, ok := opt.([]types.PortBinding); ok {
+ cc.PortBindings = pb
+ } else {
+ return nil, types.BadRequestErrorf("Invalid port mapping data in connectivity configuration: %v", opt)
+ }
+ }
+
+ if opt, ok := cOptions[netlabel.ExposedPorts]; ok {
+ if ports, ok := opt.([]types.TransportPort); ok {
+ cc.ExposedPorts = ports
+ } else {
+ return nil, types.BadRequestErrorf("Invalid exposed ports data in connectivity configuration: %v", opt)
+ }
+ }
+
+ return cc, nil
+}
+
+func electMacAddress(epConfig *endpointConfiguration, ip net.IP) net.HardwareAddr {
+ if epConfig != nil && epConfig.MacAddress != nil {
+ return epConfig.MacAddress
+ }
+ return netutils.GenerateMACFromIP(ip)
+}
--- /dev/null
+package bridge
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // network config prefix was not specific enough.
+ // To be backward compatible, need custom endpoint
+ // prefix with different root
+ bridgePrefix = "bridge"
+ bridgeEndpointPrefix = "bridge-endpoint"
+)
+
+func (d *driver) initStore(option map[string]interface{}) error {
+ if data, ok := option[netlabel.LocalKVClient]; ok {
+ var err error
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+ }
+ d.store, err = datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return types.InternalErrorf("bridge driver failed to initialize data store: %v", err)
+ }
+
+ err = d.populateNetworks()
+ if err != nil {
+ return err
+ }
+
+ err = d.populateEndpoints()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (d *driver) populateNetworks() error {
+ kvol, err := d.store.List(datastore.Key(bridgePrefix), &networkConfiguration{})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return fmt.Errorf("failed to get bridge network configurations from store: %v", err)
+ }
+
+ // It's normal for network configuration state to be empty. Just return.
+ if err == datastore.ErrKeyNotFound {
+ return nil
+ }
+
+ for _, kvo := range kvol {
+ ncfg := kvo.(*networkConfiguration)
+ if err = d.createNetwork(ncfg); err != nil {
+ logrus.Warnf("could not create bridge network for id %s bridge name %s while booting up from persistent state: %v", ncfg.ID, ncfg.BridgeName, err)
+ }
+ logrus.Debugf("Network (%.7s) restored", ncfg.ID)
+ }
+
+ return nil
+}
+
+func (d *driver) populateEndpoints() error {
+ kvol, err := d.store.List(datastore.Key(bridgeEndpointPrefix), &bridgeEndpoint{})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return fmt.Errorf("failed to get bridge endpoints from store: %v", err)
+ }
+
+ if err == datastore.ErrKeyNotFound {
+ return nil
+ }
+
+ for _, kvo := range kvol {
+ ep := kvo.(*bridgeEndpoint)
+ n, ok := d.networks[ep.nid]
+ if !ok {
+ logrus.Debugf("Network (%.7s) not found for restored bridge endpoint (%.7s)", ep.nid, ep.id)
+ logrus.Debugf("Deleting stale bridge endpoint (%.7s) from store", ep.id)
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Debugf("Failed to delete stale bridge endpoint (%.7s) from store", ep.id)
+ }
+ continue
+ }
+ n.endpoints[ep.id] = ep
+ n.restorePortAllocations(ep)
+ logrus.Debugf("Endpoint (%.7s) restored to network (%.7s)", ep.id, ep.nid)
+ }
+
+ return nil
+}
+
+func (d *driver) storeUpdate(kvObject datastore.KVObject) error {
+ if d.store == nil {
+ logrus.Warnf("bridge store not initialized. kv object %s is not added to the store", datastore.Key(kvObject.Key()...))
+ return nil
+ }
+
+ if err := d.store.PutObjectAtomic(kvObject); err != nil {
+ return fmt.Errorf("failed to update bridge store for object type %T: %v", kvObject, err)
+ }
+
+ return nil
+}
+
+func (d *driver) storeDelete(kvObject datastore.KVObject) error {
+ if d.store == nil {
+ logrus.Debugf("bridge store not initialized. kv object %s is not deleted from store", datastore.Key(kvObject.Key()...))
+ return nil
+ }
+
+retry:
+ if err := d.store.DeleteObjectAtomic(kvObject); err != nil {
+ if err == datastore.ErrKeyModified {
+ if err := d.store.GetObject(datastore.Key(kvObject.Key()...), kvObject); err != nil {
+ return fmt.Errorf("could not update the kvobject to latest when trying to delete: %v", err)
+ }
+ goto retry
+ }
+ return err
+ }
+
+ return nil
+}
+
+func (ncfg *networkConfiguration) MarshalJSON() ([]byte, error) {
+ nMap := make(map[string]interface{})
+ nMap["ID"] = ncfg.ID
+ nMap["BridgeName"] = ncfg.BridgeName
+ nMap["EnableIPv6"] = ncfg.EnableIPv6
+ nMap["EnableIPMasquerade"] = ncfg.EnableIPMasquerade
+ nMap["EnableICC"] = ncfg.EnableICC
+ nMap["Mtu"] = ncfg.Mtu
+ nMap["Internal"] = ncfg.Internal
+ nMap["DefaultBridge"] = ncfg.DefaultBridge
+ nMap["DefaultBindingIP"] = ncfg.DefaultBindingIP.String()
+ nMap["DefaultGatewayIPv4"] = ncfg.DefaultGatewayIPv4.String()
+ nMap["DefaultGatewayIPv6"] = ncfg.DefaultGatewayIPv6.String()
+ nMap["ContainerIfacePrefix"] = ncfg.ContainerIfacePrefix
+ nMap["BridgeIfaceCreator"] = ncfg.BridgeIfaceCreator
+
+ if ncfg.AddressIPv4 != nil {
+ nMap["AddressIPv4"] = ncfg.AddressIPv4.String()
+ }
+
+ if ncfg.AddressIPv6 != nil {
+ nMap["AddressIPv6"] = ncfg.AddressIPv6.String()
+ }
+
+ return json.Marshal(nMap)
+}
+
+func (ncfg *networkConfiguration) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ nMap map[string]interface{}
+ )
+
+ if err = json.Unmarshal(b, &nMap); err != nil {
+ return err
+ }
+
+ if v, ok := nMap["AddressIPv4"]; ok {
+ if ncfg.AddressIPv4, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode bridge network address IPv4 after json unmarshal: %s", v.(string))
+ }
+ }
+
+ if v, ok := nMap["AddressIPv6"]; ok {
+ if ncfg.AddressIPv6, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode bridge network address IPv6 after json unmarshal: %s", v.(string))
+ }
+ }
+
+ if v, ok := nMap["ContainerIfacePrefix"]; ok {
+ ncfg.ContainerIfacePrefix = v.(string)
+ }
+
+ ncfg.DefaultBridge = nMap["DefaultBridge"].(bool)
+ ncfg.DefaultBindingIP = net.ParseIP(nMap["DefaultBindingIP"].(string))
+ ncfg.DefaultGatewayIPv4 = net.ParseIP(nMap["DefaultGatewayIPv4"].(string))
+ ncfg.DefaultGatewayIPv6 = net.ParseIP(nMap["DefaultGatewayIPv6"].(string))
+ ncfg.ID = nMap["ID"].(string)
+ ncfg.BridgeName = nMap["BridgeName"].(string)
+ ncfg.EnableIPv6 = nMap["EnableIPv6"].(bool)
+ ncfg.EnableIPMasquerade = nMap["EnableIPMasquerade"].(bool)
+ ncfg.EnableICC = nMap["EnableICC"].(bool)
+ ncfg.Mtu = int(nMap["Mtu"].(float64))
+ if v, ok := nMap["Internal"]; ok {
+ ncfg.Internal = v.(bool)
+ }
+
+ if v, ok := nMap["BridgeIfaceCreator"]; ok {
+ ncfg.BridgeIfaceCreator = ifaceCreator(v.(float64))
+ }
+
+ return nil
+}
+
+func (ncfg *networkConfiguration) Key() []string {
+ return []string{bridgePrefix, ncfg.ID}
+}
+
+func (ncfg *networkConfiguration) KeyPrefix() []string {
+ return []string{bridgePrefix}
+}
+
+func (ncfg *networkConfiguration) Value() []byte {
+ b, err := json.Marshal(ncfg)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (ncfg *networkConfiguration) SetValue(value []byte) error {
+ return json.Unmarshal(value, ncfg)
+}
+
+func (ncfg *networkConfiguration) Index() uint64 {
+ return ncfg.dbIndex
+}
+
+func (ncfg *networkConfiguration) SetIndex(index uint64) {
+ ncfg.dbIndex = index
+ ncfg.dbExists = true
+}
+
+func (ncfg *networkConfiguration) Exists() bool {
+ return ncfg.dbExists
+}
+
+func (ncfg *networkConfiguration) Skip() bool {
+ return false
+}
+
+func (ncfg *networkConfiguration) New() datastore.KVObject {
+ return &networkConfiguration{}
+}
+
+func (ncfg *networkConfiguration) CopyTo(o datastore.KVObject) error {
+ dstNcfg := o.(*networkConfiguration)
+ *dstNcfg = *ncfg
+ return nil
+}
+
+func (ncfg *networkConfiguration) DataScope() string {
+ return datastore.LocalScope
+}
+
+func (ep *bridgeEndpoint) MarshalJSON() ([]byte, error) {
+ epMap := make(map[string]interface{})
+ epMap["id"] = ep.id
+ epMap["nid"] = ep.nid
+ epMap["SrcName"] = ep.srcName
+ epMap["MacAddress"] = ep.macAddress.String()
+ epMap["Addr"] = ep.addr.String()
+ if ep.addrv6 != nil {
+ epMap["Addrv6"] = ep.addrv6.String()
+ }
+ epMap["Config"] = ep.config
+ epMap["ContainerConfig"] = ep.containerConfig
+ epMap["ExternalConnConfig"] = ep.extConnConfig
+ epMap["PortMapping"] = ep.portMapping
+
+ return json.Marshal(epMap)
+}
+
+func (ep *bridgeEndpoint) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ epMap map[string]interface{}
+ )
+
+ if err = json.Unmarshal(b, &epMap); err != nil {
+ return fmt.Errorf("Failed to unmarshal to bridge endpoint: %v", err)
+ }
+
+ if v, ok := epMap["MacAddress"]; ok {
+ if ep.macAddress, err = net.ParseMAC(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode bridge endpoint MAC address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ if v, ok := epMap["Addr"]; ok {
+ if ep.addr, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode bridge endpoint IPv4 address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ if v, ok := epMap["Addrv6"]; ok {
+ if ep.addrv6, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode bridge endpoint IPv6 address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ ep.id = epMap["id"].(string)
+ ep.nid = epMap["nid"].(string)
+ ep.srcName = epMap["SrcName"].(string)
+ d, _ := json.Marshal(epMap["Config"])
+ if err := json.Unmarshal(d, &ep.config); err != nil {
+ logrus.Warnf("Failed to decode endpoint config %v", err)
+ }
+ d, _ = json.Marshal(epMap["ContainerConfig"])
+ if err := json.Unmarshal(d, &ep.containerConfig); err != nil {
+ logrus.Warnf("Failed to decode endpoint container config %v", err)
+ }
+ d, _ = json.Marshal(epMap["ExternalConnConfig"])
+ if err := json.Unmarshal(d, &ep.extConnConfig); err != nil {
+ logrus.Warnf("Failed to decode endpoint external connectivity configuration %v", err)
+ }
+ d, _ = json.Marshal(epMap["PortMapping"])
+ if err := json.Unmarshal(d, &ep.portMapping); err != nil {
+ logrus.Warnf("Failed to decode endpoint port mapping %v", err)
+ }
+
+ return nil
+}
+
+func (ep *bridgeEndpoint) Key() []string {
+ return []string{bridgeEndpointPrefix, ep.id}
+}
+
+func (ep *bridgeEndpoint) KeyPrefix() []string {
+ return []string{bridgeEndpointPrefix}
+}
+
+func (ep *bridgeEndpoint) Value() []byte {
+ b, err := json.Marshal(ep)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (ep *bridgeEndpoint) SetValue(value []byte) error {
+ return json.Unmarshal(value, ep)
+}
+
+func (ep *bridgeEndpoint) Index() uint64 {
+ return ep.dbIndex
+}
+
+func (ep *bridgeEndpoint) SetIndex(index uint64) {
+ ep.dbIndex = index
+ ep.dbExists = true
+}
+
+func (ep *bridgeEndpoint) Exists() bool {
+ return ep.dbExists
+}
+
+func (ep *bridgeEndpoint) Skip() bool {
+ return false
+}
+
+func (ep *bridgeEndpoint) New() datastore.KVObject {
+ return &bridgeEndpoint{}
+}
+
+func (ep *bridgeEndpoint) CopyTo(o datastore.KVObject) error {
+ dstEp := o.(*bridgeEndpoint)
+ *dstEp = *ep
+ return nil
+}
+
+func (ep *bridgeEndpoint) DataScope() string {
+ return datastore.LocalScope
+}
+
+func (n *bridgeNetwork) restorePortAllocations(ep *bridgeEndpoint) {
+ if ep.extConnConfig == nil ||
+ ep.extConnConfig.ExposedPorts == nil ||
+ ep.extConnConfig.PortBindings == nil {
+ return
+ }
+ tmp := ep.extConnConfig.PortBindings
+ ep.extConnConfig.PortBindings = ep.portMapping
+ _, err := n.allocatePorts(ep, n.config.DefaultBindingIP, n.driver.config.EnableUserlandProxy)
+ if err != nil {
+ logrus.Warnf("Failed to reserve existing port mapping for endpoint %.7s:%v", ep.id, err)
+ }
+ ep.extConnConfig.PortBindings = tmp
+}
--- /dev/null
+package bridge
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "testing"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/iptables"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+ "github.com/vishvananda/netlink"
+)
+
+func TestEndpointMarshalling(t *testing.T) {
+ ip1, _ := types.ParseCIDR("172.22.0.9/16")
+ ip2, _ := types.ParseCIDR("2001:db8::9")
+ mac, _ := net.ParseMAC("ac:bd:24:57:66:77")
+ e := &bridgeEndpoint{
+ id: "d2c015a1fe5930650cbcd50493efba0500bcebd8ee1f4401a16319f8a567de33",
+ nid: "ee33fbb43c323f1920b6b35a0101552ac22ede960d0e5245e9738bccc68b2415",
+ addr: ip1,
+ addrv6: ip2,
+ macAddress: mac,
+ srcName: "veth123456",
+ config: &endpointConfiguration{MacAddress: mac},
+ containerConfig: &containerConfiguration{
+ ParentEndpoints: []string{"one", "due", "three"},
+ ChildEndpoints: []string{"four", "five", "six"},
+ },
+ extConnConfig: &connectivityConfiguration{
+ ExposedPorts: []types.TransportPort{
+ {
+ Proto: 6,
+ Port: uint16(18),
+ },
+ },
+ PortBindings: []types.PortBinding{
+ {
+ Proto: 6,
+ IP: net.ParseIP("17210.33.9.56"),
+ Port: uint16(18),
+ HostPort: uint16(3000),
+ HostPortEnd: uint16(14000),
+ },
+ },
+ },
+ portMapping: []types.PortBinding{
+ {
+ Proto: 17,
+ IP: net.ParseIP("172.33.9.56"),
+ Port: uint16(99),
+ HostIP: net.ParseIP("10.10.100.2"),
+ HostPort: uint16(9900),
+ HostPortEnd: uint16(10000),
+ },
+ {
+ Proto: 6,
+ IP: net.ParseIP("171.33.9.56"),
+ Port: uint16(55),
+ HostIP: net.ParseIP("10.11.100.2"),
+ HostPort: uint16(5500),
+ HostPortEnd: uint16(55000),
+ },
+ },
+ }
+
+ b, err := json.Marshal(e)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ee := &bridgeEndpoint{}
+ err = json.Unmarshal(b, ee)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if e.id != ee.id || e.nid != ee.nid || e.srcName != ee.srcName || !bytes.Equal(e.macAddress, ee.macAddress) ||
+ !types.CompareIPNet(e.addr, ee.addr) || !types.CompareIPNet(e.addrv6, ee.addrv6) ||
+ !compareEpConfig(e.config, ee.config) ||
+ !compareContainerConfig(e.containerConfig, ee.containerConfig) ||
+ !compareConnConfig(e.extConnConfig, ee.extConnConfig) ||
+ !compareBindings(e.portMapping, ee.portMapping) {
+ t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%#v\nDecoded:\n%#v", e, ee)
+ }
+}
+
+func compareEpConfig(a, b *endpointConfiguration) bool {
+ if a == b {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ return bytes.Equal(a.MacAddress, b.MacAddress)
+}
+
+func compareContainerConfig(a, b *containerConfiguration) bool {
+ if a == b {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ if len(a.ParentEndpoints) != len(b.ParentEndpoints) ||
+ len(a.ChildEndpoints) != len(b.ChildEndpoints) {
+ return false
+ }
+ for i := 0; i < len(a.ParentEndpoints); i++ {
+ if a.ParentEndpoints[i] != b.ParentEndpoints[i] {
+ return false
+ }
+ }
+ for i := 0; i < len(a.ChildEndpoints); i++ {
+ if a.ChildEndpoints[i] != b.ChildEndpoints[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func compareConnConfig(a, b *connectivityConfiguration) bool {
+ if a == b {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ if len(a.ExposedPorts) != len(b.ExposedPorts) ||
+ len(a.PortBindings) != len(b.PortBindings) {
+ return false
+ }
+ for i := 0; i < len(a.ExposedPorts); i++ {
+ if !a.ExposedPorts[i].Equal(&b.ExposedPorts[i]) {
+ return false
+ }
+ }
+ for i := 0; i < len(a.PortBindings); i++ {
+ if !a.PortBindings[i].Equal(&b.PortBindings[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+func compareBindings(a, b []types.PortBinding) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i := 0; i < len(a); i++ {
+ if !a[i].Equal(&b[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+func getIPv4Data(t *testing.T, iface string) []driverapi.IPAMData {
+ ipd := driverapi.IPAMData{AddressSpace: "full"}
+ nws, _, err := netutils.ElectInterfaceAddresses(iface)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ipd.Pool = nws[0]
+ // Set network gateway to X.X.X.1
+ ipd.Gateway = types.GetIPNetCopy(nws[0])
+ ipd.Gateway.IP[len(ipd.Gateway.IP)-1] = 1
+ return []driverapi.IPAMData{ipd}
+}
+
+func TestCreateFullOptions(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+ d := newDriver()
+
+ config := &configuration{
+ EnableIPForwarding: true,
+ EnableIPTables: true,
+ }
+
+ // Test this scenario: Default gw address does not belong to
+ // container network and it's greater than bridge address
+ cnw, _ := types.ParseCIDR("172.16.122.0/24")
+ bnw, _ := types.ParseCIDR("172.16.0.0/24")
+ br, _ := types.ParseCIDR("172.16.0.1/16")
+ defgw, _ := types.ParseCIDR("172.16.0.100/16")
+
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ if err := d.configure(genericOption); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ netOption := make(map[string]interface{})
+ netOption[netlabel.EnableIPv6] = true
+ netOption[netlabel.GenericData] = &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ }
+
+ ipdList := []driverapi.IPAMData{
+ {
+ Pool: bnw,
+ Gateway: br,
+ AuxAddresses: map[string]*net.IPNet{DefaultGatewayV4AuxKey: defgw},
+ },
+ }
+ err := d.CreateNetwork("dummy", netOption, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ // Verify the IP address allocated for the endpoint belongs to the container network
+ epOptions := make(map[string]interface{})
+ te := newTestEndpoint(cnw, 10)
+ err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
+ if err != nil {
+ t.Fatalf("Failed to create an endpoint : %s", err.Error())
+ }
+
+ if !cnw.Contains(te.Interface().Address().IP) {
+ t.Fatalf("endpoint got assigned address outside of container network(%s): %s", cnw.String(), te.Interface().Address())
+ }
+}
+
+func TestCreateNoConfig(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+ d := newDriver()
+
+ netconfig := &networkConfiguration{BridgeName: DefaultBridgeName}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = netconfig
+
+ if err := d.CreateNetwork("dummy", genericOption, nil, getIPv4Data(t, ""), nil); err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+}
+
+func TestCreateFullOptionsLabels(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+ d := newDriver()
+
+ config := &configuration{
+ EnableIPForwarding: true,
+ }
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ if err := d.configure(genericOption); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ bndIPs := "127.0.0.1"
+ nwV6s := "2001:db8:2600:2700:2800::/80"
+ gwV6s := "2001:db8:2600:2700:2800::25/80"
+ nwV6, _ := types.ParseCIDR(nwV6s)
+ gwV6, _ := types.ParseCIDR(gwV6s)
+
+ labels := map[string]string{
+ BridgeName: DefaultBridgeName,
+ DefaultBridge: "true",
+ EnableICC: "true",
+ EnableIPMasquerade: "true",
+ DefaultBindingIP: bndIPs,
+ }
+
+ netOption := make(map[string]interface{})
+ netOption[netlabel.EnableIPv6] = true
+ netOption[netlabel.GenericData] = labels
+
+ ipdList := getIPv4Data(t, "")
+ ipd6List := []driverapi.IPAMData{
+ {
+ Pool: nwV6,
+ AuxAddresses: map[string]*net.IPNet{
+ DefaultGatewayV6AuxKey: gwV6,
+ },
+ },
+ }
+
+ err := d.CreateNetwork("dummy", netOption, nil, ipdList, ipd6List)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ nw, ok := d.networks["dummy"]
+ if !ok {
+ t.Fatal("Cannot find dummy network in bridge driver")
+ }
+
+ if nw.config.BridgeName != DefaultBridgeName {
+ t.Fatal("incongruent name in bridge network")
+ }
+
+ if !nw.config.EnableIPv6 {
+ t.Fatal("incongruent EnableIPv6 in bridge network")
+ }
+
+ if !nw.config.EnableICC {
+ t.Fatal("incongruent EnableICC in bridge network")
+ }
+
+ if !nw.config.EnableIPMasquerade {
+ t.Fatal("incongruent EnableIPMasquerade in bridge network")
+ }
+
+ bndIP := net.ParseIP(bndIPs)
+ if !bndIP.Equal(nw.config.DefaultBindingIP) {
+ t.Fatalf("Unexpected: %v", nw.config.DefaultBindingIP)
+ }
+
+ if !types.CompareIPNet(nw.config.AddressIPv6, nwV6) {
+ t.Fatalf("Unexpected: %v", nw.config.AddressIPv6)
+ }
+
+ if !gwV6.IP.Equal(nw.config.DefaultGatewayIPv6) {
+ t.Fatalf("Unexpected: %v", nw.config.DefaultGatewayIPv6)
+ }
+
+ // In short here we are testing --fixed-cidr-v6 daemon option
+ // plus --mac-address run option
+ mac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
+ epOptions := map[string]interface{}{netlabel.MacAddress: mac}
+ te := newTestEndpoint(ipdList[0].Pool, 20)
+ err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !nwV6.Contains(te.Interface().AddressIPv6().IP) {
+ t.Fatalf("endpoint got assigned address outside of container network(%s): %s", nwV6.String(), te.Interface().AddressIPv6())
+ }
+ if te.Interface().AddressIPv6().IP.String() != "2001:db8:2600:2700:2800:aabb:ccdd:eeff" {
+ t.Fatalf("Unexpected endpoint IPv6 address: %v", te.Interface().AddressIPv6().IP)
+ }
+}
+
+func TestCreate(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ d := newDriver()
+
+ if err := d.configure(nil); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ netconfig := &networkConfiguration{BridgeName: DefaultBridgeName}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = netconfig
+
+ if err := d.CreateNetwork("dummy", genericOption, nil, getIPv4Data(t, ""), nil); err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ err := d.CreateNetwork("dummy", genericOption, nil, getIPv4Data(t, ""), nil)
+ if err == nil {
+ t.Fatal("Expected bridge driver to refuse creation of second network with default name")
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatal("Creation of second network with default name failed with unexpected error type")
+ }
+}
+
+func TestCreateFail(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ d := newDriver()
+
+ if err := d.configure(nil); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ netconfig := &networkConfiguration{BridgeName: "dummy0", DefaultBridge: true}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = netconfig
+
+ if err := d.CreateNetwork("dummy", genericOption, nil, getIPv4Data(t, ""), nil); err == nil {
+ t.Fatal("Bridge creation was expected to fail")
+ }
+}
+
+func TestCreateMultipleNetworks(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ d := newDriver()
+
+ config := &configuration{
+ EnableIPTables: true,
+ }
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ if err := d.configure(genericOption); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ config1 := &networkConfiguration{BridgeName: "net_test_1"}
+ genericOption = make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config1
+ if err := d.CreateNetwork("1", genericOption, nil, getIPv4Data(t, ""), nil); err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ verifyV4INCEntries(d.networks, t)
+
+ config2 := &networkConfiguration{BridgeName: "net_test_2"}
+ genericOption[netlabel.GenericData] = config2
+ if err := d.CreateNetwork("2", genericOption, nil, getIPv4Data(t, ""), nil); err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ verifyV4INCEntries(d.networks, t)
+
+ config3 := &networkConfiguration{BridgeName: "net_test_3"}
+ genericOption[netlabel.GenericData] = config3
+ if err := d.CreateNetwork("3", genericOption, nil, getIPv4Data(t, ""), nil); err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ verifyV4INCEntries(d.networks, t)
+
+ config4 := &networkConfiguration{BridgeName: "net_test_4"}
+ genericOption[netlabel.GenericData] = config4
+ if err := d.CreateNetwork("4", genericOption, nil, getIPv4Data(t, ""), nil); err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ verifyV4INCEntries(d.networks, t)
+
+ d.DeleteNetwork("1")
+ verifyV4INCEntries(d.networks, t)
+
+ d.DeleteNetwork("2")
+ verifyV4INCEntries(d.networks, t)
+
+ d.DeleteNetwork("3")
+ verifyV4INCEntries(d.networks, t)
+
+ d.DeleteNetwork("4")
+ verifyV4INCEntries(d.networks, t)
+}
+
+// Verify the network isolation rules are installed for each network
+func verifyV4INCEntries(networks map[string]*bridgeNetwork, t *testing.T) {
+ out1, err := iptables.Raw("-S", IsolationChain1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ out2, err := iptables.Raw("-S", IsolationChain2)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, n := range networks {
+ re := regexp.MustCompile(fmt.Sprintf("-i %s ! -o %s -j %s", n.config.BridgeName, n.config.BridgeName, IsolationChain2))
+ matches := re.FindAllString(string(out1[:]), -1)
+ if len(matches) != 1 {
+ t.Fatalf("Cannot find expected inter-network isolation rules in IP Tables for network %s:\n%s.", n.id, string(out1[:]))
+ }
+ re = regexp.MustCompile(fmt.Sprintf("-o %s -j DROP", n.config.BridgeName))
+ matches = re.FindAllString(string(out2[:]), -1)
+ if len(matches) != 1 {
+ t.Fatalf("Cannot find expected inter-network isolation rules in IP Tables for network %s:\n%s.", n.id, string(out2[:]))
+ }
+ }
+}
+
+type testInterface struct {
+ mac net.HardwareAddr
+ addr *net.IPNet
+ addrv6 *net.IPNet
+ srcName string
+ dstName string
+}
+
+type testEndpoint struct {
+ iface *testInterface
+ gw net.IP
+ gw6 net.IP
+ hostsPath string
+ resolvConfPath string
+ routes []types.StaticRoute
+}
+
+func newTestEndpoint(nw *net.IPNet, ordinal byte) *testEndpoint {
+ addr := types.GetIPNetCopy(nw)
+ addr.IP[len(addr.IP)-1] = ordinal
+ return &testEndpoint{iface: &testInterface{addr: addr}}
+}
+
+func (te *testEndpoint) Interface() driverapi.InterfaceInfo {
+ if te.iface != nil {
+ return te.iface
+ }
+
+ return nil
+}
+
+func (i *testInterface) MacAddress() net.HardwareAddr {
+ return i.mac
+}
+
+func (i *testInterface) Address() *net.IPNet {
+ return i.addr
+}
+
+func (i *testInterface) AddressIPv6() *net.IPNet {
+ return i.addrv6
+}
+
+func (i *testInterface) SetMacAddress(mac net.HardwareAddr) error {
+ if i.mac != nil {
+ return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", i.mac, mac)
+ }
+ if mac == nil {
+ return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
+ }
+ i.mac = types.GetMacCopy(mac)
+ return nil
+}
+
+func (i *testInterface) SetIPAddress(address *net.IPNet) error {
+ if address.IP == nil {
+ return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
+ }
+ if address.IP.To4() == nil {
+ return setAddress(&i.addrv6, address)
+ }
+ return setAddress(&i.addr, address)
+}
+
+func setAddress(ifaceAddr **net.IPNet, address *net.IPNet) error {
+ if *ifaceAddr != nil {
+ return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
+ }
+ *ifaceAddr = types.GetIPNetCopy(address)
+ return nil
+}
+
+func (i *testInterface) SetNames(srcName string, dstName string) error {
+ i.srcName = srcName
+ i.dstName = dstName
+ return nil
+}
+
+func (te *testEndpoint) InterfaceName() driverapi.InterfaceNameInfo {
+ if te.iface != nil {
+ return te.iface
+ }
+
+ return nil
+}
+
+func (te *testEndpoint) SetGateway(gw net.IP) error {
+ te.gw = gw
+ return nil
+}
+
+func (te *testEndpoint) SetGatewayIPv6(gw6 net.IP) error {
+ te.gw6 = gw6
+ return nil
+}
+
+func (te *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP) error {
+ te.routes = append(te.routes, types.StaticRoute{Destination: destination, RouteType: routeType, NextHop: nextHop})
+ return nil
+}
+
+func (te *testEndpoint) AddTableEntry(tableName string, key string, value []byte) error {
+ return nil
+}
+
+func (te *testEndpoint) DisableGatewayService() {}
+
+func TestQueryEndpointInfo(t *testing.T) {
+ testQueryEndpointInfo(t, true)
+}
+
+func TestQueryEndpointInfoHairpin(t *testing.T) {
+ testQueryEndpointInfo(t, false)
+}
+
+func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+ d := newDriver()
+
+ config := &configuration{
+ EnableIPTables: true,
+ EnableUserlandProxy: ulPxyEnabled,
+ }
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ if err := d.configure(genericOption); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ netconfig := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ EnableICC: false,
+ }
+ genericOption = make(map[string]interface{})
+ genericOption[netlabel.GenericData] = netconfig
+
+ ipdList := getIPv4Data(t, "")
+ err := d.CreateNetwork("net1", genericOption, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ sbOptions := make(map[string]interface{})
+ sbOptions[netlabel.PortMap] = getPortMapping()
+
+ te := newTestEndpoint(ipdList[0].Pool, 11)
+ err = d.CreateEndpoint("net1", "ep1", te.Interface(), nil)
+ if err != nil {
+ t.Fatalf("Failed to create an endpoint : %s", err.Error())
+ }
+
+ err = d.Join("net1", "ep1", "sbox", te, sbOptions)
+ if err != nil {
+ t.Fatalf("Failed to join the endpoint: %v", err)
+ }
+
+ err = d.ProgramExternalConnectivity("net1", "ep1", sbOptions)
+ if err != nil {
+ t.Fatalf("Failed to program external connectivity: %v", err)
+ }
+
+ network, ok := d.networks["net1"]
+ if !ok {
+ t.Fatalf("Cannot find network %s inside driver", "net1")
+ }
+ ep, _ := network.endpoints["ep1"]
+ data, err := d.EndpointOperInfo(network.id, ep.id)
+ if err != nil {
+ t.Fatalf("Failed to ask for endpoint operational data: %v", err)
+ }
+ pmd, ok := data[netlabel.PortMap]
+ if !ok {
+ t.Fatal("Endpoint operational data does not contain port mapping data")
+ }
+ pm, ok := pmd.([]types.PortBinding)
+ if !ok {
+ t.Fatal("Unexpected format for port mapping in endpoint operational data")
+ }
+ if len(ep.portMapping) != len(pm) {
+ t.Fatal("Incomplete data for port mapping in endpoint operational data")
+ }
+ for i, pb := range ep.portMapping {
+ if !pb.Equal(&pm[i]) {
+ t.Fatal("Unexpected data for port mapping in endpoint operational data")
+ }
+ }
+
+ err = d.RevokeExternalConnectivity("net1", "ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // release host mapped ports
+ err = d.Leave("net1", "ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func getExposedPorts() []types.TransportPort {
+ return []types.TransportPort{
+ {Proto: types.TCP, Port: uint16(5000)},
+ {Proto: types.UDP, Port: uint16(400)},
+ {Proto: types.TCP, Port: uint16(600)},
+ }
+}
+
+func getPortMapping() []types.PortBinding {
+ return []types.PortBinding{
+ {Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
+ {Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
+ {Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
+ }
+}
+
+func TestLinkContainers(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ d := newDriver()
+
+ config := &configuration{
+ EnableIPTables: true,
+ }
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ if err := d.configure(genericOption); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ netconfig := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ EnableICC: false,
+ }
+ genericOption = make(map[string]interface{})
+ genericOption[netlabel.GenericData] = netconfig
+
+ ipdList := getIPv4Data(t, "")
+ err := d.CreateNetwork("net1", genericOption, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ te1 := newTestEndpoint(ipdList[0].Pool, 11)
+ err = d.CreateEndpoint("net1", "ep1", te1.Interface(), nil)
+ if err != nil {
+ t.Fatalf("Failed to create an endpoint : %s", err.Error())
+ }
+
+ exposedPorts := getExposedPorts()
+ sbOptions := make(map[string]interface{})
+ sbOptions[netlabel.ExposedPorts] = exposedPorts
+
+ err = d.Join("net1", "ep1", "sbox", te1, sbOptions)
+ if err != nil {
+ t.Fatalf("Failed to join the endpoint: %v", err)
+ }
+
+ err = d.ProgramExternalConnectivity("net1", "ep1", sbOptions)
+ if err != nil {
+ t.Fatalf("Failed to program external connectivity: %v", err)
+ }
+
+ addr1 := te1.iface.addr
+ if addr1.IP.To4() == nil {
+ t.Fatal("No Ipv4 address assigned to the endpoint: ep1")
+ }
+
+ te2 := newTestEndpoint(ipdList[0].Pool, 22)
+ err = d.CreateEndpoint("net1", "ep2", te2.Interface(), nil)
+ if err != nil {
+ t.Fatalf("Failed to create an endpoint : %s", err.Error())
+ }
+
+ addr2 := te2.iface.addr
+ if addr2.IP.To4() == nil {
+ t.Fatal("No Ipv4 address assigned to the endpoint: ep2")
+ }
+
+ sbOptions = make(map[string]interface{})
+ sbOptions[netlabel.GenericData] = options.Generic{
+ "ChildEndpoints": []string{"ep1"},
+ }
+
+ err = d.Join("net1", "ep2", "", te2, sbOptions)
+ if err != nil {
+ t.Fatal("Failed to link ep1 and ep2")
+ }
+
+ err = d.ProgramExternalConnectivity("net1", "ep2", sbOptions)
+ if err != nil {
+ t.Fatalf("Failed to program external connectivity: %v", err)
+ }
+
+ out, err := iptables.Raw("-L", DockerChain)
+ for _, pm := range exposedPorts {
+ regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
+ re := regexp.MustCompile(regex)
+ matches := re.FindAllString(string(out[:]), -1)
+ if len(matches) != 1 {
+ t.Fatalf("IP Tables programming failed %s", string(out[:]))
+ }
+
+ regex = fmt.Sprintf("%s spt:%d", pm.Proto.String(), pm.Port)
+ matched, _ := regexp.MatchString(regex, string(out[:]))
+ if !matched {
+ t.Fatalf("IP Tables programming failed %s", string(out[:]))
+ }
+ }
+
+ err = d.RevokeExternalConnectivity("net1", "ep2")
+ if err != nil {
+ t.Fatalf("Failed to revoke external connectivity: %v", err)
+ }
+
+ err = d.Leave("net1", "ep2")
+ if err != nil {
+ t.Fatal("Failed to unlink ep1 and ep2")
+ }
+
+ out, err = iptables.Raw("-L", DockerChain)
+ for _, pm := range exposedPorts {
+ regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
+ re := regexp.MustCompile(regex)
+ matches := re.FindAllString(string(out[:]), -1)
+ if len(matches) != 0 {
+ t.Fatalf("Leave should have deleted relevant IPTables rules %s", string(out[:]))
+ }
+
+ regex = fmt.Sprintf("%s spt:%d", pm.Proto.String(), pm.Port)
+ matched, _ := regexp.MatchString(regex, string(out[:]))
+ if matched {
+ t.Fatalf("Leave should have deleted relevant IPTables rules %s", string(out[:]))
+ }
+ }
+
+ // Error condition test with an invalid endpoint-id "ep4"
+ sbOptions = make(map[string]interface{})
+ sbOptions[netlabel.GenericData] = options.Generic{
+ "ChildEndpoints": []string{"ep1", "ep4"},
+ }
+
+ err = d.Join("net1", "ep2", "", te2, sbOptions)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = d.ProgramExternalConnectivity("net1", "ep2", sbOptions)
+ if err != nil {
+ out, err = iptables.Raw("-L", DockerChain)
+ for _, pm := range exposedPorts {
+ regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
+ re := regexp.MustCompile(regex)
+ matches := re.FindAllString(string(out[:]), -1)
+ if len(matches) != 0 {
+ t.Fatalf("Error handling should rollback relevant IPTables rules %s", string(out[:]))
+ }
+
+ regex = fmt.Sprintf("%s spt:%d", pm.Proto.String(), pm.Port)
+ matched, _ := regexp.MatchString(regex, string(out[:]))
+ if matched {
+ t.Fatalf("Error handling should rollback relevant IPTables rules %s", string(out[:]))
+ }
+ }
+ } else {
+ t.Fatal("Expected Join to fail given link conditions are not satisfied")
+ }
+}
+
+func TestValidateConfig(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ // Test mtu
+ c := networkConfiguration{Mtu: -2}
+ err := c.Validate()
+ if err == nil {
+ t.Fatal("Failed to detect invalid MTU number")
+ }
+
+ c.Mtu = 9000
+ err = c.Validate()
+ if err != nil {
+ t.Fatal("unexpected validation error on MTU number")
+ }
+
+ // Bridge network
+ _, network, _ := net.ParseCIDR("172.28.0.0/16")
+ c = networkConfiguration{
+ AddressIPv4: network,
+ }
+
+ err = c.Validate()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Test v4 gw
+ c.DefaultGatewayIPv4 = net.ParseIP("172.27.30.234")
+ err = c.Validate()
+ if err == nil {
+ t.Fatal("Failed to detect invalid default gateway")
+ }
+
+ c.DefaultGatewayIPv4 = net.ParseIP("172.28.30.234")
+ err = c.Validate()
+ if err != nil {
+ t.Fatal("Unexpected validation error on default gateway")
+ }
+
+ // Test v6 gw
+ _, v6nw, _ := net.ParseCIDR("2001:db8:ae:b004::/64")
+ c = networkConfiguration{
+ EnableIPv6: true,
+ AddressIPv6: v6nw,
+ DefaultGatewayIPv6: net.ParseIP("2001:db8:ac:b004::bad:a55"),
+ }
+ err = c.Validate()
+ if err == nil {
+ t.Fatal("Failed to detect invalid v6 default gateway")
+ }
+
+ c.DefaultGatewayIPv6 = net.ParseIP("2001:db8:ae:b004::bad:a55")
+ err = c.Validate()
+ if err != nil {
+ t.Fatal("Unexpected validation error on v6 default gateway")
+ }
+
+ c.AddressIPv6 = nil
+ err = c.Validate()
+ if err == nil {
+ t.Fatal("Failed to detect invalid v6 default gateway")
+ }
+
+ c.AddressIPv6 = nil
+ err = c.Validate()
+ if err == nil {
+ t.Fatal("Failed to detect invalid v6 default gateway")
+ }
+}
+
+func TestSetDefaultGw(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ d := newDriver()
+
+ if err := d.configure(nil); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ _, subnetv6, _ := net.ParseCIDR("2001:db8:ea9:9abc:b0c4::/80")
+
+ ipdList := getIPv4Data(t, "")
+ gw4 := types.GetIPCopy(ipdList[0].Pool.IP).To4()
+ gw4[3] = 254
+ gw6 := net.ParseIP("2001:db8:ea9:9abc:b0c4::254")
+
+ config := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ AddressIPv6: subnetv6,
+ DefaultGatewayIPv4: gw4,
+ DefaultGatewayIPv6: gw6,
+ }
+
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.EnableIPv6] = true
+ genericOption[netlabel.GenericData] = config
+
+ err := d.CreateNetwork("dummy", genericOption, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ te := newTestEndpoint(ipdList[0].Pool, 10)
+ err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil)
+ if err != nil {
+ t.Fatalf("Failed to create endpoint: %v", err)
+ }
+
+ err = d.Join("dummy", "ep", "sbox", te, nil)
+ if err != nil {
+ t.Fatalf("Failed to join endpoint: %v", err)
+ }
+
+ if !gw4.Equal(te.gw) {
+ t.Fatalf("Failed to configure default gateway. Expected %v. Found %v", gw4, te.gw)
+ }
+
+ if !gw6.Equal(te.gw6) {
+ t.Fatalf("Failed to configure default gateway. Expected %v. Found %v", gw6, te.gw6)
+ }
+}
+
+func TestCleanupIptableRules(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+ bridgeChain := []iptables.ChainInfo{
+ {Name: DockerChain, Table: iptables.Nat},
+ {Name: DockerChain, Table: iptables.Filter},
+ {Name: IsolationChain1, Table: iptables.Filter},
+ }
+ if _, _, _, _, err := setupIPChains(&configuration{EnableIPTables: true}); err != nil {
+ t.Fatalf("Error setting up ip chains: %v", err)
+ }
+ for _, chainInfo := range bridgeChain {
+ if !iptables.ExistChain(chainInfo.Name, chainInfo.Table) {
+ t.Fatalf("iptables chain %s of %s table should have been created", chainInfo.Name, chainInfo.Table)
+ }
+ }
+ removeIPChains()
+ for _, chainInfo := range bridgeChain {
+ if iptables.ExistChain(chainInfo.Name, chainInfo.Table) {
+ t.Fatalf("iptables chain %s of %s table should have been deleted", chainInfo.Name, chainInfo.Table)
+ }
+ }
+}
+
+func TestCreateWithExistingBridge(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+ d := newDriver()
+
+ if err := d.configure(nil); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ brName := "br111"
+ br := &netlink.Bridge{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: brName,
+ },
+ }
+ if err := netlink.LinkAdd(br); err != nil {
+ t.Fatalf("Failed to create bridge interface: %v", err)
+ }
+ defer netlink.LinkDel(br)
+ if err := netlink.LinkSetUp(br); err != nil {
+ t.Fatalf("Failed to set bridge interface up: %v", err)
+ }
+
+ ip := net.IP{192, 168, 122, 1}
+ addr := &netlink.Addr{IPNet: &net.IPNet{
+ IP: ip,
+ Mask: net.IPv4Mask(255, 255, 255, 0),
+ }}
+ if err := netlink.AddrAdd(br, addr); err != nil {
+ t.Fatalf("Failed to add IP address to bridge: %v", err)
+ }
+
+ netconfig := &networkConfiguration{BridgeName: brName}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = netconfig
+
+ if err := d.CreateNetwork(brName, genericOption, nil, getIPv4Data(t, brName), nil); err != nil {
+ t.Fatalf("Failed to create bridge network: %v", err)
+ }
+
+ nw, err := d.getNetwork(brName)
+ if err != nil {
+ t.Fatalf("Failed to getNetwork(%s): %v", brName, err)
+ }
+
+ addrs4, _, err := nw.bridge.addresses()
+ if err != nil {
+ t.Fatalf("Failed to get the bridge network's address: %v", err)
+ }
+
+ if !addrs4[0].IP.Equal(ip) {
+ t.Fatal("Creating bridge network with existing bridge interface unexpectedly modified the IP address of the bridge")
+ }
+
+ if err := d.DeleteNetwork(brName); err != nil {
+ t.Fatalf("Failed to delete network %s: %v", brName, err)
+ }
+
+ if _, err := netlink.LinkByName(brName); err != nil {
+ t.Fatal("Deleting bridge network that using existing bridge interface unexpectedly deleted the bridge interface")
+ }
+}
+
+func TestCreateParallel(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ d := newDriver()
+
+ if err := d.configure(nil); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ ch := make(chan error, 100)
+ for i := 0; i < 100; i++ {
+ go func(name string, ch chan<- error) {
+ config := &networkConfiguration{BridgeName: name}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+ if err := d.CreateNetwork(name, genericOption, nil, getIPv4Data(t, "docker0"), nil); err != nil {
+ ch <- fmt.Errorf("failed to create %s", name)
+ return
+ }
+ if err := d.CreateNetwork(name, genericOption, nil, getIPv4Data(t, "docker0"), nil); err == nil {
+ ch <- fmt.Errorf("failed was able to create overlap %s", name)
+ return
+ }
+ ch <- nil
+ }("net"+strconv.Itoa(i), ch)
+ }
+ // wait for the go routines
+ var success int
+ for i := 0; i < 100; i++ {
+ val := <-ch
+ if val == nil {
+ success++
+ }
+ }
+ if success != 1 {
+ t.Fatalf("Success should be 1 instead: %d", success)
+ }
+}
--- /dev/null
+package brmanager
+
+import (
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/types"
+)
+
+const networkType = "bridge"
+
+type driver struct{}
+
+// Init registers a new instance of bridge manager driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ c := driverapi.Capability{
+ DataScope: datastore.LocalScope,
+ ConnectivityScope: datastore.LocalScope,
+ }
+ return dc.RegisterDriver(networkType, &driver{}, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Leave(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Type() string {
+ return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
--- /dev/null
+package bridge
+
+import (
+ "fmt"
+ "net"
+)
+
+// ErrConfigExists error is returned when driver already has a config applied.
+type ErrConfigExists struct{}
+
+func (ece *ErrConfigExists) Error() string {
+ return "configuration already exists, bridge configuration can be applied only once"
+}
+
+// Forbidden denotes the type of this error
+func (ece *ErrConfigExists) Forbidden() {}
+
+// ErrInvalidDriverConfig error is returned when Bridge Driver is passed an invalid config
+type ErrInvalidDriverConfig struct{}
+
+func (eidc *ErrInvalidDriverConfig) Error() string {
+ return "Invalid configuration passed to Bridge Driver"
+}
+
+// BadRequest denotes the type of this error
+func (eidc *ErrInvalidDriverConfig) BadRequest() {}
+
+// ErrInvalidNetworkConfig error is returned when a network is created on a driver without valid config.
+type ErrInvalidNetworkConfig struct{}
+
+func (einc *ErrInvalidNetworkConfig) Error() string {
+ return "trying to create a network on a driver without valid config"
+}
+
+// Forbidden denotes the type of this error
+func (einc *ErrInvalidNetworkConfig) Forbidden() {}
+
+// ErrInvalidContainerConfig error is returned when an endpoint create is attempted with an invalid configuration.
+type ErrInvalidContainerConfig struct{}
+
+func (eicc *ErrInvalidContainerConfig) Error() string {
+ return "Error in joining a container due to invalid configuration"
+}
+
+// BadRequest denotes the type of this error
+func (eicc *ErrInvalidContainerConfig) BadRequest() {}
+
+// ErrInvalidEndpointConfig error is returned when an endpoint create is attempted with an invalid endpoint configuration.
+type ErrInvalidEndpointConfig struct{}
+
+func (eiec *ErrInvalidEndpointConfig) Error() string {
+ return "trying to create an endpoint with an invalid endpoint configuration"
+}
+
+// BadRequest denotes the type of this error
+func (eiec *ErrInvalidEndpointConfig) BadRequest() {}
+
+// ErrNetworkExists error is returned when a network already exists and another network is created.
+type ErrNetworkExists struct{}
+
+func (ene *ErrNetworkExists) Error() string {
+ return "network already exists, bridge can only have one network"
+}
+
+// Forbidden denotes the type of this error
+func (ene *ErrNetworkExists) Forbidden() {}
+
+// ErrIfaceName error is returned when a new name could not be generated.
+type ErrIfaceName struct{}
+
+func (ein *ErrIfaceName) Error() string {
+ return "failed to find name for new interface"
+}
+
+// InternalError denotes the type of this error
+func (ein *ErrIfaceName) InternalError() {}
+
+// ErrNoIPAddr error is returned when bridge has no IPv4 address configured.
+type ErrNoIPAddr struct{}
+
+func (enip *ErrNoIPAddr) Error() string {
+ return "bridge has no IPv4 address configured"
+}
+
+// InternalError denotes the type of this error
+func (enip *ErrNoIPAddr) InternalError() {}
+
+// ErrInvalidGateway is returned when the user provided default gateway (v4/v6) is not not valid.
+type ErrInvalidGateway struct{}
+
+func (eig *ErrInvalidGateway) Error() string {
+ return "default gateway ip must be part of the network"
+}
+
+// BadRequest denotes the type of this error
+func (eig *ErrInvalidGateway) BadRequest() {}
+
+// ErrInvalidContainerSubnet is returned when the container subnet (FixedCIDR) is not valid.
+type ErrInvalidContainerSubnet struct{}
+
+func (eis *ErrInvalidContainerSubnet) Error() string {
+ return "container subnet must be a subset of bridge network"
+}
+
+// BadRequest denotes the type of this error
+func (eis *ErrInvalidContainerSubnet) BadRequest() {}
+
+// ErrInvalidMtu is returned when the user provided MTU is not valid.
+type ErrInvalidMtu int
+
+func (eim ErrInvalidMtu) Error() string {
+ return fmt.Sprintf("invalid MTU number: %d", int(eim))
+}
+
+// BadRequest denotes the type of this error
+func (eim ErrInvalidMtu) BadRequest() {}
+
+// ErrInvalidPort is returned when the container or host port specified in the port binding is not valid.
+type ErrInvalidPort string
+
+func (ip ErrInvalidPort) Error() string {
+ return fmt.Sprintf("invalid transport port: %s", string(ip))
+}
+
+// BadRequest denotes the type of this error
+func (ip ErrInvalidPort) BadRequest() {}
+
+// ErrUnsupportedAddressType is returned when the specified address type is not supported.
+type ErrUnsupportedAddressType string
+
+func (uat ErrUnsupportedAddressType) Error() string {
+ return fmt.Sprintf("unsupported address type: %s", string(uat))
+}
+
+// BadRequest denotes the type of this error
+func (uat ErrUnsupportedAddressType) BadRequest() {}
+
+// ErrInvalidAddressBinding is returned when the host address specified in the port binding is not valid.
+type ErrInvalidAddressBinding string
+
+func (iab ErrInvalidAddressBinding) Error() string {
+ return fmt.Sprintf("invalid host address in port binding: %s", string(iab))
+}
+
+// BadRequest denotes the type of this error
+func (iab ErrInvalidAddressBinding) BadRequest() {}
+
+// ActiveEndpointsError is returned when there are
+// still active endpoints in the network being deleted.
+type ActiveEndpointsError string
+
+func (aee ActiveEndpointsError) Error() string {
+ return fmt.Sprintf("network %s has active endpoint", string(aee))
+}
+
+// Forbidden denotes the type of this error
+func (aee ActiveEndpointsError) Forbidden() {}
+
+// InvalidNetworkIDError is returned when the passed
+// network id for an existing network is not a known id.
+type InvalidNetworkIDError string
+
+func (inie InvalidNetworkIDError) Error() string {
+ return fmt.Sprintf("invalid network id %s", string(inie))
+}
+
+// NotFound denotes the type of this error
+func (inie InvalidNetworkIDError) NotFound() {}
+
+// InvalidEndpointIDError is returned when the passed
+// endpoint id is not valid.
+type InvalidEndpointIDError string
+
+func (ieie InvalidEndpointIDError) Error() string {
+ return fmt.Sprintf("invalid endpoint id: %s", string(ieie))
+}
+
+// BadRequest denotes the type of this error
+func (ieie InvalidEndpointIDError) BadRequest() {}
+
+// InvalidSandboxIDError is returned when the passed
+// sandbox id is not valid.
+type InvalidSandboxIDError string
+
+func (isie InvalidSandboxIDError) Error() string {
+ return fmt.Sprintf("invalid sandbox id: %s", string(isie))
+}
+
+// BadRequest denotes the type of this error
+func (isie InvalidSandboxIDError) BadRequest() {}
+
+// EndpointNotFoundError is returned when the no endpoint
+// with the passed endpoint id is found.
+type EndpointNotFoundError string
+
+func (enfe EndpointNotFoundError) Error() string {
+ return fmt.Sprintf("endpoint not found: %s", string(enfe))
+}
+
+// NotFound denotes the type of this error
+func (enfe EndpointNotFoundError) NotFound() {}
+
+// NonDefaultBridgeExistError is returned when a non-default
+// bridge config is passed but it does not already exist.
+type NonDefaultBridgeExistError string
+
+func (ndbee NonDefaultBridgeExistError) Error() string {
+ return fmt.Sprintf("bridge device with non default name %s must be created manually", string(ndbee))
+}
+
+// Forbidden denotes the type of this error
+func (ndbee NonDefaultBridgeExistError) Forbidden() {}
+
+// NonDefaultBridgeNeedsIPError is returned when a non-default
+// bridge config is passed but it has no ip configured
+type NonDefaultBridgeNeedsIPError string
+
+func (ndbee NonDefaultBridgeNeedsIPError) Error() string {
+ return fmt.Sprintf("bridge device with non default name %s must have a valid IP address", string(ndbee))
+}
+
+// Forbidden denotes the type of this error
+func (ndbee NonDefaultBridgeNeedsIPError) Forbidden() {}
+
+// FixedCIDRv4Error is returned when fixed-cidrv4 configuration
+// failed.
+type FixedCIDRv4Error struct {
+ Net *net.IPNet
+ Subnet *net.IPNet
+ Err error
+}
+
+func (fcv4 *FixedCIDRv4Error) Error() string {
+ return fmt.Sprintf("setup FixedCIDRv4 failed for subnet %s in %s: %v", fcv4.Subnet, fcv4.Net, fcv4.Err)
+}
+
+// InternalError denotes the type of this error
+func (fcv4 *FixedCIDRv4Error) InternalError() {}
+
+// FixedCIDRv6Error is returned when fixed-cidrv6 configuration
+// failed.
+type FixedCIDRv6Error struct {
+ Net *net.IPNet
+ Err error
+}
+
+func (fcv6 *FixedCIDRv6Error) Error() string {
+ return fmt.Sprintf("setup FixedCIDRv6 failed for subnet %s in %s: %v", fcv6.Net, fcv6.Net, fcv6.Err)
+}
+
+// InternalError denotes the type of this error
+func (fcv6 *FixedCIDRv6Error) InternalError() {}
+
+// IPTableCfgError is returned when an unexpected ip tables configuration is entered
+type IPTableCfgError string
+
+func (name IPTableCfgError) Error() string {
+ return fmt.Sprintf("unexpected request to set IP tables for interface: %s", string(name))
+}
+
+// BadRequest denotes the type of this error
+func (name IPTableCfgError) BadRequest() {}
+
+// InvalidIPTablesCfgError is returned when an invalid ip tables configuration is entered
+type InvalidIPTablesCfgError string
+
+func (action InvalidIPTablesCfgError) Error() string {
+ return fmt.Sprintf("Invalid IPTables action '%s'", string(action))
+}
+
+// BadRequest denotes the type of this error
+func (action InvalidIPTablesCfgError) BadRequest() {}
+
+// IPv4AddrRangeError is returned when a valid IP address range couldn't be found.
+type IPv4AddrRangeError string
+
+func (name IPv4AddrRangeError) Error() string {
+ return fmt.Sprintf("can't find an address range for interface %q", string(name))
+}
+
+// BadRequest denotes the type of this error
+func (name IPv4AddrRangeError) BadRequest() {}
+
+// IPv4AddrAddError is returned when IPv4 address could not be added to the bridge.
+type IPv4AddrAddError struct {
+ IP *net.IPNet
+ Err error
+}
+
+func (ipv4 *IPv4AddrAddError) Error() string {
+ return fmt.Sprintf("failed to add IPv4 address %s to bridge: %v", ipv4.IP, ipv4.Err)
+}
+
+// InternalError denotes the type of this error
+func (ipv4 *IPv4AddrAddError) InternalError() {}
+
+// IPv6AddrAddError is returned when IPv6 address could not be added to the bridge.
+type IPv6AddrAddError struct {
+ IP *net.IPNet
+ Err error
+}
+
+func (ipv6 *IPv6AddrAddError) Error() string {
+ return fmt.Sprintf("failed to add IPv6 address %s to bridge: %v", ipv6.IP, ipv6.Err)
+}
+
+// InternalError denotes the type of this error
+func (ipv6 *IPv6AddrAddError) InternalError() {}
+
+// IPv4AddrNoMatchError is returned when the bridge's IPv4 address does not match configured.
+type IPv4AddrNoMatchError struct {
+ IP net.IP
+ CfgIP net.IP
+}
+
+func (ipv4 *IPv4AddrNoMatchError) Error() string {
+ return fmt.Sprintf("bridge IPv4 (%s) does not match requested configuration %s", ipv4.IP, ipv4.CfgIP)
+}
+
+// BadRequest denotes the type of this error
+func (ipv4 *IPv4AddrNoMatchError) BadRequest() {}
+
+// IPv6AddrNoMatchError is returned when the bridge's IPv6 address does not match configured.
+type IPv6AddrNoMatchError net.IPNet
+
+func (ipv6 *IPv6AddrNoMatchError) Error() string {
+ return fmt.Sprintf("bridge IPv6 addresses do not match the expected bridge configuration %s", (*net.IPNet)(ipv6).String())
+}
+
+// BadRequest denotes the type of this error
+func (ipv6 *IPv6AddrNoMatchError) BadRequest() {}
+
+// InvalidLinkIPAddrError is returned when a link is configured to a container with an invalid ip address
+type InvalidLinkIPAddrError string
+
+func (address InvalidLinkIPAddrError) Error() string {
+ return fmt.Sprintf("Cannot link to a container with Invalid IP Address '%s'", string(address))
+}
+
+// BadRequest denotes the type of this error
+func (address InvalidLinkIPAddrError) BadRequest() {}
--- /dev/null
+package bridge
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+const (
+ // DefaultBridgeName is the default name for the bridge interface managed
+ // by the driver when unspecified by the caller.
+ DefaultBridgeName = "docker0"
+)
+
+// Interface models the bridge network device.
+type bridgeInterface struct {
+ Link netlink.Link
+ bridgeIPv4 *net.IPNet
+ bridgeIPv6 *net.IPNet
+ gatewayIPv4 net.IP
+ gatewayIPv6 net.IP
+ nlh *netlink.Handle
+}
+
+// newInterface creates a new bridge interface structure. It attempts to find
+// an already existing device identified by the configuration BridgeName field,
+// or the default bridge name when unspecified, but doesn't attempt to create
+// one when missing
+func newInterface(nlh *netlink.Handle, config *networkConfiguration) (*bridgeInterface, error) {
+ var err error
+ i := &bridgeInterface{nlh: nlh}
+
+ // Initialize the bridge name to the default if unspecified.
+ if config.BridgeName == "" {
+ config.BridgeName = DefaultBridgeName
+ }
+
+ // Attempt to find an existing bridge named with the specified name.
+ i.Link, err = nlh.LinkByName(config.BridgeName)
+ if err != nil {
+ logrus.Debugf("Did not find any interface with name %s: %v", config.BridgeName, err)
+ } else if _, ok := i.Link.(*netlink.Bridge); !ok {
+ return nil, fmt.Errorf("existing interface %s is not a bridge", i.Link.Attrs().Name)
+ }
+ return i, nil
+}
+
+// exists indicates if the existing bridge interface exists on the system.
+func (i *bridgeInterface) exists() bool {
+ return i.Link != nil
+}
+
+// addresses returns all IPv4 addresses and all IPv6 addresses for the bridge interface.
+func (i *bridgeInterface) addresses() ([]netlink.Addr, []netlink.Addr, error) {
+ v4addr, err := i.nlh.AddrList(i.Link, netlink.FAMILY_V4)
+ if err != nil {
+ return nil, nil, fmt.Errorf("Failed to retrieve V4 addresses: %v", err)
+ }
+
+ v6addr, err := i.nlh.AddrList(i.Link, netlink.FAMILY_V6)
+ if err != nil {
+ return nil, nil, fmt.Errorf("Failed to retrieve V6 addresses: %v", err)
+ }
+
+ if len(v4addr) == 0 {
+ return nil, v6addr, nil
+ }
+ return v4addr, v6addr, nil
+}
+
+func (i *bridgeInterface) programIPv6Address() error {
+ _, nlAddressList, err := i.addresses()
+ if err != nil {
+ return &IPv6AddrAddError{IP: i.bridgeIPv6, Err: fmt.Errorf("failed to retrieve address list: %v", err)}
+ }
+ nlAddr := netlink.Addr{IPNet: i.bridgeIPv6}
+ if findIPv6Address(nlAddr, nlAddressList) {
+ return nil
+ }
+ if err := i.nlh.AddrAdd(i.Link, &nlAddr); err != nil {
+ return &IPv6AddrAddError{IP: i.bridgeIPv6, Err: err}
+ }
+ return nil
+}
--- /dev/null
+package bridge
+
+import (
+ "testing"
+
+ "github.com/docker/libnetwork/testutils"
+ "github.com/vishvananda/netlink"
+)
+
+func TestInterfaceDefaultName(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ config := &networkConfiguration{}
+ _, err = newInterface(nh, config)
+ if err != nil {
+ t.Fatalf("newInterface() failed: %v", err)
+ }
+
+ if config.BridgeName != DefaultBridgeName {
+ t.Fatalf("Expected default interface name %q, got %q", DefaultBridgeName, config.BridgeName)
+ }
+}
+
+func TestAddressesEmptyInterface(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ inf, err := newInterface(nh, &networkConfiguration{})
+ if err != nil {
+ t.Fatalf("newInterface() failed: %v", err)
+ }
+
+ addrsv4, addrsv6, err := inf.addresses()
+ if err != nil {
+ t.Fatalf("Failed to get addresses of default interface: %v", err)
+ }
+ if len(addrsv4) != 0 {
+ t.Fatalf("Default interface has unexpected IPv4: %s", addrsv4)
+ }
+ if len(addrsv6) != 0 {
+ t.Fatalf("Default interface has unexpected IPv6: %v", addrsv6)
+ }
+}
--- /dev/null
+package bridge
+
+const (
+ // BridgeName label for bridge driver
+ BridgeName = "com.docker.network.bridge.name"
+
+ // EnableIPMasquerade label for bridge driver
+ EnableIPMasquerade = "com.docker.network.bridge.enable_ip_masquerade"
+
+ // EnableICC label
+ EnableICC = "com.docker.network.bridge.enable_icc"
+
+ // DefaultBindingIP label
+ DefaultBindingIP = "com.docker.network.bridge.host_binding_ipv4"
+
+ // DefaultBridge label
+ DefaultBridge = "com.docker.network.bridge.default_bridge"
+)
--- /dev/null
+package bridge
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/iptables"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+type link struct {
+ parentIP string
+ childIP string
+ ports []types.TransportPort
+ bridge string
+}
+
+func (l *link) String() string {
+ return fmt.Sprintf("%s <-> %s [%v] on %s", l.parentIP, l.childIP, l.ports, l.bridge)
+}
+
+func newLink(parentIP, childIP string, ports []types.TransportPort, bridge string) *link {
+ return &link{
+ childIP: childIP,
+ parentIP: parentIP,
+ ports: ports,
+ bridge: bridge,
+ }
+
+}
+
+func (l *link) Enable() error {
+ // -A == iptables append flag
+ linkFunction := func() error {
+ return linkContainers("-A", l.parentIP, l.childIP, l.ports, l.bridge, false)
+ }
+
+ iptables.OnReloaded(func() { linkFunction() })
+ return linkFunction()
+}
+
+func (l *link) Disable() {
+ // -D == iptables delete flag
+ err := linkContainers("-D", l.parentIP, l.childIP, l.ports, l.bridge, true)
+ if err != nil {
+ logrus.Errorf("Error removing IPTables rules for a link %s due to %s", l.String(), err.Error())
+ }
+ // Return proper error once we move to use a proper iptables package
+ // that returns typed errors
+}
+
+func linkContainers(action, parentIP, childIP string, ports []types.TransportPort, bridge string,
+ ignoreErrors bool) error {
+ var nfAction iptables.Action
+
+ switch action {
+ case "-A":
+ nfAction = iptables.Append
+ case "-I":
+ nfAction = iptables.Insert
+ case "-D":
+ nfAction = iptables.Delete
+ default:
+ return InvalidIPTablesCfgError(action)
+ }
+
+ ip1 := net.ParseIP(parentIP)
+ if ip1 == nil {
+ return InvalidLinkIPAddrError(parentIP)
+ }
+ ip2 := net.ParseIP(childIP)
+ if ip2 == nil {
+ return InvalidLinkIPAddrError(childIP)
+ }
+
+ chain := iptables.ChainInfo{Name: DockerChain}
+ for _, port := range ports {
+ err := chain.Link(nfAction, ip1, ip2, int(port.Port), port.Proto.String(), bridge)
+ if !ignoreErrors && err != nil {
+ return err
+ }
+ }
+ return nil
+}
--- /dev/null
+package bridge
+
+import (
+ "testing"
+
+ "github.com/docker/libnetwork/types"
+)
+
+func getPorts() []types.TransportPort {
+ return []types.TransportPort{
+ {Proto: types.TCP, Port: uint16(5000)},
+ {Proto: types.UDP, Port: uint16(400)},
+ {Proto: types.TCP, Port: uint16(600)},
+ }
+}
+
+func TestLinkNew(t *testing.T) {
+ ports := getPorts()
+
+ link := newLink("172.0.17.3", "172.0.17.2", ports, "docker0")
+
+ if link == nil {
+ t.FailNow()
+ }
+ if link.parentIP != "172.0.17.3" {
+ t.Fail()
+ }
+ if link.childIP != "172.0.17.2" {
+ t.Fail()
+ }
+ for i, p := range link.ports {
+ if p != ports[i] {
+ t.Fail()
+ }
+ }
+ if link.bridge != "docker0" {
+ t.Fail()
+ }
+}
--- /dev/null
+package bridge
+
+import (
+ "fmt"
+ "math/rand"
+ "net"
+ "syscall"
+ "time"
+ "unsafe"
+
+ "github.com/docker/libnetwork/netutils"
+)
+
+const (
+ ifNameSize = 16
+ ioctlBrAdd = 0x89a0
+ ioctlBrAddIf = 0x89a2
+)
+
+type ifreqIndex struct {
+ IfrnName [ifNameSize]byte
+ IfruIndex int32
+}
+
+type ifreqHwaddr struct {
+ IfrnName [ifNameSize]byte
+ IfruHwaddr syscall.RawSockaddr
+}
+
+var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
+
+// THIS CODE DOES NOT COMMUNICATE WITH KERNEL VIA RTNETLINK INTERFACE
+// IT IS HERE FOR BACKWARDS COMPATIBILITY WITH OLDER LINUX KERNELS
+// WHICH SHIP WITH OLDER NOT ENTIRELY FUNCTIONAL VERSION OF NETLINK
+func getIfSocket() (fd int, err error) {
+ for _, socket := range []int{
+ syscall.AF_INET,
+ syscall.AF_PACKET,
+ syscall.AF_INET6,
+ } {
+ if fd, err = syscall.Socket(socket, syscall.SOCK_DGRAM, 0); err == nil {
+ break
+ }
+ }
+ if err == nil {
+ return fd, nil
+ }
+ return -1, err
+}
+
+func ifIoctBridge(iface, master *net.Interface, op uintptr) error {
+ if len(master.Name) >= ifNameSize {
+ return fmt.Errorf("Interface name %s too long", master.Name)
+ }
+
+ s, err := getIfSocket()
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(s)
+
+ ifr := ifreqIndex{}
+ copy(ifr.IfrnName[:len(ifr.IfrnName)-1], master.Name)
+ ifr.IfruIndex = int32(iface.Index)
+
+ if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), op, uintptr(unsafe.Pointer(&ifr))); err != 0 {
+ return err
+ }
+
+ return nil
+}
+
+// Add a slave to a bridge device. This is more backward-compatible than
+// netlink.NetworkSetMaster and works on RHEL 6.
+func ioctlAddToBridge(iface, master *net.Interface) error {
+ return ifIoctBridge(iface, master, ioctlBrAddIf)
+}
+
+func ioctlSetMacAddress(name, addr string) error {
+ if len(name) >= ifNameSize {
+ return fmt.Errorf("Interface name %s too long", name)
+ }
+
+ hw, err := net.ParseMAC(addr)
+ if err != nil {
+ return err
+ }
+
+ s, err := getIfSocket()
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(s)
+
+ ifr := ifreqHwaddr{}
+ ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER
+ copy(ifr.IfrnName[:len(ifr.IfrnName)-1], name)
+
+ for i := 0; i < 6; i++ {
+ ifr.IfruHwaddr.Data[i] = ifrDataByte(hw[i])
+ }
+
+ if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 {
+ return err
+ }
+ return nil
+}
+
+func ioctlCreateBridge(name string, setMacAddr bool) error {
+ if len(name) >= ifNameSize {
+ return fmt.Errorf("Interface name %s too long", name)
+ }
+
+ s, err := getIfSocket()
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(s)
+
+ nameBytePtr, err := syscall.BytePtrFromString(name)
+ if err != nil {
+ return err
+ }
+ if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), ioctlBrAdd, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 {
+ return err
+ }
+ if setMacAddr {
+ return ioctlSetMacAddress(name, netutils.GenerateRandomMAC().String())
+ }
+ return nil
+}
--- /dev/null
+// +build arm ppc64 ppc64le
+
+package bridge
+
+func ifrDataByte(b byte) uint8 {
+ return uint8(b)
+}
--- /dev/null
+// +build !arm,!ppc64,!ppc64le
+
+package bridge
+
+func ifrDataByte(b byte) int8 {
+ return int8(b)
+}
--- /dev/null
+// +build !linux
+
+package bridge
+
+import (
+ "errors"
+ "net"
+)
+
+// Add a slave to a bridge device. This is more backward-compatible than
+// netlink.NetworkSetMaster and works on RHEL 6.
+func ioctlAddToBridge(iface, master *net.Interface) error {
+ return errors.New("not implemented")
+}
+
+func ioctlCreateBridge(name string, setMacAddr bool) error {
+ return errors.New("not implemented")
+}
--- /dev/null
+package bridge
+
+import (
+ "testing"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/vishvananda/netlink"
+)
+
+func TestLinkCreate(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+ d := newDriver()
+
+ if err := d.configure(nil); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ mtu := 1490
+ config := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ Mtu: mtu,
+ EnableIPv6: true,
+ }
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ ipdList := getIPv4Data(t, "")
+ err := d.CreateNetwork("dummy", genericOption, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ te := newTestEndpoint(ipdList[0].Pool, 10)
+ err = d.CreateEndpoint("dummy", "", te.Interface(), nil)
+ if err != nil {
+ if _, ok := err.(InvalidEndpointIDError); !ok {
+ t.Fatalf("Failed with a wrong error :%s", err.Error())
+ }
+ } else {
+ t.Fatal("Failed to detect invalid config")
+ }
+
+ // Good endpoint creation
+ err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil)
+ if err != nil {
+ t.Fatalf("Failed to create a link: %s", err.Error())
+ }
+
+ err = d.Join("dummy", "ep", "sbox", te, nil)
+ if err != nil {
+ t.Fatalf("Failed to create a link: %s", err.Error())
+ }
+
+ // Verify sbox endpoint interface inherited MTU value from bridge config
+ sboxLnk, err := netlink.LinkByName(te.iface.srcName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if mtu != sboxLnk.Attrs().MTU {
+ t.Fatal("Sandbox endpoint interface did not inherit bridge interface MTU config")
+ }
+ // TODO: if we could get peer name from (sboxLnk.(*netlink.Veth)).PeerName
+ // then we could check the MTU on hostLnk as well.
+
+ te1 := newTestEndpoint(ipdList[0].Pool, 11)
+ err = d.CreateEndpoint("dummy", "ep", te1.Interface(), nil)
+ if err == nil {
+ t.Fatal("Failed to detect duplicate endpoint id on same network")
+ }
+
+ if te.iface.dstName == "" {
+ t.Fatal("Invalid Dstname returned")
+ }
+
+ _, err = netlink.LinkByName(te.iface.srcName)
+ if err != nil {
+ t.Fatalf("Could not find source link %s: %v", te.iface.srcName, err)
+ }
+
+ n, ok := d.networks["dummy"]
+ if !ok {
+ t.Fatalf("Cannot find network %s inside driver", "dummy")
+ }
+ ip := te.iface.addr.IP
+ if !n.bridge.bridgeIPv4.Contains(ip) {
+ t.Fatalf("IP %s is not a valid ip in the subnet %s", ip.String(), n.bridge.bridgeIPv4.String())
+ }
+
+ ip6 := te.iface.addrv6.IP
+ if !n.bridge.bridgeIPv6.Contains(ip6) {
+ t.Fatalf("IP %s is not a valid ip in the subnet %s", ip6.String(), bridgeIPv6.String())
+ }
+
+ if !te.gw.Equal(n.bridge.bridgeIPv4.IP) {
+ t.Fatalf("Invalid default gateway. Expected %s. Got %s", n.bridge.bridgeIPv4.IP.String(),
+ te.gw.String())
+ }
+
+ if !te.gw6.Equal(n.bridge.bridgeIPv6.IP) {
+ t.Fatalf("Invalid default gateway for IPv6. Expected %s. Got %s", n.bridge.bridgeIPv6.IP.String(),
+ te.gw6.String())
+ }
+}
+
+func TestLinkCreateTwo(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+ d := newDriver()
+
+ if err := d.configure(nil); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ config := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ EnableIPv6: true}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ ipdList := getIPv4Data(t, "")
+ err := d.CreateNetwork("dummy", genericOption, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ te1 := newTestEndpoint(ipdList[0].Pool, 11)
+ err = d.CreateEndpoint("dummy", "ep", te1.Interface(), nil)
+ if err != nil {
+ t.Fatalf("Failed to create a link: %s", err.Error())
+ }
+
+ te2 := newTestEndpoint(ipdList[0].Pool, 12)
+ err = d.CreateEndpoint("dummy", "ep", te2.Interface(), nil)
+ if err != nil {
+ if _, ok := err.(driverapi.ErrEndpointExists); !ok {
+ t.Fatalf("Failed with a wrong error: %s", err.Error())
+ }
+ } else {
+ t.Fatal("Expected to fail while trying to add same endpoint twice")
+ }
+}
+
+func TestLinkCreateNoEnableIPv6(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+ d := newDriver()
+
+ if err := d.configure(nil); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ config := &networkConfiguration{
+ BridgeName: DefaultBridgeName}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ ipdList := getIPv4Data(t, "")
+ err := d.CreateNetwork("dummy", genericOption, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+ te := newTestEndpoint(ipdList[0].Pool, 30)
+ err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil)
+ if err != nil {
+ t.Fatalf("Failed to create a link: %s", err.Error())
+ }
+
+ iface := te.iface
+ if iface.addrv6 != nil && iface.addrv6.IP.To16() != nil {
+ t.Fatalf("Expected IPv6 address to be nil when IPv6 is not enabled. Got IPv6 = %s", iface.addrv6.String())
+ }
+
+ if te.gw6.To16() != nil {
+ t.Fatalf("Expected GatewayIPv6 to be nil when IPv6 is not enabled. Got GatewayIPv6 = %s", te.gw6.String())
+ }
+}
+
+func TestLinkDelete(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+ d := newDriver()
+
+ if err := d.configure(nil); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ config := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ EnableIPv6: true}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ ipdList := getIPv4Data(t, "")
+ err := d.CreateNetwork("dummy", genericOption, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ te := newTestEndpoint(ipdList[0].Pool, 30)
+ err = d.CreateEndpoint("dummy", "ep1", te.Interface(), nil)
+ if err != nil {
+ t.Fatalf("Failed to create a link: %s", err.Error())
+ }
+
+ err = d.DeleteEndpoint("dummy", "")
+ if err != nil {
+ if _, ok := err.(InvalidEndpointIDError); !ok {
+ t.Fatalf("Failed with a wrong error :%s", err.Error())
+ }
+ } else {
+ t.Fatal("Failed to detect invalid config")
+ }
+
+ err = d.DeleteEndpoint("dummy", "ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
--- /dev/null
+package bridge
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/types"
+ "github.com/ishidawataru/sctp"
+ "github.com/sirupsen/logrus"
+)
+
+var (
+ defaultBindingIP = net.IPv4(0, 0, 0, 0)
+)
+
+func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
+ if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil {
+ return nil, nil
+ }
+
+ defHostIP := defaultBindingIP
+ if reqDefBindIP != nil {
+ defHostIP = reqDefBindIP
+ }
+
+ return n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled)
+}
+
+func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
+ bs := make([]types.PortBinding, 0, len(bindings))
+ for _, c := range bindings {
+ b := c.GetCopy()
+ if err := n.allocatePort(&b, containerIP, defHostIP, ulPxyEnabled); err != nil {
+ // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
+ if cuErr := n.releasePortsInternal(bs); cuErr != nil {
+ logrus.Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr)
+ }
+ return nil, err
+ }
+ bs = append(bs, b)
+ }
+ return bs, nil
+}
+
+func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) error {
+ var (
+ host net.Addr
+ err error
+ )
+
+ // Store the container interface address in the operational binding
+ bnd.IP = containerIP
+
+ // Adjust the host address in the operational binding
+ if len(bnd.HostIP) == 0 {
+ bnd.HostIP = defHostIP
+ }
+
+ // Adjust HostPortEnd if this is not a range.
+ if bnd.HostPortEnd == 0 {
+ bnd.HostPortEnd = bnd.HostPort
+ }
+
+ // Construct the container side transport address
+ container, err := bnd.ContainerAddr()
+ if err != nil {
+ return err
+ }
+
+ // Try up to maxAllocatePortAttempts times to get a port that's not already allocated.
+ for i := 0; i < maxAllocatePortAttempts; i++ {
+ if host, err = n.portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), ulPxyEnabled); err == nil {
+ break
+ }
+ // There is no point in immediately retrying to map an explicitly chosen port.
+ if bnd.HostPort != 0 {
+ logrus.Warnf("Failed to allocate and map port %d-%d: %s", bnd.HostPort, bnd.HostPortEnd, err)
+ break
+ }
+ logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
+ }
+ if err != nil {
+ return err
+ }
+
+ // Save the host port (regardless it was or not specified in the binding)
+ switch netAddr := host.(type) {
+ case *net.TCPAddr:
+ bnd.HostPort = uint16(host.(*net.TCPAddr).Port)
+ return nil
+ case *net.UDPAddr:
+ bnd.HostPort = uint16(host.(*net.UDPAddr).Port)
+ return nil
+ case *sctp.SCTPAddr:
+ bnd.HostPort = uint16(host.(*sctp.SCTPAddr).Port)
+ return nil
+ default:
+ // For completeness
+ return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
+ }
+}
+
+func (n *bridgeNetwork) releasePorts(ep *bridgeEndpoint) error {
+ return n.releasePortsInternal(ep.portMapping)
+}
+
+func (n *bridgeNetwork) releasePortsInternal(bindings []types.PortBinding) error {
+ var errorBuf bytes.Buffer
+
+ // Attempt to release all port bindings, do not stop on failure
+ for _, m := range bindings {
+ if err := n.releasePort(m); err != nil {
+ errorBuf.WriteString(fmt.Sprintf("\ncould not release %v because of %v", m, err))
+ }
+ }
+
+ if errorBuf.Len() != 0 {
+ return errors.New(errorBuf.String())
+ }
+ return nil
+}
+
+func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error {
+ // Construct the host side transport address
+ host, err := bnd.HostAddr()
+ if err != nil {
+ return err
+ }
+ return n.portMapper.Unmap(host)
+}
--- /dev/null
+package bridge
+
+import (
+ "os"
+ "testing"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+func TestMain(m *testing.M) {
+ if reexec.Init() {
+ return
+ }
+ os.Exit(m.Run())
+}
+
+func TestPortMappingConfig(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+ d := newDriver()
+
+ config := &configuration{
+ EnableIPTables: true,
+ }
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = config
+
+ if err := d.configure(genericOption); err != nil {
+ t.Fatalf("Failed to setup driver config: %v", err)
+ }
+
+ binding1 := types.PortBinding{Proto: types.UDP, Port: uint16(400), HostPort: uint16(54000)}
+ binding2 := types.PortBinding{Proto: types.TCP, Port: uint16(500), HostPort: uint16(65000)}
+ binding3 := types.PortBinding{Proto: types.SCTP, Port: uint16(300), HostPort: uint16(65000)}
+ portBindings := []types.PortBinding{binding1, binding2, binding3}
+
+ sbOptions := make(map[string]interface{})
+ sbOptions[netlabel.PortMap] = portBindings
+
+ netConfig := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ }
+ netOptions := make(map[string]interface{})
+ netOptions[netlabel.GenericData] = netConfig
+
+ ipdList := getIPv4Data(t, "")
+ err := d.CreateNetwork("dummy", netOptions, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+
+ te := newTestEndpoint(ipdList[0].Pool, 11)
+ err = d.CreateEndpoint("dummy", "ep1", te.Interface(), nil)
+ if err != nil {
+ t.Fatalf("Failed to create the endpoint: %s", err.Error())
+ }
+
+ if err = d.Join("dummy", "ep1", "sbox", te, sbOptions); err != nil {
+ t.Fatalf("Failed to join the endpoint: %v", err)
+ }
+
+ if err = d.ProgramExternalConnectivity("dummy", "ep1", sbOptions); err != nil {
+ t.Fatalf("Failed to program external connectivity: %v", err)
+ }
+
+ network, ok := d.networks["dummy"]
+ if !ok {
+ t.Fatalf("Cannot find network %s inside driver", "dummy")
+ }
+ ep, _ := network.endpoints["ep1"]
+ if len(ep.portMapping) != 3 {
+ t.Fatalf("Failed to store the port bindings into the sandbox info. Found: %v", ep.portMapping)
+ }
+ if ep.portMapping[0].Proto != binding1.Proto || ep.portMapping[0].Port != binding1.Port ||
+ ep.portMapping[1].Proto != binding2.Proto || ep.portMapping[1].Port != binding2.Port ||
+ ep.portMapping[2].Proto != binding3.Proto || ep.portMapping[2].Port != binding3.Port {
+ t.Fatal("bridgeEndpoint has incorrect port mapping values")
+ }
+ if ep.portMapping[0].HostIP == nil || ep.portMapping[0].HostPort == 0 ||
+ ep.portMapping[1].HostIP == nil || ep.portMapping[1].HostPort == 0 ||
+ ep.portMapping[2].HostIP == nil || ep.portMapping[2].HostPort == 0 {
+ t.Fatal("operational port mapping data not found on bridgeEndpoint")
+ }
+
+ // release host mapped ports
+ err = d.Leave("dummy", "ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = d.RevokeExternalConnectivity("dummy", "ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
--- /dev/null
+package bridge
+
+type setupStep func(*networkConfiguration, *bridgeInterface) error
+
+type bridgeSetup struct {
+ config *networkConfiguration
+ bridge *bridgeInterface
+ steps []setupStep
+}
+
+func newBridgeSetup(c *networkConfiguration, i *bridgeInterface) *bridgeSetup {
+ return &bridgeSetup{config: c, bridge: i}
+}
+
+func (b *bridgeSetup) apply() error {
+ for _, fn := range b.steps {
+ if err := fn(b.config, b.bridge); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (b *bridgeSetup) queueStep(step setupStep) {
+ b.steps = append(b.steps, step)
+}
--- /dev/null
+package bridge
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "syscall"
+
+ "github.com/sirupsen/logrus"
+)
+
+// Enumeration type saying which versions of IP protocol to process.
+type ipVersion int
+
+const (
+ ipvnone ipVersion = iota
+ ipv4
+ ipv6
+ ipvboth
+)
+
+//Gets the IP version in use ( [ipv4], [ipv6] or [ipv4 and ipv6] )
+func getIPVersion(config *networkConfiguration) ipVersion {
+ ipVersion := ipv4
+ if config.AddressIPv6 != nil || config.EnableIPv6 {
+ ipVersion |= ipv6
+ }
+ return ipVersion
+}
+
+func setupBridgeNetFiltering(config *networkConfiguration, i *bridgeInterface) error {
+ err := checkBridgeNetFiltering(config, i)
+ if err != nil {
+ if ptherr, ok := err.(*os.PathError); ok {
+ if errno, ok := ptherr.Err.(syscall.Errno); ok && errno == syscall.ENOENT {
+ if isRunningInContainer() {
+ logrus.Warnf("running inside docker container, ignoring missing kernel params: %v", err)
+ err = nil
+ } else {
+ err = errors.New("please ensure that br_netfilter kernel module is loaded")
+ }
+ }
+ }
+ if err != nil {
+ return fmt.Errorf("cannot restrict inter-container communication: %v", err)
+ }
+ }
+ return nil
+}
+
+//Enable bridge net filtering if ip forwarding is enabled. See github issue #11404
+func checkBridgeNetFiltering(config *networkConfiguration, i *bridgeInterface) error {
+ ipVer := getIPVersion(config)
+ iface := config.BridgeName
+ doEnable := func(ipVer ipVersion) error {
+ var ipVerName string
+ if ipVer == ipv4 {
+ ipVerName = "IPv4"
+ } else {
+ ipVerName = "IPv6"
+ }
+ enabled, err := isPacketForwardingEnabled(ipVer, iface)
+ if err != nil {
+ logrus.Warnf("failed to check %s forwarding: %v", ipVerName, err)
+ } else if enabled {
+ enabled, err := getKernelBoolParam(getBridgeNFKernelParam(ipVer))
+ if err != nil || enabled {
+ return err
+ }
+ return setKernelBoolParam(getBridgeNFKernelParam(ipVer), true)
+ }
+ return nil
+ }
+
+ switch ipVer {
+ case ipv4, ipv6:
+ return doEnable(ipVer)
+ case ipvboth:
+ v4err := doEnable(ipv4)
+ v6err := doEnable(ipv6)
+ if v4err == nil {
+ return v6err
+ }
+ return v4err
+ default:
+ return nil
+ }
+}
+
+// Get kernel param path saying whether IPv${ipVer} traffic is being forwarded
+// on particular interface. Interface may be specified for IPv6 only. If
+// `iface` is empty, `default` will be assumed, which represents default value
+// for new interfaces.
+func getForwardingKernelParam(ipVer ipVersion, iface string) string {
+ switch ipVer {
+ case ipv4:
+ return "/proc/sys/net/ipv4/ip_forward"
+ case ipv6:
+ if iface == "" {
+ iface = "default"
+ }
+ return fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/forwarding", iface)
+ default:
+ return ""
+ }
+}
+
+// Get kernel param path saying whether bridged IPv${ipVer} traffic shall be
+// passed to ip${ipVer}tables' chains.
+func getBridgeNFKernelParam(ipVer ipVersion) string {
+ switch ipVer {
+ case ipv4:
+ return "/proc/sys/net/bridge/bridge-nf-call-iptables"
+ case ipv6:
+ return "/proc/sys/net/bridge/bridge-nf-call-ip6tables"
+ default:
+ return ""
+ }
+}
+
+//Gets the value of the kernel parameters located at the given path
+func getKernelBoolParam(path string) (bool, error) {
+ enabled := false
+ line, err := ioutil.ReadFile(path)
+ if err != nil {
+ return false, err
+ }
+ if len(line) > 0 {
+ enabled = line[0] == '1'
+ }
+ return enabled, err
+}
+
+//Sets the value of the kernel parameter located at the given path
+func setKernelBoolParam(path string, on bool) error {
+ value := byte('0')
+ if on {
+ value = byte('1')
+ }
+ return ioutil.WriteFile(path, []byte{value, '\n'}, 0644)
+}
+
+//Checks to see if packet forwarding is enabled
+func isPacketForwardingEnabled(ipVer ipVersion, iface string) (bool, error) {
+ switch ipVer {
+ case ipv4, ipv6:
+ return getKernelBoolParam(getForwardingKernelParam(ipVer, iface))
+ case ipvboth:
+ enabled, err := getKernelBoolParam(getForwardingKernelParam(ipv4, ""))
+ if err != nil || !enabled {
+ return enabled, err
+ }
+ return getKernelBoolParam(getForwardingKernelParam(ipv6, iface))
+ default:
+ return true, nil
+ }
+}
+
+func isRunningInContainer() bool {
+ _, err := os.Stat("/.dockerenv")
+ return !os.IsNotExist(err)
+}
--- /dev/null
+package bridge
+
+import "testing"
+
+func TestIPConstantValues(t *testing.T) {
+ if ipv4|ipv6 != ipvboth {
+ t.Fatalf("bitwise or of ipv4(%04b) and ipv6(%04b) must yield ipvboth(%04b)", ipv4, ipv6, ipvboth)
+ }
+ if ipvboth&(^(ipv4 | ipv6)) != ipvnone {
+ t.Fatalf("ipvboth(%04b) with unset ipv4(%04b) and ipv6(%04b) bits shall equal to ipvnone", ipvboth, ipv4, ipv6)
+ }
+}
--- /dev/null
+package bridge
+
+import (
+ "fmt"
+
+ "github.com/docker/docker/pkg/parsers/kernel"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+// SetupDevice create a new bridge interface/
+func setupDevice(config *networkConfiguration, i *bridgeInterface) error {
+ var setMac bool
+
+ // We only attempt to create the bridge when the requested device name is
+ // the default one.
+ if config.BridgeName != DefaultBridgeName && config.DefaultBridge {
+ return NonDefaultBridgeExistError(config.BridgeName)
+ }
+
+ // Set the bridgeInterface netlink.Bridge.
+ i.Link = &netlink.Bridge{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: config.BridgeName,
+ },
+ }
+
+ // Only set the bridge's MAC address if the kernel version is > 3.3, as it
+ // was not supported before that.
+ kv, err := kernel.GetKernelVersion()
+ if err != nil {
+ logrus.Errorf("Failed to check kernel versions: %v. Will not assign a MAC address to the bridge interface", err)
+ } else {
+ setMac = kv.Kernel > 3 || (kv.Kernel == 3 && kv.Major >= 3)
+ }
+
+ if err = i.nlh.LinkAdd(i.Link); err != nil {
+ logrus.Debugf("Failed to create bridge %s via netlink. Trying ioctl", config.BridgeName)
+ return ioctlCreateBridge(config.BridgeName, setMac)
+ }
+
+ if setMac {
+ hwAddr := netutils.GenerateRandomMAC()
+ if err = i.nlh.LinkSetHardwareAddr(i.Link, hwAddr); err != nil {
+ return fmt.Errorf("failed to set bridge mac-address %s : %s", hwAddr, err.Error())
+ }
+ logrus.Debugf("Setting bridge mac address to %s", hwAddr)
+ }
+ return err
+}
+
+// SetupDeviceUp ups the given bridge interface.
+func setupDeviceUp(config *networkConfiguration, i *bridgeInterface) error {
+ err := i.nlh.LinkSetUp(i.Link)
+ if err != nil {
+ return fmt.Errorf("Failed to set link up for %s: %v", config.BridgeName, err)
+ }
+
+ // Attempt to update the bridge interface to refresh the flags status,
+ // ignoring any failure to do so.
+ if lnk, err := i.nlh.LinkByName(config.BridgeName); err == nil {
+ i.Link = lnk
+ } else {
+ logrus.Warnf("Failed to retrieve link for interface (%s): %v", config.BridgeName, err)
+ }
+ return nil
+}
--- /dev/null
+package bridge
+
+import (
+ "bytes"
+ "net"
+ "testing"
+
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/vishvananda/netlink"
+)
+
+func TestSetupNewBridge(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ config := &networkConfiguration{BridgeName: DefaultBridgeName}
+ br := &bridgeInterface{nlh: nh}
+
+ if err := setupDevice(config, br); err != nil {
+ t.Fatalf("Bridge creation failed: %v", err)
+ }
+ if br.Link == nil {
+ t.Fatal("bridgeInterface link is nil (expected valid link)")
+ }
+ if _, err := nh.LinkByName(DefaultBridgeName); err != nil {
+ t.Fatalf("Failed to retrieve bridge device: %v", err)
+ }
+ if br.Link.Attrs().Flags&net.FlagUp == net.FlagUp {
+ t.Fatal("bridgeInterface should be created down")
+ }
+}
+
+func TestSetupNewNonDefaultBridge(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ config := &networkConfiguration{BridgeName: "test0", DefaultBridge: true}
+ br := &bridgeInterface{nlh: nh}
+
+ err = setupDevice(config, br)
+ if err == nil {
+ t.Fatal("Expected bridge creation failure with \"non default name\", succeeded")
+ }
+
+ if _, ok := err.(NonDefaultBridgeExistError); !ok {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+}
+
+func TestSetupDeviceUp(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ config := &networkConfiguration{BridgeName: DefaultBridgeName}
+ br := &bridgeInterface{nlh: nh}
+
+ if err := setupDevice(config, br); err != nil {
+ t.Fatalf("Bridge creation failed: %v", err)
+ }
+ if err := setupDeviceUp(config, br); err != nil {
+ t.Fatalf("Failed to up bridge device: %v", err)
+ }
+
+ lnk, _ := nh.LinkByName(DefaultBridgeName)
+ if lnk.Attrs().Flags&net.FlagUp != net.FlagUp {
+ t.Fatal("bridgeInterface should be up")
+ }
+}
+
+func TestGenerateRandomMAC(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ mac1 := netutils.GenerateRandomMAC()
+ mac2 := netutils.GenerateRandomMAC()
+ if bytes.Compare(mac1, mac2) == 0 {
+ t.Fatalf("Generated twice the same MAC address %v", mac1)
+ }
+}
--- /dev/null
+package bridge
+
+import "github.com/docker/libnetwork/iptables"
+
+func (n *bridgeNetwork) setupFirewalld(config *networkConfiguration, i *bridgeInterface) error {
+ d := n.driver
+ d.Lock()
+ driverConfig := d.config
+ d.Unlock()
+
+ // Sanity check.
+ if !driverConfig.EnableIPTables {
+ return IPTableCfgError(config.BridgeName)
+ }
+
+ iptables.OnReloaded(func() { n.setupIPTables(config, i) })
+ iptables.OnReloaded(n.portMapper.ReMapAll)
+
+ return nil
+}
--- /dev/null
+package bridge
+
+import (
+ "fmt"
+ "io/ioutil"
+
+ "github.com/docker/libnetwork/iptables"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ ipv4ForwardConf = "/proc/sys/net/ipv4/ip_forward"
+ ipv4ForwardConfPerm = 0644
+)
+
+func configureIPForwarding(enable bool) error {
+ var val byte
+ if enable {
+ val = '1'
+ }
+ return ioutil.WriteFile(ipv4ForwardConf, []byte{val, '\n'}, ipv4ForwardConfPerm)
+}
+
+func setupIPForwarding(enableIPTables bool) error {
+ // Get current IPv4 forward setup
+ ipv4ForwardData, err := ioutil.ReadFile(ipv4ForwardConf)
+ if err != nil {
+ return fmt.Errorf("Cannot read IP forwarding setup: %v", err)
+ }
+
+ // Enable IPv4 forwarding only if it is not already enabled
+ if ipv4ForwardData[0] != '1' {
+ // Enable IPv4 forwarding
+ if err := configureIPForwarding(true); err != nil {
+ return fmt.Errorf("Enabling IP forwarding failed: %v", err)
+ }
+ // When enabling ip_forward set the default policy on forward chain to
+ // drop only if the daemon option iptables is not set to false.
+ if !enableIPTables {
+ return nil
+ }
+ if err := iptables.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil {
+ if err := configureIPForwarding(false); err != nil {
+ logrus.Errorf("Disabling IP forwarding failed, %v", err)
+ }
+ return err
+ }
+ iptables.OnReloaded(func() {
+ logrus.Debug("Setting the default DROP policy on firewall reload")
+ if err := iptables.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil {
+ logrus.Warnf("Settig the default DROP policy on firewall reload failed, %v", err)
+ }
+ })
+ }
+ return nil
+}
--- /dev/null
+package bridge
+
+import (
+ "bytes"
+ "io/ioutil"
+ "testing"
+)
+
+func TestSetupIPForwarding(t *testing.T) {
+ // Read current setting and ensure the original value gets restored
+ procSetting := readCurrentIPForwardingSetting(t)
+ defer reconcileIPForwardingSetting(t, procSetting)
+
+ // Disable IP Forwarding if enabled
+ if bytes.Compare(procSetting, []byte("1\n")) == 0 {
+ writeIPForwardingSetting(t, []byte{'0', '\n'})
+ }
+
+ // Set IP Forwarding
+ if err := setupIPForwarding(true); err != nil {
+ t.Fatalf("Failed to setup IP forwarding: %v", err)
+ }
+
+ // Read new setting
+ procSetting = readCurrentIPForwardingSetting(t)
+ if bytes.Compare(procSetting, []byte("1\n")) != 0 {
+ t.Fatal("Failed to effectively setup IP forwarding")
+ }
+}
+
+func readCurrentIPForwardingSetting(t *testing.T) []byte {
+ procSetting, err := ioutil.ReadFile(ipv4ForwardConf)
+ if err != nil {
+ t.Fatalf("Can't execute test: Failed to read current IP forwarding setting: %v", err)
+ }
+ return procSetting
+}
+
+func writeIPForwardingSetting(t *testing.T, chars []byte) {
+ err := ioutil.WriteFile(ipv4ForwardConf, chars, ipv4ForwardConfPerm)
+ if err != nil {
+ t.Fatalf("Can't execute or cleanup after test: Failed to reset IP forwarding: %v", err)
+ }
+}
+
+func reconcileIPForwardingSetting(t *testing.T, original []byte) {
+ current := readCurrentIPForwardingSetting(t)
+ if bytes.Compare(original, current) != 0 {
+ writeIPForwardingSetting(t, original)
+ }
+}
--- /dev/null
+package bridge
+
+import (
+ "errors"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/iptables"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+// DockerChain: DOCKER iptable chain name
+const (
+ DockerChain = "DOCKER"
+ // Isolation between bridge networks is achieved in two stages by means
+ // of the following two chains in the filter table. The first chain matches
+ // on the source interface being a bridge network's bridge and the
+ // destination being a different interface. A positive match leads to the
+ // second isolation chain. No match returns to the parent chain. The second
+ // isolation chain matches on destination interface being a bridge network's
+ // bridge. A positive match identifies a packet originated from one bridge
+ // network's bridge destined to another bridge network's bridge and will
+ // result in the packet being dropped. No match returns to the parent chain.
+ IsolationChain1 = "DOCKER-ISOLATION-STAGE-1"
+ IsolationChain2 = "DOCKER-ISOLATION-STAGE-2"
+)
+
+func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
+ // Sanity check.
+ if config.EnableIPTables == false {
+ return nil, nil, nil, nil, errors.New("cannot create new chains, EnableIPTable is disabled")
+ }
+
+ hairpinMode := !config.EnableUserlandProxy
+
+ natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode)
+ if err != nil {
+ return nil, nil, nil, nil, fmt.Errorf("failed to create NAT chain %s: %v", DockerChain, err)
+ }
+ defer func() {
+ if err != nil {
+ if err := iptables.RemoveExistingChain(DockerChain, iptables.Nat); err != nil {
+ logrus.Warnf("failed on removing iptables NAT chain %s on cleanup: %v", DockerChain, err)
+ }
+ }
+ }()
+
+ filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, false)
+ if err != nil {
+ return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER chain %s: %v", DockerChain, err)
+ }
+ defer func() {
+ if err != nil {
+ if err := iptables.RemoveExistingChain(DockerChain, iptables.Filter); err != nil {
+ logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", DockerChain, err)
+ }
+ }
+ }()
+
+ isolationChain1, err := iptables.NewChain(IsolationChain1, iptables.Filter, false)
+ if err != nil {
+ return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
+ }
+ defer func() {
+ if err != nil {
+ if err := iptables.RemoveExistingChain(IsolationChain1, iptables.Filter); err != nil {
+ logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain1, err)
+ }
+ }
+ }()
+
+ isolationChain2, err := iptables.NewChain(IsolationChain2, iptables.Filter, false)
+ if err != nil {
+ return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
+ }
+ defer func() {
+ if err != nil {
+ if err := iptables.RemoveExistingChain(IsolationChain2, iptables.Filter); err != nil {
+ logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain2, err)
+ }
+ }
+ }()
+
+ if err := iptables.AddReturnRule(IsolationChain1); err != nil {
+ return nil, nil, nil, nil, err
+ }
+
+ if err := iptables.AddReturnRule(IsolationChain2); err != nil {
+ return nil, nil, nil, nil, err
+ }
+
+ return natChain, filterChain, isolationChain1, isolationChain2, nil
+}
+
+func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error {
+ var err error
+
+ d := n.driver
+ d.Lock()
+ driverConfig := d.config
+ d.Unlock()
+
+ // Sanity check.
+ if driverConfig.EnableIPTables == false {
+ return errors.New("Cannot program chains, EnableIPTable is disabled")
+ }
+
+ // Pickup this configuration option from driver
+ hairpinMode := !driverConfig.EnableUserlandProxy
+
+ maskedAddrv4 := &net.IPNet{
+ IP: i.bridgeIPv4.IP.Mask(i.bridgeIPv4.Mask),
+ Mask: i.bridgeIPv4.Mask,
+ }
+ if config.Internal {
+ if err = setupInternalNetworkRules(config.BridgeName, maskedAddrv4, config.EnableICC, true); err != nil {
+ return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
+ }
+ n.registerIptCleanFunc(func() error {
+ return setupInternalNetworkRules(config.BridgeName, maskedAddrv4, config.EnableICC, false)
+ })
+ } else {
+ 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, true)
+ if err != nil {
+ return fmt.Errorf("Failed to program NAT chain: %s", err.Error())
+ }
+
+ 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(natChain, n.getNetworkBridgeName())
+ }
+
+ d.Lock()
+ err = iptables.EnsureJumpRule("FORWARD", IsolationChain1)
+ d.Unlock()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type iptRule struct {
+ table iptables.Table
+ chain string
+ preArgs []string
+ args []string
+}
+
+func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairpin, enable bool) error {
+
+ var (
+ address = addr.String()
+ natRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"}}
+ hpNatRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}}
+ skipDNAT = iptRule{table: iptables.Nat, chain: DockerChain, preArgs: []string{"-t", "nat"}, args: []string{"-i", bridgeIface, "-j", "RETURN"}}
+ outRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}}
+ )
+
+ // Set NAT.
+ if ipmasq {
+ if err := programChainRule(natRule, "NAT", enable); err != nil {
+ return err
+ }
+ }
+
+ if ipmasq && !hairpin {
+ if err := programChainRule(skipDNAT, "SKIP DNAT", enable); err != nil {
+ return err
+ }
+ }
+
+ // In hairpin mode, masquerade traffic from localhost
+ if hairpin {
+ if err := programChainRule(hpNatRule, "MASQ LOCAL HOST", enable); err != nil {
+ return err
+ }
+ }
+
+ // Set Inter Container Communication.
+ if err := setIcc(bridgeIface, icc, enable); err != nil {
+ return err
+ }
+
+ // Set Accept on all non-intercontainer outgoing packets.
+ return programChainRule(outRule, "ACCEPT NON_ICC OUTGOING", enable)
+}
+
+func programChainRule(rule iptRule, ruleDescr string, insert bool) error {
+ var (
+ prefix []string
+ operation string
+ condition bool
+ doesExist = iptables.Exists(rule.table, rule.chain, rule.args...)
+ )
+
+ if insert {
+ condition = !doesExist
+ prefix = []string{"-I", rule.chain}
+ operation = "enable"
+ } else {
+ condition = doesExist
+ prefix = []string{"-D", rule.chain}
+ operation = "disable"
+ }
+ if rule.preArgs != nil {
+ prefix = append(rule.preArgs, prefix...)
+ }
+
+ if condition {
+ if err := iptables.RawCombinedOutput(append(prefix, rule.args...)...); err != nil {
+ return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
+ }
+ }
+
+ return nil
+}
+
+func setIcc(bridgeIface string, iccEnable, insert bool) error {
+ var (
+ table = iptables.Filter
+ chain = "FORWARD"
+ args = []string{"-i", bridgeIface, "-o", bridgeIface, "-j"}
+ acceptArgs = append(args, "ACCEPT")
+ dropArgs = append(args, "DROP")
+ )
+
+ if insert {
+ if !iccEnable {
+ iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
+
+ if !iptables.Exists(table, chain, dropArgs...) {
+ if err := iptables.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil {
+ return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
+ }
+ }
+ } else {
+ iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
+
+ if !iptables.Exists(table, chain, acceptArgs...) {
+ if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
+ return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
+ }
+ }
+ }
+ } else {
+ // Remove any ICC rule.
+ if !iccEnable {
+ if iptables.Exists(table, chain, dropArgs...) {
+ iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
+ }
+ } else {
+ if iptables.Exists(table, chain, acceptArgs...) {
+ iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
+ }
+ }
+ }
+
+ return nil
+}
+
+// Control Inter Network Communication. Install[Remove] only if it is [not] present.
+func setINC(iface string, enable bool) error {
+ var (
+ action = iptables.Insert
+ actionMsg = "add"
+ chains = []string{IsolationChain1, IsolationChain2}
+ rules = [][]string{
+ {"-i", iface, "!", "-o", iface, "-j", IsolationChain2},
+ {"-o", iface, "-j", "DROP"},
+ }
+ )
+
+ if !enable {
+ action = iptables.Delete
+ actionMsg = "remove"
+ }
+
+ for i, chain := range chains {
+ if err := iptables.ProgramRule(iptables.Filter, chain, action, rules[i]); err != nil {
+ msg := fmt.Sprintf("unable to %s inter-network communication rule: %v", actionMsg, err)
+ if enable {
+ if i == 1 {
+ // Rollback the rule installed on first chain
+ if err2 := iptables.ProgramRule(iptables.Filter, chains[0], iptables.Delete, rules[0]); err2 != nil {
+ logrus.Warnf("Failed to rollback iptables rule after failure (%v): %v", err, err2)
+ }
+ }
+ return fmt.Errorf(msg)
+ }
+ logrus.Warn(msg)
+ }
+ }
+
+ return nil
+}
+
+// Obsolete chain from previous docker versions
+const oldIsolationChain = "DOCKER-ISOLATION"
+
+func removeIPChains() {
+ // Remove obsolete rules from default chains
+ iptables.ProgramRule(iptables.Filter, "FORWARD", iptables.Delete, []string{"-j", oldIsolationChain})
+
+ // Remove chains
+ for _, chainInfo := range []iptables.ChainInfo{
+ {Name: DockerChain, Table: iptables.Nat},
+ {Name: DockerChain, Table: iptables.Filter},
+ {Name: IsolationChain1, Table: iptables.Filter},
+ {Name: IsolationChain2, Table: iptables.Filter},
+ {Name: oldIsolationChain, Table: iptables.Filter},
+ } {
+ if err := chainInfo.Remove(); err != nil {
+ logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err)
+ }
+ }
+}
+
+func setupInternalNetworkRules(bridgeIface string, addr net.Addr, icc, insert bool) error {
+ var (
+ inDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}}
+ outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}}
+ )
+ if err := programChainRule(inDropRule, "DROP INCOMING", insert); err != nil {
+ return err
+ }
+ if err := programChainRule(outDropRule, "DROP OUTGOING", insert); err != nil {
+ return err
+ }
+ // Set Inter Container Communication.
+ return setIcc(bridgeIface, icc, insert)
+}
+
+func clearEndpointConnections(nlh *netlink.Handle, ep *bridgeEndpoint) {
+ var ipv4List []net.IP
+ var ipv6List []net.IP
+ if ep.addr != nil {
+ ipv4List = append(ipv4List, ep.addr.IP)
+ }
+ if ep.addrv6 != nil {
+ ipv6List = append(ipv6List, ep.addrv6.IP)
+ }
+ iptables.DeleteConntrackEntries(nlh, ipv4List, ipv6List)
+}
--- /dev/null
+package bridge
+
+import (
+ "net"
+ "testing"
+
+ "github.com/docker/libnetwork/iptables"
+ "github.com/docker/libnetwork/portmapper"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/vishvananda/netlink"
+)
+
+const (
+ iptablesTestBridgeIP = "192.168.42.1"
+)
+
+func TestProgramIPTable(t *testing.T) {
+ // Create a test bridge with a basic bridge configuration (name + IPv4).
+ defer testutils.SetupTestOSContext(t)()
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ createTestBridge(getBasicTestConfig(), &bridgeInterface{nlh: nh}, t)
+
+ // Store various iptables chain rules we care for.
+ rules := []struct {
+ rule iptRule
+ descr string
+ }{
+ {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-d", "127.1.2.3", "-i", "lo", "-o", "lo", "-j", "DROP"}}, "Test Loopback"},
+ {iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-s", iptablesTestBridgeIP, "!", "-o", DefaultBridgeName, "-j", "MASQUERADE"}}, "NAT Test"},
+ {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-o", DefaultBridgeName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}, "Test ACCEPT INCOMING"},
+ {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "!", "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test ACCEPT NON_ICC OUTGOING"},
+ {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test enable ICC"},
+ {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "DROP"}}, "Test disable ICC"},
+ }
+
+ // Assert the chain rules' insertion and removal.
+ for _, c := range rules {
+ assertIPTableChainProgramming(c.rule, c.descr, t)
+ }
+}
+
+func TestSetupIPChains(t *testing.T) {
+ // Create a test bridge with a basic bridge configuration (name + IPv4).
+ defer testutils.SetupTestOSContext(t)()
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ driverconfig := &configuration{
+ EnableIPTables: true,
+ }
+ d := &driver{
+ config: driverconfig,
+ }
+ assertChainConfig(d, t)
+
+ config := getBasicTestConfig()
+ br := &bridgeInterface{nlh: nh}
+ createTestBridge(config, br, t)
+
+ assertBridgeConfig(config, br, d, t)
+
+ config.EnableIPMasquerade = true
+ assertBridgeConfig(config, br, d, t)
+
+ config.EnableICC = true
+ assertBridgeConfig(config, br, d, t)
+
+ config.EnableIPMasquerade = false
+ assertBridgeConfig(config, br, d, t)
+}
+
+func getBasicTestConfig() *networkConfiguration {
+ config := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ AddressIPv4: &net.IPNet{IP: net.ParseIP(iptablesTestBridgeIP), Mask: net.CIDRMask(16, 32)}}
+ return config
+}
+
+func createTestBridge(config *networkConfiguration, br *bridgeInterface, t *testing.T) {
+ if err := setupDevice(config, br); err != nil {
+ t.Fatalf("Failed to create the testing Bridge: %s", err.Error())
+ }
+ if err := setupBridgeIPv4(config, br); err != nil {
+ t.Fatalf("Failed to bring up the testing Bridge: %s", err.Error())
+ }
+}
+
+// Assert base function which pushes iptables chain rules on insertion and removal.
+func assertIPTableChainProgramming(rule iptRule, descr string, t *testing.T) {
+ // Add
+ if err := programChainRule(rule, descr, true); err != nil {
+ t.Fatalf("Failed to program iptable rule %s: %s", descr, err.Error())
+ }
+ if iptables.Exists(rule.table, rule.chain, rule.args...) == false {
+ t.Fatalf("Failed to effectively program iptable rule: %s", descr)
+ }
+
+ // Remove
+ if err := programChainRule(rule, descr, false); err != nil {
+ t.Fatalf("Failed to remove iptable rule %s: %s", descr, err.Error())
+ }
+ if iptables.Exists(rule.table, rule.chain, rule.args...) == true {
+ t.Fatalf("Failed to effectively remove iptable rule: %s", descr)
+ }
+}
+
+// Assert function which create chains.
+func assertChainConfig(d *driver, t *testing.T) {
+ var err error
+
+ d.natChain, d.filterChain, d.isolationChain1, d.isolationChain2, err = setupIPChains(d.config)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Assert function which pushes chains based on bridge config parameters.
+func assertBridgeConfig(config *networkConfiguration, br *bridgeInterface, d *driver, t *testing.T) {
+ nw := bridgeNetwork{portMapper: portmapper.New(""),
+ config: config}
+ nw.driver = d
+
+ // Attempt programming of ip tables.
+ err := nw.setupIPTables(config, br)
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+}
--- /dev/null
+package bridge
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "path/filepath"
+
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+func selectIPv4Address(addresses []netlink.Addr, selector *net.IPNet) (netlink.Addr, error) {
+ if len(addresses) == 0 {
+ return netlink.Addr{}, errors.New("unable to select an address as the address pool is empty")
+ }
+ if selector != nil {
+ for _, addr := range addresses {
+ if selector.Contains(addr.IP) {
+ return addr, nil
+ }
+ }
+ }
+ return addresses[0], nil
+}
+
+func setupBridgeIPv4(config *networkConfiguration, i *bridgeInterface) error {
+ addrv4List, _, err := i.addresses()
+ if err != nil {
+ return fmt.Errorf("failed to retrieve bridge interface addresses: %v", err)
+ }
+
+ addrv4, _ := selectIPv4Address(addrv4List, config.AddressIPv4)
+
+ if !types.CompareIPNet(addrv4.IPNet, config.AddressIPv4) {
+ if addrv4.IPNet != nil {
+ if err := i.nlh.AddrDel(i.Link, &addrv4); err != nil {
+ return fmt.Errorf("failed to remove current ip address from bridge: %v", err)
+ }
+ }
+ logrus.Debugf("Assigning address to bridge interface %s: %s", config.BridgeName, config.AddressIPv4)
+ if err := i.nlh.AddrAdd(i.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
+ return &IPv4AddrAddError{IP: config.AddressIPv4, Err: err}
+ }
+ }
+
+ // Store bridge network and default gateway
+ i.bridgeIPv4 = config.AddressIPv4
+ i.gatewayIPv4 = config.AddressIPv4.IP
+
+ return nil
+}
+
+func setupGatewayIPv4(config *networkConfiguration, i *bridgeInterface) error {
+ if !i.bridgeIPv4.Contains(config.DefaultGatewayIPv4) {
+ return &ErrInvalidGateway{}
+ }
+
+ // Store requested default gateway
+ i.gatewayIPv4 = config.DefaultGatewayIPv4
+
+ return nil
+}
+
+func setupLoopbackAddressesRouting(config *networkConfiguration, i *bridgeInterface) error {
+ sysPath := filepath.Join("/proc/sys/net/ipv4/conf", config.BridgeName, "route_localnet")
+ ipv4LoRoutingData, err := ioutil.ReadFile(sysPath)
+ if err != nil {
+ return fmt.Errorf("Cannot read IPv4 local routing setup: %v", err)
+ }
+ // Enable loopback addresses routing only if it isn't already enabled
+ if ipv4LoRoutingData[0] != '1' {
+ if err := ioutil.WriteFile(sysPath, []byte{'1', '\n'}, 0644); err != nil {
+ return fmt.Errorf("Unable to enable local routing for hairpin mode: %v", err)
+ }
+ }
+ return nil
+}
--- /dev/null
+package bridge
+
+import (
+ "net"
+ "testing"
+
+ "github.com/docker/libnetwork/testutils"
+ "github.com/vishvananda/netlink"
+)
+
+func setupTestInterface(t *testing.T, nh *netlink.Handle) (*networkConfiguration, *bridgeInterface) {
+ config := &networkConfiguration{
+ BridgeName: DefaultBridgeName}
+ br := &bridgeInterface{nlh: nh}
+
+ if err := setupDevice(config, br); err != nil {
+ t.Fatalf("Bridge creation failed: %v", err)
+ }
+ return config, br
+}
+
+func TestSetupBridgeIPv4Fixed(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ ip, netw, err := net.ParseCIDR("192.168.1.1/24")
+ if err != nil {
+ t.Fatalf("Failed to parse bridge IPv4: %v", err)
+ }
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ config, br := setupTestInterface(t, nh)
+ config.AddressIPv4 = &net.IPNet{IP: ip, Mask: netw.Mask}
+ if err := setupBridgeIPv4(config, br); err != nil {
+ t.Fatalf("Failed to setup bridge IPv4: %v", err)
+ }
+
+ addrsv4, err := nh.AddrList(br.Link, netlink.FAMILY_V4)
+ if err != nil {
+ t.Fatalf("Failed to list device IPv4 addresses: %v", err)
+ }
+
+ var found bool
+ for _, addr := range addrsv4 {
+ if config.AddressIPv4.String() == addr.IPNet.String() {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatalf("Bridge device does not have requested IPv4 address %v", config.AddressIPv4)
+ }
+}
+
+func TestSetupGatewayIPv4(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ ip, nw, _ := net.ParseCIDR("192.168.0.24/16")
+ nw.IP = ip
+ gw := net.ParseIP("192.168.2.254")
+
+ config := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ DefaultGatewayIPv4: gw}
+
+ br := &bridgeInterface{bridgeIPv4: nw, nlh: nh}
+
+ if err := setupGatewayIPv4(config, br); err != nil {
+ t.Fatalf("Set Default Gateway failed: %v", err)
+ }
+
+ if !gw.Equal(br.gatewayIPv4) {
+ t.Fatalf("Set Default Gateway failed. Expected %v, Found %v", gw, br.gatewayIPv4)
+ }
+}
--- /dev/null
+package bridge
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+var bridgeIPv6 *net.IPNet
+
+const (
+ bridgeIPv6Str = "fe80::1/64"
+ ipv6ForwardConfPerm = 0644
+ ipv6ForwardConfDefault = "/proc/sys/net/ipv6/conf/default/forwarding"
+ ipv6ForwardConfAll = "/proc/sys/net/ipv6/conf/all/forwarding"
+)
+
+func init() {
+ // We allow ourselves to panic in this special case because we indicate a
+ // failure to parse a compile-time define constant.
+ var err error
+ if bridgeIPv6, err = types.ParseCIDR(bridgeIPv6Str); err != nil {
+ panic(fmt.Sprintf("Cannot parse default bridge IPv6 address %q: %v", bridgeIPv6Str, err))
+ }
+}
+
+func setupBridgeIPv6(config *networkConfiguration, i *bridgeInterface) error {
+ procFile := "/proc/sys/net/ipv6/conf/" + config.BridgeName + "/disable_ipv6"
+ ipv6BridgeData, err := ioutil.ReadFile(procFile)
+ if err != nil {
+ return fmt.Errorf("Cannot read IPv6 setup for bridge %v: %v", config.BridgeName, err)
+ }
+ // Enable IPv6 on the bridge only if it isn't already enabled
+ if ipv6BridgeData[0] != '0' {
+ if err := ioutil.WriteFile(procFile, []byte{'0', '\n'}, ipv6ForwardConfPerm); err != nil {
+ return fmt.Errorf("Unable to enable IPv6 addresses on bridge: %v", err)
+ }
+ }
+
+ // Store bridge network and default gateway
+ i.bridgeIPv6 = bridgeIPv6
+ i.gatewayIPv6 = i.bridgeIPv6.IP
+
+ if err := i.programIPv6Address(); err != nil {
+ return err
+ }
+
+ if config.AddressIPv6 == nil {
+ return nil
+ }
+
+ // Store the user specified bridge network and network gateway and program it
+ i.bridgeIPv6 = config.AddressIPv6
+ i.gatewayIPv6 = config.AddressIPv6.IP
+
+ if err := i.programIPv6Address(); err != nil {
+ return err
+ }
+
+ // Setting route to global IPv6 subnet
+ logrus.Debugf("Adding route to IPv6 network %s via device %s", config.AddressIPv6.String(), config.BridgeName)
+ err = i.nlh.RouteAdd(&netlink.Route{
+ Scope: netlink.SCOPE_UNIVERSE,
+ LinkIndex: i.Link.Attrs().Index,
+ Dst: config.AddressIPv6,
+ })
+ if err != nil && !os.IsExist(err) {
+ logrus.Errorf("Could not add route to IPv6 network %s via device %s", config.AddressIPv6.String(), config.BridgeName)
+ }
+
+ return nil
+}
+
+func setupGatewayIPv6(config *networkConfiguration, i *bridgeInterface) error {
+ if config.AddressIPv6 == nil {
+ return &ErrInvalidContainerSubnet{}
+ }
+ if !config.AddressIPv6.Contains(config.DefaultGatewayIPv6) {
+ return &ErrInvalidGateway{}
+ }
+
+ // Store requested default gateway
+ i.gatewayIPv6 = config.DefaultGatewayIPv6
+
+ return nil
+}
+
+func setupIPv6Forwarding(config *networkConfiguration, i *bridgeInterface) error {
+ // Get current IPv6 default forwarding setup
+ ipv6ForwardDataDefault, err := ioutil.ReadFile(ipv6ForwardConfDefault)
+ if err != nil {
+ return fmt.Errorf("Cannot read IPv6 default forwarding setup: %v", err)
+ }
+ // Enable IPv6 default forwarding only if it is not already enabled
+ if ipv6ForwardDataDefault[0] != '1' {
+ if err := ioutil.WriteFile(ipv6ForwardConfDefault, []byte{'1', '\n'}, ipv6ForwardConfPerm); err != nil {
+ logrus.Warnf("Unable to enable IPv6 default forwarding: %v", err)
+ }
+ }
+
+ // Get current IPv6 all forwarding setup
+ ipv6ForwardDataAll, err := ioutil.ReadFile(ipv6ForwardConfAll)
+ if err != nil {
+ return fmt.Errorf("Cannot read IPv6 all forwarding setup: %v", err)
+ }
+ // Enable IPv6 all forwarding only if it is not already enabled
+ if ipv6ForwardDataAll[0] != '1' {
+ if err := ioutil.WriteFile(ipv6ForwardConfAll, []byte{'1', '\n'}, ipv6ForwardConfPerm); err != nil {
+ logrus.Warnf("Unable to enable IPv6 all forwarding: %v", err)
+ }
+ }
+
+ return nil
+}
--- /dev/null
+package bridge
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "testing"
+
+ "github.com/docker/libnetwork/testutils"
+ "github.com/vishvananda/netlink"
+)
+
+func TestSetupIPv6(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ config, br := setupTestInterface(t, nh)
+ if err := setupBridgeIPv6(config, br); err != nil {
+ t.Fatalf("Failed to setup bridge IPv6: %v", err)
+ }
+
+ procSetting, err := ioutil.ReadFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", config.BridgeName))
+ if err != nil {
+ t.Fatalf("Failed to read disable_ipv6 kernel setting: %v", err)
+ }
+
+ if expected := []byte("0\n"); bytes.Compare(expected, procSetting) != 0 {
+ t.Fatalf("Invalid kernel setting disable_ipv6: expected %q, got %q", string(expected), string(procSetting))
+ }
+
+ addrsv6, err := nh.AddrList(br.Link, netlink.FAMILY_V6)
+ if err != nil {
+ t.Fatalf("Failed to list device IPv6 addresses: %v", err)
+ }
+
+ var found bool
+ for _, addr := range addrsv6 {
+ if bridgeIPv6Str == addr.IPNet.String() {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatalf("Bridge device does not have requested IPv6 address %v", bridgeIPv6Str)
+ }
+
+}
+
+func TestSetupGatewayIPv6(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ _, nw, _ := net.ParseCIDR("2001:db8:ea9:9abc:ffff::/80")
+ gw := net.ParseIP("2001:db8:ea9:9abc:ffff::254")
+
+ config := &networkConfiguration{
+ BridgeName: DefaultBridgeName,
+ AddressIPv6: nw,
+ DefaultGatewayIPv6: gw}
+
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ br := &bridgeInterface{nlh: nh}
+
+ if err := setupGatewayIPv6(config, br); err != nil {
+ t.Fatalf("Set Default Gateway failed: %v", err)
+ }
+
+ if !gw.Equal(br.gatewayIPv6) {
+ t.Fatalf("Set Default Gateway failed. Expected %v, Found %v", gw, br.gatewayIPv6)
+ }
+}
--- /dev/null
+package bridge
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+func setupVerifyAndReconcile(config *networkConfiguration, i *bridgeInterface) error {
+ // Fetch a slice of IPv4 addresses and a slice of IPv6 addresses from the bridge.
+ addrsv4, addrsv6, err := i.addresses()
+ if err != nil {
+ return fmt.Errorf("Failed to verify ip addresses: %v", err)
+ }
+
+ addrv4, _ := selectIPv4Address(addrsv4, config.AddressIPv4)
+
+ // Verify that the bridge does have an IPv4 address.
+ if addrv4.IPNet == nil {
+ return &ErrNoIPAddr{}
+ }
+
+ // Verify that the bridge IPv4 address matches the requested configuration.
+ if config.AddressIPv4 != nil && !addrv4.IP.Equal(config.AddressIPv4.IP) {
+ return &IPv4AddrNoMatchError{IP: addrv4.IP, CfgIP: config.AddressIPv4.IP}
+ }
+
+ // Verify that one of the bridge IPv6 addresses matches the requested
+ // configuration.
+ if config.EnableIPv6 && !findIPv6Address(netlink.Addr{IPNet: bridgeIPv6}, addrsv6) {
+ return (*IPv6AddrNoMatchError)(bridgeIPv6)
+ }
+
+ // Release any residual IPv6 address that might be there because of older daemon instances
+ for _, addrv6 := range addrsv6 {
+ if addrv6.IP.IsGlobalUnicast() && !types.CompareIPNet(addrv6.IPNet, i.bridgeIPv6) {
+ if err := i.nlh.AddrDel(i.Link, &addrv6); err != nil {
+ logrus.Warnf("Failed to remove residual IPv6 address %s from bridge: %v", addrv6.IPNet, err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func findIPv6Address(addr netlink.Addr, addresses []netlink.Addr) bool {
+ for _, addrv6 := range addresses {
+ if addrv6.String() == addr.String() {
+ return true
+ }
+ }
+ return false
+}
+
+func bridgeInterfaceExists(name string) (bool, error) {
+ nlh := ns.NlHandle()
+ link, err := nlh.LinkByName(name)
+ if err != nil {
+ if strings.Contains(err.Error(), "Link not found") {
+ return false, nil
+ }
+ return false, fmt.Errorf("failed to check bridge interface existence: %v", err)
+ }
+
+ if link.Type() == "bridge" {
+ return true, nil
+ }
+ return false, fmt.Errorf("existing interface %s is not a bridge", name)
+}
--- /dev/null
+package bridge
+
+import (
+ "net"
+ "testing"
+
+ "github.com/docker/libnetwork/testutils"
+ "github.com/vishvananda/netlink"
+)
+
+func setupVerifyTest(t *testing.T) *bridgeInterface {
+ nh, err := netlink.NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ inf := &bridgeInterface{nlh: nh}
+
+ br := netlink.Bridge{}
+ br.LinkAttrs.Name = "default0"
+ if err := nh.LinkAdd(&br); err == nil {
+ inf.Link = &br
+ } else {
+ t.Fatalf("Failed to create bridge interface: %v", err)
+ }
+
+ return inf
+}
+
+func TestSetupVerify(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ addrv4 := net.IPv4(192, 168, 1, 1)
+ inf := setupVerifyTest(t)
+ config := &networkConfiguration{}
+ config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+
+ if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
+ t.Fatalf("Failed to assign IPv4 %s to interface: %v", config.AddressIPv4, err)
+ }
+
+ if err := setupVerifyAndReconcile(config, inf); err != nil {
+ t.Fatalf("Address verification failed: %v", err)
+ }
+}
+
+func TestSetupVerifyBad(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ addrv4 := net.IPv4(192, 168, 1, 1)
+ inf := setupVerifyTest(t)
+ config := &networkConfiguration{}
+ config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+
+ ipnet := &net.IPNet{IP: net.IPv4(192, 168, 1, 2), Mask: addrv4.DefaultMask()}
+ if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: ipnet}); err != nil {
+ t.Fatalf("Failed to assign IPv4 %s to interface: %v", ipnet, err)
+ }
+
+ if err := setupVerifyAndReconcile(config, inf); err == nil {
+ t.Fatal("Address verification was expected to fail")
+ }
+}
+
+func TestSetupVerifyMissing(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ addrv4 := net.IPv4(192, 168, 1, 1)
+ inf := setupVerifyTest(t)
+ config := &networkConfiguration{}
+ config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+
+ if err := setupVerifyAndReconcile(config, inf); err == nil {
+ t.Fatal("Address verification was expected to fail")
+ }
+}
+
+func TestSetupVerifyIPv6(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ addrv4 := net.IPv4(192, 168, 1, 1)
+ inf := setupVerifyTest(t)
+ config := &networkConfiguration{}
+ config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+ config.EnableIPv6 = true
+
+ if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: bridgeIPv6}); err != nil {
+ t.Fatalf("Failed to assign IPv6 %s to interface: %v", bridgeIPv6, err)
+ }
+ if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
+ t.Fatalf("Failed to assign IPv4 %s to interface: %v", config.AddressIPv4, err)
+ }
+
+ if err := setupVerifyAndReconcile(config, inf); err != nil {
+ t.Fatalf("Address verification failed: %v", err)
+ }
+}
+
+func TestSetupVerifyIPv6Missing(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ addrv4 := net.IPv4(192, 168, 1, 1)
+ inf := setupVerifyTest(t)
+ config := &networkConfiguration{}
+ config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+ config.EnableIPv6 = true
+
+ if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
+ t.Fatalf("Failed to assign IPv4 %s to interface: %v", config.AddressIPv4, err)
+ }
+
+ if err := setupVerifyAndReconcile(config, inf); err == nil {
+ t.Fatal("Address verification was expected to fail")
+ }
+}
--- /dev/null
+package host
+
+import (
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/types"
+)
+
+const networkType = "host"
+
+type driver struct {
+ network string
+ sync.Mutex
+}
+
+// Init registers a new instance of host driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ c := driverapi.Capability{
+ DataScope: datastore.LocalScope,
+ ConnectivityScope: datastore.LocalScope,
+ }
+ return dc.RegisterDriver(networkType, &driver{}, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ d.Lock()
+ defer d.Unlock()
+
+ if d.network != "" {
+ return types.ForbiddenErrorf("only one instance of \"%s\" network is allowed", networkType)
+ }
+
+ d.network = id
+
+ return nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType)
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+ return nil
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ return nil
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return make(map[string]interface{}, 0), nil
+}
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ return nil
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ return nil
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+}
+
+func (d *driver) Type() string {
+ return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
--- /dev/null
+package host
+
+import (
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+func TestDriver(t *testing.T) {
+ d := &driver{}
+
+ if d.Type() != networkType {
+ t.Fatal("Unexpected network type returned by driver")
+ }
+
+ err := d.CreateNetwork("first", nil, nil, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if d.network != "first" {
+ t.Fatal("Unexpected network id stored")
+ }
+
+ err = d.CreateNetwork("second", nil, nil, nil, nil)
+ if err == nil {
+ t.Fatal("Second network creation should fail on this driver")
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatal("Second network creation failed with unexpected error type")
+ }
+
+ err = d.DeleteNetwork("first")
+ if err == nil {
+ t.Fatal("network deletion should fail on this driver")
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatal("network deletion failed with unexpected error type")
+ }
+
+ // we don't really check if it is there or not, delete is not allowed for this driver, period.
+ err = d.DeleteNetwork("unknown")
+ if err == nil {
+ t.Fatal("any network deletion should fail on this driver")
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatal("any network deletion failed with unexpected error type")
+ }
+}
--- /dev/null
+package ipvlan
+
+import (
+ "net"
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+)
+
+const (
+ vethLen = 7
+ containerVethPrefix = "eth"
+ vethPrefix = "veth"
+ ipvlanType = "ipvlan" // driver type name
+ modeL2 = "l2" // ipvlan mode l2 is the default
+ modeL3 = "l3" // ipvlan L3 mode
+ parentOpt = "parent" // parent interface -o parent
+ modeOpt = "_mode" // ipvlan mode ux opt suffix
+)
+
+var driverModeOpt = ipvlanType + modeOpt // mode -o ipvlan_mode
+
+type endpointTable map[string]*endpoint
+
+type networkTable map[string]*network
+
+type driver struct {
+ networks networkTable
+ sync.Once
+ sync.Mutex
+ store datastore.DataStore
+}
+
+type endpoint struct {
+ id string
+ nid string
+ mac net.HardwareAddr
+ addr *net.IPNet
+ addrv6 *net.IPNet
+ srcName string
+ dbIndex uint64
+ dbExists bool
+}
+
+type network struct {
+ id string
+ sbox osl.Sandbox
+ endpoints endpointTable
+ driver *driver
+ config *configuration
+ sync.Mutex
+}
+
+// Init initializes and registers the libnetwork ipvlan driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ c := driverapi.Capability{
+ DataScope: datastore.LocalScope,
+ ConnectivityScope: datastore.GlobalScope,
+ }
+ d := &driver{
+ networks: networkTable{},
+ }
+ d.initStore(config)
+
+ return dc.RegisterDriver(ipvlanType, d, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return make(map[string]interface{}, 0), nil
+}
+
+func (d *driver) Type() string {
+ return ipvlanType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+}
+
+// DiscoverNew is a notification for a new discovery event.
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+// DiscoverDelete is a notification for a discovery delete event.
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
--- /dev/null
+package ipvlan
+
+import (
+ "fmt"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateEndpoint assigns the mac, ip and endpoint id for the new container
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
+ epOptions map[string]interface{}) error {
+ defer osl.InitOSContext()()
+
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+ n, err := d.getNetwork(nid)
+ if err != nil {
+ return fmt.Errorf("network id %q not found", nid)
+ }
+ if ifInfo.MacAddress() != nil {
+ return fmt.Errorf("%s interfaces do not support custom mac address assigment", ipvlanType)
+ }
+ ep := &endpoint{
+ id: eid,
+ nid: nid,
+ addr: ifInfo.Address(),
+ addrv6: ifInfo.AddressIPv6(),
+ }
+ if ep.addr == nil {
+ return fmt.Errorf("create endpoint was not passed an IP address")
+ }
+ // disallow port mapping -p
+ if opt, ok := epOptions[netlabel.PortMap]; ok {
+ if _, ok := opt.([]types.PortBinding); ok {
+ if len(opt.([]types.PortBinding)) > 0 {
+ logrus.Warnf("%s driver does not support port mappings", ipvlanType)
+ }
+ }
+ }
+ // disallow port exposure --expose
+ if opt, ok := epOptions[netlabel.ExposedPorts]; ok {
+ if _, ok := opt.([]types.TransportPort); ok {
+ if len(opt.([]types.TransportPort)) > 0 {
+ logrus.Warnf("%s driver does not support port exposures", ipvlanType)
+ }
+ }
+ }
+
+ if err := d.storeUpdate(ep); err != nil {
+ return fmt.Errorf("failed to save ipvlan endpoint %.7s to store: %v", ep.id, err)
+ }
+
+ n.addEndpoint(ep)
+
+ return nil
+}
+
+// DeleteEndpoint remove the endpoint and associated netlink interface
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ defer osl.InitOSContext()()
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("network id %q not found", nid)
+ }
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return fmt.Errorf("endpoint id %q not found", eid)
+ }
+ if link, err := ns.NlHandle().LinkByName(ep.srcName); err == nil {
+ if err := ns.NlHandle().LinkDel(link); err != nil {
+ logrus.WithError(err).Warnf("Failed to delete interface (%s)'s link on endpoint (%s) delete", ep.srcName, ep.id)
+ }
+ }
+
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Warnf("Failed to remove ipvlan endpoint %.7s from store: %v", ep.id, err)
+ }
+ n.deleteEndpoint(ep.id)
+ return nil
+}
--- /dev/null
+package ipvlan
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+type staticRoute struct {
+ Destination *net.IPNet
+ RouteType int
+ NextHop net.IP
+}
+
+const (
+ defaultV4RouteCidr = "0.0.0.0/0"
+ defaultV6RouteCidr = "::/0"
+)
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ defer osl.InitOSContext()()
+ n, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+ }
+ endpoint := n.endpoint(eid)
+ if endpoint == nil {
+ return fmt.Errorf("could not find endpoint with id %s", eid)
+ }
+ // generate a name for the iface that will be renamed to eth0 in the sbox
+ containerIfName, err := netutils.GenerateIfaceName(ns.NlHandle(), vethPrefix, vethLen)
+ if err != nil {
+ return fmt.Errorf("error generating an interface name: %v", err)
+ }
+ // create the netlink ipvlan interface
+ vethName, err := createIPVlan(containerIfName, n.config.Parent, n.config.IpvlanMode)
+ if err != nil {
+ return err
+ }
+ // bind the generated iface name to the endpoint
+ endpoint.srcName = vethName
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return fmt.Errorf("could not find endpoint with id %s", eid)
+ }
+ if n.config.IpvlanMode == modeL3 {
+ // disable gateway services to add a default gw using dev eth0 only
+ jinfo.DisableGatewayService()
+ defaultRoute, err := ifaceGateway(defaultV4RouteCidr)
+ if err != nil {
+ return err
+ }
+ if err := jinfo.AddStaticRoute(defaultRoute.Destination, defaultRoute.RouteType, defaultRoute.NextHop); err != nil {
+ return fmt.Errorf("failed to set an ipvlan l3 mode ipv4 default gateway: %v", err)
+ }
+ logrus.Debugf("Ipvlan Endpoint Joined with IPv4_Addr: %s, Ipvlan_Mode: %s, Parent: %s",
+ ep.addr.IP.String(), n.config.IpvlanMode, n.config.Parent)
+ // If the endpoint has a v6 address, set a v6 default route
+ if ep.addrv6 != nil {
+ default6Route, err := ifaceGateway(defaultV6RouteCidr)
+ if err != nil {
+ return err
+ }
+ if err = jinfo.AddStaticRoute(default6Route.Destination, default6Route.RouteType, default6Route.NextHop); err != nil {
+ return fmt.Errorf("failed to set an ipvlan l3 mode ipv6 default gateway: %v", err)
+ }
+ logrus.Debugf("Ipvlan Endpoint Joined with IPv6_Addr: %s, Ipvlan_Mode: %s, Parent: %s",
+ ep.addrv6.IP.String(), n.config.IpvlanMode, n.config.Parent)
+ }
+ }
+ if n.config.IpvlanMode == modeL2 {
+ // parse and correlate the endpoint v4 address with the available v4 subnets
+ if len(n.config.Ipv4Subnets) > 0 {
+ s := n.getSubnetforIPv4(ep.addr)
+ if s == nil {
+ return fmt.Errorf("could not find a valid ipv4 subnet for endpoint %s", eid)
+ }
+ v4gw, _, err := net.ParseCIDR(s.GwIP)
+ if err != nil {
+ return fmt.Errorf("gateway %s is not a valid ipv4 address: %v", s.GwIP, err)
+ }
+ err = jinfo.SetGateway(v4gw)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("Ipvlan Endpoint Joined with IPv4_Addr: %s, Gateway: %s, Ipvlan_Mode: %s, Parent: %s",
+ ep.addr.IP.String(), v4gw.String(), n.config.IpvlanMode, n.config.Parent)
+ }
+ // parse and correlate the endpoint v6 address with the available v6 subnets
+ if len(n.config.Ipv6Subnets) > 0 {
+ s := n.getSubnetforIPv6(ep.addrv6)
+ if s == nil {
+ return fmt.Errorf("could not find a valid ipv6 subnet for endpoint %s", eid)
+ }
+ v6gw, _, err := net.ParseCIDR(s.GwIP)
+ if err != nil {
+ return fmt.Errorf("gateway %s is not a valid ipv6 address: %v", s.GwIP, err)
+ }
+ err = jinfo.SetGatewayIPv6(v6gw)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("Ipvlan Endpoint Joined with IPv6_Addr: %s, Gateway: %s, Ipvlan_Mode: %s, Parent: %s",
+ ep.addrv6.IP.String(), v6gw.String(), n.config.IpvlanMode, n.config.Parent)
+ }
+ }
+ iNames := jinfo.InterfaceName()
+ err = iNames.SetNames(vethName, containerVethPrefix)
+ if err != nil {
+ return err
+ }
+ if err = d.storeUpdate(ep); err != nil {
+ return fmt.Errorf("failed to save ipvlan endpoint %.7s to store: %v", ep.id, err)
+ }
+
+ return nil
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ defer osl.InitOSContext()()
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+ }
+ endpoint, err := network.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+ if endpoint == nil {
+ return fmt.Errorf("could not find endpoint with id %s", eid)
+ }
+
+ return nil
+}
+
+// ifaceGateway returns a static route for either v4/v6 to be set to the container eth0
+func ifaceGateway(dfNet string) (*staticRoute, error) {
+ nh, dst, err := net.ParseCIDR(dfNet)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse default route %v", err)
+ }
+ defaultRoute := &staticRoute{
+ Destination: dst,
+ RouteType: types.CONNECTED,
+ NextHop: nh,
+ }
+
+ return defaultRoute, nil
+}
+
+// getSubnetforIPv4 returns the ipv4 subnet to which the given IP belongs
+func (n *network) getSubnetforIPv4(ip *net.IPNet) *ipv4Subnet {
+ for _, s := range n.config.Ipv4Subnets {
+ _, snet, err := net.ParseCIDR(s.SubnetIP)
+ if err != nil {
+ return nil
+ }
+ // first check if the mask lengths are the same
+ i, _ := snet.Mask.Size()
+ j, _ := ip.Mask.Size()
+ if i != j {
+ continue
+ }
+ if snet.Contains(ip.IP) {
+ return s
+ }
+ }
+
+ return nil
+}
+
+// getSubnetforIPv6 returns the ipv6 subnet to which the given IP belongs
+func (n *network) getSubnetforIPv6(ip *net.IPNet) *ipv6Subnet {
+ for _, s := range n.config.Ipv6Subnets {
+ _, snet, err := net.ParseCIDR(s.SubnetIP)
+ if err != nil {
+ return nil
+ }
+ // first check if the mask lengths are the same
+ i, _ := snet.Mask.Size()
+ j, _ := ip.Mask.Size()
+ if i != j {
+ continue
+ }
+ if snet.Contains(ip.IP) {
+ return s
+ }
+ }
+
+ return nil
+}
--- /dev/null
+package ipvlan
+
+import (
+ "fmt"
+
+ "github.com/docker/docker/pkg/parsers/kernel"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateNetwork the network for the specified driver type
+func (d *driver) CreateNetwork(nid string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ defer osl.InitOSContext()()
+ kv, err := kernel.GetKernelVersion()
+ if err != nil {
+ return fmt.Errorf("Failed to check kernel version for %s driver support: %v", ipvlanType, err)
+ }
+ // ensure Kernel version is >= v4.2 for ipvlan support
+ if kv.Kernel < ipvlanKernelVer || (kv.Kernel == ipvlanKernelVer && kv.Major < ipvlanMajorVer) {
+ return fmt.Errorf("kernel version failed to meet the minimum ipvlan kernel requirement of %d.%d, found %d.%d.%d",
+ ipvlanKernelVer, ipvlanMajorVer, kv.Kernel, kv.Major, kv.Minor)
+ }
+ // reject a null v4 network
+ if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {
+ return fmt.Errorf("ipv4 pool is empty")
+ }
+ // parse and validate the config and bind to networkConfiguration
+ config, err := parseNetworkOptions(nid, option)
+ if err != nil {
+ return err
+ }
+ config.ID = nid
+ err = config.processIPAM(nid, ipV4Data, ipV6Data)
+ if err != nil {
+ return err
+ }
+ // verify the ipvlan mode from -o ipvlan_mode option
+ switch config.IpvlanMode {
+ case "", modeL2:
+ // default to ipvlan L2 mode if -o ipvlan_mode is empty
+ config.IpvlanMode = modeL2
+ case modeL3:
+ config.IpvlanMode = modeL3
+ default:
+ return fmt.Errorf("requested ipvlan mode '%s' is not valid, 'l2' mode is the ipvlan driver default", config.IpvlanMode)
+ }
+ // loopback is not a valid parent link
+ if config.Parent == "lo" {
+ return fmt.Errorf("loopback interface is not a valid %s parent link", ipvlanType)
+ }
+ // if parent interface not specified, create a dummy type link to use named dummy+net_id
+ if config.Parent == "" {
+ config.Parent = getDummyName(stringid.TruncateID(config.ID))
+ // empty parent and --internal are handled the same. Set here to update k/v
+ config.Internal = true
+ }
+ err = d.createNetwork(config)
+ if err != nil {
+ return err
+ }
+ // update persistent db, rollback on fail
+ err = d.storeUpdate(config)
+ if err != nil {
+ d.deleteNetwork(config.ID)
+ logrus.Debugf("encountered an error rolling back a network create for %s : %v", config.ID, err)
+ return err
+ }
+
+ return nil
+}
+
+// createNetwork is used by new network callbacks and persistent network cache
+func (d *driver) createNetwork(config *configuration) error {
+ networkList := d.getNetworks()
+ for _, nw := range networkList {
+ if config.Parent == nw.config.Parent {
+ return fmt.Errorf("network %s is already using parent interface %s",
+ getDummyName(stringid.TruncateID(nw.config.ID)), config.Parent)
+ }
+ }
+ if !parentExists(config.Parent) {
+ // if the --internal flag is set, create a dummy link
+ if config.Internal {
+ err := createDummyLink(config.Parent, getDummyName(stringid.TruncateID(config.ID)))
+ if err != nil {
+ return err
+ }
+ config.CreatedSlaveLink = true
+ // notify the user in logs they have limited communications
+ if config.Parent == getDummyName(stringid.TruncateID(config.ID)) {
+ logrus.Debugf("Empty -o parent= and --internal flags limit communications to other containers inside of network: %s",
+ config.Parent)
+ }
+ } else {
+ // if the subinterface parent_iface.vlan_id checks do not pass, return err.
+ // a valid example is 'eth0.10' for a parent iface 'eth0' with a vlan id '10'
+ err := createVlanLink(config.Parent)
+ if err != nil {
+ return err
+ }
+ // if driver created the networks slave link, record it for future deletion
+ config.CreatedSlaveLink = true
+ }
+ }
+ n := &network{
+ id: config.ID,
+ driver: d,
+ endpoints: endpointTable{},
+ config: config,
+ }
+ // add the *network
+ d.addNetwork(n)
+
+ return nil
+}
+
+// DeleteNetwork the network for the specified driver type
+func (d *driver) DeleteNetwork(nid string) error {
+ defer osl.InitOSContext()()
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("network id %s not found", nid)
+ }
+ // if the driver created the slave interface, delete it, otherwise leave it
+ if ok := n.config.CreatedSlaveLink; ok {
+ // if the interface exists, only delete if it matches iface.vlan or dummy.net_id naming
+ if ok := parentExists(n.config.Parent); ok {
+ // only delete the link if it is named the net_id
+ if n.config.Parent == getDummyName(stringid.TruncateID(nid)) {
+ err := delDummyLink(n.config.Parent)
+ if err != nil {
+ logrus.Debugf("link %s was not deleted, continuing the delete network operation: %v",
+ n.config.Parent, err)
+ }
+ } else {
+ // only delete the link if it matches iface.vlan naming
+ err := delVlanLink(n.config.Parent)
+ if err != nil {
+ logrus.Debugf("link %s was not deleted, continuing the delete network operation: %v",
+ n.config.Parent, err)
+ }
+ }
+ }
+ }
+ for _, ep := range n.endpoints {
+ if link, err := ns.NlHandle().LinkByName(ep.srcName); err == nil {
+ if err := ns.NlHandle().LinkDel(link); err != nil {
+ logrus.WithError(err).Warnf("Failed to delete interface (%s)'s link on endpoint (%s) delete", ep.srcName, ep.id)
+ }
+ }
+
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Warnf("Failed to remove ipvlan endpoint %.7s from store: %v", ep.id, err)
+ }
+ }
+ // delete the *network
+ d.deleteNetwork(nid)
+ // delete the network record from persistent cache
+ err := d.storeDelete(n.config)
+ if err != nil {
+ return fmt.Errorf("error deleting deleting id %s from datastore: %v", nid, err)
+ }
+ return nil
+}
+
+// parseNetworkOptions parse docker network options
+func parseNetworkOptions(id string, option options.Generic) (*configuration, error) {
+ var (
+ err error
+ config = &configuration{}
+ )
+ // parse generic labels first
+ if genData, ok := option[netlabel.GenericData]; ok && genData != nil {
+ if config, err = parseNetworkGenericOptions(genData); err != nil {
+ return nil, err
+ }
+ }
+ // setting the parent to "" will trigger an isolated network dummy parent link
+ if _, ok := option[netlabel.Internal]; ok {
+ config.Internal = true
+ // empty --parent= and --internal are handled the same.
+ config.Parent = ""
+ }
+ return config, nil
+}
+
+// parseNetworkGenericOptions parse generic driver docker network options
+func parseNetworkGenericOptions(data interface{}) (*configuration, error) {
+ var (
+ err error
+ config *configuration
+ )
+ switch opt := data.(type) {
+ case *configuration:
+ config = opt
+ case map[string]string:
+ config = &configuration{}
+ err = config.fromOptions(opt)
+ case options.Generic:
+ var opaqueConfig interface{}
+ if opaqueConfig, err = options.GenerateFromModel(opt, config); err == nil {
+ config = opaqueConfig.(*configuration)
+ }
+ default:
+ err = types.BadRequestErrorf("unrecognized network configuration format: %v", opt)
+ }
+ return config, err
+}
+
+// fromOptions binds the generic options to networkConfiguration to cache
+func (config *configuration) fromOptions(labels map[string]string) error {
+ for label, value := range labels {
+ switch label {
+ case parentOpt:
+ // parse driver option '-o parent'
+ config.Parent = value
+ case driverModeOpt:
+ // parse driver option '-o ipvlan_mode'
+ config.IpvlanMode = value
+ }
+ }
+ return nil
+}
+
+// processIPAM parses v4 and v6 IP information and binds it to the network configuration
+func (config *configuration) processIPAM(id string, ipamV4Data, ipamV6Data []driverapi.IPAMData) error {
+ if len(ipamV4Data) > 0 {
+ for _, ipd := range ipamV4Data {
+ s := &ipv4Subnet{
+ SubnetIP: ipd.Pool.String(),
+ GwIP: ipd.Gateway.String(),
+ }
+ config.Ipv4Subnets = append(config.Ipv4Subnets, s)
+ }
+ }
+ if len(ipamV6Data) > 0 {
+ for _, ipd := range ipamV6Data {
+ s := &ipv6Subnet{
+ SubnetIP: ipd.Pool.String(),
+ GwIP: ipd.Gateway.String(),
+ }
+ config.Ipv6Subnets = append(config.Ipv6Subnets, s)
+ }
+ }
+ return nil
+}
--- /dev/null
+package ipvlan
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/docker/libnetwork/ns"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+const (
+ dummyPrefix = "di-" // ipvlan prefix for dummy parent interface
+ ipvlanKernelVer = 4 // minimum ipvlan kernel support
+ ipvlanMajorVer = 2 // minimum ipvlan major kernel support
+)
+
+// createIPVlan Create the ipvlan slave specifying the source name
+func createIPVlan(containerIfName, parent, ipvlanMode string) (string, error) {
+ // Set the ipvlan mode. Default is bridge mode
+ mode, err := setIPVlanMode(ipvlanMode)
+ if err != nil {
+ return "", fmt.Errorf("Unsupported %s ipvlan mode: %v", ipvlanMode, err)
+ }
+ // verify the Docker host interface acting as the macvlan parent iface exists
+ if !parentExists(parent) {
+ return "", fmt.Errorf("the requested parent interface %s was not found on the Docker host", parent)
+ }
+ // Get the link for the master index (Example: the docker host eth iface)
+ parentLink, err := ns.NlHandle().LinkByName(parent)
+ if err != nil {
+ return "", fmt.Errorf("error occurred looking up the %s parent iface %s error: %s", ipvlanType, parent, err)
+ }
+ // Create an ipvlan link
+ ipvlan := &netlink.IPVlan{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: containerIfName,
+ ParentIndex: parentLink.Attrs().Index,
+ },
+ Mode: mode,
+ }
+ if err := ns.NlHandle().LinkAdd(ipvlan); err != nil {
+ // If a user creates a macvlan and ipvlan on same parent, only one slave iface can be active at a time.
+ return "", fmt.Errorf("failed to create the %s port: %v", ipvlanType, err)
+ }
+
+ return ipvlan.Attrs().Name, nil
+}
+
+// setIPVlanMode setter for one of the two ipvlan port types
+func setIPVlanMode(mode string) (netlink.IPVlanMode, error) {
+ switch mode {
+ case modeL2:
+ return netlink.IPVLAN_MODE_L2, nil
+ case modeL3:
+ return netlink.IPVLAN_MODE_L3, nil
+ default:
+ return 0, fmt.Errorf("Unknown ipvlan mode: %s", mode)
+ }
+}
+
+// parentExists check if the specified interface exists in the default namespace
+func parentExists(ifaceStr string) bool {
+ _, err := ns.NlHandle().LinkByName(ifaceStr)
+ if err != nil {
+ return false
+ }
+
+ return true
+}
+
+// createVlanLink parses sub-interfaces and vlan id for creation
+func createVlanLink(parentName string) error {
+ if strings.Contains(parentName, ".") {
+ parent, vidInt, err := parseVlan(parentName)
+ if err != nil {
+ return err
+ }
+ // VLAN identifier or VID is a 12-bit field specifying the VLAN to which the frame belongs
+ if vidInt > 4094 || vidInt < 1 {
+ return fmt.Errorf("vlan id must be between 1-4094, received: %d", vidInt)
+ }
+ // get the parent link to attach a vlan subinterface
+ parentLink, err := ns.NlHandle().LinkByName(parent)
+ if err != nil {
+ return fmt.Errorf("failed to find master interface %s on the Docker host: %v", parent, err)
+ }
+ vlanLink := &netlink.Vlan{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: parentName,
+ ParentIndex: parentLink.Attrs().Index,
+ },
+ VlanId: vidInt,
+ }
+ // create the subinterface
+ if err := ns.NlHandle().LinkAdd(vlanLink); err != nil {
+ return fmt.Errorf("failed to create %s vlan link: %v", vlanLink.Name, err)
+ }
+ // Bring the new netlink iface up
+ if err := ns.NlHandle().LinkSetUp(vlanLink); err != nil {
+ return fmt.Errorf("failed to enable %s the ipvlan parent link %v", vlanLink.Name, err)
+ }
+ logrus.Debugf("Added a vlan tagged netlink subinterface: %s with a vlan id: %d", parentName, vidInt)
+ return nil
+ }
+
+ return fmt.Errorf("invalid subinterface vlan name %s, example formatting is eth0.10", parentName)
+}
+
+// delVlanLink verifies only sub-interfaces with a vlan id get deleted
+func delVlanLink(linkName string) error {
+ if strings.Contains(linkName, ".") {
+ _, _, err := parseVlan(linkName)
+ if err != nil {
+ return err
+ }
+ // delete the vlan subinterface
+ vlanLink, err := ns.NlHandle().LinkByName(linkName)
+ if err != nil {
+ return fmt.Errorf("failed to find interface %s on the Docker host : %v", linkName, err)
+ }
+ // verify a parent interface isn't being deleted
+ if vlanLink.Attrs().ParentIndex == 0 {
+ return fmt.Errorf("interface %s does not appear to be a slave device: %v", linkName, err)
+ }
+ // delete the ipvlan slave device
+ if err := ns.NlHandle().LinkDel(vlanLink); err != nil {
+ return fmt.Errorf("failed to delete %s link: %v", linkName, err)
+ }
+ logrus.Debugf("Deleted a vlan tagged netlink subinterface: %s", linkName)
+ }
+ // if the subinterface doesn't parse to iface.vlan_id leave the interface in
+ // place since it could be a user specified name not created by the driver.
+ return nil
+}
+
+// parseVlan parses and verifies a slave interface name: -o parent=eth0.10
+func parseVlan(linkName string) (string, int, error) {
+ // parse -o parent=eth0.10
+ splitName := strings.Split(linkName, ".")
+ if len(splitName) != 2 {
+ return "", 0, fmt.Errorf("required interface name format is: name.vlan_id, ex. eth0.10 for vlan 10, instead received %s", linkName)
+ }
+ parent, vidStr := splitName[0], splitName[1]
+ // validate type and convert vlan id to int
+ vidInt, err := strconv.Atoi(vidStr)
+ if err != nil {
+ return "", 0, fmt.Errorf("unable to parse a valid vlan id from: %s (ex. eth0.10 for vlan 10)", vidStr)
+ }
+ // Check if the interface exists
+ if !parentExists(parent) {
+ return "", 0, fmt.Errorf("-o parent interface was not found on the host: %s", parent)
+ }
+
+ return parent, vidInt, nil
+}
+
+// createDummyLink creates a dummy0 parent link
+func createDummyLink(dummyName, truncNetID string) error {
+ // create a parent interface since one was not specified
+ parent := &netlink.Dummy{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: dummyName,
+ },
+ }
+ if err := ns.NlHandle().LinkAdd(parent); err != nil {
+ return err
+ }
+ parentDummyLink, err := ns.NlHandle().LinkByName(dummyName)
+ if err != nil {
+ return fmt.Errorf("error occurred looking up the %s parent iface %s error: %s", ipvlanType, dummyName, err)
+ }
+ // bring the new netlink iface up
+ if err := ns.NlHandle().LinkSetUp(parentDummyLink); err != nil {
+ return fmt.Errorf("failed to enable %s the ipvlan parent link: %v", dummyName, err)
+ }
+
+ return nil
+}
+
+// delDummyLink deletes the link type dummy used when -o parent is not passed
+func delDummyLink(linkName string) error {
+ // delete the vlan subinterface
+ dummyLink, err := ns.NlHandle().LinkByName(linkName)
+ if err != nil {
+ return fmt.Errorf("failed to find link %s on the Docker host : %v", linkName, err)
+ }
+ // verify a parent interface is being deleted
+ if dummyLink.Attrs().ParentIndex != 0 {
+ return fmt.Errorf("link %s is not a parent dummy interface", linkName)
+ }
+ // delete the ipvlan dummy device
+ if err := ns.NlHandle().LinkDel(dummyLink); err != nil {
+ return fmt.Errorf("failed to delete the dummy %s link: %v", linkName, err)
+ }
+ logrus.Debugf("Deleted a dummy parent link: %s", linkName)
+
+ return nil
+}
+
+// getDummyName returns the name of a dummy parent with truncated net ID and driver prefix
+func getDummyName(netID string) string {
+ return dummyPrefix + netID
+}
--- /dev/null
+package ipvlan
+
+import (
+ "testing"
+
+ "github.com/vishvananda/netlink"
+)
+
+// TestValidateLink tests the parentExists function
+func TestValidateLink(t *testing.T) {
+ validIface := "lo"
+ invalidIface := "foo12345"
+
+ // test a valid parent interface validation
+ if ok := parentExists(validIface); !ok {
+ t.Fatalf("failed validating loopback %s", validIface)
+ }
+ // test an invalid parent interface validation
+ if ok := parentExists(invalidIface); ok {
+ t.Fatalf("failed to invalidate interface %s", invalidIface)
+ }
+}
+
+// TestValidateSubLink tests valid 802.1q naming convention
+func TestValidateSubLink(t *testing.T) {
+ validSubIface := "lo.10"
+ invalidSubIface1 := "lo"
+ invalidSubIface2 := "lo:10"
+ invalidSubIface3 := "foo123.456"
+
+ // test a valid parent_iface.vlan_id
+ _, _, err := parseVlan(validSubIface)
+ if err != nil {
+ t.Fatalf("failed subinterface validation: %v", err)
+ }
+ // test an invalid vid with a valid parent link
+ _, _, err = parseVlan(invalidSubIface1)
+ if err == nil {
+ t.Fatalf("failed subinterface validation test: %s", invalidSubIface1)
+ }
+ // test a valid vid with a valid parent link with an invalid delimiter
+ _, _, err = parseVlan(invalidSubIface2)
+ if err == nil {
+ t.Fatalf("failed subinterface validation test: %v", invalidSubIface2)
+ }
+ // test an invalid parent link with a valid vid
+ _, _, err = parseVlan(invalidSubIface3)
+ if err == nil {
+ t.Fatalf("failed subinterface validation test: %v", invalidSubIface3)
+ }
+}
+
+// TestSetIPVlanMode tests the ipvlan mode setter
+func TestSetIPVlanMode(t *testing.T) {
+ // test ipvlan l2 mode
+ mode, err := setIPVlanMode(modeL2)
+ if err != nil {
+ t.Fatalf("error parsing %v vlan mode: %v", mode, err)
+ }
+ if mode != netlink.IPVLAN_MODE_L2 {
+ t.Fatalf("expected %d got %d", netlink.IPVLAN_MODE_L2, mode)
+ }
+ // test ipvlan l3 mode
+ mode, err = setIPVlanMode(modeL3)
+ if err != nil {
+ t.Fatalf("error parsing %v vlan mode: %v", mode, err)
+ }
+ if mode != netlink.IPVLAN_MODE_L3 {
+ t.Fatalf("expected %d got %d", netlink.IPVLAN_MODE_L3, mode)
+ }
+ // test invalid mode
+ mode, err = setIPVlanMode("foo")
+ if err == nil {
+ t.Fatal("invalid ipvlan mode should have returned an error")
+ }
+ if mode != 0 {
+ t.Fatalf("expected 0 got %d", mode)
+ }
+ // test null mode
+ mode, err = setIPVlanMode("")
+ if err == nil {
+ t.Fatal("invalid ipvlan mode should have returned an error")
+ }
+ if mode != 0 {
+ t.Fatalf("expected 0 got %d", mode)
+ }
+}
--- /dev/null
+package ipvlan
+
+import (
+ "fmt"
+
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+func (d *driver) network(nid string) *network {
+ d.Lock()
+ n, ok := d.networks[nid]
+ d.Unlock()
+ if !ok {
+ logrus.Errorf("network id %s not found", nid)
+ }
+
+ return n
+}
+
+func (d *driver) addNetwork(n *network) {
+ d.Lock()
+ d.networks[n.id] = n
+ d.Unlock()
+}
+
+func (d *driver) deleteNetwork(nid string) {
+ d.Lock()
+ delete(d.networks, nid)
+ d.Unlock()
+}
+
+// getNetworks Safely returns a slice of existing networks
+func (d *driver) getNetworks() []*network {
+ d.Lock()
+ defer d.Unlock()
+
+ ls := make([]*network, 0, len(d.networks))
+ for _, nw := range d.networks {
+ ls = append(ls, nw)
+ }
+
+ return ls
+}
+
+func (n *network) endpoint(eid string) *endpoint {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.endpoints[eid]
+}
+
+func (n *network) addEndpoint(ep *endpoint) {
+ n.Lock()
+ n.endpoints[ep.id] = ep
+ n.Unlock()
+}
+
+func (n *network) deleteEndpoint(eid string) {
+ n.Lock()
+ delete(n.endpoints, eid)
+ n.Unlock()
+}
+
+func (n *network) getEndpoint(eid string) (*endpoint, error) {
+ n.Lock()
+ defer n.Unlock()
+ if eid == "" {
+ return nil, fmt.Errorf("endpoint id %s not found", eid)
+ }
+ if ep, ok := n.endpoints[eid]; ok {
+ return ep, nil
+ }
+
+ return nil, nil
+}
+
+func validateID(nid, eid string) error {
+ if nid == "" {
+ return fmt.Errorf("invalid network id")
+ }
+ if eid == "" {
+ return fmt.Errorf("invalid endpoint id")
+ }
+
+ return nil
+}
+
+func (n *network) sandbox() osl.Sandbox {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.sbox
+}
+
+func (n *network) setSandbox(sbox osl.Sandbox) {
+ n.Lock()
+ n.sbox = sbox
+ n.Unlock()
+}
+
+func (d *driver) getNetwork(id string) (*network, error) {
+ d.Lock()
+ defer d.Unlock()
+ if id == "" {
+ return nil, types.BadRequestErrorf("invalid network id: %s", id)
+ }
+
+ if nw, ok := d.networks[id]; ok {
+ return nw, nil
+ }
+
+ return nil, types.NotFoundErrorf("network not found: %s", id)
+}
--- /dev/null
+package ipvlan
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ ipvlanPrefix = "ipvlan"
+ ipvlanNetworkPrefix = ipvlanPrefix + "/network"
+ ipvlanEndpointPrefix = ipvlanPrefix + "/endpoint"
+)
+
+// networkConfiguration for this driver's network specific configuration
+type configuration struct {
+ ID string
+ Mtu int
+ dbIndex uint64
+ dbExists bool
+ Internal bool
+ Parent string
+ IpvlanMode string
+ CreatedSlaveLink bool
+ Ipv4Subnets []*ipv4Subnet
+ Ipv6Subnets []*ipv6Subnet
+}
+
+type ipv4Subnet struct {
+ SubnetIP string
+ GwIP string
+}
+
+type ipv6Subnet struct {
+ SubnetIP string
+ GwIP string
+}
+
+// initStore drivers are responsible for caching their own persistent state
+func (d *driver) initStore(option map[string]interface{}) error {
+ if data, ok := option[netlabel.LocalKVClient]; ok {
+ var err error
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+ }
+ d.store, err = datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return types.InternalErrorf("ipvlan driver failed to initialize data store: %v", err)
+ }
+
+ return d.populateNetworks()
+ }
+
+ return nil
+}
+
+// populateNetworks is invoked at driver init to recreate persistently stored networks
+func (d *driver) populateNetworks() error {
+ kvol, err := d.store.List(datastore.Key(ipvlanNetworkPrefix), &configuration{})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return fmt.Errorf("failed to get ipvlan network configurations from store: %v", err)
+ }
+ // If empty it simply means no ipvlan networks have been created yet
+ if err == datastore.ErrKeyNotFound {
+ return nil
+ }
+ for _, kvo := range kvol {
+ config := kvo.(*configuration)
+ if err = d.createNetwork(config); err != nil {
+ logrus.Warnf("could not create ipvlan network for id %s from persistent state", config.ID)
+ }
+ }
+
+ return nil
+}
+
+func (d *driver) populateEndpoints() error {
+ kvol, err := d.store.List(datastore.Key(ipvlanEndpointPrefix), &endpoint{})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return fmt.Errorf("failed to get ipvlan endpoints from store: %v", err)
+ }
+
+ if err == datastore.ErrKeyNotFound {
+ return nil
+ }
+
+ for _, kvo := range kvol {
+ ep := kvo.(*endpoint)
+ n, ok := d.networks[ep.nid]
+ if !ok {
+ logrus.Debugf("Network (%.7s) not found for restored ipvlan endpoint (%.7s)", ep.nid, ep.id)
+ logrus.Debugf("Deleting stale ipvlan endpoint (%.7s) from store", ep.id)
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Debugf("Failed to delete stale ipvlan endpoint (%.7s) from store", ep.id)
+ }
+ continue
+ }
+ n.endpoints[ep.id] = ep
+ logrus.Debugf("Endpoint (%.7s) restored to network (%.7s)", ep.id, ep.nid)
+ }
+
+ return nil
+}
+
+// storeUpdate used to update persistent ipvlan network records as they are created
+func (d *driver) storeUpdate(kvObject datastore.KVObject) error {
+ if d.store == nil {
+ logrus.Warnf("ipvlan store not initialized. kv object %s is not added to the store", datastore.Key(kvObject.Key()...))
+ return nil
+ }
+ if err := d.store.PutObjectAtomic(kvObject); err != nil {
+ return fmt.Errorf("failed to update ipvlan store for object type %T: %v", kvObject, err)
+ }
+
+ return nil
+}
+
+// storeDelete used to delete ipvlan network records from persistent cache as they are deleted
+func (d *driver) storeDelete(kvObject datastore.KVObject) error {
+ if d.store == nil {
+ logrus.Debugf("ipvlan store not initialized. kv object %s is not deleted from store", datastore.Key(kvObject.Key()...))
+ return nil
+ }
+retry:
+ if err := d.store.DeleteObjectAtomic(kvObject); err != nil {
+ if err == datastore.ErrKeyModified {
+ if err := d.store.GetObject(datastore.Key(kvObject.Key()...), kvObject); err != nil {
+ return fmt.Errorf("could not update the kvobject to latest when trying to delete: %v", err)
+ }
+ goto retry
+ }
+ return err
+ }
+
+ return nil
+}
+
+func (config *configuration) MarshalJSON() ([]byte, error) {
+ nMap := make(map[string]interface{})
+ nMap["ID"] = config.ID
+ nMap["Mtu"] = config.Mtu
+ nMap["Parent"] = config.Parent
+ nMap["IpvlanMode"] = config.IpvlanMode
+ nMap["Internal"] = config.Internal
+ nMap["CreatedSubIface"] = config.CreatedSlaveLink
+ if len(config.Ipv4Subnets) > 0 {
+ iis, err := json.Marshal(config.Ipv4Subnets)
+ if err != nil {
+ return nil, err
+ }
+ nMap["Ipv4Subnets"] = string(iis)
+ }
+ if len(config.Ipv6Subnets) > 0 {
+ iis, err := json.Marshal(config.Ipv6Subnets)
+ if err != nil {
+ return nil, err
+ }
+ nMap["Ipv6Subnets"] = string(iis)
+ }
+
+ return json.Marshal(nMap)
+}
+
+func (config *configuration) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ nMap map[string]interface{}
+ )
+
+ if err = json.Unmarshal(b, &nMap); err != nil {
+ return err
+ }
+ config.ID = nMap["ID"].(string)
+ config.Mtu = int(nMap["Mtu"].(float64))
+ config.Parent = nMap["Parent"].(string)
+ config.IpvlanMode = nMap["IpvlanMode"].(string)
+ config.Internal = nMap["Internal"].(bool)
+ config.CreatedSlaveLink = nMap["CreatedSubIface"].(bool)
+ if v, ok := nMap["Ipv4Subnets"]; ok {
+ if err := json.Unmarshal([]byte(v.(string)), &config.Ipv4Subnets); err != nil {
+ return err
+ }
+ }
+ if v, ok := nMap["Ipv6Subnets"]; ok {
+ if err := json.Unmarshal([]byte(v.(string)), &config.Ipv6Subnets); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (config *configuration) Key() []string {
+ return []string{ipvlanNetworkPrefix, config.ID}
+}
+
+func (config *configuration) KeyPrefix() []string {
+ return []string{ipvlanNetworkPrefix}
+}
+
+func (config *configuration) Value() []byte {
+ b, err := json.Marshal(config)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (config *configuration) SetValue(value []byte) error {
+ return json.Unmarshal(value, config)
+}
+
+func (config *configuration) Index() uint64 {
+ return config.dbIndex
+}
+
+func (config *configuration) SetIndex(index uint64) {
+ config.dbIndex = index
+ config.dbExists = true
+}
+
+func (config *configuration) Exists() bool {
+ return config.dbExists
+}
+
+func (config *configuration) Skip() bool {
+ return false
+}
+
+func (config *configuration) New() datastore.KVObject {
+ return &configuration{}
+}
+
+func (config *configuration) CopyTo(o datastore.KVObject) error {
+ dstNcfg := o.(*configuration)
+ *dstNcfg = *config
+ return nil
+}
+
+func (config *configuration) DataScope() string {
+ return datastore.LocalScope
+}
+
+func (ep *endpoint) MarshalJSON() ([]byte, error) {
+ epMap := make(map[string]interface{})
+ epMap["id"] = ep.id
+ epMap["nid"] = ep.nid
+ epMap["SrcName"] = ep.srcName
+ if len(ep.mac) != 0 {
+ epMap["MacAddress"] = ep.mac.String()
+ }
+ if ep.addr != nil {
+ epMap["Addr"] = ep.addr.String()
+ }
+ if ep.addrv6 != nil {
+ epMap["Addrv6"] = ep.addrv6.String()
+ }
+ return json.Marshal(epMap)
+}
+
+func (ep *endpoint) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ epMap map[string]interface{}
+ )
+
+ if err = json.Unmarshal(b, &epMap); err != nil {
+ return fmt.Errorf("Failed to unmarshal to ipvlan endpoint: %v", err)
+ }
+
+ if v, ok := epMap["MacAddress"]; ok {
+ if ep.mac, err = net.ParseMAC(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode ipvlan endpoint MAC address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ if v, ok := epMap["Addr"]; ok {
+ if ep.addr, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode ipvlan endpoint IPv4 address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ if v, ok := epMap["Addrv6"]; ok {
+ if ep.addrv6, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode ipvlan endpoint IPv6 address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ ep.id = epMap["id"].(string)
+ ep.nid = epMap["nid"].(string)
+ ep.srcName = epMap["SrcName"].(string)
+
+ return nil
+}
+
+func (ep *endpoint) Key() []string {
+ return []string{ipvlanEndpointPrefix, ep.id}
+}
+
+func (ep *endpoint) KeyPrefix() []string {
+ return []string{ipvlanEndpointPrefix}
+}
+
+func (ep *endpoint) Value() []byte {
+ b, err := json.Marshal(ep)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (ep *endpoint) SetValue(value []byte) error {
+ return json.Unmarshal(value, ep)
+}
+
+func (ep *endpoint) Index() uint64 {
+ return ep.dbIndex
+}
+
+func (ep *endpoint) SetIndex(index uint64) {
+ ep.dbIndex = index
+ ep.dbExists = true
+}
+
+func (ep *endpoint) Exists() bool {
+ return ep.dbExists
+}
+
+func (ep *endpoint) Skip() bool {
+ return false
+}
+
+func (ep *endpoint) New() datastore.KVObject {
+ return &endpoint{}
+}
+
+func (ep *endpoint) CopyTo(o datastore.KVObject) error {
+ dstEp := o.(*endpoint)
+ *dstEp = *ep
+ return nil
+}
+
+func (ep *endpoint) DataScope() string {
+ return datastore.LocalScope
+}
--- /dev/null
+package ipvlan
+
+import (
+ "testing"
+
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/libnetwork/driverapi"
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+const testNetworkType = "ipvlan"
+
+type driverTester struct {
+ t *testing.T
+ d *driver
+}
+
+func (dt *driverTester) GetPluginGetter() plugingetter.PluginGetter {
+ return nil
+}
+
+func (dt *driverTester) RegisterDriver(name string, drv driverapi.Driver,
+ cap driverapi.Capability) error {
+ if name != testNetworkType {
+ dt.t.Fatalf("Expected driver register name to be %q. Instead got %q",
+ testNetworkType, name)
+ }
+
+ if _, ok := drv.(*driver); !ok {
+ dt.t.Fatalf("Expected driver type to be %T. Instead got %T",
+ &driver{}, drv)
+ }
+
+ dt.d = drv.(*driver)
+ return nil
+}
+
+func TestIpvlanInit(t *testing.T) {
+ if err := Init(&driverTester{t: t}, nil); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestIpvlanNilConfig(t *testing.T) {
+ dt := &driverTester{t: t}
+ if err := Init(dt, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := dt.d.initStore(nil); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestIpvlanType(t *testing.T) {
+ dt := &driverTester{t: t}
+ if err := Init(dt, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ if dt.d.Type() != testNetworkType {
+ t.Fatalf("Expected Type() to return %q. Instead got %q", testNetworkType,
+ dt.d.Type())
+ }
+}
--- /dev/null
+package ivmanager
+
+import (
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/types"
+)
+
+const networkType = "ipvlan"
+
+type driver struct{}
+
+// Init registers a new instance of ipvlan manager driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ c := driverapi.Capability{
+ DataScope: datastore.LocalScope,
+ ConnectivityScope: datastore.GlobalScope,
+ }
+ return dc.RegisterDriver(networkType, &driver{}, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Leave(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Type() string {
+ return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
--- /dev/null
+package macvlan
+
+import (
+ "net"
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+)
+
+const (
+ vethLen = 7
+ containerVethPrefix = "eth"
+ vethPrefix = "veth"
+ macvlanType = "macvlan" // driver type name
+ modePrivate = "private" // macvlan mode private
+ modeVepa = "vepa" // macvlan mode vepa
+ modeBridge = "bridge" // macvlan mode bridge
+ modePassthru = "passthru" // macvlan mode passthrough
+ parentOpt = "parent" // parent interface -o parent
+ modeOpt = "_mode" // macvlan mode ux opt suffix
+)
+
+var driverModeOpt = macvlanType + modeOpt // mode --option macvlan_mode
+
+type endpointTable map[string]*endpoint
+
+type networkTable map[string]*network
+
+type driver struct {
+ networks networkTable
+ sync.Once
+ sync.Mutex
+ store datastore.DataStore
+}
+
+type endpoint struct {
+ id string
+ nid string
+ mac net.HardwareAddr
+ addr *net.IPNet
+ addrv6 *net.IPNet
+ srcName string
+ dbIndex uint64
+ dbExists bool
+}
+
+type network struct {
+ id string
+ sbox osl.Sandbox
+ endpoints endpointTable
+ driver *driver
+ config *configuration
+ sync.Mutex
+}
+
+// Init initializes and registers the libnetwork macvlan driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ c := driverapi.Capability{
+ DataScope: datastore.LocalScope,
+ ConnectivityScope: datastore.GlobalScope,
+ }
+ d := &driver{
+ networks: networkTable{},
+ }
+ d.initStore(config)
+
+ return dc.RegisterDriver(macvlanType, d, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return make(map[string]interface{}, 0), nil
+}
+
+func (d *driver) Type() string {
+ return macvlanType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+}
+
+// DiscoverNew is a notification for a new discovery event
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+// DiscoverDelete is a notification for a discovery delete event
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
--- /dev/null
+package macvlan
+
+import (
+ "fmt"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateEndpoint assigns the mac, ip and endpoint id for the new container
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
+ epOptions map[string]interface{}) error {
+ defer osl.InitOSContext()()
+
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+ n, err := d.getNetwork(nid)
+ if err != nil {
+ return fmt.Errorf("network id %q not found", nid)
+ }
+ ep := &endpoint{
+ id: eid,
+ nid: nid,
+ addr: ifInfo.Address(),
+ addrv6: ifInfo.AddressIPv6(),
+ mac: ifInfo.MacAddress(),
+ }
+ if ep.addr == nil {
+ return fmt.Errorf("create endpoint was not passed an IP address")
+ }
+ if ep.mac == nil {
+ ep.mac = netutils.GenerateMACFromIP(ep.addr.IP)
+ if err := ifInfo.SetMacAddress(ep.mac); err != nil {
+ return err
+ }
+ }
+ // disallow portmapping -p
+ if opt, ok := epOptions[netlabel.PortMap]; ok {
+ if _, ok := opt.([]types.PortBinding); ok {
+ if len(opt.([]types.PortBinding)) > 0 {
+ logrus.Warnf("%s driver does not support port mappings", macvlanType)
+ }
+ }
+ }
+ // disallow port exposure --expose
+ if opt, ok := epOptions[netlabel.ExposedPorts]; ok {
+ if _, ok := opt.([]types.TransportPort); ok {
+ if len(opt.([]types.TransportPort)) > 0 {
+ logrus.Warnf("%s driver does not support port exposures", macvlanType)
+ }
+ }
+ }
+
+ if err := d.storeUpdate(ep); err != nil {
+ return fmt.Errorf("failed to save macvlan endpoint %.7s to store: %v", ep.id, err)
+ }
+
+ n.addEndpoint(ep)
+
+ return nil
+}
+
+// DeleteEndpoint removes the endpoint and associated netlink interface
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ defer osl.InitOSContext()()
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("network id %q not found", nid)
+ }
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return fmt.Errorf("endpoint id %q not found", eid)
+ }
+ if link, err := ns.NlHandle().LinkByName(ep.srcName); err == nil {
+ if err := ns.NlHandle().LinkDel(link); err != nil {
+ logrus.WithError(err).Warnf("Failed to delete interface (%s)'s link on endpoint (%s) delete", ep.srcName, ep.id)
+ }
+ }
+
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Warnf("Failed to remove macvlan endpoint %.7s from store: %v", ep.id, err)
+ }
+
+ n.deleteEndpoint(ep.id)
+
+ return nil
+}
--- /dev/null
+package macvlan
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/osl"
+ "github.com/sirupsen/logrus"
+)
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ defer osl.InitOSContext()()
+ n, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+ }
+ endpoint := n.endpoint(eid)
+ if endpoint == nil {
+ return fmt.Errorf("could not find endpoint with id %s", eid)
+ }
+ // generate a name for the iface that will be renamed to eth0 in the sbox
+ containerIfName, err := netutils.GenerateIfaceName(ns.NlHandle(), vethPrefix, vethLen)
+ if err != nil {
+ return fmt.Errorf("error generating an interface name: %s", err)
+ }
+ // create the netlink macvlan interface
+ vethName, err := createMacVlan(containerIfName, n.config.Parent, n.config.MacvlanMode)
+ if err != nil {
+ return err
+ }
+ // bind the generated iface name to the endpoint
+ endpoint.srcName = vethName
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return fmt.Errorf("could not find endpoint with id %s", eid)
+ }
+ // parse and match the endpoint address with the available v4 subnets
+ if len(n.config.Ipv4Subnets) > 0 {
+ s := n.getSubnetforIPv4(ep.addr)
+ if s == nil {
+ return fmt.Errorf("could not find a valid ipv4 subnet for endpoint %s", eid)
+ }
+ v4gw, _, err := net.ParseCIDR(s.GwIP)
+ if err != nil {
+ return fmt.Errorf("gateway %s is not a valid ipv4 address: %v", s.GwIP, err)
+ }
+ err = jinfo.SetGateway(v4gw)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("Macvlan Endpoint Joined with IPv4_Addr: %s, Gateway: %s, MacVlan_Mode: %s, Parent: %s",
+ ep.addr.IP.String(), v4gw.String(), n.config.MacvlanMode, n.config.Parent)
+ }
+ // parse and match the endpoint address with the available v6 subnets
+ if len(n.config.Ipv6Subnets) > 0 {
+ s := n.getSubnetforIPv6(ep.addrv6)
+ if s == nil {
+ return fmt.Errorf("could not find a valid ipv6 subnet for endpoint %s", eid)
+ }
+ v6gw, _, err := net.ParseCIDR(s.GwIP)
+ if err != nil {
+ return fmt.Errorf("gateway %s is not a valid ipv6 address: %v", s.GwIP, err)
+ }
+ err = jinfo.SetGatewayIPv6(v6gw)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("Macvlan Endpoint Joined with IPv6_Addr: %s Gateway: %s MacVlan_Mode: %s, Parent: %s",
+ ep.addrv6.IP.String(), v6gw.String(), n.config.MacvlanMode, n.config.Parent)
+ }
+ iNames := jinfo.InterfaceName()
+ err = iNames.SetNames(vethName, containerVethPrefix)
+ if err != nil {
+ return err
+ }
+ if err := d.storeUpdate(ep); err != nil {
+ return fmt.Errorf("failed to save macvlan endpoint %.7s to store: %v", ep.id, err)
+ }
+ return nil
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ defer osl.InitOSContext()()
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+ }
+ endpoint, err := network.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+ if endpoint == nil {
+ return fmt.Errorf("could not find endpoint with id %s", eid)
+ }
+
+ return nil
+}
+
+// getSubnetforIP returns the ipv4 subnet to which the given IP belongs
+func (n *network) getSubnetforIPv4(ip *net.IPNet) *ipv4Subnet {
+ for _, s := range n.config.Ipv4Subnets {
+ _, snet, err := net.ParseCIDR(s.SubnetIP)
+ if err != nil {
+ return nil
+ }
+ // first check if the mask lengths are the same
+ i, _ := snet.Mask.Size()
+ j, _ := ip.Mask.Size()
+ if i != j {
+ continue
+ }
+ if snet.Contains(ip.IP) {
+ return s
+ }
+ }
+
+ return nil
+}
+
+// getSubnetforIPv6 returns the ipv6 subnet to which the given IP belongs
+func (n *network) getSubnetforIPv6(ip *net.IPNet) *ipv6Subnet {
+ for _, s := range n.config.Ipv6Subnets {
+ _, snet, err := net.ParseCIDR(s.SubnetIP)
+ if err != nil {
+ return nil
+ }
+ // first check if the mask lengths are the same
+ i, _ := snet.Mask.Size()
+ j, _ := ip.Mask.Size()
+ if i != j {
+ continue
+ }
+ if snet.Contains(ip.IP) {
+ return s
+ }
+ }
+
+ return nil
+}
--- /dev/null
+package macvlan
+
+import (
+ "fmt"
+
+ "github.com/docker/docker/pkg/parsers/kernel"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateNetwork the network for the specified driver type
+func (d *driver) CreateNetwork(nid string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ defer osl.InitOSContext()()
+ kv, err := kernel.GetKernelVersion()
+ if err != nil {
+ return fmt.Errorf("failed to check kernel version for %s driver support: %v", macvlanType, err)
+ }
+ // ensure Kernel version is >= v3.9 for macvlan support
+ if kv.Kernel < macvlanKernelVer || (kv.Kernel == macvlanKernelVer && kv.Major < macvlanMajorVer) {
+ return fmt.Errorf("kernel version failed to meet the minimum macvlan kernel requirement of %d.%d, found %d.%d.%d",
+ macvlanKernelVer, macvlanMajorVer, kv.Kernel, kv.Major, kv.Minor)
+ }
+ // reject a null v4 network
+ if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {
+ return fmt.Errorf("ipv4 pool is empty")
+ }
+ // parse and validate the config and bind to networkConfiguration
+ config, err := parseNetworkOptions(nid, option)
+ if err != nil {
+ return err
+ }
+ config.ID = nid
+ err = config.processIPAM(nid, ipV4Data, ipV6Data)
+ if err != nil {
+ return err
+ }
+ // verify the macvlan mode from -o macvlan_mode option
+ switch config.MacvlanMode {
+ case "", modeBridge:
+ // default to macvlan bridge mode if -o macvlan_mode is empty
+ config.MacvlanMode = modeBridge
+ case modePrivate:
+ config.MacvlanMode = modePrivate
+ case modePassthru:
+ config.MacvlanMode = modePassthru
+ case modeVepa:
+ config.MacvlanMode = modeVepa
+ default:
+ return fmt.Errorf("requested macvlan mode '%s' is not valid, 'bridge' mode is the macvlan driver default", config.MacvlanMode)
+ }
+ // loopback is not a valid parent link
+ if config.Parent == "lo" {
+ return fmt.Errorf("loopback interface is not a valid %s parent link", macvlanType)
+ }
+ // if parent interface not specified, create a dummy type link to use named dummy+net_id
+ if config.Parent == "" {
+ config.Parent = getDummyName(stringid.TruncateID(config.ID))
+ // empty parent and --internal are handled the same. Set here to update k/v
+ config.Internal = true
+ }
+ err = d.createNetwork(config)
+ if err != nil {
+ return err
+ }
+ // update persistent db, rollback on fail
+ err = d.storeUpdate(config)
+ if err != nil {
+ d.deleteNetwork(config.ID)
+ logrus.Debugf("encountered an error rolling back a network create for %s : %v", config.ID, err)
+ return err
+ }
+
+ return nil
+}
+
+// createNetwork is used by new network callbacks and persistent network cache
+func (d *driver) createNetwork(config *configuration) error {
+ networkList := d.getNetworks()
+ for _, nw := range networkList {
+ if config.Parent == nw.config.Parent {
+ return fmt.Errorf("network %s is already using parent interface %s",
+ getDummyName(stringid.TruncateID(nw.config.ID)), config.Parent)
+ }
+ }
+ if !parentExists(config.Parent) {
+ // if the --internal flag is set, create a dummy link
+ if config.Internal {
+ err := createDummyLink(config.Parent, getDummyName(stringid.TruncateID(config.ID)))
+ if err != nil {
+ return err
+ }
+ config.CreatedSlaveLink = true
+ // notify the user in logs they have limited communications
+ if config.Parent == getDummyName(stringid.TruncateID(config.ID)) {
+ logrus.Debugf("Empty -o parent= and --internal flags limit communications to other containers inside of network: %s",
+ config.Parent)
+ }
+ } else {
+ // if the subinterface parent_iface.vlan_id checks do not pass, return err.
+ // a valid example is 'eth0.10' for a parent iface 'eth0' with a vlan id '10'
+ err := createVlanLink(config.Parent)
+ if err != nil {
+ return err
+ }
+ // if driver created the networks slave link, record it for future deletion
+ config.CreatedSlaveLink = true
+ }
+ }
+ n := &network{
+ id: config.ID,
+ driver: d,
+ endpoints: endpointTable{},
+ config: config,
+ }
+ // add the *network
+ d.addNetwork(n)
+
+ return nil
+}
+
+// DeleteNetwork deletes the network for the specified driver type
+func (d *driver) DeleteNetwork(nid string) error {
+ defer osl.InitOSContext()()
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("network id %s not found", nid)
+ }
+ // if the driver created the slave interface, delete it, otherwise leave it
+ if ok := n.config.CreatedSlaveLink; ok {
+ // if the interface exists, only delete if it matches iface.vlan or dummy.net_id naming
+ if ok := parentExists(n.config.Parent); ok {
+ // only delete the link if it is named the net_id
+ if n.config.Parent == getDummyName(stringid.TruncateID(nid)) {
+ err := delDummyLink(n.config.Parent)
+ if err != nil {
+ logrus.Debugf("link %s was not deleted, continuing the delete network operation: %v",
+ n.config.Parent, err)
+ }
+ } else {
+ // only delete the link if it matches iface.vlan naming
+ err := delVlanLink(n.config.Parent)
+ if err != nil {
+ logrus.Debugf("link %s was not deleted, continuing the delete network operation: %v",
+ n.config.Parent, err)
+ }
+ }
+ }
+ }
+ for _, ep := range n.endpoints {
+ if link, err := ns.NlHandle().LinkByName(ep.srcName); err == nil {
+ if err := ns.NlHandle().LinkDel(link); err != nil {
+ logrus.WithError(err).Warnf("Failed to delete interface (%s)'s link on endpoint (%s) delete", ep.srcName, ep.id)
+ }
+ }
+
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Warnf("Failed to remove macvlan endpoint %.7s from store: %v", ep.id, err)
+ }
+ }
+ // delete the *network
+ d.deleteNetwork(nid)
+ // delete the network record from persistent cache
+ err := d.storeDelete(n.config)
+ if err != nil {
+ return fmt.Errorf("error deleting deleting id %s from datastore: %v", nid, err)
+ }
+ return nil
+}
+
+// parseNetworkOptions parses docker network options
+func parseNetworkOptions(id string, option options.Generic) (*configuration, error) {
+ var (
+ err error
+ config = &configuration{}
+ )
+ // parse generic labels first
+ if genData, ok := option[netlabel.GenericData]; ok && genData != nil {
+ if config, err = parseNetworkGenericOptions(genData); err != nil {
+ return nil, err
+ }
+ }
+ // setting the parent to "" will trigger an isolated network dummy parent link
+ if _, ok := option[netlabel.Internal]; ok {
+ config.Internal = true
+ // empty --parent= and --internal are handled the same.
+ config.Parent = ""
+ }
+
+ return config, nil
+}
+
+// parseNetworkGenericOptions parses generic driver docker network options
+func parseNetworkGenericOptions(data interface{}) (*configuration, error) {
+ var (
+ err error
+ config *configuration
+ )
+ switch opt := data.(type) {
+ case *configuration:
+ config = opt
+ case map[string]string:
+ config = &configuration{}
+ err = config.fromOptions(opt)
+ case options.Generic:
+ var opaqueConfig interface{}
+ if opaqueConfig, err = options.GenerateFromModel(opt, config); err == nil {
+ config = opaqueConfig.(*configuration)
+ }
+ default:
+ err = types.BadRequestErrorf("unrecognized network configuration format: %v", opt)
+ }
+
+ return config, err
+}
+
+// fromOptions binds the generic options to networkConfiguration to cache
+func (config *configuration) fromOptions(labels map[string]string) error {
+ for label, value := range labels {
+ switch label {
+ case parentOpt:
+ // parse driver option '-o parent'
+ config.Parent = value
+ case driverModeOpt:
+ // parse driver option '-o macvlan_mode'
+ config.MacvlanMode = value
+ }
+ }
+
+ return nil
+}
+
+// processIPAM parses v4 and v6 IP information and binds it to the network configuration
+func (config *configuration) processIPAM(id string, ipamV4Data, ipamV6Data []driverapi.IPAMData) error {
+ if len(ipamV4Data) > 0 {
+ for _, ipd := range ipamV4Data {
+ s := &ipv4Subnet{
+ SubnetIP: ipd.Pool.String(),
+ GwIP: ipd.Gateway.String(),
+ }
+ config.Ipv4Subnets = append(config.Ipv4Subnets, s)
+ }
+ }
+ if len(ipamV6Data) > 0 {
+ for _, ipd := range ipamV6Data {
+ s := &ipv6Subnet{
+ SubnetIP: ipd.Pool.String(),
+ GwIP: ipd.Gateway.String(),
+ }
+ config.Ipv6Subnets = append(config.Ipv6Subnets, s)
+ }
+ }
+
+ return nil
+}
--- /dev/null
+package macvlan
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/docker/libnetwork/ns"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+const (
+ dummyPrefix = "dm-" // macvlan prefix for dummy parent interface
+ macvlanKernelVer = 3 // minimum macvlan kernel support
+ macvlanMajorVer = 9 // minimum macvlan major kernel support
+)
+
+// Create the macvlan slave specifying the source name
+func createMacVlan(containerIfName, parent, macvlanMode string) (string, error) {
+ // Set the macvlan mode. Default is bridge mode
+ mode, err := setMacVlanMode(macvlanMode)
+ if err != nil {
+ return "", fmt.Errorf("Unsupported %s macvlan mode: %v", macvlanMode, err)
+ }
+ // verify the Docker host interface acting as the macvlan parent iface exists
+ if !parentExists(parent) {
+ return "", fmt.Errorf("the requested parent interface %s was not found on the Docker host", parent)
+ }
+ // Get the link for the master index (Example: the docker host eth iface)
+ parentLink, err := ns.NlHandle().LinkByName(parent)
+ if err != nil {
+ return "", fmt.Errorf("error occurred looking up the %s parent iface %s error: %s", macvlanType, parent, err)
+ }
+ // Create a macvlan link
+ macvlan := &netlink.Macvlan{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: containerIfName,
+ ParentIndex: parentLink.Attrs().Index,
+ },
+ Mode: mode,
+ }
+ if err := ns.NlHandle().LinkAdd(macvlan); err != nil {
+ // If a user creates a macvlan and ipvlan on same parent, only one slave iface can be active at a time.
+ return "", fmt.Errorf("failed to create the %s port: %v", macvlanType, err)
+ }
+
+ return macvlan.Attrs().Name, nil
+}
+
+// setMacVlanMode setter for one of the four macvlan port types
+func setMacVlanMode(mode string) (netlink.MacvlanMode, error) {
+ switch mode {
+ case modePrivate:
+ return netlink.MACVLAN_MODE_PRIVATE, nil
+ case modeVepa:
+ return netlink.MACVLAN_MODE_VEPA, nil
+ case modeBridge:
+ return netlink.MACVLAN_MODE_BRIDGE, nil
+ case modePassthru:
+ return netlink.MACVLAN_MODE_PASSTHRU, nil
+ default:
+ return 0, fmt.Errorf("unknown macvlan mode: %s", mode)
+ }
+}
+
+// parentExists checks if the specified interface exists in the default namespace
+func parentExists(ifaceStr string) bool {
+ _, err := ns.NlHandle().LinkByName(ifaceStr)
+ if err != nil {
+ return false
+ }
+
+ return true
+}
+
+// createVlanLink parses sub-interfaces and vlan id for creation
+func createVlanLink(parentName string) error {
+ if strings.Contains(parentName, ".") {
+ parent, vidInt, err := parseVlan(parentName)
+ if err != nil {
+ return err
+ }
+ // VLAN identifier or VID is a 12-bit field specifying the VLAN to which the frame belongs
+ if vidInt > 4094 || vidInt < 1 {
+ return fmt.Errorf("vlan id must be between 1-4094, received: %d", vidInt)
+ }
+ // get the parent link to attach a vlan subinterface
+ parentLink, err := ns.NlHandle().LinkByName(parent)
+ if err != nil {
+ return fmt.Errorf("failed to find master interface %s on the Docker host: %v", parent, err)
+ }
+ vlanLink := &netlink.Vlan{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: parentName,
+ ParentIndex: parentLink.Attrs().Index,
+ },
+ VlanId: vidInt,
+ }
+ // create the subinterface
+ if err := ns.NlHandle().LinkAdd(vlanLink); err != nil {
+ return fmt.Errorf("failed to create %s vlan link: %v", vlanLink.Name, err)
+ }
+ // Bring the new netlink iface up
+ if err := ns.NlHandle().LinkSetUp(vlanLink); err != nil {
+ return fmt.Errorf("failed to enable %s the macvlan parent link %v", vlanLink.Name, err)
+ }
+ logrus.Debugf("Added a vlan tagged netlink subinterface: %s with a vlan id: %d", parentName, vidInt)
+ return nil
+ }
+
+ return fmt.Errorf("invalid subinterface vlan name %s, example formatting is eth0.10", parentName)
+}
+
+// delVlanLink verifies only sub-interfaces with a vlan id get deleted
+func delVlanLink(linkName string) error {
+ if strings.Contains(linkName, ".") {
+ _, _, err := parseVlan(linkName)
+ if err != nil {
+ return err
+ }
+ // delete the vlan subinterface
+ vlanLink, err := ns.NlHandle().LinkByName(linkName)
+ if err != nil {
+ return fmt.Errorf("failed to find interface %s on the Docker host : %v", linkName, err)
+ }
+ // verify a parent interface isn't being deleted
+ if vlanLink.Attrs().ParentIndex == 0 {
+ return fmt.Errorf("interface %s does not appear to be a slave device: %v", linkName, err)
+ }
+ // delete the macvlan slave device
+ if err := ns.NlHandle().LinkDel(vlanLink); err != nil {
+ return fmt.Errorf("failed to delete %s link: %v", linkName, err)
+ }
+ logrus.Debugf("Deleted a vlan tagged netlink subinterface: %s", linkName)
+ }
+ // if the subinterface doesn't parse to iface.vlan_id leave the interface in
+ // place since it could be a user specified name not created by the driver.
+ return nil
+}
+
+// parseVlan parses and verifies a slave interface name: -o parent=eth0.10
+func parseVlan(linkName string) (string, int, error) {
+ // parse -o parent=eth0.10
+ splitName := strings.Split(linkName, ".")
+ if len(splitName) != 2 {
+ return "", 0, fmt.Errorf("required interface name format is: name.vlan_id, ex. eth0.10 for vlan 10, instead received %s", linkName)
+ }
+ parent, vidStr := splitName[0], splitName[1]
+ // validate type and convert vlan id to int
+ vidInt, err := strconv.Atoi(vidStr)
+ if err != nil {
+ return "", 0, fmt.Errorf("unable to parse a valid vlan id from: %s (ex. eth0.10 for vlan 10)", vidStr)
+ }
+ // Check if the interface exists
+ if !parentExists(parent) {
+ return "", 0, fmt.Errorf("-o parent interface does was not found on the host: %s", parent)
+ }
+
+ return parent, vidInt, nil
+}
+
+// createDummyLink creates a dummy0 parent link
+func createDummyLink(dummyName, truncNetID string) error {
+ // create a parent interface since one was not specified
+ parent := &netlink.Dummy{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: dummyName,
+ },
+ }
+ if err := ns.NlHandle().LinkAdd(parent); err != nil {
+ return err
+ }
+ parentDummyLink, err := ns.NlHandle().LinkByName(dummyName)
+ if err != nil {
+ return fmt.Errorf("error occurred looking up the %s parent iface %s error: %s", macvlanType, dummyName, err)
+ }
+ // bring the new netlink iface up
+ if err := ns.NlHandle().LinkSetUp(parentDummyLink); err != nil {
+ return fmt.Errorf("failed to enable %s the macvlan parent link: %v", dummyName, err)
+ }
+
+ return nil
+}
+
+// delDummyLink deletes the link type dummy used when -o parent is not passed
+func delDummyLink(linkName string) error {
+ // delete the vlan subinterface
+ dummyLink, err := ns.NlHandle().LinkByName(linkName)
+ if err != nil {
+ return fmt.Errorf("failed to find link %s on the Docker host : %v", linkName, err)
+ }
+ // verify a parent interface is being deleted
+ if dummyLink.Attrs().ParentIndex != 0 {
+ return fmt.Errorf("link %s is not a parent dummy interface", linkName)
+ }
+ // delete the macvlan dummy device
+ if err := ns.NlHandle().LinkDel(dummyLink); err != nil {
+ return fmt.Errorf("failed to delete the dummy %s link: %v", linkName, err)
+ }
+ logrus.Debugf("Deleted a dummy parent link: %s", linkName)
+
+ return nil
+}
+
+// getDummyName returns the name of a dummy parent with truncated net ID and driver prefix
+func getDummyName(netID string) string {
+ return dummyPrefix + netID
+}
--- /dev/null
+package macvlan
+
+import (
+ "testing"
+
+ "github.com/vishvananda/netlink"
+)
+
+// TestValidateLink tests the parentExists function
+func TestValidateLink(t *testing.T) {
+ validIface := "lo"
+ invalidIface := "foo12345"
+
+ // test a valid parent interface validation
+ if ok := parentExists(validIface); !ok {
+ t.Fatalf("failed validating loopback %s", validIface)
+ }
+ // test an invalid parent interface validation
+ if ok := parentExists(invalidIface); ok {
+ t.Fatalf("failed to invalidate interface %s", invalidIface)
+ }
+}
+
+// TestValidateSubLink tests valid 802.1q naming convention
+func TestValidateSubLink(t *testing.T) {
+ validSubIface := "lo.10"
+ invalidSubIface1 := "lo"
+ invalidSubIface2 := "lo:10"
+ invalidSubIface3 := "foo123.456"
+
+ // test a valid parent_iface.vlan_id
+ _, _, err := parseVlan(validSubIface)
+ if err != nil {
+ t.Fatalf("failed subinterface validation: %v", err)
+ }
+ // test an invalid vid with a valid parent link
+ _, _, err = parseVlan(invalidSubIface1)
+ if err == nil {
+ t.Fatalf("failed subinterface validation test: %s", invalidSubIface1)
+ }
+ // test a valid vid with a valid parent link with an invalid delimiter
+ _, _, err = parseVlan(invalidSubIface2)
+ if err == nil {
+ t.Fatalf("failed subinterface validation test: %v", invalidSubIface2)
+ }
+ // test an invalid parent link with a valid vid
+ _, _, err = parseVlan(invalidSubIface3)
+ if err == nil {
+ t.Fatalf("failed subinterface validation test: %v", invalidSubIface3)
+ }
+}
+
+// TestSetMacVlanMode tests the macvlan mode setter
+func TestSetMacVlanMode(t *testing.T) {
+ // test macvlan bridge mode
+ mode, err := setMacVlanMode(modeBridge)
+ if err != nil {
+ t.Fatalf("error parsing %v vlan mode: %v", mode, err)
+ }
+ if mode != netlink.MACVLAN_MODE_BRIDGE {
+ t.Fatalf("expected %d got %d", netlink.MACVLAN_MODE_BRIDGE, mode)
+ }
+ // test macvlan passthrough mode
+ mode, err = setMacVlanMode(modePassthru)
+ if err != nil {
+ t.Fatalf("error parsing %v vlan mode: %v", mode, err)
+ }
+ if mode != netlink.MACVLAN_MODE_PASSTHRU {
+ t.Fatalf("expected %d got %d", netlink.MACVLAN_MODE_PASSTHRU, mode)
+ }
+ // test macvlan private mode
+ mode, err = setMacVlanMode(modePrivate)
+ if err != nil {
+ t.Fatalf("error parsing %v vlan mode: %v", mode, err)
+ }
+ if mode != netlink.MACVLAN_MODE_PRIVATE {
+ t.Fatalf("expected %d got %d", netlink.MACVLAN_MODE_PRIVATE, mode)
+ }
+ // test macvlan vepa mode
+ mode, err = setMacVlanMode(modeVepa)
+ if err != nil {
+ t.Fatalf("error parsing %v vlan mode: %v", mode, err)
+ }
+ if mode != netlink.MACVLAN_MODE_VEPA {
+ t.Fatalf("expected %d got %d", netlink.MACVLAN_MODE_VEPA, mode)
+ }
+ // test invalid mode
+ mode, err = setMacVlanMode("foo")
+ if err == nil {
+ t.Fatal("invalid macvlan mode should have returned an error")
+ }
+ if mode != 0 {
+ t.Fatalf("expected 0 got %d", mode)
+ }
+ // test null mode
+ mode, err = setMacVlanMode("")
+ if err == nil {
+ t.Fatal("invalid macvlan mode should have returned an error")
+ }
+ if mode != 0 {
+ t.Fatalf("expected 0 got %d", mode)
+ }
+}
--- /dev/null
+package macvlan
+
+import (
+ "fmt"
+
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+func (d *driver) network(nid string) *network {
+ d.Lock()
+ n, ok := d.networks[nid]
+ d.Unlock()
+ if !ok {
+ logrus.Errorf("network id %s not found", nid)
+ }
+
+ return n
+}
+
+func (d *driver) addNetwork(n *network) {
+ d.Lock()
+ d.networks[n.id] = n
+ d.Unlock()
+}
+
+func (d *driver) deleteNetwork(nid string) {
+ d.Lock()
+ delete(d.networks, nid)
+ d.Unlock()
+}
+
+// getNetworks Safely returns a slice of existing networks
+func (d *driver) getNetworks() []*network {
+ d.Lock()
+ defer d.Unlock()
+
+ ls := make([]*network, 0, len(d.networks))
+ for _, nw := range d.networks {
+ ls = append(ls, nw)
+ }
+
+ return ls
+}
+
+func (n *network) endpoint(eid string) *endpoint {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.endpoints[eid]
+}
+
+func (n *network) addEndpoint(ep *endpoint) {
+ n.Lock()
+ n.endpoints[ep.id] = ep
+ n.Unlock()
+}
+
+func (n *network) deleteEndpoint(eid string) {
+ n.Lock()
+ delete(n.endpoints, eid)
+ n.Unlock()
+}
+
+func (n *network) getEndpoint(eid string) (*endpoint, error) {
+ n.Lock()
+ defer n.Unlock()
+ if eid == "" {
+ return nil, fmt.Errorf("endpoint id %s not found", eid)
+ }
+ if ep, ok := n.endpoints[eid]; ok {
+ return ep, nil
+ }
+
+ return nil, nil
+}
+
+func validateID(nid, eid string) error {
+ if nid == "" {
+ return fmt.Errorf("invalid network id")
+ }
+ if eid == "" {
+ return fmt.Errorf("invalid endpoint id")
+ }
+ return nil
+}
+
+func (n *network) sandbox() osl.Sandbox {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.sbox
+}
+
+func (n *network) setSandbox(sbox osl.Sandbox) {
+ n.Lock()
+ n.sbox = sbox
+ n.Unlock()
+}
+
+func (d *driver) getNetwork(id string) (*network, error) {
+ d.Lock()
+ defer d.Unlock()
+ if id == "" {
+ return nil, types.BadRequestErrorf("invalid network id: %s", id)
+ }
+ if nw, ok := d.networks[id]; ok {
+ return nw, nil
+ }
+
+ return nil, types.NotFoundErrorf("network not found: %s", id)
+}
--- /dev/null
+package macvlan
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ macvlanPrefix = "macvlan"
+ macvlanNetworkPrefix = macvlanPrefix + "/network"
+ macvlanEndpointPrefix = macvlanPrefix + "/endpoint"
+)
+
+// networkConfiguration for this driver's network specific configuration
+type configuration struct {
+ ID string
+ Mtu int
+ dbIndex uint64
+ dbExists bool
+ Internal bool
+ Parent string
+ MacvlanMode string
+ CreatedSlaveLink bool
+ Ipv4Subnets []*ipv4Subnet
+ Ipv6Subnets []*ipv6Subnet
+}
+
+type ipv4Subnet struct {
+ SubnetIP string
+ GwIP string
+}
+
+type ipv6Subnet struct {
+ SubnetIP string
+ GwIP string
+}
+
+// initStore drivers are responsible for caching their own persistent state
+func (d *driver) initStore(option map[string]interface{}) error {
+ if data, ok := option[netlabel.LocalKVClient]; ok {
+ var err error
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+ }
+ d.store, err = datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return types.InternalErrorf("macvlan driver failed to initialize data store: %v", err)
+ }
+
+ return d.populateNetworks()
+ }
+
+ return nil
+}
+
+// populateNetworks is invoked at driver init to recreate persistently stored networks
+func (d *driver) populateNetworks() error {
+ kvol, err := d.store.List(datastore.Key(macvlanPrefix), &configuration{})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return fmt.Errorf("failed to get macvlan network configurations from store: %v", err)
+ }
+ // If empty it simply means no macvlan networks have been created yet
+ if err == datastore.ErrKeyNotFound {
+ return nil
+ }
+ for _, kvo := range kvol {
+ config := kvo.(*configuration)
+ if err = d.createNetwork(config); err != nil {
+ logrus.Warnf("Could not create macvlan network for id %s from persistent state", config.ID)
+ }
+ }
+
+ return nil
+}
+
+func (d *driver) populateEndpoints() error {
+ kvol, err := d.store.List(datastore.Key(macvlanEndpointPrefix), &endpoint{})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return fmt.Errorf("failed to get macvlan endpoints from store: %v", err)
+ }
+
+ if err == datastore.ErrKeyNotFound {
+ return nil
+ }
+
+ for _, kvo := range kvol {
+ ep := kvo.(*endpoint)
+ n, ok := d.networks[ep.nid]
+ if !ok {
+ logrus.Debugf("Network (%.7s) not found for restored macvlan endpoint (%.7s)", ep.nid, ep.id)
+ logrus.Debugf("Deleting stale macvlan endpoint (%.7s) from store", ep.id)
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Debugf("Failed to delete stale macvlan endpoint (%.7s) from store", ep.id)
+ }
+ continue
+ }
+ n.endpoints[ep.id] = ep
+ logrus.Debugf("Endpoint (%.7s) restored to network (%.7s)", ep.id, ep.nid)
+ }
+
+ return nil
+}
+
+// storeUpdate used to update persistent macvlan network records as they are created
+func (d *driver) storeUpdate(kvObject datastore.KVObject) error {
+ if d.store == nil {
+ logrus.Warnf("macvlan store not initialized. kv object %s is not added to the store", datastore.Key(kvObject.Key()...))
+ return nil
+ }
+ if err := d.store.PutObjectAtomic(kvObject); err != nil {
+ return fmt.Errorf("failed to update macvlan store for object type %T: %v", kvObject, err)
+ }
+
+ return nil
+}
+
+// storeDelete used to delete macvlan records from persistent cache as they are deleted
+func (d *driver) storeDelete(kvObject datastore.KVObject) error {
+ if d.store == nil {
+ logrus.Debugf("macvlan store not initialized. kv object %s is not deleted from store", datastore.Key(kvObject.Key()...))
+ return nil
+ }
+retry:
+ if err := d.store.DeleteObjectAtomic(kvObject); err != nil {
+ if err == datastore.ErrKeyModified {
+ if err := d.store.GetObject(datastore.Key(kvObject.Key()...), kvObject); err != nil {
+ return fmt.Errorf("could not update the kvobject to latest when trying to delete: %v", err)
+ }
+ goto retry
+ }
+ return err
+ }
+
+ return nil
+}
+
+func (config *configuration) MarshalJSON() ([]byte, error) {
+ nMap := make(map[string]interface{})
+ nMap["ID"] = config.ID
+ nMap["Mtu"] = config.Mtu
+ nMap["Parent"] = config.Parent
+ nMap["MacvlanMode"] = config.MacvlanMode
+ nMap["Internal"] = config.Internal
+ nMap["CreatedSubIface"] = config.CreatedSlaveLink
+ if len(config.Ipv4Subnets) > 0 {
+ iis, err := json.Marshal(config.Ipv4Subnets)
+ if err != nil {
+ return nil, err
+ }
+ nMap["Ipv4Subnets"] = string(iis)
+ }
+ if len(config.Ipv6Subnets) > 0 {
+ iis, err := json.Marshal(config.Ipv6Subnets)
+ if err != nil {
+ return nil, err
+ }
+ nMap["Ipv6Subnets"] = string(iis)
+ }
+
+ return json.Marshal(nMap)
+}
+
+func (config *configuration) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ nMap map[string]interface{}
+ )
+
+ if err = json.Unmarshal(b, &nMap); err != nil {
+ return err
+ }
+ config.ID = nMap["ID"].(string)
+ config.Mtu = int(nMap["Mtu"].(float64))
+ config.Parent = nMap["Parent"].(string)
+ config.MacvlanMode = nMap["MacvlanMode"].(string)
+ config.Internal = nMap["Internal"].(bool)
+ config.CreatedSlaveLink = nMap["CreatedSubIface"].(bool)
+ if v, ok := nMap["Ipv4Subnets"]; ok {
+ if err := json.Unmarshal([]byte(v.(string)), &config.Ipv4Subnets); err != nil {
+ return err
+ }
+ }
+ if v, ok := nMap["Ipv6Subnets"]; ok {
+ if err := json.Unmarshal([]byte(v.(string)), &config.Ipv6Subnets); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (config *configuration) Key() []string {
+ return []string{macvlanNetworkPrefix, config.ID}
+}
+
+func (config *configuration) KeyPrefix() []string {
+ return []string{macvlanNetworkPrefix}
+}
+
+func (config *configuration) Value() []byte {
+ b, err := json.Marshal(config)
+ if err != nil {
+ return nil
+ }
+
+ return b
+}
+
+func (config *configuration) SetValue(value []byte) error {
+ return json.Unmarshal(value, config)
+}
+
+func (config *configuration) Index() uint64 {
+ return config.dbIndex
+}
+
+func (config *configuration) SetIndex(index uint64) {
+ config.dbIndex = index
+ config.dbExists = true
+}
+
+func (config *configuration) Exists() bool {
+ return config.dbExists
+}
+
+func (config *configuration) Skip() bool {
+ return false
+}
+
+func (config *configuration) New() datastore.KVObject {
+ return &configuration{}
+}
+
+func (config *configuration) CopyTo(o datastore.KVObject) error {
+ dstNcfg := o.(*configuration)
+ *dstNcfg = *config
+
+ return nil
+}
+
+func (config *configuration) DataScope() string {
+ return datastore.LocalScope
+}
+
+func (ep *endpoint) MarshalJSON() ([]byte, error) {
+ epMap := make(map[string]interface{})
+ epMap["id"] = ep.id
+ epMap["nid"] = ep.nid
+ epMap["SrcName"] = ep.srcName
+ if len(ep.mac) != 0 {
+ epMap["MacAddress"] = ep.mac.String()
+ }
+ if ep.addr != nil {
+ epMap["Addr"] = ep.addr.String()
+ }
+ if ep.addrv6 != nil {
+ epMap["Addrv6"] = ep.addrv6.String()
+ }
+ return json.Marshal(epMap)
+}
+
+func (ep *endpoint) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ epMap map[string]interface{}
+ )
+
+ if err = json.Unmarshal(b, &epMap); err != nil {
+ return fmt.Errorf("Failed to unmarshal to macvlan endpoint: %v", err)
+ }
+
+ if v, ok := epMap["MacAddress"]; ok {
+ if ep.mac, err = net.ParseMAC(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode macvlan endpoint MAC address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ if v, ok := epMap["Addr"]; ok {
+ if ep.addr, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode macvlan endpoint IPv4 address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ if v, ok := epMap["Addrv6"]; ok {
+ if ep.addrv6, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode macvlan endpoint IPv6 address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ ep.id = epMap["id"].(string)
+ ep.nid = epMap["nid"].(string)
+ ep.srcName = epMap["SrcName"].(string)
+
+ return nil
+}
+
+func (ep *endpoint) Key() []string {
+ return []string{macvlanEndpointPrefix, ep.id}
+}
+
+func (ep *endpoint) KeyPrefix() []string {
+ return []string{macvlanEndpointPrefix}
+}
+
+func (ep *endpoint) Value() []byte {
+ b, err := json.Marshal(ep)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (ep *endpoint) SetValue(value []byte) error {
+ return json.Unmarshal(value, ep)
+}
+
+func (ep *endpoint) Index() uint64 {
+ return ep.dbIndex
+}
+
+func (ep *endpoint) SetIndex(index uint64) {
+ ep.dbIndex = index
+ ep.dbExists = true
+}
+
+func (ep *endpoint) Exists() bool {
+ return ep.dbExists
+}
+
+func (ep *endpoint) Skip() bool {
+ return false
+}
+
+func (ep *endpoint) New() datastore.KVObject {
+ return &endpoint{}
+}
+
+func (ep *endpoint) CopyTo(o datastore.KVObject) error {
+ dstEp := o.(*endpoint)
+ *dstEp = *ep
+ return nil
+}
+
+func (ep *endpoint) DataScope() string {
+ return datastore.LocalScope
+}
--- /dev/null
+package macvlan
+
+import (
+ "testing"
+
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/libnetwork/driverapi"
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+const testNetworkType = "macvlan"
+
+type driverTester struct {
+ t *testing.T
+ d *driver
+}
+
+func (dt *driverTester) GetPluginGetter() plugingetter.PluginGetter {
+ return nil
+}
+
+func (dt *driverTester) RegisterDriver(name string, drv driverapi.Driver,
+ cap driverapi.Capability) error {
+ if name != testNetworkType {
+ dt.t.Fatalf("Expected driver register name to be %q. Instead got %q",
+ testNetworkType, name)
+ }
+
+ if _, ok := drv.(*driver); !ok {
+ dt.t.Fatalf("Expected driver type to be %T. Instead got %T",
+ &driver{}, drv)
+ }
+
+ dt.d = drv.(*driver)
+ return nil
+}
+
+func TestMacvlanInit(t *testing.T) {
+ if err := Init(&driverTester{t: t}, nil); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMacvlanNilConfig(t *testing.T) {
+ dt := &driverTester{t: t}
+ if err := Init(dt, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := dt.d.initStore(nil); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMacvlanType(t *testing.T) {
+ dt := &driverTester{t: t}
+ if err := Init(dt, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ if dt.d.Type() != testNetworkType {
+ t.Fatalf("Expected Type() to return %q. Instead got %q", testNetworkType,
+ dt.d.Type())
+ }
+}
--- /dev/null
+package mvmanager
+
+import (
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/types"
+)
+
+const networkType = "macvlan"
+
+type driver struct{}
+
+// Init registers a new instance of macvlan manager driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ c := driverapi.Capability{
+ DataScope: datastore.LocalScope,
+ ConnectivityScope: datastore.GlobalScope,
+ }
+ return dc.RegisterDriver(networkType, &driver{}, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Leave(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Type() string {
+ return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
--- /dev/null
+package null
+
+import (
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/types"
+)
+
+const networkType = "null"
+
+type driver struct {
+ network string
+ sync.Mutex
+}
+
+// Init registers a new instance of null driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ c := driverapi.Capability{
+ DataScope: datastore.LocalScope,
+ }
+ return dc.RegisterDriver(networkType, &driver{}, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ d.Lock()
+ defer d.Unlock()
+
+ if d.network != "" {
+ return types.ForbiddenErrorf("only one instance of \"%s\" network is allowed", networkType)
+ }
+
+ d.network = id
+
+ return nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType)
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+ return nil
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ return nil
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return make(map[string]interface{}, 0), nil
+}
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ return nil
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ return nil
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+}
+
+func (d *driver) Type() string {
+ return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
--- /dev/null
+package null
+
+import (
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+func TestDriver(t *testing.T) {
+ d := &driver{}
+
+ if d.Type() != networkType {
+ t.Fatalf("Unexpected network type returned by driver")
+ }
+
+ err := d.CreateNetwork("first", nil, nil, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if d.network != "first" {
+ t.Fatalf("Unexpected network id stored")
+ }
+
+ err = d.CreateNetwork("second", nil, nil, nil, nil)
+ if err == nil {
+ t.Fatalf("Second network creation should fail on this driver")
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("Second network creation failed with unexpected error type")
+ }
+
+ err = d.DeleteNetwork("first")
+ if err == nil {
+ t.Fatalf("network deletion should fail on this driver")
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("network deletion failed with unexpected error type")
+ }
+
+ // we don't really check if it is there or not, delete is not allowed for this driver, period.
+ err = d.DeleteNetwork("unknown")
+ if err == nil {
+ t.Fatalf("any network deletion should fail on this driver")
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("any network deletion failed with unexpected error type")
+ }
+}
--- /dev/null
+package overlay
+
+import (
+ "bytes"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "hash/fnv"
+ "net"
+ "sync"
+ "syscall"
+
+ "strconv"
+
+ "github.com/docker/libnetwork/iptables"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+const (
+ r = 0xD0C4E3
+ pktExpansion = 26 // SPI(4) + SeqN(4) + IV(8) + PadLength(1) + NextHeader(1) + ICV(8)
+)
+
+const (
+ forward = iota + 1
+ reverse
+ bidir
+)
+
+var spMark = netlink.XfrmMark{Value: uint32(r), Mask: 0xffffffff}
+
+type key struct {
+ value []byte
+ tag uint32
+}
+
+func (k *key) String() string {
+ if k != nil {
+ return fmt.Sprintf("(key: %s, tag: 0x%x)", hex.EncodeToString(k.value)[0:5], k.tag)
+ }
+ return ""
+}
+
+type spi struct {
+ forward int
+ reverse int
+}
+
+func (s *spi) String() string {
+ return fmt.Sprintf("SPI(FWD: 0x%x, REV: 0x%x)", uint32(s.forward), uint32(s.reverse))
+}
+
+type encrMap struct {
+ nodes map[string][]*spi
+ sync.Mutex
+}
+
+func (e *encrMap) String() string {
+ e.Lock()
+ defer e.Unlock()
+ b := new(bytes.Buffer)
+ for k, v := range e.nodes {
+ b.WriteString("\n")
+ b.WriteString(k)
+ b.WriteString(":")
+ b.WriteString("[")
+ for _, s := range v {
+ b.WriteString(s.String())
+ b.WriteString(",")
+ }
+ b.WriteString("]")
+
+ }
+ return b.String()
+}
+
+func (d *driver) checkEncryption(nid string, rIP net.IP, vxlanID uint32, isLocal, add bool) error {
+ logrus.Debugf("checkEncryption(%.7s, %v, %d, %t)", nid, rIP, vxlanID, isLocal)
+
+ n := d.network(nid)
+ if n == nil || !n.secure {
+ return nil
+ }
+
+ if len(d.keys) == 0 {
+ return types.ForbiddenErrorf("encryption key is not present")
+ }
+
+ lIP := net.ParseIP(d.bindAddress)
+ aIP := net.ParseIP(d.advertiseAddress)
+ nodes := map[string]net.IP{}
+
+ switch {
+ case isLocal:
+ if err := d.peerDbNetworkWalk(nid, func(pKey *peerKey, pEntry *peerEntry) bool {
+ if !aIP.Equal(pEntry.vtep) {
+ nodes[pEntry.vtep.String()] = pEntry.vtep
+ }
+ return false
+ }); err != nil {
+ logrus.Warnf("Failed to retrieve list of participating nodes in overlay network %.5s: %v", nid, err)
+ }
+ default:
+ if len(d.network(nid).endpoints) > 0 {
+ nodes[rIP.String()] = rIP
+ }
+ }
+
+ logrus.Debugf("List of nodes: %s", nodes)
+
+ if add {
+ for _, rIP := range nodes {
+ if err := setupEncryption(lIP, aIP, rIP, vxlanID, d.secMap, d.keys); err != nil {
+ logrus.Warnf("Failed to program network encryption between %s and %s: %v", lIP, rIP, err)
+ }
+ }
+ } else {
+ if len(nodes) == 0 {
+ if err := removeEncryption(lIP, rIP, d.secMap); err != nil {
+ logrus.Warnf("Failed to remove network encryption between %s and %s: %v", lIP, rIP, err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func setupEncryption(localIP, advIP, remoteIP net.IP, vni uint32, em *encrMap, keys []*key) error {
+ logrus.Debugf("Programming encryption for vxlan %d between %s and %s", vni, localIP, remoteIP)
+ rIPs := remoteIP.String()
+
+ indices := make([]*spi, 0, len(keys))
+
+ err := programMangle(vni, true)
+ if err != nil {
+ logrus.Warn(err)
+ }
+
+ err = programInput(vni, true)
+ if err != nil {
+ logrus.Warn(err)
+ }
+
+ for i, k := range keys {
+ spis := &spi{buildSPI(advIP, remoteIP, k.tag), buildSPI(remoteIP, advIP, k.tag)}
+ dir := reverse
+ if i == 0 {
+ dir = bidir
+ }
+ fSA, rSA, err := programSA(localIP, remoteIP, spis, k, dir, true)
+ if err != nil {
+ logrus.Warn(err)
+ }
+ indices = append(indices, spis)
+ if i != 0 {
+ continue
+ }
+ err = programSP(fSA, rSA, true)
+ if err != nil {
+ logrus.Warn(err)
+ }
+ }
+
+ em.Lock()
+ em.nodes[rIPs] = indices
+ em.Unlock()
+
+ return nil
+}
+
+func removeEncryption(localIP, remoteIP net.IP, em *encrMap) error {
+ em.Lock()
+ indices, ok := em.nodes[remoteIP.String()]
+ em.Unlock()
+ if !ok {
+ return nil
+ }
+ for i, idxs := range indices {
+ dir := reverse
+ if i == 0 {
+ dir = bidir
+ }
+ fSA, rSA, err := programSA(localIP, remoteIP, idxs, nil, dir, false)
+ if err != nil {
+ logrus.Warn(err)
+ }
+ if i != 0 {
+ continue
+ }
+ err = programSP(fSA, rSA, false)
+ if err != nil {
+ logrus.Warn(err)
+ }
+ }
+ return nil
+}
+
+func programMangle(vni uint32, add bool) (err error) {
+ var (
+ p = strconv.FormatUint(uint64(vxlanPort), 10)
+ c = fmt.Sprintf("0>>22&0x3C@12&0xFFFFFF00=%d", int(vni)<<8)
+ m = strconv.FormatUint(uint64(r), 10)
+ chain = "OUTPUT"
+ rule = []string{"-p", "udp", "--dport", p, "-m", "u32", "--u32", c, "-j", "MARK", "--set-mark", m}
+ a = "-A"
+ action = "install"
+ )
+
+ if add == iptables.Exists(iptables.Mangle, chain, rule...) {
+ return
+ }
+
+ if !add {
+ a = "-D"
+ action = "remove"
+ }
+
+ if err = iptables.RawCombinedOutput(append([]string{"-t", string(iptables.Mangle), a, chain}, rule...)...); err != nil {
+ logrus.Warnf("could not %s mangle rule: %v", action, err)
+ }
+
+ return
+}
+
+func programInput(vni uint32, add bool) (err error) {
+ var (
+ port = strconv.FormatUint(uint64(vxlanPort), 10)
+ vniMatch = fmt.Sprintf("0>>22&0x3C@12&0xFFFFFF00=%d", int(vni)<<8)
+ plainVxlan = []string{"-p", "udp", "--dport", port, "-m", "u32", "--u32", vniMatch, "-j"}
+ ipsecVxlan = append([]string{"-m", "policy", "--dir", "in", "--pol", "ipsec"}, plainVxlan...)
+ block = append(plainVxlan, "DROP")
+ accept = append(ipsecVxlan, "ACCEPT")
+ chain = "INPUT"
+ action = iptables.Append
+ msg = "add"
+ )
+
+ if !add {
+ action = iptables.Delete
+ msg = "remove"
+ }
+
+ if err := iptables.ProgramRule(iptables.Filter, chain, action, accept); err != nil {
+ logrus.Errorf("could not %s input rule: %v. Please do it manually.", msg, err)
+ }
+
+ if err := iptables.ProgramRule(iptables.Filter, chain, action, block); err != nil {
+ logrus.Errorf("could not %s input rule: %v. Please do it manually.", msg, err)
+ }
+
+ return
+}
+
+func programSA(localIP, remoteIP net.IP, spi *spi, k *key, dir int, add bool) (fSA *netlink.XfrmState, rSA *netlink.XfrmState, err error) {
+ var (
+ action = "Removing"
+ xfrmProgram = ns.NlHandle().XfrmStateDel
+ )
+
+ if add {
+ action = "Adding"
+ xfrmProgram = ns.NlHandle().XfrmStateAdd
+ }
+
+ if dir&reverse > 0 {
+ rSA = &netlink.XfrmState{
+ Src: remoteIP,
+ Dst: localIP,
+ Proto: netlink.XFRM_PROTO_ESP,
+ Spi: spi.reverse,
+ Mode: netlink.XFRM_MODE_TRANSPORT,
+ Reqid: r,
+ }
+ if add {
+ rSA.Aead = buildAeadAlgo(k, spi.reverse)
+ }
+
+ exists, err := saExists(rSA)
+ if err != nil {
+ exists = !add
+ }
+
+ if add != exists {
+ logrus.Debugf("%s: rSA{%s}", action, rSA)
+ if err := xfrmProgram(rSA); err != nil {
+ logrus.Warnf("Failed %s rSA{%s}: %v", action, rSA, err)
+ }
+ }
+ }
+
+ if dir&forward > 0 {
+ fSA = &netlink.XfrmState{
+ Src: localIP,
+ Dst: remoteIP,
+ Proto: netlink.XFRM_PROTO_ESP,
+ Spi: spi.forward,
+ Mode: netlink.XFRM_MODE_TRANSPORT,
+ Reqid: r,
+ }
+ if add {
+ fSA.Aead = buildAeadAlgo(k, spi.forward)
+ }
+
+ exists, err := saExists(fSA)
+ if err != nil {
+ exists = !add
+ }
+
+ if add != exists {
+ logrus.Debugf("%s fSA{%s}", action, fSA)
+ if err := xfrmProgram(fSA); err != nil {
+ logrus.Warnf("Failed %s fSA{%s}: %v.", action, fSA, err)
+ }
+ }
+ }
+
+ return
+}
+
+func programSP(fSA *netlink.XfrmState, rSA *netlink.XfrmState, add bool) error {
+ action := "Removing"
+ xfrmProgram := ns.NlHandle().XfrmPolicyDel
+ if add {
+ action = "Adding"
+ xfrmProgram = ns.NlHandle().XfrmPolicyAdd
+ }
+
+ // Create a congruent cidr
+ s := types.GetMinimalIP(fSA.Src)
+ d := types.GetMinimalIP(fSA.Dst)
+ fullMask := net.CIDRMask(8*len(s), 8*len(s))
+
+ fPol := &netlink.XfrmPolicy{
+ Src: &net.IPNet{IP: s, Mask: fullMask},
+ Dst: &net.IPNet{IP: d, Mask: fullMask},
+ Dir: netlink.XFRM_DIR_OUT,
+ Proto: 17,
+ DstPort: 4789,
+ Mark: &spMark,
+ Tmpls: []netlink.XfrmPolicyTmpl{
+ {
+ Src: fSA.Src,
+ Dst: fSA.Dst,
+ Proto: netlink.XFRM_PROTO_ESP,
+ Mode: netlink.XFRM_MODE_TRANSPORT,
+ Spi: fSA.Spi,
+ Reqid: r,
+ },
+ },
+ }
+
+ exists, err := spExists(fPol)
+ if err != nil {
+ exists = !add
+ }
+
+ if add != exists {
+ logrus.Debugf("%s fSP{%s}", action, fPol)
+ if err := xfrmProgram(fPol); err != nil {
+ logrus.Warnf("%s fSP{%s}: %v", action, fPol, err)
+ }
+ }
+
+ return nil
+}
+
+func saExists(sa *netlink.XfrmState) (bool, error) {
+ _, err := ns.NlHandle().XfrmStateGet(sa)
+ switch err {
+ case nil:
+ return true, nil
+ case syscall.ESRCH:
+ return false, nil
+ default:
+ err = fmt.Errorf("Error while checking for SA existence: %v", err)
+ logrus.Warn(err)
+ return false, err
+ }
+}
+
+func spExists(sp *netlink.XfrmPolicy) (bool, error) {
+ _, err := ns.NlHandle().XfrmPolicyGet(sp)
+ switch err {
+ case nil:
+ return true, nil
+ case syscall.ENOENT:
+ return false, nil
+ default:
+ err = fmt.Errorf("Error while checking for SP existence: %v", err)
+ logrus.Warn(err)
+ return false, err
+ }
+}
+
+func buildSPI(src, dst net.IP, st uint32) int {
+ b := make([]byte, 4)
+ binary.BigEndian.PutUint32(b, st)
+ h := fnv.New32a()
+ h.Write(src)
+ h.Write(b)
+ h.Write(dst)
+ return int(binary.BigEndian.Uint32(h.Sum(nil)))
+}
+
+func buildAeadAlgo(k *key, s int) *netlink.XfrmStateAlgo {
+ salt := make([]byte, 4)
+ binary.BigEndian.PutUint32(salt, uint32(s))
+ return &netlink.XfrmStateAlgo{
+ Name: "rfc4106(gcm(aes))",
+ Key: append(k.value, salt...),
+ ICVLen: 64,
+ }
+}
+
+func (d *driver) secMapWalk(f func(string, []*spi) ([]*spi, bool)) error {
+ d.secMap.Lock()
+ for node, indices := range d.secMap.nodes {
+ idxs, stop := f(node, indices)
+ if idxs != nil {
+ d.secMap.nodes[node] = idxs
+ }
+ if stop {
+ break
+ }
+ }
+ d.secMap.Unlock()
+ return nil
+}
+
+func (d *driver) setKeys(keys []*key) error {
+ // Remove any stale policy, state
+ clearEncryptionStates()
+ // Accept the encryption keys and clear any stale encryption map
+ d.Lock()
+ d.keys = keys
+ d.secMap = &encrMap{nodes: map[string][]*spi{}}
+ d.Unlock()
+ logrus.Debugf("Initial encryption keys: %v", keys)
+ return nil
+}
+
+// updateKeys allows to add a new key and/or change the primary key and/or prune an existing key
+// The primary key is the key used in transmission and will go in first position in the list.
+func (d *driver) updateKeys(newKey, primary, pruneKey *key) error {
+ logrus.Debugf("Updating Keys. New: %v, Primary: %v, Pruned: %v", newKey, primary, pruneKey)
+
+ logrus.Debugf("Current: %v", d.keys)
+
+ var (
+ newIdx = -1
+ priIdx = -1
+ delIdx = -1
+ lIP = net.ParseIP(d.bindAddress)
+ aIP = net.ParseIP(d.advertiseAddress)
+ )
+
+ d.Lock()
+ defer d.Unlock()
+
+ // add new
+ if newKey != nil {
+ d.keys = append(d.keys, newKey)
+ newIdx += len(d.keys)
+ }
+ for i, k := range d.keys {
+ if primary != nil && k.tag == primary.tag {
+ priIdx = i
+ }
+ if pruneKey != nil && k.tag == pruneKey.tag {
+ delIdx = i
+ }
+ }
+
+ if (newKey != nil && newIdx == -1) ||
+ (primary != nil && priIdx == -1) ||
+ (pruneKey != nil && delIdx == -1) {
+ return types.BadRequestErrorf("cannot find proper key indices while processing key update:"+
+ "(newIdx,priIdx,delIdx):(%d, %d, %d)", newIdx, priIdx, delIdx)
+ }
+
+ if priIdx != -1 && priIdx == delIdx {
+ return types.BadRequestErrorf("attempting to both make a key (index %d) primary and delete it", priIdx)
+ }
+
+ d.secMapWalk(func(rIPs string, spis []*spi) ([]*spi, bool) {
+ rIP := net.ParseIP(rIPs)
+ return updateNodeKey(lIP, aIP, rIP, spis, d.keys, newIdx, priIdx, delIdx), false
+ })
+
+ // swap primary
+ if priIdx != -1 {
+ d.keys[0], d.keys[priIdx] = d.keys[priIdx], d.keys[0]
+ }
+ // prune
+ if delIdx != -1 {
+ if delIdx == 0 {
+ delIdx = priIdx
+ }
+ d.keys = append(d.keys[:delIdx], d.keys[delIdx+1:]...)
+ }
+
+ logrus.Debugf("Updated: %v", d.keys)
+
+ return nil
+}
+
+/********************************************************
+ * Steady state: rSA0, rSA1, rSA2, fSA1, fSP1
+ * Rotation --> -rSA0, +rSA3, +fSA2, +fSP2/-fSP1, -fSA1
+ * Steady state: rSA1, rSA2, rSA3, fSA2, fSP2
+ *********************************************************/
+
+// Spis and keys are sorted in such away the one in position 0 is the primary
+func updateNodeKey(lIP, aIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, priIdx, delIdx int) []*spi {
+ logrus.Debugf("Updating keys for node: %s (%d,%d,%d)", rIP, newIdx, priIdx, delIdx)
+
+ spis := idxs
+ logrus.Debugf("Current: %v", spis)
+
+ // add new
+ if newIdx != -1 {
+ spis = append(spis, &spi{
+ forward: buildSPI(aIP, rIP, curKeys[newIdx].tag),
+ reverse: buildSPI(rIP, aIP, curKeys[newIdx].tag),
+ })
+ }
+
+ if delIdx != -1 {
+ // -rSA0
+ programSA(lIP, rIP, spis[delIdx], nil, reverse, false)
+ }
+
+ if newIdx > -1 {
+ // +rSA2
+ programSA(lIP, rIP, spis[newIdx], curKeys[newIdx], reverse, true)
+ }
+
+ if priIdx > 0 {
+ // +fSA2
+ fSA2, _, _ := programSA(lIP, rIP, spis[priIdx], curKeys[priIdx], forward, true)
+
+ // +fSP2, -fSP1
+ s := types.GetMinimalIP(fSA2.Src)
+ d := types.GetMinimalIP(fSA2.Dst)
+ fullMask := net.CIDRMask(8*len(s), 8*len(s))
+
+ fSP1 := &netlink.XfrmPolicy{
+ Src: &net.IPNet{IP: s, Mask: fullMask},
+ Dst: &net.IPNet{IP: d, Mask: fullMask},
+ Dir: netlink.XFRM_DIR_OUT,
+ Proto: 17,
+ DstPort: 4789,
+ Mark: &spMark,
+ Tmpls: []netlink.XfrmPolicyTmpl{
+ {
+ Src: fSA2.Src,
+ Dst: fSA2.Dst,
+ Proto: netlink.XFRM_PROTO_ESP,
+ Mode: netlink.XFRM_MODE_TRANSPORT,
+ Spi: fSA2.Spi,
+ Reqid: r,
+ },
+ },
+ }
+ logrus.Debugf("Updating fSP{%s}", fSP1)
+ if err := ns.NlHandle().XfrmPolicyUpdate(fSP1); err != nil {
+ logrus.Warnf("Failed to update fSP{%s}: %v", fSP1, err)
+ }
+
+ // -fSA1
+ programSA(lIP, rIP, spis[0], nil, forward, false)
+ }
+
+ // swap
+ if priIdx > 0 {
+ swp := spis[0]
+ spis[0] = spis[priIdx]
+ spis[priIdx] = swp
+ }
+ // prune
+ if delIdx != -1 {
+ if delIdx == 0 {
+ delIdx = priIdx
+ }
+ spis = append(spis[:delIdx], spis[delIdx+1:]...)
+ }
+
+ logrus.Debugf("Updated: %v", spis)
+
+ return spis
+}
+
+func (n *network) maxMTU() int {
+ mtu := 1500
+ if n.mtu != 0 {
+ mtu = n.mtu
+ }
+ mtu -= vxlanEncap
+ if n.secure {
+ // In case of encryption account for the
+ // esp packet expansion and padding
+ mtu -= pktExpansion
+ mtu -= (mtu % 4)
+ }
+ return mtu
+}
+
+func clearEncryptionStates() {
+ nlh := ns.NlHandle()
+ spList, err := nlh.XfrmPolicyList(netlink.FAMILY_ALL)
+ if err != nil {
+ logrus.Warnf("Failed to retrieve SP list for cleanup: %v", err)
+ }
+ saList, err := nlh.XfrmStateList(netlink.FAMILY_ALL)
+ if err != nil {
+ logrus.Warnf("Failed to retrieve SA list for cleanup: %v", err)
+ }
+ for _, sp := range spList {
+ if sp.Mark != nil && sp.Mark.Value == spMark.Value {
+ if err := nlh.XfrmPolicyDel(&sp); err != nil {
+ logrus.Warnf("Failed to delete stale SP %s: %v", sp, err)
+ continue
+ }
+ logrus.Debugf("Removed stale SP: %s", sp)
+ }
+ }
+ for _, sa := range saList {
+ if sa.Reqid == r {
+ if err := nlh.XfrmStateDel(&sa); err != nil {
+ logrus.Warnf("Failed to delete stale SA %s: %v", sa, err)
+ continue
+ }
+ logrus.Debugf("Removed stale SA: %s", sa)
+ }
+ }
+}
--- /dev/null
+package overlay
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/docker/libnetwork/iptables"
+ "github.com/sirupsen/logrus"
+)
+
+const globalChain = "DOCKER-OVERLAY"
+
+var filterOnce sync.Once
+
+var filterChan = make(chan struct{}, 1)
+
+func filterWait() func() {
+ filterChan <- struct{}{}
+ return func() { <-filterChan }
+}
+
+func chainExists(cname string) bool {
+ if _, err := iptables.Raw("-L", cname); err != nil {
+ return false
+ }
+
+ return true
+}
+
+func setupGlobalChain() {
+ // Because of an ungraceful shutdown, chain could already be present
+ if !chainExists(globalChain) {
+ if err := iptables.RawCombinedOutput("-N", globalChain); err != nil {
+ logrus.Errorf("could not create global overlay chain: %v", err)
+ return
+ }
+ }
+
+ if !iptables.Exists(iptables.Filter, globalChain, "-j", "RETURN") {
+ if err := iptables.RawCombinedOutput("-A", globalChain, "-j", "RETURN"); err != nil {
+ logrus.Errorf("could not install default return chain in the overlay global chain: %v", err)
+ }
+ }
+}
+
+func setNetworkChain(cname string, remove bool) error {
+ // Initialize the onetime global overlay chain
+ filterOnce.Do(setupGlobalChain)
+
+ exists := chainExists(cname)
+
+ opt := "-N"
+ // In case of remove, make sure to flush the rules in the chain
+ if remove && exists {
+ if err := iptables.RawCombinedOutput("-F", cname); err != nil {
+ return fmt.Errorf("failed to flush overlay network chain %s rules: %v", cname, err)
+ }
+ opt = "-X"
+ }
+
+ if (!remove && !exists) || (remove && exists) {
+ if err := iptables.RawCombinedOutput(opt, cname); err != nil {
+ return fmt.Errorf("failed network chain operation %q for chain %s: %v", opt, cname, err)
+ }
+ }
+
+ if !remove {
+ if !iptables.Exists(iptables.Filter, cname, "-j", "DROP") {
+ if err := iptables.RawCombinedOutput("-A", cname, "-j", "DROP"); err != nil {
+ return fmt.Errorf("failed adding default drop rule to overlay network chain %s: %v", cname, err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func addNetworkChain(cname string) error {
+ defer filterWait()()
+
+ return setNetworkChain(cname, false)
+}
+
+func removeNetworkChain(cname string) error {
+ defer filterWait()()
+
+ return setNetworkChain(cname, true)
+}
+
+func setFilters(cname, brName string, remove bool) error {
+ opt := "-I"
+ if remove {
+ opt = "-D"
+ }
+
+ // Every time we set filters for a new subnet make sure to move the global overlay hook to the top of the both the OUTPUT and forward chains
+ if !remove {
+ for _, chain := range []string{"OUTPUT", "FORWARD"} {
+ exists := iptables.Exists(iptables.Filter, chain, "-j", globalChain)
+ if exists {
+ if err := iptables.RawCombinedOutput("-D", chain, "-j", globalChain); err != nil {
+ return fmt.Errorf("failed to delete overlay hook in chain %s while moving the hook: %v", chain, err)
+ }
+ }
+
+ if err := iptables.RawCombinedOutput("-I", chain, "-j", globalChain); err != nil {
+ return fmt.Errorf("failed to insert overlay hook in chain %s: %v", chain, err)
+ }
+ }
+ }
+
+ // Insert/Delete the rule to jump to per-bridge chain
+ exists := iptables.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname)
+ if (!remove && !exists) || (remove && exists) {
+ if err := iptables.RawCombinedOutput(opt, globalChain, "-o", brName, "-j", cname); err != nil {
+ return fmt.Errorf("failed to add per-bridge filter rule for bridge %s, network chain %s: %v", brName, cname, err)
+ }
+ }
+
+ exists = iptables.Exists(iptables.Filter, cname, "-i", brName, "-j", "ACCEPT")
+ if (!remove && exists) || (remove && !exists) {
+ return nil
+ }
+
+ if err := iptables.RawCombinedOutput(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil {
+ return fmt.Errorf("failed to add overlay filter rile for network chain %s, bridge %s: %v", cname, brName, err)
+ }
+
+ return nil
+}
+
+func addFilters(cname, brName string) error {
+ defer filterWait()()
+
+ return setFilters(cname, brName, false)
+}
+
+func removeFilters(cname, brName string) error {
+ defer filterWait()()
+
+ return setFilters(cname, brName, true)
+}
--- /dev/null
+package overlay
+
+import (
+ "fmt"
+ "net"
+ "syscall"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/types"
+ "github.com/gogo/protobuf/proto"
+ "github.com/sirupsen/logrus"
+)
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("could not find network with id %s", nid)
+ }
+
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return fmt.Errorf("could not find endpoint with id %s", eid)
+ }
+
+ if n.secure && len(d.keys) == 0 {
+ return fmt.Errorf("cannot join secure network: encryption keys not present")
+ }
+
+ nlh := ns.NlHandle()
+
+ if n.secure && !nlh.SupportsNetlinkFamily(syscall.NETLINK_XFRM) {
+ return fmt.Errorf("cannot join secure network: required modules to install IPSEC rules are missing on host")
+ }
+
+ s := n.getSubnetforIP(ep.addr)
+ if s == nil {
+ return fmt.Errorf("could not find subnet for endpoint %s", eid)
+ }
+
+ if err := n.obtainVxlanID(s); err != nil {
+ return fmt.Errorf("couldn't get vxlan id for %q: %v", s.subnetIP.String(), err)
+ }
+
+ if err := n.joinSandbox(s, false, true); err != nil {
+ return fmt.Errorf("network sandbox join failed: %v", err)
+ }
+
+ sbox := n.sandbox()
+
+ overlayIfName, containerIfName, err := createVethPair()
+ if err != nil {
+ return err
+ }
+
+ ep.ifName = containerIfName
+
+ if err = d.writeEndpointToStore(ep); err != nil {
+ return fmt.Errorf("failed to update overlay endpoint %.7s to local data store: %v", ep.id, err)
+ }
+
+ // Set the container interface and its peer MTU to 1450 to allow
+ // for 50 bytes vxlan encap (inner eth header(14) + outer IP(20) +
+ // outer UDP(8) + vxlan header(8))
+ mtu := n.maxMTU()
+
+ veth, err := nlh.LinkByName(overlayIfName)
+ if err != nil {
+ return fmt.Errorf("cound not find link by name %s: %v", overlayIfName, err)
+ }
+ err = nlh.LinkSetMTU(veth, mtu)
+ if err != nil {
+ return err
+ }
+
+ if err = sbox.AddInterface(overlayIfName, "veth",
+ sbox.InterfaceOptions().Master(s.brName)); err != nil {
+ return fmt.Errorf("could not add veth pair inside the network sandbox: %v", err)
+ }
+
+ veth, err = nlh.LinkByName(containerIfName)
+ if err != nil {
+ return fmt.Errorf("could not find link by name %s: %v", containerIfName, err)
+ }
+ err = nlh.LinkSetMTU(veth, mtu)
+ if err != nil {
+ return err
+ }
+
+ if err = nlh.LinkSetHardwareAddr(veth, ep.mac); err != nil {
+ return fmt.Errorf("could not set mac address (%v) to the container interface: %v", ep.mac, err)
+ }
+
+ for _, sub := range n.subnets {
+ if sub == s {
+ continue
+ }
+ if err = jinfo.AddStaticRoute(sub.subnetIP, types.NEXTHOP, s.gwIP.IP); err != nil {
+ logrus.Errorf("Adding subnet %s static route in network %q failed\n", s.subnetIP, n.id)
+ }
+ }
+
+ if iNames := jinfo.InterfaceName(); iNames != nil {
+ err = iNames.SetNames(containerIfName, "eth")
+ if err != nil {
+ return err
+ }
+ }
+
+ d.peerAdd(nid, eid, ep.addr.IP, ep.addr.Mask, ep.mac, net.ParseIP(d.advertiseAddress), false, false, true)
+
+ if err = d.checkEncryption(nid, nil, n.vxlanID(s), true, true); err != nil {
+ logrus.Warn(err)
+ }
+
+ buf, err := proto.Marshal(&PeerRecord{
+ EndpointIP: ep.addr.String(),
+ EndpointMAC: ep.mac.String(),
+ TunnelEndpointIP: d.advertiseAddress,
+ })
+ if err != nil {
+ return err
+ }
+
+ if err := jinfo.AddTableEntry(ovPeerTable, eid, buf); err != nil {
+ logrus.Errorf("overlay: Failed adding table entry to joininfo: %v", err)
+ }
+
+ d.pushLocalEndpointEvent("join", nid, eid)
+
+ return nil
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ if tablename != ovPeerTable {
+ logrus.Errorf("DecodeTableEntry: unexpected table name %s", tablename)
+ return "", nil
+ }
+
+ var peer PeerRecord
+ if err := proto.Unmarshal(value, &peer); err != nil {
+ logrus.Errorf("DecodeTableEntry: failed to unmarshal peer record for key %s: %v", key, err)
+ return "", nil
+ }
+
+ return key, map[string]string{
+ "Host IP": peer.TunnelEndpointIP,
+ }
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+ if tableName != ovPeerTable {
+ logrus.Errorf("Unexpected table notification for table %s received", tableName)
+ return
+ }
+
+ eid := key
+
+ var peer PeerRecord
+ if err := proto.Unmarshal(value, &peer); err != nil {
+ logrus.Errorf("Failed to unmarshal peer record: %v", err)
+ return
+ }
+
+ // Ignore local peers. We already know about them and they
+ // should not be added to vxlan fdb.
+ if peer.TunnelEndpointIP == d.advertiseAddress {
+ return
+ }
+
+ addr, err := types.ParseCIDR(peer.EndpointIP)
+ if err != nil {
+ logrus.Errorf("Invalid peer IP %s received in event notify", peer.EndpointIP)
+ return
+ }
+
+ mac, err := net.ParseMAC(peer.EndpointMAC)
+ if err != nil {
+ logrus.Errorf("Invalid mac %s received in event notify", peer.EndpointMAC)
+ return
+ }
+
+ vtep := net.ParseIP(peer.TunnelEndpointIP)
+ if vtep == nil {
+ logrus.Errorf("Invalid VTEP %s received in event notify", peer.TunnelEndpointIP)
+ return
+ }
+
+ if etype == driverapi.Delete {
+ d.peerDelete(nid, eid, addr.IP, addr.Mask, mac, vtep, false)
+ return
+ }
+
+ d.peerAdd(nid, eid, addr.IP, addr.Mask, mac, vtep, false, false, false)
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("could not find network with id %s", nid)
+ }
+
+ ep := n.endpoint(eid)
+
+ if ep == nil {
+ return types.InternalMaskableErrorf("could not find endpoint with id %s", eid)
+ }
+
+ if d.notifyCh != nil {
+ d.notifyCh <- ovNotify{
+ action: "leave",
+ nw: n,
+ ep: ep,
+ }
+ }
+
+ d.peerDelete(nid, eid, ep.addr.IP, ep.addr.Mask, ep.mac, net.ParseIP(d.advertiseAddress), true)
+
+ n.leaveSandbox()
+
+ return nil
+}
--- /dev/null
+package overlay
+
+import (
+ "strconv"
+
+ "github.com/docker/libnetwork/osl/kernel"
+)
+
+var ovConfig = map[string]*kernel.OSValue{
+ "net.ipv4.neigh.default.gc_thresh1": {Value: "8192", CheckFn: checkHigher},
+ "net.ipv4.neigh.default.gc_thresh2": {Value: "49152", CheckFn: checkHigher},
+ "net.ipv4.neigh.default.gc_thresh3": {Value: "65536", CheckFn: checkHigher},
+}
+
+func checkHigher(val1, val2 string) bool {
+ val1Int, _ := strconv.ParseInt(val1, 10, 32)
+ val2Int, _ := strconv.ParseInt(val2, 10, 32)
+ return val1Int < val2Int
+}
+
+func applyOStweaks() {
+ kernel.ApplyOSTweaks(ovConfig)
+}
--- /dev/null
+// +build !linux
+
+package overlay
+
+func applyOStweaks() {}
--- /dev/null
+package overlay
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+type endpointTable map[string]*endpoint
+
+const overlayEndpointPrefix = "overlay/endpoint"
+
+type endpoint struct {
+ id string
+ nid string
+ ifName string
+ mac net.HardwareAddr
+ addr *net.IPNet
+ dbExists bool
+ dbIndex uint64
+}
+
+func (n *network) endpoint(eid string) *endpoint {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.endpoints[eid]
+}
+
+func (n *network) addEndpoint(ep *endpoint) {
+ n.Lock()
+ n.endpoints[ep.id] = ep
+ n.Unlock()
+}
+
+func (n *network) deleteEndpoint(eid string) {
+ n.Lock()
+ delete(n.endpoints, eid)
+ n.Unlock()
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
+ epOptions map[string]interface{}) error {
+ var err error
+
+ if err = validateID(nid, eid); err != nil {
+ return err
+ }
+
+ // Since we perform lazy configuration make sure we try
+ // configuring the driver when we enter CreateEndpoint since
+ // CreateNetwork may not be called in every node.
+ if err := d.configure(); err != nil {
+ return err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("network id %q not found", nid)
+ }
+
+ ep := &endpoint{
+ id: eid,
+ nid: n.id,
+ addr: ifInfo.Address(),
+ mac: ifInfo.MacAddress(),
+ }
+ if ep.addr == nil {
+ return fmt.Errorf("create endpoint was not passed interface IP address")
+ }
+
+ if s := n.getSubnetforIP(ep.addr); s == nil {
+ return fmt.Errorf("no matching subnet for IP %q in network %q", ep.addr, nid)
+ }
+
+ if ep.mac == nil {
+ ep.mac = netutils.GenerateMACFromIP(ep.addr.IP)
+ if err := ifInfo.SetMacAddress(ep.mac); err != nil {
+ return err
+ }
+ }
+
+ n.addEndpoint(ep)
+
+ if err := d.writeEndpointToStore(ep); err != nil {
+ return fmt.Errorf("failed to update overlay endpoint %.7s to local store: %v", ep.id, err)
+ }
+
+ return nil
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ nlh := ns.NlHandle()
+
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("network id %q not found", nid)
+ }
+
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return fmt.Errorf("endpoint id %q not found", eid)
+ }
+
+ n.deleteEndpoint(eid)
+
+ if err := d.deleteEndpointFromStore(ep); err != nil {
+ logrus.Warnf("Failed to delete overlay endpoint %.7s from local store: %v", ep.id, err)
+ }
+
+ if ep.ifName == "" {
+ return nil
+ }
+
+ link, err := nlh.LinkByName(ep.ifName)
+ if err != nil {
+ logrus.Debugf("Failed to retrieve interface (%s)'s link on endpoint (%s) delete: %v", ep.ifName, ep.id, err)
+ return nil
+ }
+ if err := nlh.LinkDel(link); err != nil {
+ logrus.Debugf("Failed to delete interface (%s)'s link on endpoint (%s) delete: %v", ep.ifName, ep.id, err)
+ }
+
+ return nil
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return make(map[string]interface{}, 0), nil
+}
+
+func (d *driver) deleteEndpointFromStore(e *endpoint) error {
+ if d.localStore == nil {
+ return fmt.Errorf("overlay local store not initialized, ep not deleted")
+ }
+
+ return d.localStore.DeleteObjectAtomic(e)
+}
+
+func (d *driver) writeEndpointToStore(e *endpoint) error {
+ if d.localStore == nil {
+ return fmt.Errorf("overlay local store not initialized, ep not added")
+ }
+
+ return d.localStore.PutObjectAtomic(e)
+}
+
+func (ep *endpoint) DataScope() string {
+ return datastore.LocalScope
+}
+
+func (ep *endpoint) New() datastore.KVObject {
+ return &endpoint{}
+}
+
+func (ep *endpoint) CopyTo(o datastore.KVObject) error {
+ dstep := o.(*endpoint)
+ *dstep = *ep
+ return nil
+}
+
+func (ep *endpoint) Key() []string {
+ return []string{overlayEndpointPrefix, ep.id}
+}
+
+func (ep *endpoint) KeyPrefix() []string {
+ return []string{overlayEndpointPrefix}
+}
+
+func (ep *endpoint) Index() uint64 {
+ return ep.dbIndex
+}
+
+func (ep *endpoint) SetIndex(index uint64) {
+ ep.dbIndex = index
+ ep.dbExists = true
+}
+
+func (ep *endpoint) Exists() bool {
+ return ep.dbExists
+}
+
+func (ep *endpoint) Skip() bool {
+ return false
+}
+
+func (ep *endpoint) Value() []byte {
+ b, err := json.Marshal(ep)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (ep *endpoint) SetValue(value []byte) error {
+ return json.Unmarshal(value, ep)
+}
+
+func (ep *endpoint) MarshalJSON() ([]byte, error) {
+ epMap := make(map[string]interface{})
+
+ epMap["id"] = ep.id
+ epMap["nid"] = ep.nid
+ if ep.ifName != "" {
+ epMap["ifName"] = ep.ifName
+ }
+ if ep.addr != nil {
+ epMap["addr"] = ep.addr.String()
+ }
+ if len(ep.mac) != 0 {
+ epMap["mac"] = ep.mac.String()
+ }
+
+ return json.Marshal(epMap)
+}
+
+func (ep *endpoint) UnmarshalJSON(value []byte) error {
+ var (
+ err error
+ epMap map[string]interface{}
+ )
+
+ json.Unmarshal(value, &epMap)
+
+ ep.id = epMap["id"].(string)
+ ep.nid = epMap["nid"].(string)
+ if v, ok := epMap["mac"]; ok {
+ if ep.mac, err = net.ParseMAC(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode endpoint interface mac address after json unmarshal: %s", v.(string))
+ }
+ }
+ if v, ok := epMap["addr"]; ok {
+ if ep.addr, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode endpoint interface ipv4 address after json unmarshal: %v", err)
+ }
+ }
+ if v, ok := epMap["ifName"]; ok {
+ ep.ifName = v.(string)
+ }
+
+ return nil
+}
--- /dev/null
+package overlay
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/resolvconf"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+ "github.com/vishvananda/netlink/nl"
+ "github.com/vishvananda/netns"
+)
+
+var (
+ hostMode bool
+ networkOnce sync.Once
+ networkMu sync.Mutex
+ vniTbl = make(map[uint32]string)
+)
+
+type networkTable map[string]*network
+
+type subnet struct {
+ sboxInit bool
+ vxlanName string
+ brName string
+ vni uint32
+ initErr error
+ subnetIP *net.IPNet
+ gwIP *net.IPNet
+}
+
+type subnetJSON struct {
+ SubnetIP string
+ GwIP string
+ Vni uint32
+}
+
+type network struct {
+ id string
+ dbIndex uint64
+ dbExists bool
+ sbox osl.Sandbox
+ nlSocket *nl.NetlinkSocket
+ endpoints endpointTable
+ driver *driver
+ joinCnt int
+ sboxInit bool
+ initEpoch int
+ initErr error
+ subnets []*subnet
+ secure bool
+ mtu int
+ sync.Mutex
+}
+
+func init() {
+ reexec.Register("set-default-vlan", setDefaultVlan)
+}
+
+func setDefaultVlan() {
+ if len(os.Args) < 3 {
+ logrus.Error("insufficient number of arguments")
+ os.Exit(1)
+ }
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ nsPath := os.Args[1]
+ ns, err := netns.GetFromPath(nsPath)
+ if err != nil {
+ logrus.Errorf("overlay namespace get failed, %v", err)
+ os.Exit(1)
+ }
+ if err = netns.Set(ns); err != nil {
+ logrus.Errorf("setting into overlay namespace failed, %v", err)
+ os.Exit(1)
+ }
+
+ // make sure the sysfs mount doesn't propagate back
+ if err = syscall.Unshare(syscall.CLONE_NEWNS); err != nil {
+ logrus.Errorf("unshare failed, %v", err)
+ os.Exit(1)
+ }
+
+ flag := syscall.MS_PRIVATE | syscall.MS_REC
+ if err = syscall.Mount("", "/", "", uintptr(flag), ""); err != nil {
+ logrus.Errorf("root mount failed, %v", err)
+ os.Exit(1)
+ }
+
+ if err = syscall.Mount("sysfs", "/sys", "sysfs", 0, ""); err != nil {
+ logrus.Errorf("mounting sysfs failed, %v", err)
+ os.Exit(1)
+ }
+
+ brName := os.Args[2]
+ path := filepath.Join("/sys/class/net", brName, "bridge/default_pvid")
+ data := []byte{'0', '\n'}
+
+ if err = ioutil.WriteFile(path, data, 0644); err != nil {
+ logrus.Errorf("enabling default vlan on bridge %s failed %v", brName, err)
+ os.Exit(1)
+ }
+ os.Exit(0)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ if id == "" {
+ return fmt.Errorf("invalid network id")
+ }
+ if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {
+ return types.BadRequestErrorf("ipv4 pool is empty")
+ }
+
+ // Since we perform lazy configuration make sure we try
+ // configuring the driver when we enter CreateNetwork
+ if err := d.configure(); err != nil {
+ return err
+ }
+
+ n := &network{
+ id: id,
+ driver: d,
+ endpoints: endpointTable{},
+ subnets: []*subnet{},
+ }
+
+ vnis := make([]uint32, 0, len(ipV4Data))
+ if gval, ok := option[netlabel.GenericData]; ok {
+ optMap := gval.(map[string]string)
+ if val, ok := optMap[netlabel.OverlayVxlanIDList]; ok {
+ logrus.Debugf("overlay: Received vxlan IDs: %s", val)
+ vniStrings := strings.Split(val, ",")
+ for _, vniStr := range vniStrings {
+ vni, err := strconv.Atoi(vniStr)
+ if err != nil {
+ return fmt.Errorf("invalid vxlan id value %q passed", vniStr)
+ }
+
+ vnis = append(vnis, uint32(vni))
+ }
+ }
+ if _, ok := optMap[secureOption]; ok {
+ n.secure = true
+ }
+ if val, ok := optMap[netlabel.DriverMTU]; ok {
+ var err error
+ if n.mtu, err = strconv.Atoi(val); err != nil {
+ return fmt.Errorf("failed to parse %v: %v", val, err)
+ }
+ if n.mtu < 0 {
+ return fmt.Errorf("invalid MTU value: %v", n.mtu)
+ }
+ }
+ }
+
+ // If we are getting vnis from libnetwork, either we get for
+ // all subnets or none.
+ if len(vnis) != 0 && len(vnis) < len(ipV4Data) {
+ return fmt.Errorf("insufficient vnis(%d) passed to overlay", len(vnis))
+ }
+
+ for i, ipd := range ipV4Data {
+ s := &subnet{
+ subnetIP: ipd.Pool,
+ gwIP: ipd.Gateway,
+ }
+
+ if len(vnis) != 0 {
+ s.vni = vnis[i]
+ }
+
+ n.subnets = append(n.subnets, s)
+ }
+
+ d.Lock()
+ defer d.Unlock()
+ if d.networks[n.id] != nil {
+ return fmt.Errorf("attempt to create overlay network %v that already exists", n.id)
+ }
+
+ if err := n.writeToStore(); err != nil {
+ return fmt.Errorf("failed to update data store for network %v: %v", n.id, err)
+ }
+
+ // Make sure no rule is on the way from any stale secure network
+ if !n.secure {
+ for _, vni := range vnis {
+ programMangle(vni, false)
+ programInput(vni, false)
+ }
+ }
+
+ if nInfo != nil {
+ if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil {
+ // XXX Undo writeToStore? No method to so. Why?
+ return err
+ }
+ }
+
+ d.networks[id] = n
+
+ return nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ if nid == "" {
+ return fmt.Errorf("invalid network id")
+ }
+
+ // Make sure driver resources are initialized before proceeding
+ if err := d.configure(); err != nil {
+ return err
+ }
+
+ d.Lock()
+ // Only perform a peer flush operation (if required) AFTER unlocking
+ // the driver lock to avoid deadlocking w/ the peerDB.
+ var doPeerFlush bool
+ defer func() {
+ d.Unlock()
+ if doPeerFlush {
+ d.peerFlush(nid)
+ }
+ }()
+
+ // This is similar to d.network(), but we need to keep holding the lock
+ // until we are done removing this network.
+ n, ok := d.networks[nid]
+ if !ok {
+ n = d.restoreNetworkFromStore(nid)
+ }
+ if n == nil {
+ return fmt.Errorf("could not find network with id %s", nid)
+ }
+
+ for _, ep := range n.endpoints {
+ if ep.ifName != "" {
+ if link, err := ns.NlHandle().LinkByName(ep.ifName); err == nil {
+ if err := ns.NlHandle().LinkDel(link); err != nil {
+ logrus.WithError(err).Warnf("Failed to delete interface (%s)'s link on endpoint (%s) delete", ep.ifName, ep.id)
+ }
+ }
+ }
+
+ if err := d.deleteEndpointFromStore(ep); err != nil {
+ logrus.Warnf("Failed to delete overlay endpoint %.7s from local store: %v", ep.id, err)
+ }
+ }
+
+ doPeerFlush = true
+ delete(d.networks, nid)
+
+ vnis, err := n.releaseVxlanID()
+ if err != nil {
+ return err
+ }
+
+ if n.secure {
+ for _, vni := range vnis {
+ programMangle(vni, false)
+ programInput(vni, false)
+ }
+ }
+
+ return nil
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+}
+
+func (n *network) joinSandbox(s *subnet, restore bool, incJoinCount bool) error {
+ // If there is a race between two go routines here only one will win
+ // the other will wait.
+ networkOnce.Do(networkOnceInit)
+
+ n.Lock()
+ // If non-restore initialization occurred and was successful then
+ // tell the peerDB to initialize the sandbox with all the peers
+ // previously received from networkdb. But only do this after
+ // unlocking the network. Otherwise we could deadlock with
+ // on the peerDB channel while peerDB is waiting for the network lock.
+ var doInitPeerDB bool
+ defer func() {
+ n.Unlock()
+ if doInitPeerDB {
+ n.driver.initSandboxPeerDB(n.id)
+ }
+ }()
+
+ if !n.sboxInit {
+ n.initErr = n.initSandbox(restore)
+ doInitPeerDB = n.initErr == nil && !restore
+ // If there was an error, we cannot recover it
+ n.sboxInit = true
+ }
+
+ if n.initErr != nil {
+ return fmt.Errorf("network sandbox join failed: %v", n.initErr)
+ }
+
+ subnetErr := s.initErr
+ if !s.sboxInit {
+ subnetErr = n.initSubnetSandbox(s, restore)
+ // We can recover from these errors, but not on restore
+ if restore || subnetErr == nil {
+ s.initErr = subnetErr
+ s.sboxInit = true
+ }
+ }
+ if subnetErr != nil {
+ return fmt.Errorf("subnet sandbox join failed for %q: %v", s.subnetIP.String(), subnetErr)
+ }
+
+ if incJoinCount {
+ n.joinCnt++
+ }
+
+ return nil
+}
+
+func (n *network) leaveSandbox() {
+ n.Lock()
+ defer n.Unlock()
+ n.joinCnt--
+ if n.joinCnt != 0 {
+ return
+ }
+
+ n.destroySandbox()
+
+ n.sboxInit = false
+ n.initErr = nil
+ for _, s := range n.subnets {
+ s.sboxInit = false
+ s.initErr = nil
+ }
+}
+
+// to be called while holding network lock
+func (n *network) destroySandbox() {
+ if n.sbox != nil {
+ for _, iface := range n.sbox.Info().Interfaces() {
+ if err := iface.Remove(); err != nil {
+ logrus.Debugf("Remove interface %s failed: %v", iface.SrcName(), err)
+ }
+ }
+
+ for _, s := range n.subnets {
+ if hostMode {
+ if err := removeFilters(n.id[:12], s.brName); err != nil {
+ logrus.Warnf("Could not remove overlay filters: %v", err)
+ }
+ }
+
+ if s.vxlanName != "" {
+ err := deleteInterface(s.vxlanName)
+ if err != nil {
+ logrus.Warnf("could not cleanup sandbox properly: %v", err)
+ }
+ }
+ }
+
+ if hostMode {
+ if err := removeNetworkChain(n.id[:12]); err != nil {
+ logrus.Warnf("could not remove network chain: %v", err)
+ }
+ }
+
+ // Close the netlink socket, this will also release the watchMiss goroutine that is using it
+ if n.nlSocket != nil {
+ n.nlSocket.Close()
+ n.nlSocket = nil
+ }
+
+ n.sbox.Destroy()
+ n.sbox = nil
+ }
+}
+
+func populateVNITbl() {
+ filepath.Walk(filepath.Dir(osl.GenerateKey("walk")),
+ func(path string, info os.FileInfo, err error) error {
+ _, fname := filepath.Split(path)
+
+ if len(strings.Split(fname, "-")) <= 1 {
+ return nil
+ }
+
+ ns, err := netns.GetFromPath(path)
+ if err != nil {
+ logrus.Errorf("Could not open namespace path %s during vni population: %v", path, err)
+ return nil
+ }
+ defer ns.Close()
+
+ nlh, err := netlink.NewHandleAt(ns, syscall.NETLINK_ROUTE)
+ if err != nil {
+ logrus.Errorf("Could not open netlink handle during vni population for ns %s: %v", path, err)
+ return nil
+ }
+ defer nlh.Delete()
+
+ err = nlh.SetSocketTimeout(soTimeout)
+ if err != nil {
+ logrus.Warnf("Failed to set the timeout on the netlink handle sockets for vni table population: %v", err)
+ }
+
+ links, err := nlh.LinkList()
+ if err != nil {
+ logrus.Errorf("Failed to list interfaces during vni population for ns %s: %v", path, err)
+ return nil
+ }
+
+ for _, l := range links {
+ if l.Type() == "vxlan" {
+ vniTbl[uint32(l.(*netlink.Vxlan).VxlanId)] = path
+ }
+ }
+
+ return nil
+ })
+}
+
+func networkOnceInit() {
+ populateVNITbl()
+
+ if os.Getenv("_OVERLAY_HOST_MODE") != "" {
+ hostMode = true
+ return
+ }
+
+ err := createVxlan("testvxlan", 1, 0)
+ if err != nil {
+ logrus.Errorf("Failed to create testvxlan interface: %v", err)
+ return
+ }
+
+ defer deleteInterface("testvxlan")
+
+ path := "/proc/self/ns/net"
+ hNs, err := netns.GetFromPath(path)
+ if err != nil {
+ logrus.Errorf("Failed to get network namespace from path %s while setting host mode: %v", path, err)
+ return
+ }
+ defer hNs.Close()
+
+ nlh := ns.NlHandle()
+
+ iface, err := nlh.LinkByName("testvxlan")
+ if err != nil {
+ logrus.Errorf("Failed to get link testvxlan while setting host mode: %v", err)
+ return
+ }
+
+ // If we are not able to move the vxlan interface to a namespace
+ // then fallback to host mode
+ if err := nlh.LinkSetNsFd(iface, int(hNs)); err != nil {
+ hostMode = true
+ }
+}
+
+func (n *network) generateVxlanName(s *subnet) string {
+ id := n.id
+ if len(n.id) > 5 {
+ id = n.id[:5]
+ }
+
+ return fmt.Sprintf("vx-%06x-%v", s.vni, id)
+}
+
+func (n *network) generateBridgeName(s *subnet) string {
+ id := n.id
+ if len(n.id) > 5 {
+ id = n.id[:5]
+ }
+
+ return n.getBridgeNamePrefix(s) + "-" + id
+}
+
+func (n *network) getBridgeNamePrefix(s *subnet) string {
+ return fmt.Sprintf("ov-%06x", s.vni)
+}
+
+func checkOverlap(nw *net.IPNet) error {
+ var nameservers []string
+
+ if rc, err := resolvconf.Get(); err == nil {
+ nameservers = resolvconf.GetNameserversAsCIDR(rc.Content)
+ }
+
+ if err := netutils.CheckNameserverOverlaps(nameservers, nw); err != nil {
+ return fmt.Errorf("overlay subnet %s failed check with nameserver: %v: %v", nw.String(), nameservers, err)
+ }
+
+ if err := netutils.CheckRouteOverlaps(nw); err != nil {
+ return fmt.Errorf("overlay subnet %s failed check with host route table: %v", nw.String(), err)
+ }
+
+ return nil
+}
+
+func (n *network) restoreSubnetSandbox(s *subnet, brName, vxlanName string) error {
+ sbox := n.sbox
+
+ // restore overlay osl sandbox
+ Ifaces := make(map[string][]osl.IfaceOption)
+ brIfaceOption := make([]osl.IfaceOption, 2)
+ brIfaceOption = append(brIfaceOption, sbox.InterfaceOptions().Address(s.gwIP))
+ brIfaceOption = append(brIfaceOption, sbox.InterfaceOptions().Bridge(true))
+ Ifaces[brName+"+br"] = brIfaceOption
+
+ err := sbox.Restore(Ifaces, nil, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ Ifaces = make(map[string][]osl.IfaceOption)
+ vxlanIfaceOption := make([]osl.IfaceOption, 1)
+ vxlanIfaceOption = append(vxlanIfaceOption, sbox.InterfaceOptions().Master(brName))
+ Ifaces[vxlanName+"+vxlan"] = vxlanIfaceOption
+ return sbox.Restore(Ifaces, nil, nil, nil)
+}
+
+func (n *network) setupSubnetSandbox(s *subnet, brName, vxlanName string) error {
+
+ if hostMode {
+ // Try to delete stale bridge interface if it exists
+ if err := deleteInterface(brName); err != nil {
+ deleteInterfaceBySubnet(n.getBridgeNamePrefix(s), s)
+ }
+ // Try to delete the vxlan interface by vni if already present
+ deleteVxlanByVNI("", s.vni)
+
+ if err := checkOverlap(s.subnetIP); err != nil {
+ return err
+ }
+ }
+
+ if !hostMode {
+ // Try to find this subnet's vni is being used in some
+ // other namespace by looking at vniTbl that we just
+ // populated in the once init. If a hit is found then
+ // it must a stale namespace from previous
+ // life. Destroy it completely and reclaim resourced.
+ networkMu.Lock()
+ path, ok := vniTbl[s.vni]
+ networkMu.Unlock()
+
+ if ok {
+ deleteVxlanByVNI(path, s.vni)
+ if err := syscall.Unmount(path, syscall.MNT_FORCE); err != nil {
+ logrus.Errorf("unmount of %s failed: %v", path, err)
+ }
+ os.Remove(path)
+
+ networkMu.Lock()
+ delete(vniTbl, s.vni)
+ networkMu.Unlock()
+ }
+ }
+
+ // create a bridge and vxlan device for this subnet and move it to the sandbox
+ sbox := n.sbox
+
+ if err := sbox.AddInterface(brName, "br",
+ sbox.InterfaceOptions().Address(s.gwIP),
+ sbox.InterfaceOptions().Bridge(true)); err != nil {
+ return fmt.Errorf("bridge creation in sandbox failed for subnet %q: %v", s.subnetIP.String(), err)
+ }
+
+ err := createVxlan(vxlanName, s.vni, n.maxMTU())
+ if err != nil {
+ return err
+ }
+
+ if err := sbox.AddInterface(vxlanName, "vxlan",
+ sbox.InterfaceOptions().Master(brName)); err != nil {
+ // If adding vxlan device to the overlay namespace fails, remove the bridge interface we
+ // already added to the namespace. This allows the caller to try the setup again.
+ for _, iface := range sbox.Info().Interfaces() {
+ if iface.SrcName() == brName {
+ if ierr := iface.Remove(); ierr != nil {
+ logrus.Errorf("removing bridge failed from ov ns %v failed, %v", n.sbox.Key(), ierr)
+ }
+ }
+ }
+
+ // Also, delete the vxlan interface. Since a global vni id is associated
+ // with the vxlan interface, an orphaned vxlan interface will result in
+ // failure of vxlan device creation if the vni is assigned to some other
+ // network.
+ if deleteErr := deleteInterface(vxlanName); deleteErr != nil {
+ logrus.Warnf("could not delete vxlan interface, %s, error %v, after config error, %v", vxlanName, deleteErr, err)
+ }
+ return fmt.Errorf("vxlan interface creation failed for subnet %q: %v", s.subnetIP.String(), err)
+ }
+
+ if !hostMode {
+ var name string
+ for _, i := range sbox.Info().Interfaces() {
+ if i.Bridge() {
+ name = i.DstName()
+ }
+ }
+ cmd := &exec.Cmd{
+ Path: reexec.Self(),
+ Args: []string{"set-default-vlan", sbox.Key(), name},
+ Stdout: os.Stdout,
+ Stderr: os.Stderr,
+ }
+ if err := cmd.Run(); err != nil {
+ // not a fatal error
+ logrus.Errorf("reexec to set bridge default vlan failed %v", err)
+ }
+ }
+
+ if hostMode {
+ if err := addFilters(n.id[:12], brName); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Must be called with the network lock
+func (n *network) initSubnetSandbox(s *subnet, restore bool) error {
+ brName := n.generateBridgeName(s)
+ vxlanName := n.generateVxlanName(s)
+
+ if restore {
+ if err := n.restoreSubnetSandbox(s, brName, vxlanName); err != nil {
+ return err
+ }
+ } else {
+ if err := n.setupSubnetSandbox(s, brName, vxlanName); err != nil {
+ return err
+ }
+ }
+
+ s.vxlanName = vxlanName
+ s.brName = brName
+
+ return nil
+}
+
+func (n *network) cleanupStaleSandboxes() {
+ filepath.Walk(filepath.Dir(osl.GenerateKey("walk")),
+ func(path string, info os.FileInfo, err error) error {
+ _, fname := filepath.Split(path)
+
+ pList := strings.Split(fname, "-")
+ if len(pList) <= 1 {
+ return nil
+ }
+
+ pattern := pList[1]
+ if strings.Contains(n.id, pattern) {
+ // Delete all vnis
+ deleteVxlanByVNI(path, 0)
+ syscall.Unmount(path, syscall.MNT_DETACH)
+ os.Remove(path)
+
+ // Now that we have destroyed this
+ // sandbox, remove all references to
+ // it in vniTbl so that we don't
+ // inadvertently destroy the sandbox
+ // created in this life.
+ networkMu.Lock()
+ for vni, tblPath := range vniTbl {
+ if tblPath == path {
+ delete(vniTbl, vni)
+ }
+ }
+ networkMu.Unlock()
+ }
+
+ return nil
+ })
+}
+
+func (n *network) initSandbox(restore bool) error {
+ n.initEpoch++
+
+ if !restore {
+ if hostMode {
+ if err := addNetworkChain(n.id[:12]); err != nil {
+ return err
+ }
+ }
+
+ // If there are any stale sandboxes related to this network
+ // from previous daemon life clean it up here
+ n.cleanupStaleSandboxes()
+ }
+
+ // In the restore case network sandbox already exist; but we don't know
+ // what epoch number it was created with. It has to be retrieved by
+ // searching the net namespaces.
+ var key string
+ if restore {
+ key = osl.GenerateKey("-" + n.id)
+ } else {
+ key = osl.GenerateKey(fmt.Sprintf("%d-", n.initEpoch) + n.id)
+ }
+
+ sbox, err := osl.NewSandbox(key, !hostMode, restore)
+ if err != nil {
+ return fmt.Errorf("could not get network sandbox (oper %t): %v", restore, err)
+ }
+
+ // this is needed to let the peerAdd configure the sandbox
+ n.sbox = sbox
+
+ // If we are in swarm mode, we don't need anymore the watchMiss routine.
+ // This will save 1 thread and 1 netlink socket per network
+ if !n.driver.isSerfAlive() {
+ return nil
+ }
+
+ var nlSock *nl.NetlinkSocket
+ sbox.InvokeFunc(func() {
+ nlSock, err = nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_NEIGH)
+ if err != nil {
+ return
+ }
+ // set the receive timeout to not remain stuck on the RecvFrom if the fd gets closed
+ tv := syscall.NsecToTimeval(soTimeout.Nanoseconds())
+ err = nlSock.SetReceiveTimeout(&tv)
+ })
+ n.nlSocket = nlSock
+
+ if err == nil {
+ go n.watchMiss(nlSock, key)
+ } else {
+ logrus.Errorf("failed to subscribe to neighbor group netlink messages for overlay network %s in sbox %s: %v",
+ n.id, sbox.Key(), err)
+ }
+
+ return nil
+}
+
+func (n *network) watchMiss(nlSock *nl.NetlinkSocket, nsPath string) {
+ // With the new version of the netlink library the deserialize function makes
+ // requests about the interface of the netlink message. This can succeed only
+ // if this go routine is in the target namespace. For this reason following we
+ // lock the thread on that namespace
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+ newNs, err := netns.GetFromPath(nsPath)
+ if err != nil {
+ logrus.WithError(err).Errorf("failed to get the namespace %s", nsPath)
+ return
+ }
+ defer newNs.Close()
+ if err = netns.Set(newNs); err != nil {
+ logrus.WithError(err).Errorf("failed to enter the namespace %s", nsPath)
+ return
+ }
+ for {
+ msgs, err := nlSock.Receive()
+ if err != nil {
+ n.Lock()
+ nlFd := nlSock.GetFd()
+ n.Unlock()
+ if nlFd == -1 {
+ // The netlink socket got closed, simply exit to not leak this goroutine
+ return
+ }
+ // When the receive timeout expires the receive will return EAGAIN
+ if err == syscall.EAGAIN {
+ // we continue here to avoid spam for timeouts
+ continue
+ }
+ logrus.Errorf("Failed to receive from netlink: %v ", err)
+ continue
+ }
+
+ for _, msg := range msgs {
+ if msg.Header.Type != syscall.RTM_GETNEIGH && msg.Header.Type != syscall.RTM_NEWNEIGH {
+ continue
+ }
+
+ neigh, err := netlink.NeighDeserialize(msg.Data)
+ if err != nil {
+ logrus.Errorf("Failed to deserialize netlink ndmsg: %v", err)
+ continue
+ }
+
+ var (
+ ip net.IP
+ mac net.HardwareAddr
+ l2Miss, l3Miss bool
+ )
+ if neigh.IP.To4() != nil {
+ ip = neigh.IP
+ l3Miss = true
+ } else if neigh.HardwareAddr != nil {
+ mac = []byte(neigh.HardwareAddr)
+ ip = net.IP(mac[2:])
+ l2Miss = true
+ } else {
+ continue
+ }
+
+ // Not any of the network's subnets. Ignore.
+ if !n.contains(ip) {
+ continue
+ }
+
+ if neigh.State&(netlink.NUD_STALE|netlink.NUD_INCOMPLETE) == 0 {
+ continue
+ }
+
+ logrus.Debugf("miss notification: dest IP %v, dest MAC %v", ip, mac)
+ mac, IPmask, vtep, err := n.driver.resolvePeer(n.id, ip)
+ if err != nil {
+ logrus.Errorf("could not resolve peer %q: %v", ip, err)
+ continue
+ }
+ n.driver.peerAdd(n.id, "dummy", ip, IPmask, mac, vtep, l2Miss, l3Miss, false)
+ }
+ }
+}
+
+// Restore a network from the store to the driver if it is present.
+// Must be called with the driver locked!
+func (d *driver) restoreNetworkFromStore(nid string) *network {
+ n := d.getNetworkFromStore(nid)
+ if n != nil {
+ n.driver = d
+ n.endpoints = endpointTable{}
+ d.networks[nid] = n
+ }
+ return n
+}
+
+func (d *driver) network(nid string) *network {
+ d.Lock()
+ n, ok := d.networks[nid]
+ if !ok {
+ n = d.restoreNetworkFromStore(nid)
+ }
+ d.Unlock()
+
+ return n
+}
+
+func (d *driver) getNetworkFromStore(nid string) *network {
+ if d.store == nil {
+ return nil
+ }
+
+ n := &network{id: nid}
+ if err := d.store.GetObject(datastore.Key(n.Key()...), n); err != nil {
+ return nil
+ }
+
+ return n
+}
+
+func (n *network) sandbox() osl.Sandbox {
+ n.Lock()
+ defer n.Unlock()
+ return n.sbox
+}
+
+func (n *network) vxlanID(s *subnet) uint32 {
+ n.Lock()
+ defer n.Unlock()
+ return s.vni
+}
+
+func (n *network) setVxlanID(s *subnet, vni uint32) {
+ n.Lock()
+ s.vni = vni
+ n.Unlock()
+}
+
+func (n *network) Key() []string {
+ return []string{"overlay", "network", n.id}
+}
+
+func (n *network) KeyPrefix() []string {
+ return []string{"overlay", "network"}
+}
+
+func (n *network) Value() []byte {
+ m := map[string]interface{}{}
+
+ netJSON := []*subnetJSON{}
+
+ for _, s := range n.subnets {
+ sj := &subnetJSON{
+ SubnetIP: s.subnetIP.String(),
+ GwIP: s.gwIP.String(),
+ Vni: s.vni,
+ }
+ netJSON = append(netJSON, sj)
+ }
+
+ m["secure"] = n.secure
+ m["subnets"] = netJSON
+ m["mtu"] = n.mtu
+ b, err := json.Marshal(m)
+ if err != nil {
+ return []byte{}
+ }
+
+ return b
+}
+
+func (n *network) Index() uint64 {
+ return n.dbIndex
+}
+
+func (n *network) SetIndex(index uint64) {
+ n.dbIndex = index
+ n.dbExists = true
+}
+
+func (n *network) Exists() bool {
+ return n.dbExists
+}
+
+func (n *network) Skip() bool {
+ return false
+}
+
+func (n *network) SetValue(value []byte) error {
+ var (
+ m map[string]interface{}
+ newNet bool
+ isMap = true
+ netJSON = []*subnetJSON{}
+ )
+
+ if err := json.Unmarshal(value, &m); err != nil {
+ err := json.Unmarshal(value, &netJSON)
+ if err != nil {
+ return err
+ }
+ isMap = false
+ }
+
+ if len(n.subnets) == 0 {
+ newNet = true
+ }
+
+ if isMap {
+ if val, ok := m["secure"]; ok {
+ n.secure = val.(bool)
+ }
+ if val, ok := m["mtu"]; ok {
+ n.mtu = int(val.(float64))
+ }
+ bytes, err := json.Marshal(m["subnets"])
+ if err != nil {
+ return err
+ }
+ if err := json.Unmarshal(bytes, &netJSON); err != nil {
+ return err
+ }
+ }
+
+ for _, sj := range netJSON {
+ subnetIPstr := sj.SubnetIP
+ gwIPstr := sj.GwIP
+ vni := sj.Vni
+
+ subnetIP, _ := types.ParseCIDR(subnetIPstr)
+ gwIP, _ := types.ParseCIDR(gwIPstr)
+
+ if newNet {
+ s := &subnet{
+ subnetIP: subnetIP,
+ gwIP: gwIP,
+ vni: vni,
+ }
+ n.subnets = append(n.subnets, s)
+ } else {
+ sNet := n.getMatchingSubnet(subnetIP)
+ if sNet != nil {
+ sNet.vni = vni
+ }
+ }
+ }
+ return nil
+}
+
+func (n *network) DataScope() string {
+ return datastore.GlobalScope
+}
+
+func (n *network) writeToStore() error {
+ if n.driver.store == nil {
+ return nil
+ }
+
+ return n.driver.store.PutObjectAtomic(n)
+}
+
+func (n *network) releaseVxlanID() ([]uint32, error) {
+ n.Lock()
+ nSubnets := len(n.subnets)
+ n.Unlock()
+ if nSubnets == 0 {
+ return nil, nil
+ }
+
+ if n.driver.store != nil {
+ if err := n.driver.store.DeleteObjectAtomic(n); err != nil {
+ if err == datastore.ErrKeyModified || err == datastore.ErrKeyNotFound {
+ // In both the above cases we can safely assume that the key has been removed by some other
+ // instance and so simply get out of here
+ return nil, nil
+ }
+
+ return nil, fmt.Errorf("failed to delete network to vxlan id map: %v", err)
+ }
+ }
+ var vnis []uint32
+ n.Lock()
+ for _, s := range n.subnets {
+ if n.driver.vxlanIdm != nil {
+ vnis = append(vnis, s.vni)
+ }
+ s.vni = 0
+ }
+ n.Unlock()
+
+ for _, vni := range vnis {
+ n.driver.vxlanIdm.Release(uint64(vni))
+ }
+
+ return vnis, nil
+}
+
+func (n *network) obtainVxlanID(s *subnet) error {
+ //return if the subnet already has a vxlan id assigned
+ if n.vxlanID(s) != 0 {
+ return nil
+ }
+
+ if n.driver.store == nil {
+ return fmt.Errorf("no valid vxlan id and no datastore configured, cannot obtain vxlan id")
+ }
+
+ for {
+ if err := n.driver.store.GetObject(datastore.Key(n.Key()...), n); err != nil {
+ return fmt.Errorf("getting network %q from datastore failed %v", n.id, err)
+ }
+
+ if n.vxlanID(s) == 0 {
+ vxlanID, err := n.driver.vxlanIdm.GetID(true)
+ if err != nil {
+ return fmt.Errorf("failed to allocate vxlan id: %v", err)
+ }
+
+ n.setVxlanID(s, uint32(vxlanID))
+ if err := n.writeToStore(); err != nil {
+ n.driver.vxlanIdm.Release(uint64(n.vxlanID(s)))
+ n.setVxlanID(s, 0)
+ if err == datastore.ErrKeyModified {
+ continue
+ }
+ return fmt.Errorf("network %q failed to update data store: %v", n.id, err)
+ }
+ return nil
+ }
+ return nil
+ }
+}
+
+// contains return true if the passed ip belongs to one the network's
+// subnets
+func (n *network) contains(ip net.IP) bool {
+ for _, s := range n.subnets {
+ if s.subnetIP.Contains(ip) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// getSubnetforIP returns the subnet to which the given IP belongs
+func (n *network) getSubnetforIP(ip *net.IPNet) *subnet {
+ for _, s := range n.subnets {
+ // first check if the mask lengths are the same
+ i, _ := s.subnetIP.Mask.Size()
+ j, _ := ip.Mask.Size()
+ if i != j {
+ continue
+ }
+ if s.subnetIP.Contains(ip.IP) {
+ return s
+ }
+ }
+ return nil
+}
+
+// getMatchingSubnet return the network's subnet that matches the input
+func (n *network) getMatchingSubnet(ip *net.IPNet) *subnet {
+ if ip == nil {
+ return nil
+ }
+ for _, s := range n.subnets {
+ // first check if the mask lengths are the same
+ i, _ := s.subnetIP.Mask.Size()
+ j, _ := ip.Mask.Size()
+ if i != j {
+ continue
+ }
+ if s.subnetIP.IP.Equal(ip.IP) {
+ return s
+ }
+ }
+ return nil
+}
--- /dev/null
+package overlay
+
+import (
+ "fmt"
+ "net"
+ "strings"
+ "time"
+
+ "github.com/hashicorp/serf/serf"
+ "github.com/sirupsen/logrus"
+)
+
+type ovNotify struct {
+ action string
+ ep *endpoint
+ nw *network
+}
+
+type logWriter struct{}
+
+func (l *logWriter) Write(p []byte) (int, error) {
+ str := string(p)
+
+ switch {
+ case strings.Contains(str, "[WARN]"):
+ logrus.Warn(str)
+ case strings.Contains(str, "[DEBUG]"):
+ logrus.Debug(str)
+ case strings.Contains(str, "[INFO]"):
+ logrus.Info(str)
+ case strings.Contains(str, "[ERR]"):
+ logrus.Error(str)
+ }
+
+ return len(p), nil
+}
+
+func (d *driver) serfInit() error {
+ var err error
+
+ config := serf.DefaultConfig()
+ config.Init()
+ config.MemberlistConfig.BindAddr = d.advertiseAddress
+
+ d.eventCh = make(chan serf.Event, 4)
+ config.EventCh = d.eventCh
+ config.UserCoalescePeriod = 1 * time.Second
+ config.UserQuiescentPeriod = 50 * time.Millisecond
+
+ config.LogOutput = &logWriter{}
+ config.MemberlistConfig.LogOutput = config.LogOutput
+
+ s, err := serf.Create(config)
+ if err != nil {
+ return fmt.Errorf("failed to create cluster node: %v", err)
+ }
+ defer func() {
+ if err != nil {
+ s.Shutdown()
+ }
+ }()
+
+ d.serfInstance = s
+
+ d.notifyCh = make(chan ovNotify)
+ d.exitCh = make(chan chan struct{})
+
+ go d.startSerfLoop(d.eventCh, d.notifyCh, d.exitCh)
+ return nil
+}
+
+func (d *driver) serfJoin(neighIP string) error {
+ if neighIP == "" {
+ return fmt.Errorf("no neighbor to join")
+ }
+ if _, err := d.serfInstance.Join([]string{neighIP}, true); err != nil {
+ return fmt.Errorf("Failed to join the cluster at neigh IP %s: %v",
+ neighIP, err)
+ }
+ return nil
+}
+
+func (d *driver) notifyEvent(event ovNotify) {
+ ep := event.ep
+
+ ePayload := fmt.Sprintf("%s %s %s %s", event.action, ep.addr.IP.String(),
+ net.IP(ep.addr.Mask).String(), ep.mac.String())
+ eName := fmt.Sprintf("jl %s %s %s", d.serfInstance.LocalMember().Addr.String(),
+ event.nw.id, ep.id)
+
+ if err := d.serfInstance.UserEvent(eName, []byte(ePayload), true); err != nil {
+ logrus.Errorf("Sending user event failed: %v\n", err)
+ }
+}
+
+func (d *driver) processEvent(u serf.UserEvent) {
+ logrus.Debugf("Received user event name:%s, payload:%s LTime:%d \n", u.Name,
+ string(u.Payload), uint64(u.LTime))
+
+ var dummy, action, vtepStr, nid, eid, ipStr, maskStr, macStr string
+ if _, err := fmt.Sscan(u.Name, &dummy, &vtepStr, &nid, &eid); err != nil {
+ fmt.Printf("Failed to scan name string: %v\n", err)
+ }
+
+ if _, err := fmt.Sscan(string(u.Payload), &action,
+ &ipStr, &maskStr, &macStr); err != nil {
+ fmt.Printf("Failed to scan value string: %v\n", err)
+ }
+
+ logrus.Debugf("Parsed data = %s/%s/%s/%s/%s/%s\n", nid, eid, vtepStr, ipStr, maskStr, macStr)
+
+ mac, err := net.ParseMAC(macStr)
+ if err != nil {
+ logrus.Errorf("Failed to parse mac: %v\n", err)
+ }
+
+ if d.serfInstance.LocalMember().Addr.String() == vtepStr {
+ return
+ }
+
+ switch action {
+ case "join":
+ d.peerAdd(nid, eid, net.ParseIP(ipStr), net.IPMask(net.ParseIP(maskStr).To4()), mac, net.ParseIP(vtepStr), false, false, false)
+ case "leave":
+ d.peerDelete(nid, eid, net.ParseIP(ipStr), net.IPMask(net.ParseIP(maskStr).To4()), mac, net.ParseIP(vtepStr), false)
+ }
+}
+
+func (d *driver) processQuery(q *serf.Query) {
+ logrus.Debugf("Received query name:%s, payload:%s\n", q.Name,
+ string(q.Payload))
+
+ var nid, ipStr string
+ if _, err := fmt.Sscan(string(q.Payload), &nid, &ipStr); err != nil {
+ fmt.Printf("Failed to scan query payload string: %v\n", err)
+ }
+
+ pKey, pEntry, err := d.peerDbSearch(nid, net.ParseIP(ipStr))
+ if err != nil {
+ return
+ }
+
+ logrus.Debugf("Sending peer query resp mac %v, mask %s, vtep %s", pKey.peerMac, net.IP(pEntry.peerIPMask).String(), pEntry.vtep)
+ q.Respond([]byte(fmt.Sprintf("%s %s %s", pKey.peerMac.String(), net.IP(pEntry.peerIPMask).String(), pEntry.vtep.String())))
+}
+
+func (d *driver) resolvePeer(nid string, peerIP net.IP) (net.HardwareAddr, net.IPMask, net.IP, error) {
+ if d.serfInstance == nil {
+ return nil, nil, nil, fmt.Errorf("could not resolve peer: serf instance not initialized")
+ }
+
+ qPayload := fmt.Sprintf("%s %s", string(nid), peerIP.String())
+ resp, err := d.serfInstance.Query("peerlookup", []byte(qPayload), nil)
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("resolving peer by querying the cluster failed: %v", err)
+ }
+
+ respCh := resp.ResponseCh()
+ select {
+ case r := <-respCh:
+ var macStr, maskStr, vtepStr string
+ if _, err := fmt.Sscan(string(r.Payload), &macStr, &maskStr, &vtepStr); err != nil {
+ return nil, nil, nil, fmt.Errorf("bad response %q for the resolve query: %v", string(r.Payload), err)
+ }
+
+ mac, err := net.ParseMAC(macStr)
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("failed to parse mac: %v", err)
+ }
+
+ logrus.Debugf("Received peer query response, mac %s, vtep %s, mask %s", macStr, vtepStr, maskStr)
+ return mac, net.IPMask(net.ParseIP(maskStr).To4()), net.ParseIP(vtepStr), nil
+
+ case <-time.After(time.Second):
+ return nil, nil, nil, fmt.Errorf("timed out resolving peer by querying the cluster")
+ }
+}
+
+func (d *driver) startSerfLoop(eventCh chan serf.Event, notifyCh chan ovNotify,
+ exitCh chan chan struct{}) {
+
+ for {
+ select {
+ case notify, ok := <-notifyCh:
+ if !ok {
+ break
+ }
+
+ d.notifyEvent(notify)
+ case ch, ok := <-exitCh:
+ if !ok {
+ break
+ }
+
+ if err := d.serfInstance.Leave(); err != nil {
+ logrus.Errorf("failed leaving the cluster: %v\n", err)
+ }
+
+ d.serfInstance.Shutdown()
+ close(ch)
+ return
+ case e, ok := <-eventCh:
+ if !ok {
+ break
+ }
+
+ if e.EventType() == serf.EventQuery {
+ d.processQuery(e.(*serf.Query))
+ break
+ }
+
+ u, ok := e.(serf.UserEvent)
+ if !ok {
+ break
+ }
+ d.processEvent(u)
+ }
+ }
+}
+
+func (d *driver) isSerfAlive() bool {
+ d.Lock()
+ serfInstance := d.serfInstance
+ d.Unlock()
+ if serfInstance == nil || serfInstance.State() != serf.SerfAlive {
+ return false
+ }
+ return true
+}
--- /dev/null
+package overlay
+
+import (
+ "fmt"
+ "strings"
+ "syscall"
+
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/osl"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+ "github.com/vishvananda/netns"
+)
+
+var soTimeout = ns.NetlinkSocketsTimeout
+
+func validateID(nid, eid string) error {
+ if nid == "" {
+ return fmt.Errorf("invalid network id")
+ }
+
+ if eid == "" {
+ return fmt.Errorf("invalid endpoint id")
+ }
+
+ return nil
+}
+
+func createVethPair() (string, string, error) {
+ defer osl.InitOSContext()()
+ nlh := ns.NlHandle()
+
+ // Generate a name for what will be the host side pipe interface
+ name1, err := netutils.GenerateIfaceName(nlh, vethPrefix, vethLen)
+ if err != nil {
+ return "", "", fmt.Errorf("error generating veth name1: %v", err)
+ }
+
+ // Generate a name for what will be the sandbox side pipe interface
+ name2, err := netutils.GenerateIfaceName(nlh, vethPrefix, vethLen)
+ if err != nil {
+ return "", "", fmt.Errorf("error generating veth name2: %v", err)
+ }
+
+ // Generate and add the interface pipe host <-> sandbox
+ veth := &netlink.Veth{
+ LinkAttrs: netlink.LinkAttrs{Name: name1, TxQLen: 0},
+ PeerName: name2}
+ if err := nlh.LinkAdd(veth); err != nil {
+ return "", "", fmt.Errorf("error creating veth pair: %v", err)
+ }
+
+ return name1, name2, nil
+}
+
+func createVxlan(name string, vni uint32, mtu int) error {
+ defer osl.InitOSContext()()
+
+ vxlan := &netlink.Vxlan{
+ LinkAttrs: netlink.LinkAttrs{Name: name, MTU: mtu},
+ VxlanId: int(vni),
+ Learning: true,
+ Port: vxlanPort,
+ Proxy: true,
+ L3miss: true,
+ L2miss: true,
+ }
+
+ if err := ns.NlHandle().LinkAdd(vxlan); err != nil {
+ return fmt.Errorf("error creating vxlan interface: %v", err)
+ }
+
+ return nil
+}
+
+func deleteInterfaceBySubnet(brPrefix string, s *subnet) error {
+ defer osl.InitOSContext()()
+
+ nlh := ns.NlHandle()
+ links, err := nlh.LinkList()
+ if err != nil {
+ return fmt.Errorf("failed to list interfaces while deleting bridge interface by subnet: %v", err)
+ }
+
+ for _, l := range links {
+ name := l.Attrs().Name
+ if _, ok := l.(*netlink.Bridge); ok && strings.HasPrefix(name, brPrefix) {
+ addrList, err := nlh.AddrList(l, netlink.FAMILY_V4)
+ if err != nil {
+ logrus.Errorf("error getting AddressList for bridge %s", name)
+ continue
+ }
+ for _, addr := range addrList {
+ if netutils.NetworkOverlaps(addr.IPNet, s.subnetIP) {
+ err = nlh.LinkDel(l)
+ if err != nil {
+ logrus.Errorf("error deleting bridge (%s) with subnet %v: %v", name, addr.IPNet, err)
+ }
+ }
+ }
+ }
+ }
+ return nil
+
+}
+
+func deleteInterface(name string) error {
+ defer osl.InitOSContext()()
+
+ link, err := ns.NlHandle().LinkByName(name)
+ if err != nil {
+ return fmt.Errorf("failed to find interface with name %s: %v", name, err)
+ }
+
+ if err := ns.NlHandle().LinkDel(link); err != nil {
+ return fmt.Errorf("error deleting interface with name %s: %v", name, err)
+ }
+
+ return nil
+}
+
+func deleteVxlanByVNI(path string, vni uint32) error {
+ defer osl.InitOSContext()()
+
+ nlh := ns.NlHandle()
+ if path != "" {
+ ns, err := netns.GetFromPath(path)
+ if err != nil {
+ return fmt.Errorf("failed to get ns handle for %s: %v", path, err)
+ }
+ defer ns.Close()
+
+ nlh, err = netlink.NewHandleAt(ns, syscall.NETLINK_ROUTE)
+ if err != nil {
+ return fmt.Errorf("failed to get netlink handle for ns %s: %v", path, err)
+ }
+ defer nlh.Delete()
+ err = nlh.SetSocketTimeout(soTimeout)
+ if err != nil {
+ logrus.Warnf("Failed to set the timeout on the netlink handle sockets for vxlan deletion: %v", err)
+ }
+ }
+
+ links, err := nlh.LinkList()
+ if err != nil {
+ return fmt.Errorf("failed to list interfaces while deleting vxlan interface by vni: %v", err)
+ }
+
+ for _, l := range links {
+ if l.Type() == "vxlan" && (vni == 0 || l.(*netlink.Vxlan).VxlanId == int(vni)) {
+ err = nlh.LinkDel(l)
+ if err != nil {
+ return fmt.Errorf("error deleting vxlan interface with id %d: %v", vni, err)
+ }
+ return nil
+ }
+ }
+
+ return fmt.Errorf("could not find a vxlan interface to delete with id %d", vni)
+}
--- /dev/null
+package overlay
+
+//go:generate protoc -I.:../../Godeps/_workspace/src/github.com/gogo/protobuf --gogo_out=import_path=github.com/docker/libnetwork/drivers/overlay,Mgogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto:. overlay.proto
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/idm"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/hashicorp/serf/serf"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ networkType = "overlay"
+ vethPrefix = "veth"
+ vethLen = 7
+ vxlanIDStart = 256
+ vxlanIDEnd = (1 << 24) - 1
+ vxlanPort = 4789
+ vxlanEncap = 50
+ secureOption = "encrypted"
+)
+
+var initVxlanIdm = make(chan (bool), 1)
+
+type driver struct {
+ eventCh chan serf.Event
+ notifyCh chan ovNotify
+ exitCh chan chan struct{}
+ bindAddress string
+ advertiseAddress string
+ neighIP string
+ config map[string]interface{}
+ peerDb peerNetworkMap
+ secMap *encrMap
+ serfInstance *serf.Serf
+ networks networkTable
+ store datastore.DataStore
+ localStore datastore.DataStore
+ vxlanIdm *idm.Idm
+ initOS sync.Once
+ joinOnce sync.Once
+ localJoinOnce sync.Once
+ keys []*key
+ peerOpCh chan *peerOperation
+ peerOpCancel context.CancelFunc
+ sync.Mutex
+}
+
+// Init registers a new instance of overlay driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ c := driverapi.Capability{
+ DataScope: datastore.GlobalScope,
+ ConnectivityScope: datastore.GlobalScope,
+ }
+ d := &driver{
+ networks: networkTable{},
+ peerDb: peerNetworkMap{
+ mp: map[string]*peerMap{},
+ },
+ secMap: &encrMap{nodes: map[string][]*spi{}},
+ config: config,
+ peerOpCh: make(chan *peerOperation),
+ }
+
+ // Launch the go routine for processing peer operations
+ ctx, cancel := context.WithCancel(context.Background())
+ d.peerOpCancel = cancel
+ go d.peerOpRoutine(ctx, d.peerOpCh)
+
+ if data, ok := config[netlabel.GlobalKVClient]; ok {
+ var err error
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+ }
+ d.store, err = datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return types.InternalErrorf("failed to initialize data store: %v", err)
+ }
+ }
+
+ if data, ok := config[netlabel.LocalKVClient]; ok {
+ var err error
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+ }
+ d.localStore, err = datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return types.InternalErrorf("failed to initialize local data store: %v", err)
+ }
+ }
+
+ if err := d.restoreEndpoints(); err != nil {
+ logrus.Warnf("Failure during overlay endpoints restore: %v", err)
+ }
+
+ return dc.RegisterDriver(networkType, d, c)
+}
+
+// Endpoints are stored in the local store. Restore them and reconstruct the overlay sandbox
+func (d *driver) restoreEndpoints() error {
+ if d.localStore == nil {
+ logrus.Warn("Cannot restore overlay endpoints because local datastore is missing")
+ return nil
+ }
+ kvol, err := d.localStore.List(datastore.Key(overlayEndpointPrefix), &endpoint{})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return fmt.Errorf("failed to read overlay endpoint from store: %v", err)
+ }
+
+ if err == datastore.ErrKeyNotFound {
+ return nil
+ }
+ for _, kvo := range kvol {
+ ep := kvo.(*endpoint)
+ n := d.network(ep.nid)
+ if n == nil {
+ logrus.Debugf("Network (%.7s) not found for restored endpoint (%.7s)", ep.nid, ep.id)
+ logrus.Debugf("Deleting stale overlay endpoint (%.7s) from store", ep.id)
+ if err := d.deleteEndpointFromStore(ep); err != nil {
+ logrus.Debugf("Failed to delete stale overlay endpoint (%.7s) from store", ep.id)
+ }
+ continue
+ }
+ n.addEndpoint(ep)
+
+ s := n.getSubnetforIP(ep.addr)
+ if s == nil {
+ return fmt.Errorf("could not find subnet for endpoint %s", ep.id)
+ }
+
+ if err := n.joinSandbox(s, true, true); err != nil {
+ return fmt.Errorf("restore network sandbox failed: %v", err)
+ }
+
+ Ifaces := make(map[string][]osl.IfaceOption)
+ vethIfaceOption := make([]osl.IfaceOption, 1)
+ vethIfaceOption = append(vethIfaceOption, n.sbox.InterfaceOptions().Master(s.brName))
+ Ifaces["veth+veth"] = vethIfaceOption
+
+ err := n.sbox.Restore(Ifaces, nil, nil, nil)
+ if err != nil {
+ n.leaveSandbox()
+ return fmt.Errorf("failed to restore overlay sandbox: %v", err)
+ }
+
+ d.peerAdd(ep.nid, ep.id, ep.addr.IP, ep.addr.Mask, ep.mac, net.ParseIP(d.advertiseAddress), false, false, true)
+ }
+ return nil
+}
+
+// Fini cleans up the driver resources
+func Fini(drv driverapi.Driver) {
+ d := drv.(*driver)
+
+ // Notify the peer go routine to return
+ if d.peerOpCancel != nil {
+ d.peerOpCancel()
+ }
+
+ if d.exitCh != nil {
+ waitCh := make(chan struct{})
+
+ d.exitCh <- waitCh
+
+ <-waitCh
+ }
+}
+
+func (d *driver) configure() error {
+
+ // Apply OS specific kernel configs if needed
+ d.initOS.Do(applyOStweaks)
+
+ if d.store == nil {
+ return nil
+ }
+
+ if d.vxlanIdm == nil {
+ return d.initializeVxlanIdm()
+ }
+
+ return nil
+}
+
+func (d *driver) initializeVxlanIdm() error {
+ var err error
+
+ initVxlanIdm <- true
+ defer func() { <-initVxlanIdm }()
+
+ if d.vxlanIdm != nil {
+ return nil
+ }
+
+ d.vxlanIdm, err = idm.New(d.store, "vxlan-id", vxlanIDStart, vxlanIDEnd)
+ if err != nil {
+ return fmt.Errorf("failed to initialize vxlan id manager: %v", err)
+ }
+
+ return nil
+}
+
+func (d *driver) Type() string {
+ return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+func validateSelf(node string) error {
+ advIP := net.ParseIP(node)
+ if advIP == nil {
+ return fmt.Errorf("invalid self address (%s)", node)
+ }
+
+ addrs, err := net.InterfaceAddrs()
+ if err != nil {
+ return fmt.Errorf("Unable to get interface addresses %v", err)
+ }
+ for _, addr := range addrs {
+ ip, _, err := net.ParseCIDR(addr.String())
+ if err == nil && ip.Equal(advIP) {
+ return nil
+ }
+ }
+ return fmt.Errorf("Multi-Host overlay networking requires cluster-advertise(%s) to be configured with a local ip-address that is reachable within the cluster", advIP.String())
+}
+
+func (d *driver) nodeJoin(advertiseAddress, bindAddress string, self bool) {
+ if self && !d.isSerfAlive() {
+ d.Lock()
+ d.advertiseAddress = advertiseAddress
+ d.bindAddress = bindAddress
+ d.Unlock()
+
+ // If containers are already running on this network update the
+ // advertise address in the peerDB
+ d.localJoinOnce.Do(func() {
+ d.peerDBUpdateSelf()
+ })
+
+ // If there is no cluster store there is no need to start serf.
+ if d.store != nil {
+ if err := validateSelf(advertiseAddress); err != nil {
+ logrus.Warn(err.Error())
+ }
+ err := d.serfInit()
+ if err != nil {
+ logrus.Errorf("initializing serf instance failed: %v", err)
+ d.Lock()
+ d.advertiseAddress = ""
+ d.bindAddress = ""
+ d.Unlock()
+ return
+ }
+ }
+ }
+
+ d.Lock()
+ if !self {
+ d.neighIP = advertiseAddress
+ }
+ neighIP := d.neighIP
+ d.Unlock()
+
+ if d.serfInstance != nil && neighIP != "" {
+ var err error
+ d.joinOnce.Do(func() {
+ err = d.serfJoin(neighIP)
+ if err == nil {
+ d.pushLocalDb()
+ }
+ })
+ if err != nil {
+ logrus.Errorf("joining serf neighbor %s failed: %v", advertiseAddress, err)
+ d.Lock()
+ d.joinOnce = sync.Once{}
+ d.Unlock()
+ return
+ }
+ }
+}
+
+func (d *driver) pushLocalEndpointEvent(action, nid, eid string) {
+ n := d.network(nid)
+ if n == nil {
+ logrus.Debugf("Error pushing local endpoint event for network %s", nid)
+ return
+ }
+ ep := n.endpoint(eid)
+ if ep == nil {
+ logrus.Debugf("Error pushing local endpoint event for ep %s / %s", nid, eid)
+ return
+ }
+
+ if !d.isSerfAlive() {
+ return
+ }
+ d.notifyCh <- ovNotify{
+ action: "join",
+ nw: n,
+ ep: ep,
+ }
+}
+
+// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ var err error
+ switch dType {
+ case discoverapi.NodeDiscovery:
+ nodeData, ok := data.(discoverapi.NodeDiscoveryData)
+ if !ok || nodeData.Address == "" {
+ return fmt.Errorf("invalid discovery data")
+ }
+ d.nodeJoin(nodeData.Address, nodeData.BindAddress, nodeData.Self)
+ case discoverapi.DatastoreConfig:
+ if d.store != nil {
+ return types.ForbiddenErrorf("cannot accept datastore configuration: Overlay driver has a datastore configured already")
+ }
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+ }
+ d.store, err = datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return types.InternalErrorf("failed to initialize data store: %v", err)
+ }
+ case discoverapi.EncryptionKeysConfig:
+ encrData, ok := data.(discoverapi.DriverEncryptionConfig)
+ if !ok {
+ return fmt.Errorf("invalid encryption key notification data")
+ }
+ keys := make([]*key, 0, len(encrData.Keys))
+ for i := 0; i < len(encrData.Keys); i++ {
+ k := &key{
+ value: encrData.Keys[i],
+ tag: uint32(encrData.Tags[i]),
+ }
+ keys = append(keys, k)
+ }
+ if err := d.setKeys(keys); err != nil {
+ logrus.Warn(err)
+ }
+ case discoverapi.EncryptionKeysUpdate:
+ var newKey, delKey, priKey *key
+ encrData, ok := data.(discoverapi.DriverEncryptionUpdate)
+ if !ok {
+ return fmt.Errorf("invalid encryption key notification data")
+ }
+ if encrData.Key != nil {
+ newKey = &key{
+ value: encrData.Key,
+ tag: uint32(encrData.Tag),
+ }
+ }
+ if encrData.Primary != nil {
+ priKey = &key{
+ value: encrData.Primary,
+ tag: uint32(encrData.PrimaryTag),
+ }
+ }
+ if encrData.Prune != nil {
+ delKey = &key{
+ value: encrData.Prune,
+ tag: uint32(encrData.PruneTag),
+ }
+ }
+ if err := d.updateKeys(newKey, priKey, delKey); err != nil {
+ logrus.Warn(err)
+ }
+ default:
+ }
+ return nil
+}
+
+// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
--- /dev/null
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: drivers/overlay/overlay.proto
+
+/*
+ Package overlay is a generated protocol buffer package.
+
+ It is generated from these files:
+ drivers/overlay/overlay.proto
+
+ It has these top-level messages:
+ PeerRecord
+*/
+package overlay
+
+import proto "github.com/gogo/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import _ "github.com/gogo/protobuf/gogoproto"
+
+import strings "strings"
+import reflect "reflect"
+
+import io "io"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
+
+// PeerRecord defines the information corresponding to a peer
+// container in the overlay network.
+type PeerRecord struct {
+ // Endpoint IP is the IP of the container attachment on the
+ // given overlay network.
+ EndpointIP string `protobuf:"bytes,1,opt,name=endpoint_ip,json=endpointIp,proto3" json:"endpoint_ip,omitempty"`
+ // Endpoint MAC is the mac address of the container attachment
+ // on the given overlay network.
+ EndpointMAC string `protobuf:"bytes,2,opt,name=endpoint_mac,json=endpointMac,proto3" json:"endpoint_mac,omitempty"`
+ // Tunnel Endpoint IP defines the host IP for the host in
+ // which this container is running and can be reached by
+ // building a tunnel to that host IP.
+ TunnelEndpointIP string `protobuf:"bytes,3,opt,name=tunnel_endpoint_ip,json=tunnelEndpointIp,proto3" json:"tunnel_endpoint_ip,omitempty"`
+}
+
+func (m *PeerRecord) Reset() { *m = PeerRecord{} }
+func (*PeerRecord) ProtoMessage() {}
+func (*PeerRecord) Descriptor() ([]byte, []int) { return fileDescriptorOverlay, []int{0} }
+
+func (m *PeerRecord) GetEndpointIP() string {
+ if m != nil {
+ return m.EndpointIP
+ }
+ return ""
+}
+
+func (m *PeerRecord) GetEndpointMAC() string {
+ if m != nil {
+ return m.EndpointMAC
+ }
+ return ""
+}
+
+func (m *PeerRecord) GetTunnelEndpointIP() string {
+ if m != nil {
+ return m.TunnelEndpointIP
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*PeerRecord)(nil), "overlay.PeerRecord")
+}
+func (this *PeerRecord) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 7)
+ s = append(s, "&overlay.PeerRecord{")
+ s = append(s, "EndpointIP: "+fmt.Sprintf("%#v", this.EndpointIP)+",\n")
+ s = append(s, "EndpointMAC: "+fmt.Sprintf("%#v", this.EndpointMAC)+",\n")
+ s = append(s, "TunnelEndpointIP: "+fmt.Sprintf("%#v", this.TunnelEndpointIP)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func valueToGoStringOverlay(v interface{}, typ string) string {
+ rv := reflect.ValueOf(v)
+ if rv.IsNil() {
+ return "nil"
+ }
+ pv := reflect.Indirect(rv).Interface()
+ return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)
+}
+func (m *PeerRecord) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *PeerRecord) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if len(m.EndpointIP) > 0 {
+ dAtA[i] = 0xa
+ i++
+ i = encodeVarintOverlay(dAtA, i, uint64(len(m.EndpointIP)))
+ i += copy(dAtA[i:], m.EndpointIP)
+ }
+ if len(m.EndpointMAC) > 0 {
+ dAtA[i] = 0x12
+ i++
+ i = encodeVarintOverlay(dAtA, i, uint64(len(m.EndpointMAC)))
+ i += copy(dAtA[i:], m.EndpointMAC)
+ }
+ if len(m.TunnelEndpointIP) > 0 {
+ dAtA[i] = 0x1a
+ i++
+ i = encodeVarintOverlay(dAtA, i, uint64(len(m.TunnelEndpointIP)))
+ i += copy(dAtA[i:], m.TunnelEndpointIP)
+ }
+ return i, nil
+}
+
+func encodeVarintOverlay(dAtA []byte, offset int, v uint64) int {
+ for v >= 1<<7 {
+ dAtA[offset] = uint8(v&0x7f | 0x80)
+ v >>= 7
+ offset++
+ }
+ dAtA[offset] = uint8(v)
+ return offset + 1
+}
+func (m *PeerRecord) Size() (n int) {
+ var l int
+ _ = l
+ l = len(m.EndpointIP)
+ if l > 0 {
+ n += 1 + l + sovOverlay(uint64(l))
+ }
+ l = len(m.EndpointMAC)
+ if l > 0 {
+ n += 1 + l + sovOverlay(uint64(l))
+ }
+ l = len(m.TunnelEndpointIP)
+ if l > 0 {
+ n += 1 + l + sovOverlay(uint64(l))
+ }
+ return n
+}
+
+func sovOverlay(x uint64) (n int) {
+ for {
+ n++
+ x >>= 7
+ if x == 0 {
+ break
+ }
+ }
+ return n
+}
+func sozOverlay(x uint64) (n int) {
+ return sovOverlay(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (this *PeerRecord) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&PeerRecord{`,
+ `EndpointIP:` + fmt.Sprintf("%v", this.EndpointIP) + `,`,
+ `EndpointMAC:` + fmt.Sprintf("%v", this.EndpointMAC) + `,`,
+ `TunnelEndpointIP:` + fmt.Sprintf("%v", this.TunnelEndpointIP) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func valueToStringOverlay(v interface{}) string {
+ rv := reflect.ValueOf(v)
+ if rv.IsNil() {
+ return "nil"
+ }
+ pv := reflect.Indirect(rv).Interface()
+ return fmt.Sprintf("*%v", pv)
+}
+func (m *PeerRecord) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: PeerRecord: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: PeerRecord: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field EndpointIP", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthOverlay
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.EndpointIP = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field EndpointMAC", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthOverlay
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.EndpointMAC = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 3:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field TunnelEndpointIP", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthOverlay
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.TunnelEndpointIP = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipOverlay(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthOverlay
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func skipOverlay(dAtA []byte) (n int, err error) {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ wireType := int(wire & 0x7)
+ switch wireType {
+ case 0:
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ iNdEx++
+ if dAtA[iNdEx-1] < 0x80 {
+ break
+ }
+ }
+ return iNdEx, nil
+ case 1:
+ iNdEx += 8
+ return iNdEx, nil
+ case 2:
+ var length int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ length |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ iNdEx += length
+ if length < 0 {
+ return 0, ErrInvalidLengthOverlay
+ }
+ return iNdEx, nil
+ case 3:
+ for {
+ var innerWire uint64
+ var start int = iNdEx
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ innerWire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ innerWireType := int(innerWire & 0x7)
+ if innerWireType == 4 {
+ break
+ }
+ next, err := skipOverlay(dAtA[start:])
+ if err != nil {
+ return 0, err
+ }
+ iNdEx = start + next
+ }
+ return iNdEx, nil
+ case 4:
+ return iNdEx, nil
+ case 5:
+ iNdEx += 4
+ return iNdEx, nil
+ default:
+ return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+ }
+ }
+ panic("unreachable")
+}
+
+var (
+ ErrInvalidLengthOverlay = fmt.Errorf("proto: negative length found during unmarshaling")
+ ErrIntOverflowOverlay = fmt.Errorf("proto: integer overflow")
+)
+
+func init() { proto.RegisterFile("drivers/overlay/overlay.proto", fileDescriptorOverlay) }
+
+var fileDescriptorOverlay = []byte{
+ // 212 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4d, 0x29, 0xca, 0x2c,
+ 0x4b, 0x2d, 0x2a, 0xd6, 0xcf, 0x2f, 0x4b, 0x2d, 0xca, 0x49, 0xac, 0x84, 0xd1, 0x7a, 0x05, 0x45,
+ 0xf9, 0x25, 0xf9, 0x42, 0xec, 0x50, 0xae, 0x94, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x58, 0x4c, 0x1f,
+ 0xc4, 0x82, 0x48, 0x2b, 0x6d, 0x65, 0xe4, 0xe2, 0x0a, 0x48, 0x4d, 0x2d, 0x0a, 0x4a, 0x4d, 0xce,
+ 0x2f, 0x4a, 0x11, 0xd2, 0xe7, 0xe2, 0x4e, 0xcd, 0x4b, 0x29, 0xc8, 0xcf, 0xcc, 0x2b, 0x89, 0xcf,
+ 0x2c, 0x90, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x74, 0xe2, 0x7b, 0x74, 0x4f, 0x9e, 0xcb, 0x15, 0x2a,
+ 0xec, 0x19, 0x10, 0xc4, 0x05, 0x53, 0xe2, 0x59, 0x20, 0x64, 0xc4, 0xc5, 0x03, 0xd7, 0x90, 0x9b,
+ 0x98, 0x2c, 0xc1, 0x04, 0xd6, 0xc1, 0xff, 0xe8, 0x9e, 0x3c, 0x37, 0x4c, 0x87, 0xaf, 0xa3, 0x73,
+ 0x10, 0xdc, 0x54, 0xdf, 0xc4, 0x64, 0x21, 0x27, 0x2e, 0xa1, 0x92, 0xd2, 0xbc, 0xbc, 0xd4, 0x9c,
+ 0x78, 0x64, 0xbb, 0x98, 0xc1, 0x3a, 0x45, 0x1e, 0xdd, 0x93, 0x17, 0x08, 0x01, 0xcb, 0x22, 0xd9,
+ 0x28, 0x50, 0x82, 0x2a, 0x52, 0xe0, 0x24, 0x71, 0xe3, 0xa1, 0x1c, 0xc3, 0x87, 0x87, 0x72, 0x8c,
+ 0x0d, 0x8f, 0xe4, 0x18, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39,
+ 0xc6, 0x24, 0x36, 0xb0, 0xc7, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x48, 0x07, 0xf6, 0xf3,
+ 0x18, 0x01, 0x00, 0x00,
+}
--- /dev/null
+syntax = "proto3";
+
+import "gogoproto/gogo.proto";
+
+package overlay;
+
+option (gogoproto.marshaler_all) = true;
+option (gogoproto.unmarshaler_all) = true;
+option (gogoproto.stringer_all) = true;
+option (gogoproto.gostring_all) = true;
+option (gogoproto.sizer_all) = true;
+option (gogoproto.goproto_stringer_all) = false;
+
+// PeerRecord defines the information corresponding to a peer
+// container in the overlay network.
+message PeerRecord {
+ // Endpoint IP is the IP of the container attachment on the
+ // given overlay network.
+ string endpoint_ip = 1 [(gogoproto.customname) = "EndpointIP"];
+ // Endpoint MAC is the mac address of the container attachment
+ // on the given overlay network.
+ string endpoint_mac = 2 [(gogoproto.customname) = "EndpointMAC"];
+ // Tunnel Endpoint IP defines the host IP for the host in
+ // which this container is running and can be reached by
+ // building a tunnel to that host IP.
+ string tunnel_endpoint_ip = 3 [(gogoproto.customname) = "TunnelEndpointIP"];
+}
--- /dev/null
+package overlay
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "os"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/libkv/store/consul"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/vishvananda/netlink/nl"
+)
+
+func init() {
+ consul.Register()
+}
+
+type driverTester struct {
+ t *testing.T
+ d *driver
+}
+
+const testNetworkType = "overlay"
+
+func setupDriver(t *testing.T) *driverTester {
+ dt := &driverTester{t: t}
+ config := make(map[string]interface{})
+ config[netlabel.GlobalKVClient] = discoverapi.DatastoreConfigData{
+ Scope: datastore.GlobalScope,
+ Provider: "consul",
+ Address: "127.0.0.01:8500",
+ }
+
+ if err := Init(dt, config); err != nil {
+ t.Fatal(err)
+ }
+
+ iface, err := net.InterfaceByName("eth0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ addrs, err := iface.Addrs()
+ if err != nil || len(addrs) == 0 {
+ t.Fatal(err)
+ }
+ data := discoverapi.NodeDiscoveryData{
+ Address: addrs[0].String(),
+ Self: true,
+ }
+ dt.d.DiscoverNew(discoverapi.NodeDiscovery, data)
+ return dt
+}
+
+func cleanupDriver(t *testing.T, dt *driverTester) {
+ ch := make(chan struct{})
+ go func() {
+ Fini(dt.d)
+ close(ch)
+ }()
+
+ select {
+ case <-ch:
+ case <-time.After(10 * time.Second):
+ t.Fatal("test timed out because Fini() did not return on time")
+ }
+}
+
+func (dt *driverTester) GetPluginGetter() plugingetter.PluginGetter {
+ return nil
+}
+
+func (dt *driverTester) RegisterDriver(name string, drv driverapi.Driver,
+ cap driverapi.Capability) error {
+ if name != testNetworkType {
+ dt.t.Fatalf("Expected driver register name to be %q. Instead got %q",
+ testNetworkType, name)
+ }
+
+ if _, ok := drv.(*driver); !ok {
+ dt.t.Fatalf("Expected driver type to be %T. Instead got %T",
+ &driver{}, drv)
+ }
+
+ dt.d = drv.(*driver)
+ return nil
+}
+
+func TestOverlayInit(t *testing.T) {
+ if err := Init(&driverTester{t: t}, nil); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestOverlayFiniWithoutConfig(t *testing.T) {
+ dt := &driverTester{t: t}
+ if err := Init(dt, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ cleanupDriver(t, dt)
+}
+
+func TestOverlayConfig(t *testing.T) {
+ dt := setupDriver(t)
+
+ time.Sleep(1 * time.Second)
+
+ d := dt.d
+ if d.notifyCh == nil {
+ t.Fatal("Driver notify channel wasn't initialized after Config method")
+ }
+
+ if d.exitCh == nil {
+ t.Fatal("Driver serfloop exit channel wasn't initialized after Config method")
+ }
+
+ if d.serfInstance == nil {
+ t.Fatal("Driver serfinstance hasn't been initialized after Config method")
+ }
+
+ cleanupDriver(t, dt)
+}
+
+func TestOverlayType(t *testing.T) {
+ dt := &driverTester{t: t}
+ if err := Init(dt, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ if dt.d.Type() != testNetworkType {
+ t.Fatalf("Expected Type() to return %q. Instead got %q", testNetworkType,
+ dt.d.Type())
+ }
+}
+
+// Test that the netlink socket close unblock the watchMiss to avoid deadlock
+func TestNetlinkSocket(t *testing.T) {
+ // This is the same code used by the overlay driver to create the netlink interface
+ // for the watch miss
+ nlSock, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_NEIGH)
+ if err != nil {
+ t.Fatal()
+ }
+ // set the receive timeout to not remain stuck on the RecvFrom if the fd gets closed
+ tv := syscall.NsecToTimeval(soTimeout.Nanoseconds())
+ err = nlSock.SetReceiveTimeout(&tv)
+ if err != nil {
+ t.Fatal()
+ }
+ n := &network{id: "testnetid"}
+ ch := make(chan error)
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ go func() {
+ n.watchMiss(nlSock, fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), syscall.Gettid()))
+ ch <- nil
+ }()
+ time.Sleep(5 * time.Second)
+ nlSock.Close()
+ select {
+ case <-ch:
+ case <-ctx.Done():
+ {
+ t.Fatalf("Timeout expired")
+ }
+ }
+}
--- /dev/null
+package ovmanager
+
+import (
+ "fmt"
+ "net"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/idm"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ networkType = "overlay"
+ vxlanIDStart = 4096
+ vxlanIDEnd = (1 << 24) - 1
+)
+
+type networkTable map[string]*network
+
+type driver struct {
+ config map[string]interface{}
+ networks networkTable
+ store datastore.DataStore
+ vxlanIdm *idm.Idm
+ sync.Mutex
+}
+
+type subnet struct {
+ subnetIP *net.IPNet
+ gwIP *net.IPNet
+ vni uint32
+}
+
+type network struct {
+ id string
+ driver *driver
+ subnets []*subnet
+ sync.Mutex
+}
+
+// Init registers a new instance of overlay driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ var err error
+ c := driverapi.Capability{
+ DataScope: datastore.GlobalScope,
+ ConnectivityScope: datastore.GlobalScope,
+ }
+
+ d := &driver{
+ networks: networkTable{},
+ config: config,
+ }
+
+ d.vxlanIdm, err = idm.New(nil, "vxlan-id", 0, vxlanIDEnd)
+ if err != nil {
+ return fmt.Errorf("failed to initialize vxlan id manager: %v", err)
+ }
+
+ return dc.RegisterDriver(networkType, d, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ if id == "" {
+ return nil, fmt.Errorf("invalid network id for overlay network")
+ }
+
+ if ipV4Data == nil {
+ return nil, fmt.Errorf("empty ipv4 data passed during overlay network creation")
+ }
+
+ n := &network{
+ id: id,
+ driver: d,
+ subnets: []*subnet{},
+ }
+
+ opts := make(map[string]string)
+ vxlanIDList := make([]uint32, 0, len(ipV4Data))
+ for key, val := range option {
+ if key == netlabel.OverlayVxlanIDList {
+ logrus.Debugf("overlay network option: %s", val)
+ valStrList := strings.Split(val, ",")
+ for _, idStr := range valStrList {
+ vni, err := strconv.Atoi(idStr)
+ if err != nil {
+ return nil, fmt.Errorf("invalid vxlan id value %q passed", idStr)
+ }
+
+ vxlanIDList = append(vxlanIDList, uint32(vni))
+ }
+ } else {
+ opts[key] = val
+ }
+ }
+
+ for i, ipd := range ipV4Data {
+ s := &subnet{
+ subnetIP: ipd.Pool,
+ gwIP: ipd.Gateway,
+ }
+
+ if len(vxlanIDList) > i {
+ s.vni = vxlanIDList[i]
+ }
+
+ if err := n.obtainVxlanID(s); err != nil {
+ n.releaseVxlanID()
+ return nil, fmt.Errorf("could not obtain vxlan id for pool %s: %v", s.subnetIP, err)
+ }
+
+ n.subnets = append(n.subnets, s)
+ }
+
+ val := fmt.Sprintf("%d", n.subnets[0].vni)
+ for _, s := range n.subnets[1:] {
+ val = val + fmt.Sprintf(",%d", s.vni)
+ }
+ opts[netlabel.OverlayVxlanIDList] = val
+
+ d.Lock()
+ defer d.Unlock()
+ if _, ok := d.networks[id]; ok {
+ n.releaseVxlanID()
+ return nil, fmt.Errorf("network %s already exists", id)
+ }
+ d.networks[id] = n
+
+ return opts, nil
+}
+
+func (d *driver) NetworkFree(id string) error {
+ if id == "" {
+ return fmt.Errorf("invalid network id passed while freeing overlay network")
+ }
+
+ d.Lock()
+ defer d.Unlock()
+ n, ok := d.networks[id]
+
+ if !ok {
+ return fmt.Errorf("overlay network with id %s not found", id)
+ }
+
+ // Release all vxlan IDs in one shot.
+ n.releaseVxlanID()
+
+ delete(d.networks, id)
+
+ return nil
+}
+
+func (n *network) obtainVxlanID(s *subnet) error {
+ var (
+ err error
+ vni uint64
+ )
+
+ n.Lock()
+ vni = uint64(s.vni)
+ n.Unlock()
+
+ if vni == 0 {
+ vni, err = n.driver.vxlanIdm.GetIDInRange(vxlanIDStart, vxlanIDEnd, true)
+ if err != nil {
+ return err
+ }
+
+ n.Lock()
+ s.vni = uint32(vni)
+ n.Unlock()
+ return nil
+ }
+
+ return n.driver.vxlanIdm.GetSpecificID(vni)
+}
+
+func (n *network) releaseVxlanID() {
+ n.Lock()
+ vnis := make([]uint32, 0, len(n.subnets))
+ for _, s := range n.subnets {
+ vnis = append(vnis, s.vni)
+ s.vni = 0
+ }
+ n.Unlock()
+
+ for _, vni := range vnis {
+ n.driver.vxlanIdm.Release(uint64(vni))
+ }
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Type() string {
+ return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
--- /dev/null
+package ovmanager
+
+import (
+ "fmt"
+ "net"
+ "strings"
+ "testing"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/idm"
+ "github.com/docker/libnetwork/netlabel"
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+)
+
+func newDriver(t *testing.T) *driver {
+ d := &driver{
+ networks: networkTable{},
+ }
+
+ vxlanIdm, err := idm.New(nil, "vxlan-id", vxlanIDStart, vxlanIDEnd)
+ assert.NilError(t, err)
+
+ d.vxlanIdm = vxlanIdm
+ return d
+}
+
+func parseCIDR(t *testing.T, ipnet string) *net.IPNet {
+ subnet, err := types.ParseCIDR(ipnet)
+ assert.NilError(t, err)
+ return subnet
+}
+
+func TestNetworkAllocateFree(t *testing.T) {
+ d := newDriver(t)
+
+ ipamData := []driverapi.IPAMData{
+ {
+ Pool: parseCIDR(t, "10.1.1.0/24"),
+ },
+ {
+ Pool: parseCIDR(t, "10.1.2.0/24"),
+ },
+ }
+
+ vals, err := d.NetworkAllocate("testnetwork", nil, ipamData, nil)
+ assert.NilError(t, err)
+
+ vxlanIDs, ok := vals[netlabel.OverlayVxlanIDList]
+ assert.Check(t, is.Equal(true, ok))
+ assert.Check(t, is.Len(strings.Split(vxlanIDs, ","), 2))
+
+ err = d.NetworkFree("testnetwork")
+ assert.NilError(t, err)
+}
+
+func TestNetworkAllocateUserDefinedVNIs(t *testing.T) {
+ d := newDriver(t)
+
+ ipamData := []driverapi.IPAMData{
+ {
+ Pool: parseCIDR(t, "10.1.1.0/24"),
+ },
+ {
+ Pool: parseCIDR(t, "10.1.2.0/24"),
+ },
+ }
+
+ options := make(map[string]string)
+ // Intentionally add mode vnis than subnets
+ options[netlabel.OverlayVxlanIDList] = fmt.Sprintf("%d,%d,%d", vxlanIDStart, vxlanIDStart+1, vxlanIDStart+2)
+
+ vals, err := d.NetworkAllocate("testnetwork", options, ipamData, nil)
+ assert.NilError(t, err)
+
+ vxlanIDs, ok := vals[netlabel.OverlayVxlanIDList]
+ assert.Check(t, is.Equal(true, ok))
+
+ // We should only get exactly the same number of vnis as
+ // subnets. No more, no less, even if we passed more vnis.
+ assert.Check(t, is.Len(strings.Split(vxlanIDs, ","), 2))
+ assert.Check(t, is.Equal(fmt.Sprintf("%d,%d", vxlanIDStart, vxlanIDStart+1), vxlanIDs))
+
+ err = d.NetworkFree("testnetwork")
+ assert.NilError(t, err)
+}
--- /dev/null
+package overlay
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "sync"
+ "syscall"
+
+ "github.com/docker/libnetwork/internal/caller"
+ "github.com/docker/libnetwork/internal/setmatrix"
+ "github.com/docker/libnetwork/osl"
+ "github.com/sirupsen/logrus"
+)
+
+const ovPeerTable = "overlay_peer_table"
+
+type peerKey struct {
+ peerIP net.IP
+ peerMac net.HardwareAddr
+}
+
+type peerEntry struct {
+ eid string
+ vtep net.IP
+ peerIPMask net.IPMask
+ isLocal bool
+}
+
+func (p *peerEntry) MarshalDB() peerEntryDB {
+ ones, bits := p.peerIPMask.Size()
+ return peerEntryDB{
+ eid: p.eid,
+ vtep: p.vtep.String(),
+ peerIPMaskOnes: ones,
+ peerIPMaskBits: bits,
+ isLocal: p.isLocal,
+ }
+}
+
+// This the structure saved into the set (SetMatrix), due to the implementation of it
+// the value inserted in the set has to be Hashable so the []byte had to be converted into
+// strings
+type peerEntryDB struct {
+ eid string
+ vtep string
+ peerIPMaskOnes int
+ peerIPMaskBits int
+ isLocal bool
+}
+
+func (p *peerEntryDB) UnMarshalDB() peerEntry {
+ return peerEntry{
+ eid: p.eid,
+ vtep: net.ParseIP(p.vtep),
+ peerIPMask: net.CIDRMask(p.peerIPMaskOnes, p.peerIPMaskBits),
+ isLocal: p.isLocal,
+ }
+}
+
+type peerMap struct {
+ // set of peerEntry, note they have to be objects and not pointers to maintain the proper equality checks
+ mp setmatrix.SetMatrix
+ sync.Mutex
+}
+
+type peerNetworkMap struct {
+ // map with key peerKey
+ mp map[string]*peerMap
+ sync.Mutex
+}
+
+func (pKey peerKey) String() string {
+ return fmt.Sprintf("%s %s", pKey.peerIP, pKey.peerMac)
+}
+
+func (pKey *peerKey) Scan(state fmt.ScanState, verb rune) error {
+ ipB, err := state.Token(true, nil)
+ if err != nil {
+ return err
+ }
+
+ pKey.peerIP = net.ParseIP(string(ipB))
+
+ macB, err := state.Token(true, nil)
+ if err != nil {
+ return err
+ }
+
+ pKey.peerMac, err = net.ParseMAC(string(macB))
+ return err
+}
+
+func (d *driver) peerDbWalk(f func(string, *peerKey, *peerEntry) bool) error {
+ d.peerDb.Lock()
+ nids := []string{}
+ for nid := range d.peerDb.mp {
+ nids = append(nids, nid)
+ }
+ d.peerDb.Unlock()
+
+ for _, nid := range nids {
+ d.peerDbNetworkWalk(nid, func(pKey *peerKey, pEntry *peerEntry) bool {
+ return f(nid, pKey, pEntry)
+ })
+ }
+ return nil
+}
+
+func (d *driver) peerDbNetworkWalk(nid string, f func(*peerKey, *peerEntry) bool) error {
+ d.peerDb.Lock()
+ pMap, ok := d.peerDb.mp[nid]
+ d.peerDb.Unlock()
+
+ if !ok {
+ return nil
+ }
+
+ mp := map[string]peerEntry{}
+ pMap.Lock()
+ for _, pKeyStr := range pMap.mp.Keys() {
+ entryDBList, ok := pMap.mp.Get(pKeyStr)
+ if ok {
+ peerEntryDB := entryDBList[0].(peerEntryDB)
+ mp[pKeyStr] = peerEntryDB.UnMarshalDB()
+ }
+ }
+ pMap.Unlock()
+
+ for pKeyStr, pEntry := range mp {
+ var pKey peerKey
+ if _, err := fmt.Sscan(pKeyStr, &pKey); err != nil {
+ logrus.Warnf("Peer key scan on network %s failed: %v", nid, err)
+ }
+ if f(&pKey, &pEntry) {
+ return nil
+ }
+ }
+
+ return nil
+}
+
+func (d *driver) peerDbSearch(nid string, peerIP net.IP) (*peerKey, *peerEntry, error) {
+ var pKeyMatched *peerKey
+ var pEntryMatched *peerEntry
+ err := d.peerDbNetworkWalk(nid, func(pKey *peerKey, pEntry *peerEntry) bool {
+ if pKey.peerIP.Equal(peerIP) {
+ pKeyMatched = pKey
+ pEntryMatched = pEntry
+ return true
+ }
+
+ return false
+ })
+
+ if err != nil {
+ return nil, nil, fmt.Errorf("peerdb search for peer ip %q failed: %v", peerIP, err)
+ }
+
+ if pKeyMatched == nil || pEntryMatched == nil {
+ return nil, nil, fmt.Errorf("peer ip %q not found in peerdb", peerIP)
+ }
+
+ return pKeyMatched, pEntryMatched, nil
+}
+
+func (d *driver) peerDbAdd(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
+ peerMac net.HardwareAddr, vtep net.IP, isLocal bool) (bool, int) {
+
+ d.peerDb.Lock()
+ pMap, ok := d.peerDb.mp[nid]
+ if !ok {
+ d.peerDb.mp[nid] = &peerMap{
+ mp: setmatrix.NewSetMatrix(),
+ }
+
+ pMap = d.peerDb.mp[nid]
+ }
+ d.peerDb.Unlock()
+
+ pKey := peerKey{
+ peerIP: peerIP,
+ peerMac: peerMac,
+ }
+
+ pEntry := peerEntry{
+ eid: eid,
+ vtep: vtep,
+ peerIPMask: peerIPMask,
+ isLocal: isLocal,
+ }
+
+ pMap.Lock()
+ defer pMap.Unlock()
+ b, i := pMap.mp.Insert(pKey.String(), pEntry.MarshalDB())
+ if i != 1 {
+ // Transient case, there is more than one endpoint that is using the same IP,MAC pair
+ s, _ := pMap.mp.String(pKey.String())
+ logrus.Warnf("peerDbAdd transient condition - Key:%s cardinality:%d db state:%s", pKey.String(), i, s)
+ }
+ return b, i
+}
+
+func (d *driver) peerDbDelete(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
+ peerMac net.HardwareAddr, vtep net.IP, isLocal bool) (bool, int) {
+
+ d.peerDb.Lock()
+ pMap, ok := d.peerDb.mp[nid]
+ if !ok {
+ d.peerDb.Unlock()
+ return false, 0
+ }
+ d.peerDb.Unlock()
+
+ pKey := peerKey{
+ peerIP: peerIP,
+ peerMac: peerMac,
+ }
+
+ pEntry := peerEntry{
+ eid: eid,
+ vtep: vtep,
+ peerIPMask: peerIPMask,
+ isLocal: isLocal,
+ }
+
+ pMap.Lock()
+ defer pMap.Unlock()
+ b, i := pMap.mp.Remove(pKey.String(), pEntry.MarshalDB())
+ if i != 0 {
+ // Transient case, there is more than one endpoint that is using the same IP,MAC pair
+ s, _ := pMap.mp.String(pKey.String())
+ logrus.Warnf("peerDbDelete transient condition - Key:%s cardinality:%d db state:%s", pKey.String(), i, s)
+ }
+ return b, i
+}
+
+// The overlay uses a lazy initialization approach, this means that when a network is created
+// and the driver registered the overlay does not allocate resources till the moment that a
+// sandbox is actually created.
+// At the moment of this call, that happens when a sandbox is initialized, is possible that
+// networkDB has already delivered some events of peers already available on remote nodes,
+// these peers are saved into the peerDB and this function is used to properly configure
+// the network sandbox with all those peers that got previously notified.
+// Note also that this method sends a single message on the channel and the go routine on the
+// other side, will atomically loop on the whole table of peers and will program their state
+// in one single atomic operation. This is fundamental to guarantee consistency, and avoid that
+// new peerAdd or peerDelete gets reordered during the sandbox init.
+func (d *driver) initSandboxPeerDB(nid string) {
+ d.peerInit(nid)
+}
+
+type peerOperationType int32
+
+const (
+ peerOperationINIT peerOperationType = iota
+ peerOperationADD
+ peerOperationDELETE
+ peerOperationFLUSH
+)
+
+type peerOperation struct {
+ opType peerOperationType
+ networkID string
+ endpointID string
+ peerIP net.IP
+ peerIPMask net.IPMask
+ peerMac net.HardwareAddr
+ vtepIP net.IP
+ l2Miss bool
+ l3Miss bool
+ localPeer bool
+ callerName string
+}
+
+func (d *driver) peerOpRoutine(ctx context.Context, ch chan *peerOperation) {
+ var err error
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case op := <-ch:
+ switch op.opType {
+ case peerOperationINIT:
+ err = d.peerInitOp(op.networkID)
+ case peerOperationADD:
+ err = d.peerAddOp(op.networkID, op.endpointID, op.peerIP, op.peerIPMask, op.peerMac, op.vtepIP, op.l2Miss, op.l3Miss, true, op.localPeer)
+ case peerOperationDELETE:
+ err = d.peerDeleteOp(op.networkID, op.endpointID, op.peerIP, op.peerIPMask, op.peerMac, op.vtepIP, op.localPeer)
+ case peerOperationFLUSH:
+ err = d.peerFlushOp(op.networkID)
+ }
+ if err != nil {
+ logrus.Warnf("Peer operation failed:%s op:%v", err, op)
+ }
+ }
+ }
+}
+
+func (d *driver) peerInit(nid string) {
+ callerName := caller.Name(1)
+ d.peerOpCh <- &peerOperation{
+ opType: peerOperationINIT,
+ networkID: nid,
+ callerName: callerName,
+ }
+}
+
+func (d *driver) peerInitOp(nid string) error {
+ return d.peerDbNetworkWalk(nid, func(pKey *peerKey, pEntry *peerEntry) bool {
+ // Local entries do not need to be added
+ if pEntry.isLocal {
+ return false
+ }
+
+ d.peerAddOp(nid, pEntry.eid, pKey.peerIP, pEntry.peerIPMask, pKey.peerMac, pEntry.vtep, false, false, false, pEntry.isLocal)
+ // return false to loop on all entries
+ return false
+ })
+}
+
+func (d *driver) peerAdd(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
+ peerMac net.HardwareAddr, vtep net.IP, l2Miss, l3Miss, localPeer bool) {
+ d.peerOpCh <- &peerOperation{
+ opType: peerOperationADD,
+ networkID: nid,
+ endpointID: eid,
+ peerIP: peerIP,
+ peerIPMask: peerIPMask,
+ peerMac: peerMac,
+ vtepIP: vtep,
+ l2Miss: l2Miss,
+ l3Miss: l3Miss,
+ localPeer: localPeer,
+ callerName: caller.Name(1),
+ }
+}
+
+func (d *driver) peerAddOp(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
+ peerMac net.HardwareAddr, vtep net.IP, l2Miss, l3Miss, updateDB, localPeer bool) error {
+
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ var dbEntries int
+ var inserted bool
+ if updateDB {
+ inserted, dbEntries = d.peerDbAdd(nid, eid, peerIP, peerIPMask, peerMac, vtep, localPeer)
+ if !inserted {
+ logrus.Warnf("Entry already present in db: nid:%s eid:%s peerIP:%v peerMac:%v isLocal:%t vtep:%v",
+ nid, eid, peerIP, peerMac, localPeer, vtep)
+ }
+ }
+
+ // Local peers do not need any further configuration
+ if localPeer {
+ return nil
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return nil
+ }
+
+ sbox := n.sandbox()
+ if sbox == nil {
+ // We are hitting this case for all the events that are arriving before that the sandbox
+ // is being created. The peer got already added into the database and the sanbox init will
+ // call the peerDbUpdateSandbox that will configure all these peers from the database
+ return nil
+ }
+
+ IP := &net.IPNet{
+ IP: peerIP,
+ Mask: peerIPMask,
+ }
+
+ s := n.getSubnetforIP(IP)
+ if s == nil {
+ return fmt.Errorf("couldn't find the subnet %q in network %q", IP.String(), n.id)
+ }
+
+ if err := n.obtainVxlanID(s); err != nil {
+ return fmt.Errorf("couldn't get vxlan id for %q: %v", s.subnetIP.String(), err)
+ }
+
+ if err := n.joinSandbox(s, false, false); err != nil {
+ return fmt.Errorf("subnet sandbox join failed for %q: %v", s.subnetIP.String(), err)
+ }
+
+ if err := d.checkEncryption(nid, vtep, n.vxlanID(s), false, true); err != nil {
+ logrus.Warn(err)
+ }
+
+ // Add neighbor entry for the peer IP
+ if err := sbox.AddNeighbor(peerIP, peerMac, l3Miss, sbox.NeighborOptions().LinkName(s.vxlanName)); err != nil {
+ if _, ok := err.(osl.NeighborSearchError); ok && dbEntries > 1 {
+ // We are in the transient case so only the first configuration is programmed into the kernel
+ // Upon deletion if the active configuration is deleted the next one from the database will be restored
+ // Note we are skipping also the next configuration
+ return nil
+ }
+ return fmt.Errorf("could not add neighbor entry for nid:%s eid:%s into the sandbox:%v", nid, eid, err)
+ }
+
+ // Add fdb entry to the bridge for the peer mac
+ if err := sbox.AddNeighbor(vtep, peerMac, l2Miss, sbox.NeighborOptions().LinkName(s.vxlanName),
+ sbox.NeighborOptions().Family(syscall.AF_BRIDGE)); err != nil {
+ return fmt.Errorf("could not add fdb entry for nid:%s eid:%s into the sandbox:%v", nid, eid, err)
+ }
+
+ return nil
+}
+
+func (d *driver) peerDelete(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
+ peerMac net.HardwareAddr, vtep net.IP, localPeer bool) {
+ d.peerOpCh <- &peerOperation{
+ opType: peerOperationDELETE,
+ networkID: nid,
+ endpointID: eid,
+ peerIP: peerIP,
+ peerIPMask: peerIPMask,
+ peerMac: peerMac,
+ vtepIP: vtep,
+ callerName: caller.Name(1),
+ localPeer: localPeer,
+ }
+}
+
+func (d *driver) peerDeleteOp(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
+ peerMac net.HardwareAddr, vtep net.IP, localPeer bool) error {
+
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ deleted, dbEntries := d.peerDbDelete(nid, eid, peerIP, peerIPMask, peerMac, vtep, localPeer)
+ if !deleted {
+ logrus.Warnf("Entry was not in db: nid:%s eid:%s peerIP:%v peerMac:%v isLocal:%t vtep:%v",
+ nid, eid, peerIP, peerMac, localPeer, vtep)
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return nil
+ }
+
+ sbox := n.sandbox()
+ if sbox == nil {
+ return nil
+ }
+
+ if err := d.checkEncryption(nid, vtep, 0, localPeer, false); err != nil {
+ logrus.Warn(err)
+ }
+
+ // Local peers do not have any local configuration to delete
+ if !localPeer {
+ // Remove fdb entry to the bridge for the peer mac
+ if err := sbox.DeleteNeighbor(vtep, peerMac, true); err != nil {
+ if _, ok := err.(osl.NeighborSearchError); ok && dbEntries > 0 {
+ // We fall in here if there is a transient state and if the neighbor that is being deleted
+ // was never been configured into the kernel (we allow only 1 configuration at the time per <ip,mac> mapping)
+ return nil
+ }
+ return fmt.Errorf("could not delete fdb entry for nid:%s eid:%s into the sandbox:%v", nid, eid, err)
+ }
+
+ // Delete neighbor entry for the peer IP
+ if err := sbox.DeleteNeighbor(peerIP, peerMac, true); err != nil {
+ return fmt.Errorf("could not delete neighbor entry for nid:%s eid:%s into the sandbox:%v", nid, eid, err)
+ }
+ }
+
+ if dbEntries == 0 {
+ return nil
+ }
+
+ // If there is still an entry into the database and the deletion went through without errors means that there is now no
+ // configuration active in the kernel.
+ // Restore one configuration for the <ip,mac> directly from the database, note that is guaranteed that there is one
+ peerKey, peerEntry, err := d.peerDbSearch(nid, peerIP)
+ if err != nil {
+ logrus.Errorf("peerDeleteOp unable to restore a configuration for nid:%s ip:%v mac:%v err:%s", nid, peerIP, peerMac, err)
+ return err
+ }
+ return d.peerAddOp(nid, peerEntry.eid, peerIP, peerEntry.peerIPMask, peerKey.peerMac, peerEntry.vtep, false, false, false, peerEntry.isLocal)
+}
+
+func (d *driver) peerFlush(nid string) {
+ d.peerOpCh <- &peerOperation{
+ opType: peerOperationFLUSH,
+ networkID: nid,
+ callerName: caller.Name(1),
+ }
+}
+
+func (d *driver) peerFlushOp(nid string) error {
+ d.peerDb.Lock()
+ defer d.peerDb.Unlock()
+ _, ok := d.peerDb.mp[nid]
+ if !ok {
+ return fmt.Errorf("Unable to find the peerDB for nid:%s", nid)
+ }
+ delete(d.peerDb.mp, nid)
+ return nil
+}
+
+func (d *driver) pushLocalDb() {
+ d.peerDbWalk(func(nid string, pKey *peerKey, pEntry *peerEntry) bool {
+ if pEntry.isLocal {
+ d.pushLocalEndpointEvent("join", nid, pEntry.eid)
+ }
+ return false
+ })
+}
+
+func (d *driver) peerDBUpdateSelf() {
+ d.peerDbWalk(func(nid string, pkey *peerKey, pEntry *peerEntry) bool {
+ if pEntry.isLocal {
+ pEntry.vtep = net.ParseIP(d.advertiseAddress)
+ }
+ return false
+ })
+}
--- /dev/null
+package overlay
+
+import (
+ "net"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func TestPeerMarshal(t *testing.T) {
+ _, ipNet, _ := net.ParseCIDR("192.168.0.1/24")
+ p := &peerEntry{eid: "eid",
+ isLocal: true,
+ peerIPMask: ipNet.Mask,
+ vtep: ipNet.IP}
+ entryDB := p.MarshalDB()
+ x := entryDB.UnMarshalDB()
+ if x.eid != p.eid {
+ t.Fatalf("Incorrect Unmarshalling for eid: %v != %v", x.eid, p.eid)
+ }
+ if x.isLocal != p.isLocal {
+ t.Fatalf("Incorrect Unmarshalling for isLocal: %v != %v", x.isLocal, p.isLocal)
+ }
+ if x.peerIPMask.String() != p.peerIPMask.String() {
+ t.Fatalf("Incorrect Unmarshalling for eid: %v != %v", x.peerIPMask, p.peerIPMask)
+ }
+ if x.vtep.String() != p.vtep.String() {
+ t.Fatalf("Incorrect Unmarshalling for eid: %v != %v", x.vtep, p.vtep)
+ }
+}
--- /dev/null
+/*
+Package api represents all requests and responses suitable for conversation
+with a remote driver.
+*/
+package api
+
+import (
+ "net"
+
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+)
+
+// Response is the basic response structure used in all responses.
+type Response struct {
+ Err string
+}
+
+// GetError returns the error from the response, if any.
+func (r *Response) GetError() string {
+ return r.Err
+}
+
+// GetCapabilityResponse is the response of GetCapability request
+type GetCapabilityResponse struct {
+ Response
+ Scope string
+ ConnectivityScope string
+}
+
+// AllocateNetworkRequest requests allocation of new network by manager
+type AllocateNetworkRequest struct {
+ // A network ID that remote plugins are expected to store for future
+ // reference.
+ NetworkID string
+
+ // A free form map->object interface for communication of options.
+ Options map[string]string
+
+ // IPAMData contains the address pool information for this network
+ IPv4Data, IPv6Data []driverapi.IPAMData
+}
+
+// AllocateNetworkResponse is the response to the AllocateNetworkRequest.
+type AllocateNetworkResponse struct {
+ Response
+ // A free form plugin specific string->string object to be sent in
+ // CreateNetworkRequest call in the libnetwork agents
+ Options map[string]string
+}
+
+// FreeNetworkRequest is the request to free allocated network in the manager
+type FreeNetworkRequest struct {
+ // The ID of the network to be freed.
+ NetworkID string
+}
+
+// FreeNetworkResponse is the response to a request for freeing a network.
+type FreeNetworkResponse struct {
+ Response
+}
+
+// CreateNetworkRequest requests a new network.
+type CreateNetworkRequest struct {
+ // A network ID that remote plugins are expected to store for future
+ // reference.
+ NetworkID string
+
+ // A free form map->object interface for communication of options.
+ Options map[string]interface{}
+
+ // IPAMData contains the address pool information for this network
+ IPv4Data, IPv6Data []driverapi.IPAMData
+}
+
+// CreateNetworkResponse is the response to the CreateNetworkRequest.
+type CreateNetworkResponse struct {
+ Response
+}
+
+// DeleteNetworkRequest is the request to delete an existing network.
+type DeleteNetworkRequest struct {
+ // The ID of the network to delete.
+ NetworkID string
+}
+
+// DeleteNetworkResponse is the response to a request for deleting a network.
+type DeleteNetworkResponse struct {
+ Response
+}
+
+// CreateEndpointRequest is the request to create an endpoint within a network.
+type CreateEndpointRequest struct {
+ // Provided at create time, this will be the network id referenced.
+ NetworkID string
+ // The ID of the endpoint for later reference.
+ EndpointID string
+ Interface *EndpointInterface
+ Options map[string]interface{}
+}
+
+// EndpointInterface represents an interface endpoint.
+type EndpointInterface struct {
+ Address string
+ AddressIPv6 string
+ MacAddress string
+}
+
+// CreateEndpointResponse is the response to the CreateEndpoint action.
+type CreateEndpointResponse struct {
+ Response
+ Interface *EndpointInterface
+}
+
+// Interface is the representation of a linux interface.
+type Interface struct {
+ Address *net.IPNet
+ AddressIPv6 *net.IPNet
+ MacAddress net.HardwareAddr
+}
+
+// DeleteEndpointRequest describes the API for deleting an endpoint.
+type DeleteEndpointRequest struct {
+ NetworkID string
+ EndpointID string
+}
+
+// DeleteEndpointResponse is the response to the DeleteEndpoint action.
+type DeleteEndpointResponse struct {
+ Response
+}
+
+// EndpointInfoRequest retrieves information about the endpoint from the network driver.
+type EndpointInfoRequest struct {
+ NetworkID string
+ EndpointID string
+}
+
+// EndpointInfoResponse is the response to an EndpointInfoRequest.
+type EndpointInfoResponse struct {
+ Response
+ Value map[string]interface{}
+}
+
+// JoinRequest describes the API for joining an endpoint to a sandbox.
+type JoinRequest struct {
+ NetworkID string
+ EndpointID string
+ SandboxKey string
+ Options map[string]interface{}
+}
+
+// InterfaceName is the struct representation of a pair of devices with source
+// and destination, for the purposes of putting an endpoint into a container.
+type InterfaceName struct {
+ SrcName string
+ DstName string
+ DstPrefix string
+}
+
+// StaticRoute is the plain JSON representation of a static route.
+type StaticRoute struct {
+ Destination string
+ RouteType int
+ NextHop string
+}
+
+// JoinResponse is the response to a JoinRequest.
+type JoinResponse struct {
+ Response
+ InterfaceName *InterfaceName
+ Gateway string
+ GatewayIPv6 string
+ StaticRoutes []StaticRoute
+ DisableGatewayService bool
+}
+
+// LeaveRequest describes the API for detaching an endpoint from a sandbox.
+type LeaveRequest struct {
+ NetworkID string
+ EndpointID string
+}
+
+// LeaveResponse is the answer to LeaveRequest.
+type LeaveResponse struct {
+ Response
+}
+
+// ProgramExternalConnectivityRequest describes the API for programming the external connectivity for the given endpoint.
+type ProgramExternalConnectivityRequest struct {
+ NetworkID string
+ EndpointID string
+ Options map[string]interface{}
+}
+
+// ProgramExternalConnectivityResponse is the answer to ProgramExternalConnectivityRequest.
+type ProgramExternalConnectivityResponse struct {
+ Response
+}
+
+// RevokeExternalConnectivityRequest describes the API for revoking the external connectivity for the given endpoint.
+type RevokeExternalConnectivityRequest struct {
+ NetworkID string
+ EndpointID string
+}
+
+// RevokeExternalConnectivityResponse is the answer to RevokeExternalConnectivityRequest.
+type RevokeExternalConnectivityResponse struct {
+ Response
+}
+
+// DiscoveryNotification represents a discovery notification
+type DiscoveryNotification struct {
+ DiscoveryType discoverapi.DiscoveryType
+ DiscoveryData interface{}
+}
+
+// DiscoveryResponse is used by libnetwork to log any plugin error processing the discovery notifications
+type DiscoveryResponse struct {
+ Response
+}
--- /dev/null
+package remote
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/docker/pkg/plugins"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/drivers/remote/api"
+ "github.com/docker/libnetwork/types"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+type driver struct {
+ endpoint *plugins.Client
+ networkType string
+}
+
+type maybeError interface {
+ GetError() string
+}
+
+func newDriver(name string, client *plugins.Client) driverapi.Driver {
+ return &driver{networkType: name, endpoint: client}
+}
+
+// Init makes sure a remote driver is registered when a network driver
+// plugin is activated.
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ newPluginHandler := func(name string, client *plugins.Client) {
+ // negotiate driver capability with client
+ d := newDriver(name, client)
+ c, err := d.(*driver).getCapabilities()
+ if err != nil {
+ logrus.Errorf("error getting capability for %s due to %v", name, err)
+ return
+ }
+ if err = dc.RegisterDriver(name, d, *c); err != nil {
+ logrus.Errorf("error registering driver for %s due to %v", name, err)
+ }
+ }
+
+ // Unit test code is unaware of a true PluginStore. So we fall back to v1 plugins.
+ handleFunc := plugins.Handle
+ if pg := dc.GetPluginGetter(); pg != nil {
+ handleFunc = pg.Handle
+ activePlugins := pg.GetAllManagedPluginsByCap(driverapi.NetworkPluginEndpointType)
+ for _, ap := range activePlugins {
+ client, err := getPluginClient(ap)
+ if err != nil {
+ return err
+ }
+ newPluginHandler(ap.Name(), client)
+ }
+ }
+ handleFunc(driverapi.NetworkPluginEndpointType, newPluginHandler)
+
+ return nil
+}
+
+func getPluginClient(p plugingetter.CompatPlugin) (*plugins.Client, error) {
+ if v1, ok := p.(plugingetter.PluginWithV1Client); ok {
+ return v1.Client(), nil
+ }
+
+ pa, ok := p.(plugingetter.PluginAddr)
+ if !ok {
+ return nil, errors.Errorf("unknown plugin type %T", p)
+ }
+
+ if pa.Protocol() != plugins.ProtocolSchemeHTTPV1 {
+ return nil, errors.Errorf("unsupported plugin protocol %s", pa.Protocol())
+ }
+
+ addr := pa.Addr()
+ client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, pa.Timeout())
+ if err != nil {
+ return nil, errors.Wrap(err, "error creating plugin client")
+ }
+ return client, nil
+}
+
+// Get capability from client
+func (d *driver) getCapabilities() (*driverapi.Capability, error) {
+ var capResp api.GetCapabilityResponse
+ if err := d.call("GetCapabilities", nil, &capResp); err != nil {
+ return nil, err
+ }
+
+ c := &driverapi.Capability{}
+ switch capResp.Scope {
+ case "global":
+ c.DataScope = datastore.GlobalScope
+ case "local":
+ c.DataScope = datastore.LocalScope
+ default:
+ return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope)
+ }
+
+ switch capResp.ConnectivityScope {
+ case "global":
+ c.ConnectivityScope = datastore.GlobalScope
+ case "local":
+ c.ConnectivityScope = datastore.LocalScope
+ case "":
+ c.ConnectivityScope = c.DataScope
+ default:
+ return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope)
+ }
+
+ return c, nil
+}
+
+// Config is not implemented for remote drivers, since it is assumed
+// to be supplied to the remote process out-of-band (e.g., as command
+// line arguments).
+func (d *driver) Config(option map[string]interface{}) error {
+ return &driverapi.ErrNotImplemented{}
+}
+
+func (d *driver) call(methodName string, arg interface{}, retVal maybeError) error {
+ method := driverapi.NetworkPluginEndpointType + "." + methodName
+ err := d.endpoint.Call(method, arg, retVal)
+ if err != nil {
+ return err
+ }
+ if e := retVal.GetError(); e != "" {
+ return fmt.Errorf("remote: %s", e)
+ }
+ return nil
+}
+
+func (d *driver) NetworkAllocate(id string, options map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ create := &api.AllocateNetworkRequest{
+ NetworkID: id,
+ Options: options,
+ IPv4Data: ipV4Data,
+ IPv6Data: ipV6Data,
+ }
+ retVal := api.AllocateNetworkResponse{}
+ err := d.call("AllocateNetwork", create, &retVal)
+ return retVal.Options, err
+}
+
+func (d *driver) NetworkFree(id string) error {
+ fr := &api.FreeNetworkRequest{NetworkID: id}
+ return d.call("FreeNetwork", fr, &api.FreeNetworkResponse{})
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+func (d *driver) CreateNetwork(id string, options map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ create := &api.CreateNetworkRequest{
+ NetworkID: id,
+ Options: options,
+ IPv4Data: ipV4Data,
+ IPv6Data: ipV6Data,
+ }
+ return d.call("CreateNetwork", create, &api.CreateNetworkResponse{})
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ delete := &api.DeleteNetworkRequest{NetworkID: nid}
+ return d.call("DeleteNetwork", delete, &api.DeleteNetworkResponse{})
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+ if ifInfo == nil {
+ return errors.New("must not be called with nil InterfaceInfo")
+ }
+
+ reqIface := &api.EndpointInterface{}
+ if ifInfo.Address() != nil {
+ reqIface.Address = ifInfo.Address().String()
+ }
+ if ifInfo.AddressIPv6() != nil {
+ reqIface.AddressIPv6 = ifInfo.AddressIPv6().String()
+ }
+ if ifInfo.MacAddress() != nil {
+ reqIface.MacAddress = ifInfo.MacAddress().String()
+ }
+
+ create := &api.CreateEndpointRequest{
+ NetworkID: nid,
+ EndpointID: eid,
+ Interface: reqIface,
+ Options: epOptions,
+ }
+ var res api.CreateEndpointResponse
+ if err := d.call("CreateEndpoint", create, &res); err != nil {
+ return err
+ }
+
+ inIface, err := parseInterface(res)
+ if err != nil {
+ return err
+ }
+ if inIface == nil {
+ // Remote driver did not set any field
+ return nil
+ }
+
+ if inIface.MacAddress != nil {
+ if err := ifInfo.SetMacAddress(inIface.MacAddress); err != nil {
+ return errorWithRollback(fmt.Sprintf("driver modified interface MAC address: %v", err), d.DeleteEndpoint(nid, eid))
+ }
+ }
+ if inIface.Address != nil {
+ if err := ifInfo.SetIPAddress(inIface.Address); err != nil {
+ return errorWithRollback(fmt.Sprintf("driver modified interface address: %v", err), d.DeleteEndpoint(nid, eid))
+ }
+ }
+ if inIface.AddressIPv6 != nil {
+ if err := ifInfo.SetIPAddress(inIface.AddressIPv6); err != nil {
+ return errorWithRollback(fmt.Sprintf("driver modified interface address: %v", err), d.DeleteEndpoint(nid, eid))
+ }
+ }
+
+ return nil
+}
+
+func errorWithRollback(msg string, err error) error {
+ rollback := "rolled back"
+ if err != nil {
+ rollback = "failed to roll back: " + err.Error()
+ }
+ return fmt.Errorf("%s; %s", msg, rollback)
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ delete := &api.DeleteEndpointRequest{
+ NetworkID: nid,
+ EndpointID: eid,
+ }
+ return d.call("DeleteEndpoint", delete, &api.DeleteEndpointResponse{})
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ info := &api.EndpointInfoRequest{
+ NetworkID: nid,
+ EndpointID: eid,
+ }
+ var res api.EndpointInfoResponse
+ if err := d.call("EndpointOperInfo", info, &res); err != nil {
+ return nil, err
+ }
+ return res.Value, nil
+}
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ join := &api.JoinRequest{
+ NetworkID: nid,
+ EndpointID: eid,
+ SandboxKey: sboxKey,
+ Options: options,
+ }
+ var (
+ res api.JoinResponse
+ err error
+ )
+ if err = d.call("Join", join, &res); err != nil {
+ return err
+ }
+
+ ifaceName := res.InterfaceName
+ if iface := jinfo.InterfaceName(); iface != nil && ifaceName != nil {
+ if err := iface.SetNames(ifaceName.SrcName, ifaceName.DstPrefix); err != nil {
+ return errorWithRollback(fmt.Sprintf("failed to set interface name: %s", err), d.Leave(nid, eid))
+ }
+ }
+
+ var addr net.IP
+ if res.Gateway != "" {
+ if addr = net.ParseIP(res.Gateway); addr == nil {
+ return fmt.Errorf(`unable to parse Gateway "%s"`, res.Gateway)
+ }
+ if jinfo.SetGateway(addr) != nil {
+ return errorWithRollback(fmt.Sprintf("failed to set gateway: %v", addr), d.Leave(nid, eid))
+ }
+ }
+ if res.GatewayIPv6 != "" {
+ if addr = net.ParseIP(res.GatewayIPv6); addr == nil {
+ return fmt.Errorf(`unable to parse GatewayIPv6 "%s"`, res.GatewayIPv6)
+ }
+ if jinfo.SetGatewayIPv6(addr) != nil {
+ return errorWithRollback(fmt.Sprintf("failed to set gateway IPv6: %v", addr), d.Leave(nid, eid))
+ }
+ }
+ if len(res.StaticRoutes) > 0 {
+ routes, err := parseStaticRoutes(res)
+ if err != nil {
+ return err
+ }
+ for _, route := range routes {
+ if jinfo.AddStaticRoute(route.Destination, route.RouteType, route.NextHop) != nil {
+ return errorWithRollback(fmt.Sprintf("failed to set static route: %v", route), d.Leave(nid, eid))
+ }
+ }
+ }
+ if res.DisableGatewayService {
+ jinfo.DisableGatewayService()
+ }
+ return nil
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ leave := &api.LeaveRequest{
+ NetworkID: nid,
+ EndpointID: eid,
+ }
+ return d.call("Leave", leave, &api.LeaveResponse{})
+}
+
+// ProgramExternalConnectivity is invoked to program the rules to allow external connectivity for the endpoint.
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ data := &api.ProgramExternalConnectivityRequest{
+ NetworkID: nid,
+ EndpointID: eid,
+ Options: options,
+ }
+ err := d.call("ProgramExternalConnectivity", data, &api.ProgramExternalConnectivityResponse{})
+ if err != nil && plugins.IsNotFound(err) {
+ // It is not mandatory yet to support this method
+ return nil
+ }
+ return err
+}
+
+// RevokeExternalConnectivity method is invoked to remove any external connectivity programming related to the endpoint.
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ data := &api.RevokeExternalConnectivityRequest{
+ NetworkID: nid,
+ EndpointID: eid,
+ }
+ err := d.call("RevokeExternalConnectivity", data, &api.RevokeExternalConnectivityResponse{})
+ if err != nil && plugins.IsNotFound(err) {
+ // It is not mandatory yet to support this method
+ return nil
+ }
+ return err
+}
+
+func (d *driver) Type() string {
+ return d.networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return false
+}
+
+// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ if dType != discoverapi.NodeDiscovery {
+ return nil
+ }
+ notif := &api.DiscoveryNotification{
+ DiscoveryType: dType,
+ DiscoveryData: data,
+ }
+ return d.call("DiscoverNew", notif, &api.DiscoveryResponse{})
+}
+
+// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ if dType != discoverapi.NodeDiscovery {
+ return nil
+ }
+ notif := &api.DiscoveryNotification{
+ DiscoveryType: dType,
+ DiscoveryData: data,
+ }
+ return d.call("DiscoverDelete", notif, &api.DiscoveryResponse{})
+}
+
+func parseStaticRoutes(r api.JoinResponse) ([]*types.StaticRoute, error) {
+ var routes = make([]*types.StaticRoute, len(r.StaticRoutes))
+ for i, inRoute := range r.StaticRoutes {
+ var err error
+ outRoute := &types.StaticRoute{RouteType: inRoute.RouteType}
+
+ if inRoute.Destination != "" {
+ if outRoute.Destination, err = types.ParseCIDR(inRoute.Destination); err != nil {
+ return nil, err
+ }
+ }
+
+ if inRoute.NextHop != "" {
+ outRoute.NextHop = net.ParseIP(inRoute.NextHop)
+ if outRoute.NextHop == nil {
+ return nil, fmt.Errorf("failed to parse nexthop IP %s", inRoute.NextHop)
+ }
+ }
+
+ routes[i] = outRoute
+ }
+ return routes, nil
+}
+
+// parseInterfaces validates all the parameters of an Interface and returns them.
+func parseInterface(r api.CreateEndpointResponse) (*api.Interface, error) {
+ var outIf *api.Interface
+
+ inIf := r.Interface
+ if inIf != nil {
+ var err error
+ outIf = &api.Interface{}
+ if inIf.Address != "" {
+ if outIf.Address, err = types.ParseCIDR(inIf.Address); err != nil {
+ return nil, err
+ }
+ }
+ if inIf.AddressIPv6 != "" {
+ if outIf.AddressIPv6, err = types.ParseCIDR(inIf.AddressIPv6); err != nil {
+ return nil, err
+ }
+ }
+ if inIf.MacAddress != "" {
+ if outIf.MacAddress, err = net.ParseMAC(inIf.MacAddress); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return outIf, nil
+}
--- /dev/null
+package remote
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ "github.com/docker/docker/pkg/plugins"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+func decodeToMap(r *http.Request) (res map[string]interface{}, err error) {
+ err = json.NewDecoder(r.Body).Decode(&res)
+ return
+}
+
+func handle(t *testing.T, mux *http.ServeMux, method string, h func(map[string]interface{}) interface{}) {
+ mux.HandleFunc(fmt.Sprintf("/%s.%s", driverapi.NetworkPluginEndpointType, method), func(w http.ResponseWriter, r *http.Request) {
+ ask, err := decodeToMap(r)
+ if err != nil && err != io.EOF {
+ t.Fatal(err)
+ }
+ answer := h(ask)
+ err = json.NewEncoder(w).Encode(&answer)
+ if err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+
+func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() {
+ if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ server := httptest.NewServer(mux)
+ if server == nil {
+ t.Fatal("Failed to start an HTTP Server")
+ }
+
+ if err := ioutil.WriteFile(fmt.Sprintf("/etc/docker/plugins/%s.spec", name), []byte(server.URL), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType)
+ })
+
+ return func() {
+ if err := os.RemoveAll("/etc/docker/plugins"); err != nil {
+ t.Fatal(err)
+ }
+ server.Close()
+ }
+}
+
+type testEndpoint struct {
+ t *testing.T
+ src string
+ dst string
+ address string
+ addressIPv6 string
+ macAddress string
+ gateway string
+ gatewayIPv6 string
+ resolvConfPath string
+ hostsPath string
+ nextHop string
+ destination string
+ routeType int
+ disableGatewayService bool
+}
+
+func (test *testEndpoint) Interface() driverapi.InterfaceInfo {
+ return test
+}
+
+func (test *testEndpoint) Address() *net.IPNet {
+ if test.address == "" {
+ return nil
+ }
+ nw, _ := types.ParseCIDR(test.address)
+ return nw
+}
+
+func (test *testEndpoint) AddressIPv6() *net.IPNet {
+ if test.addressIPv6 == "" {
+ return nil
+ }
+ nw, _ := types.ParseCIDR(test.addressIPv6)
+ return nw
+}
+
+func (test *testEndpoint) MacAddress() net.HardwareAddr {
+ if test.macAddress == "" {
+ return nil
+ }
+ mac, _ := net.ParseMAC(test.macAddress)
+ return mac
+}
+
+func (test *testEndpoint) SetMacAddress(mac net.HardwareAddr) error {
+ if test.macAddress != "" {
+ return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", test.macAddress, mac)
+ }
+ if mac == nil {
+ return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
+ }
+ test.macAddress = mac.String()
+ return nil
+}
+
+func (test *testEndpoint) SetIPAddress(address *net.IPNet) error {
+ if address.IP == nil {
+ return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
+ }
+ if address.IP.To4() == nil {
+ return setAddress(&test.addressIPv6, address)
+ }
+ return setAddress(&test.address, address)
+}
+
+func setAddress(ifaceAddr *string, address *net.IPNet) error {
+ if *ifaceAddr != "" {
+ return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
+ }
+ *ifaceAddr = address.String()
+ return nil
+}
+
+func (test *testEndpoint) InterfaceName() driverapi.InterfaceNameInfo {
+ return test
+}
+
+func compareIPs(t *testing.T, kind string, shouldBe string, supplied net.IP) {
+ ip := net.ParseIP(shouldBe)
+ if ip == nil {
+ t.Fatalf(`Invalid IP to test against: "%s"`, shouldBe)
+ }
+ if !ip.Equal(supplied) {
+ t.Fatalf(`%s IPs are not equal: expected "%s", got %v`, kind, shouldBe, supplied)
+ }
+}
+
+func compareIPNets(t *testing.T, kind string, shouldBe string, supplied net.IPNet) {
+ _, net, _ := net.ParseCIDR(shouldBe)
+ if net == nil {
+ t.Fatalf(`Invalid IP network to test against: "%s"`, shouldBe)
+ }
+ if !types.CompareIPNet(net, &supplied) {
+ t.Fatalf(`%s IP networks are not equal: expected "%s", got %v`, kind, shouldBe, supplied)
+ }
+}
+
+func (test *testEndpoint) SetGateway(ipv4 net.IP) error {
+ compareIPs(test.t, "Gateway", test.gateway, ipv4)
+ return nil
+}
+
+func (test *testEndpoint) SetGatewayIPv6(ipv6 net.IP) error {
+ compareIPs(test.t, "GatewayIPv6", test.gatewayIPv6, ipv6)
+ return nil
+}
+
+func (test *testEndpoint) SetNames(src string, dst string) error {
+ if test.src != src {
+ test.t.Fatalf(`Wrong SrcName; expected "%s", got "%s"`, test.src, src)
+ }
+ if test.dst != dst {
+ test.t.Fatalf(`Wrong DstPrefix; expected "%s", got "%s"`, test.dst, dst)
+ }
+ return nil
+}
+
+func (test *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP) error {
+ compareIPNets(test.t, "Destination", test.destination, *destination)
+ compareIPs(test.t, "NextHop", test.nextHop, nextHop)
+
+ if test.routeType != routeType {
+ test.t.Fatalf(`Wrong RouteType; expected "%d", got "%d"`, test.routeType, routeType)
+ }
+
+ return nil
+}
+
+func (test *testEndpoint) DisableGatewayService() {
+ test.disableGatewayService = true
+}
+
+func (test *testEndpoint) AddTableEntry(tableName string, key string, value []byte) error {
+ return nil
+}
+
+func TestGetEmptyCapabilities(t *testing.T) {
+ var plugin = "test-net-driver-empty-cap"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{}
+ })
+
+ p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ d := newDriver(plugin, client)
+ if d.Type() != plugin {
+ t.Fatal("Driver type does not match that given")
+ }
+
+ _, err = d.(*driver).getCapabilities()
+ if err == nil {
+ t.Fatal("There should be error reported when get empty capability")
+ }
+}
+
+func TestGetExtraCapabilities(t *testing.T) {
+ var plugin = "test-net-driver-extra-cap"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{
+ "Scope": "local",
+ "foo": "bar",
+ "ConnectivityScope": "global",
+ }
+ })
+
+ p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ d := newDriver(plugin, client)
+ if d.Type() != plugin {
+ t.Fatal("Driver type does not match that given")
+ }
+
+ c, err := d.(*driver).getCapabilities()
+ if err != nil {
+ t.Fatal(err)
+ } else if c.DataScope != datastore.LocalScope {
+ t.Fatalf("get capability '%s', expecting 'local'", c.DataScope)
+ } else if c.ConnectivityScope != datastore.GlobalScope {
+ t.Fatalf("get capability '%s', expecting %q", c.ConnectivityScope, datastore.GlobalScope)
+ }
+}
+
+func TestGetInvalidCapabilities(t *testing.T) {
+ var plugin = "test-net-driver-invalid-cap"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{
+ "Scope": "fake",
+ }
+ })
+
+ p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ d := newDriver(plugin, client)
+ if d.Type() != plugin {
+ t.Fatal("Driver type does not match that given")
+ }
+
+ _, err = d.(*driver).getCapabilities()
+ if err == nil {
+ t.Fatal("There should be error reported when get invalid capability")
+ }
+}
+
+func TestRemoteDriver(t *testing.T) {
+ var plugin = "test-net-driver"
+
+ ep := &testEndpoint{
+ t: t,
+ src: "vethsrc",
+ dst: "vethdst",
+ address: "192.168.5.7/16",
+ addressIPv6: "2001:DB8::5:7/48",
+ macAddress: "ab:cd:ef:ee:ee:ee",
+ gateway: "192.168.0.1",
+ gatewayIPv6: "2001:DB8::1",
+ hostsPath: "/here/comes/the/host/path",
+ resolvConfPath: "/there/goes/the/resolv/conf",
+ destination: "10.0.0.0/8",
+ nextHop: "10.0.0.1",
+ routeType: 1,
+ }
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ var networkID string
+
+ handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{
+ "Scope": "global",
+ }
+ })
+ handle(t, mux, "CreateNetwork", func(msg map[string]interface{}) interface{} {
+ nid := msg["NetworkID"]
+ var ok bool
+ if networkID, ok = nid.(string); !ok {
+ t.Fatal("RPC did not include network ID string")
+ }
+ return map[string]interface{}{}
+ })
+ handle(t, mux, "DeleteNetwork", func(msg map[string]interface{}) interface{} {
+ if nid, ok := msg["NetworkID"]; !ok || nid != networkID {
+ t.Fatal("Network ID missing or does not match that created")
+ }
+ return map[string]interface{}{}
+ })
+ handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
+ iface := map[string]interface{}{
+ "MacAddress": ep.macAddress,
+ "Address": ep.address,
+ "AddressIPv6": ep.addressIPv6,
+ }
+ return map[string]interface{}{
+ "Interface": iface,
+ }
+ })
+ handle(t, mux, "Join", func(msg map[string]interface{}) interface{} {
+ options := msg["Options"].(map[string]interface{})
+ foo, ok := options["foo"].(string)
+ if !ok || foo != "fooValue" {
+ t.Fatalf("Did not receive expected foo string in request options: %+v", msg)
+ }
+ return map[string]interface{}{
+ "Gateway": ep.gateway,
+ "GatewayIPv6": ep.gatewayIPv6,
+ "HostsPath": ep.hostsPath,
+ "ResolvConfPath": ep.resolvConfPath,
+ "InterfaceName": map[string]interface{}{
+ "SrcName": ep.src,
+ "DstPrefix": ep.dst,
+ },
+ "StaticRoutes": []map[string]interface{}{
+ {
+ "Destination": ep.destination,
+ "RouteType": ep.routeType,
+ "NextHop": ep.nextHop,
+ },
+ },
+ }
+ })
+ handle(t, mux, "Leave", func(msg map[string]interface{}) interface{} {
+ return map[string]string{}
+ })
+ handle(t, mux, "DeleteEndpoint", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{}
+ })
+ handle(t, mux, "EndpointOperInfo", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{
+ "Value": map[string]string{
+ "Arbitrary": "key",
+ "Value": "pairs?",
+ },
+ }
+ })
+ handle(t, mux, "DiscoverNew", func(msg map[string]interface{}) interface{} {
+ return map[string]string{}
+ })
+ handle(t, mux, "DiscoverDelete", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{}
+ })
+
+ p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ d := newDriver(plugin, client)
+ if d.Type() != plugin {
+ t.Fatal("Driver type does not match that given")
+ }
+
+ c, err := d.(*driver).getCapabilities()
+ if err != nil {
+ t.Fatal(err)
+ } else if c.DataScope != datastore.GlobalScope {
+ t.Fatalf("get capability '%s', expecting 'global'", c.DataScope)
+ }
+
+ netID := "dummy-network"
+ err = d.CreateNetwork(netID, map[string]interface{}{}, nil, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ endID := "dummy-endpoint"
+ ifInfo := &testEndpoint{}
+ err = d.CreateEndpoint(netID, endID, ifInfo, map[string]interface{}{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(ep.MacAddress(), ifInfo.MacAddress()) || !types.CompareIPNet(ep.Address(), ifInfo.Address()) ||
+ !types.CompareIPNet(ep.AddressIPv6(), ifInfo.AddressIPv6()) {
+ t.Fatalf("Unexpected InterfaceInfo data. Expected (%s, %s, %s). Got (%v, %v, %v)",
+ ep.MacAddress(), ep.Address(), ep.AddressIPv6(),
+ ifInfo.MacAddress(), ifInfo.Address(), ifInfo.AddressIPv6())
+ }
+
+ joinOpts := map[string]interface{}{"foo": "fooValue"}
+ err = d.Join(netID, endID, "sandbox-key", ep, joinOpts)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err = d.EndpointOperInfo(netID, endID); err != nil {
+ t.Fatal(err)
+ }
+ if err = d.Leave(netID, endID); err != nil {
+ t.Fatal(err)
+ }
+ if err = d.DeleteEndpoint(netID, endID); err != nil {
+ t.Fatal(err)
+ }
+ if err = d.DeleteNetwork(netID); err != nil {
+ t.Fatal(err)
+ }
+
+ data := discoverapi.NodeDiscoveryData{
+ Address: "192.168.1.1",
+ }
+ if err = d.DiscoverNew(discoverapi.NodeDiscovery, data); err != nil {
+ t.Fatal(err)
+ }
+ if err = d.DiscoverDelete(discoverapi.NodeDiscovery, data); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestDriverError(t *testing.T) {
+ var plugin = "test-net-driver-error"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{
+ "Err": "this should get raised as an error",
+ }
+ })
+
+ p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ driver := newDriver(plugin, client)
+
+ if err := driver.CreateEndpoint("dummy", "dummy", &testEndpoint{t: t}, map[string]interface{}{}); err == nil {
+ t.Fatal("Expected error from driver")
+ }
+}
+
+func TestMissingValues(t *testing.T) {
+ var plugin = "test-net-driver-missing"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ ep := &testEndpoint{
+ t: t,
+ }
+
+ handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
+ iface := map[string]interface{}{
+ "Address": ep.address,
+ "AddressIPv6": ep.addressIPv6,
+ "MacAddress": ep.macAddress,
+ }
+ return map[string]interface{}{
+ "Interface": iface,
+ }
+ })
+
+ p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ driver := newDriver(plugin, client)
+
+ if err := driver.CreateEndpoint("dummy", "dummy", ep, map[string]interface{}{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+type rollbackEndpoint struct {
+}
+
+func (r *rollbackEndpoint) Interface() driverapi.InterfaceInfo {
+ return r
+}
+
+func (r *rollbackEndpoint) MacAddress() net.HardwareAddr {
+ return nil
+}
+
+func (r *rollbackEndpoint) Address() *net.IPNet {
+ return nil
+}
+
+func (r *rollbackEndpoint) AddressIPv6() *net.IPNet {
+ return nil
+}
+
+func (r *rollbackEndpoint) SetMacAddress(mac net.HardwareAddr) error {
+ return errors.New("invalid mac")
+}
+
+func (r *rollbackEndpoint) SetIPAddress(ip *net.IPNet) error {
+ return errors.New("invalid ip")
+}
+
+func TestRollback(t *testing.T) {
+ var plugin = "test-net-driver-rollback"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ rolledback := false
+
+ handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
+ iface := map[string]interface{}{
+ "Address": "192.168.4.5/16",
+ "AddressIPv6": "",
+ "MacAddress": "7a:12:34:56:78:90",
+ }
+ return map[string]interface{}{
+ "Interface": interface{}(iface),
+ }
+ })
+ handle(t, mux, "DeleteEndpoint", func(msg map[string]interface{}) interface{} {
+ rolledback = true
+ return map[string]interface{}{}
+ })
+
+ p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ driver := newDriver(plugin, client)
+
+ ep := &rollbackEndpoint{}
+
+ if err := driver.CreateEndpoint("dummy", "dummy", ep.Interface(), map[string]interface{}{}); err == nil {
+ t.Fatal("Expected error from driver")
+ }
+ if !rolledback {
+ t.Fatal("Expected to have had DeleteEndpoint called")
+ }
+}
--- /dev/null
+package windows
+
+const (
+ // NetworkName label for bridge driver
+ NetworkName = "com.docker.network.windowsshim.networkname"
+
+ // HNSID of the discovered network
+ HNSID = "com.docker.network.windowsshim.hnsid"
+
+ // RoutingDomain of the network
+ RoutingDomain = "com.docker.network.windowsshim.routingdomain"
+
+ // Interface of the network
+ Interface = "com.docker.network.windowsshim.interface"
+
+ // QosPolicies of the endpoint
+ QosPolicies = "com.docker.endpoint.windowsshim.qospolicies"
+
+ // VLAN of the network
+ VLAN = "com.docker.network.windowsshim.vlanid"
+
+ // VSID of the network
+ VSID = "com.docker.network.windowsshim.vsid"
+
+ // DNSSuffix of the network
+ DNSSuffix = "com.docker.network.windowsshim.dnssuffix"
+
+ // DNSServers of the network
+ DNSServers = "com.docker.network.windowsshim.dnsservers"
+
+ // MacPool of the network
+ MacPool = "com.docker.network.windowsshim.macpool"
+
+ // SourceMac of the network
+ SourceMac = "com.docker.network.windowsshim.sourcemac"
+
+ // DisableICC label
+ DisableICC = "com.docker.network.windowsshim.disableicc"
+
+ // DisableDNS label
+ DisableDNS = "com.docker.network.windowsshim.disable_dns"
+
+ // DisableGatewayDNS label
+ DisableGatewayDNS = "com.docker.network.windowsshim.disable_gatewaydns"
+
+ // EnableOutboundNat label
+ EnableOutboundNat = "com.docker.network.windowsshim.enable_outboundnat"
+
+ // OutboundNatExceptions label
+ OutboundNatExceptions = "com.docker.network.windowsshim.outboundnat_exceptions"
+)
--- /dev/null
+package overlay
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/types"
+ "github.com/gogo/protobuf/proto"
+ "github.com/sirupsen/logrus"
+)
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("could not find network with id %s", nid)
+ }
+
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return fmt.Errorf("could not find endpoint with id %s", eid)
+ }
+
+ buf, err := proto.Marshal(&PeerRecord{
+ EndpointIP: ep.addr.String(),
+ EndpointMAC: ep.mac.String(),
+ TunnelEndpointIP: n.providerAddress,
+ })
+
+ if err != nil {
+ return err
+ }
+
+ if err := jinfo.AddTableEntry(ovPeerTable, eid, buf); err != nil {
+ logrus.Errorf("overlay: Failed adding table entry to joininfo: %v", err)
+ }
+
+ if ep.disablegateway {
+ jinfo.DisableGatewayService()
+ }
+
+ return nil
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+ if tableName != ovPeerTable {
+ logrus.Errorf("Unexpected table notification for table %s received", tableName)
+ return
+ }
+
+ eid := key
+
+ var peer PeerRecord
+ if err := proto.Unmarshal(value, &peer); err != nil {
+ logrus.Errorf("Failed to unmarshal peer record: %v", err)
+ return
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return
+ }
+
+ // Ignore local peers. We already know about them and they
+ // should not be added to vxlan fdb.
+ if peer.TunnelEndpointIP == n.providerAddress {
+ return
+ }
+
+ addr, err := types.ParseCIDR(peer.EndpointIP)
+ if err != nil {
+ logrus.Errorf("Invalid peer IP %s received in event notify", peer.EndpointIP)
+ return
+ }
+
+ mac, err := net.ParseMAC(peer.EndpointMAC)
+ if err != nil {
+ logrus.Errorf("Invalid mac %s received in event notify", peer.EndpointMAC)
+ return
+ }
+
+ vtep := net.ParseIP(peer.TunnelEndpointIP)
+ if vtep == nil {
+ logrus.Errorf("Invalid VTEP %s received in event notify", peer.TunnelEndpointIP)
+ return
+ }
+
+ if etype == driverapi.Delete {
+ d.peerDelete(nid, eid, addr.IP, addr.Mask, mac, vtep, true)
+ return
+ }
+
+ err = d.peerAdd(nid, eid, addr.IP, addr.Mask, mac, vtep, true)
+ if err != nil {
+ logrus.Errorf("peerAdd failed (%v) for ip %s with mac %s", err, addr.IP.String(), mac.String())
+ }
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ return nil
+}
--- /dev/null
+package overlay
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "sync"
+
+ "github.com/Microsoft/hcsshim"
+ "github.com/docker/docker/pkg/system"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/drivers/windows"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+type endpointTable map[string]*endpoint
+
+const overlayEndpointPrefix = "overlay/endpoint"
+
+type endpoint struct {
+ id string
+ nid string
+ profileID string
+ remote bool
+ mac net.HardwareAddr
+ addr *net.IPNet
+ disablegateway bool
+ portMapping []types.PortBinding // Operation port bindings
+}
+
+var (
+ //Server 2016 (RS1) does not support concurrent add/delete of endpoints. Therefore, we need
+ //to use this mutex and serialize the add/delete of endpoints on RS1.
+ endpointMu sync.Mutex
+ windowsBuild = system.GetOSVersion().Build
+)
+
+func validateID(nid, eid string) error {
+ if nid == "" {
+ return fmt.Errorf("invalid network id")
+ }
+
+ if eid == "" {
+ return fmt.Errorf("invalid endpoint id")
+ }
+
+ return nil
+}
+
+func (n *network) endpoint(eid string) *endpoint {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.endpoints[eid]
+}
+
+func (n *network) addEndpoint(ep *endpoint) {
+ n.Lock()
+ n.endpoints[ep.id] = ep
+ n.Unlock()
+}
+
+func (n *network) deleteEndpoint(eid string) {
+ n.Lock()
+ delete(n.endpoints, eid)
+ n.Unlock()
+}
+
+func (n *network) removeEndpointWithAddress(addr *net.IPNet) {
+ var networkEndpoint *endpoint
+ n.Lock()
+ for _, ep := range n.endpoints {
+ if ep.addr.IP.Equal(addr.IP) {
+ networkEndpoint = ep
+ break
+ }
+ }
+
+ if networkEndpoint != nil {
+ delete(n.endpoints, networkEndpoint.id)
+ }
+ n.Unlock()
+
+ if networkEndpoint != nil {
+ logrus.Debugf("Removing stale endpoint from HNS")
+ _, err := endpointRequest("DELETE", networkEndpoint.profileID, "")
+ if err != nil {
+ logrus.Debugf("Failed to delete stale overlay endpoint (%.7s) from hns", networkEndpoint.id)
+ }
+ }
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
+ epOptions map[string]interface{}) error {
+ var err error
+ if err = validateID(nid, eid); err != nil {
+ return err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("network id %q not found", nid)
+ }
+
+ ep := n.endpoint(eid)
+ if ep != nil {
+ logrus.Debugf("Deleting stale endpoint %s", eid)
+ n.deleteEndpoint(eid)
+ _, err := endpointRequest("DELETE", ep.profileID, "")
+ if err != nil {
+ return err
+ }
+ }
+
+ ep = &endpoint{
+ id: eid,
+ nid: n.id,
+ addr: ifInfo.Address(),
+ mac: ifInfo.MacAddress(),
+ }
+
+ if ep.addr == nil {
+ return fmt.Errorf("create endpoint was not passed interface IP address")
+ }
+
+ s := n.getSubnetforIP(ep.addr)
+ if s == nil {
+ return fmt.Errorf("no matching subnet for IP %q in network %q", ep.addr, nid)
+ }
+
+ // Todo: Add port bindings and qos policies here
+
+ hnsEndpoint := &hcsshim.HNSEndpoint{
+ Name: eid,
+ VirtualNetwork: n.hnsID,
+ IPAddress: ep.addr.IP,
+ EnableInternalDNS: true,
+ GatewayAddress: s.gwIP.String(),
+ }
+
+ if ep.mac != nil {
+ hnsEndpoint.MacAddress = ep.mac.String()
+ }
+
+ paPolicy, err := json.Marshal(hcsshim.PaPolicy{
+ Type: "PA",
+ PA: n.providerAddress,
+ })
+
+ if err != nil {
+ return err
+ }
+
+ hnsEndpoint.Policies = append(hnsEndpoint.Policies, paPolicy)
+
+ if system.GetOSVersion().Build > 16236 {
+ natPolicy, err := json.Marshal(hcsshim.PaPolicy{
+ Type: "OutBoundNAT",
+ })
+
+ if err != nil {
+ return err
+ }
+
+ hnsEndpoint.Policies = append(hnsEndpoint.Policies, natPolicy)
+
+ epConnectivity, err := windows.ParseEndpointConnectivity(epOptions)
+ if err != nil {
+ return err
+ }
+
+ ep.portMapping = epConnectivity.PortBindings
+ ep.portMapping, err = windows.AllocatePorts(n.portMapper, ep.portMapping, ep.addr.IP)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil {
+ windows.ReleasePorts(n.portMapper, ep.portMapping)
+ }
+ }()
+
+ pbPolicy, err := windows.ConvertPortBindings(ep.portMapping)
+ if err != nil {
+ return err
+ }
+ hnsEndpoint.Policies = append(hnsEndpoint.Policies, pbPolicy...)
+
+ ep.disablegateway = true
+ }
+
+ configurationb, err := json.Marshal(hnsEndpoint)
+ if err != nil {
+ return err
+ }
+
+ hnsresponse, err := endpointRequest("POST", "", string(configurationb))
+ if err != nil {
+ return err
+ }
+
+ ep.profileID = hnsresponse.Id
+
+ if ep.mac == nil {
+ ep.mac, err = net.ParseMAC(hnsresponse.MacAddress)
+ if err != nil {
+ return err
+ }
+
+ if err := ifInfo.SetMacAddress(ep.mac); err != nil {
+ return err
+ }
+ }
+
+ ep.portMapping, err = windows.ParsePortBindingPolicies(hnsresponse.Policies)
+ if err != nil {
+ endpointRequest("DELETE", hnsresponse.Id, "")
+ return err
+ }
+
+ n.addEndpoint(ep)
+
+ return nil
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return fmt.Errorf("network id %q not found", nid)
+ }
+
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return fmt.Errorf("endpoint id %q not found", eid)
+ }
+
+ windows.ReleasePorts(n.portMapper, ep.portMapping)
+
+ n.deleteEndpoint(eid)
+
+ _, err := endpointRequest("DELETE", ep.profileID, "")
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ if err := validateID(nid, eid); err != nil {
+ return nil, err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return nil, fmt.Errorf("network id %q not found", nid)
+ }
+
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return nil, fmt.Errorf("endpoint id %q not found", eid)
+ }
+
+ data := make(map[string]interface{}, 1)
+ data["hnsid"] = ep.profileID
+ data["AllowUnqualifiedDNSQuery"] = true
+
+ if ep.portMapping != nil {
+ // Return a copy of the operational data
+ pmc := make([]types.PortBinding, 0, len(ep.portMapping))
+ for _, pm := range ep.portMapping {
+ pmc = append(pmc, pm.GetCopy())
+ }
+ data[netlabel.PortMap] = pmc
+ }
+
+ return data, nil
+}
+
+func endpointRequest(method, path, request string) (*hcsshim.HNSEndpoint, error) {
+ if windowsBuild == 14393 {
+ endpointMu.Lock()
+ }
+ hnsresponse, err := hcsshim.HNSEndpointRequest(method, path, request)
+ if windowsBuild == 14393 {
+ endpointMu.Unlock()
+ }
+ return hnsresponse, err
+}
--- /dev/null
+package overlay
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/Microsoft/hcsshim"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/portmapper"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+var (
+ hostMode bool
+ networkMu sync.Mutex
+)
+
+type networkTable map[string]*network
+
+type subnet struct {
+ vni uint32
+ subnetIP *net.IPNet
+ gwIP *net.IP
+}
+
+type subnetJSON struct {
+ SubnetIP string
+ GwIP string
+ Vni uint32
+}
+
+type network struct {
+ id string
+ name string
+ hnsID string
+ providerAddress string
+ interfaceName string
+ endpoints endpointTable
+ driver *driver
+ initEpoch int
+ initErr error
+ subnets []*subnet
+ secure bool
+ portMapper *portmapper.PortMapper
+ sync.Mutex
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ var (
+ networkName string
+ interfaceName string
+ staleNetworks []string
+ )
+
+ if id == "" {
+ return fmt.Errorf("invalid network id")
+ }
+
+ if nInfo == nil {
+ return fmt.Errorf("invalid network info structure")
+ }
+
+ if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {
+ return types.BadRequestErrorf("ipv4 pool is empty")
+ }
+
+ staleNetworks = make([]string, 0)
+ vnis := make([]uint32, 0, len(ipV4Data))
+
+ existingNetwork := d.network(id)
+ if existingNetwork != nil {
+ logrus.Debugf("Network preexists. Deleting %s", id)
+ err := d.DeleteNetwork(id)
+ if err != nil {
+ logrus.Errorf("Error deleting stale network %s", err.Error())
+ }
+ }
+
+ n := &network{
+ id: id,
+ driver: d,
+ endpoints: endpointTable{},
+ subnets: []*subnet{},
+ portMapper: portmapper.New(""),
+ }
+
+ genData, ok := option[netlabel.GenericData].(map[string]string)
+
+ if !ok {
+ return fmt.Errorf("Unknown generic data option")
+ }
+
+ for label, value := range genData {
+ switch label {
+ case "com.docker.network.windowsshim.networkname":
+ networkName = value
+ case "com.docker.network.windowsshim.interface":
+ interfaceName = value
+ case "com.docker.network.windowsshim.hnsid":
+ n.hnsID = value
+ case netlabel.OverlayVxlanIDList:
+ vniStrings := strings.Split(value, ",")
+ for _, vniStr := range vniStrings {
+ vni, err := strconv.Atoi(vniStr)
+ if err != nil {
+ return fmt.Errorf("invalid vxlan id value %q passed", vniStr)
+ }
+
+ vnis = append(vnis, uint32(vni))
+ }
+ }
+ }
+
+ // If we are getting vnis from libnetwork, either we get for
+ // all subnets or none.
+ if len(vnis) < len(ipV4Data) {
+ return fmt.Errorf("insufficient vnis(%d) passed to overlay. Windows driver requires VNIs to be prepopulated", len(vnis))
+ }
+
+ for i, ipd := range ipV4Data {
+ s := &subnet{
+ subnetIP: ipd.Pool,
+ gwIP: &ipd.Gateway.IP,
+ }
+
+ if len(vnis) != 0 {
+ s.vni = vnis[i]
+ }
+
+ d.Lock()
+ for _, network := range d.networks {
+ found := false
+ for _, sub := range network.subnets {
+ if sub.vni == s.vni {
+ staleNetworks = append(staleNetworks, network.id)
+ found = true
+ break
+ }
+ }
+ if found {
+ break
+ }
+ }
+ d.Unlock()
+
+ n.subnets = append(n.subnets, s)
+ }
+
+ for _, staleNetwork := range staleNetworks {
+ d.DeleteNetwork(staleNetwork)
+ }
+
+ n.name = networkName
+ if n.name == "" {
+ n.name = id
+ }
+
+ n.interfaceName = interfaceName
+
+ if nInfo != nil {
+ if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil {
+ return err
+ }
+ }
+
+ d.addNetwork(n)
+
+ err := d.createHnsNetwork(n)
+
+ if err != nil {
+ d.deleteNetwork(id)
+ } else {
+ genData["com.docker.network.windowsshim.hnsid"] = n.hnsID
+ }
+
+ return err
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ if nid == "" {
+ return fmt.Errorf("invalid network id")
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return types.ForbiddenErrorf("could not find network with id %s", nid)
+ }
+
+ _, err := hcsshim.HNSNetworkRequest("DELETE", n.hnsID, "")
+ if err != nil {
+ return types.ForbiddenErrorf(err.Error())
+ }
+
+ d.deleteNetwork(nid)
+
+ return nil
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+}
+
+func (d *driver) addNetwork(n *network) {
+ d.Lock()
+ d.networks[n.id] = n
+ d.Unlock()
+}
+
+func (d *driver) deleteNetwork(nid string) {
+ d.Lock()
+ delete(d.networks, nid)
+ d.Unlock()
+}
+
+func (d *driver) network(nid string) *network {
+ d.Lock()
+ defer d.Unlock()
+ return d.networks[nid]
+}
+
+// func (n *network) restoreNetworkEndpoints() error {
+// logrus.Infof("Restoring endpoints for overlay network: %s", n.id)
+
+// hnsresponse, err := hcsshim.HNSListEndpointRequest("GET", "", "")
+// if err != nil {
+// return err
+// }
+
+// for _, endpoint := range hnsresponse {
+// if endpoint.VirtualNetwork != n.hnsID {
+// continue
+// }
+
+// ep := n.convertToOverlayEndpoint(&endpoint)
+
+// if ep != nil {
+// logrus.Debugf("Restored endpoint:%s Remote:%t", ep.id, ep.remote)
+// n.addEndpoint(ep)
+// }
+// }
+
+// return nil
+// }
+
+func (n *network) convertToOverlayEndpoint(v *hcsshim.HNSEndpoint) *endpoint {
+ ep := &endpoint{
+ id: v.Name,
+ profileID: v.Id,
+ nid: n.id,
+ remote: v.IsRemoteEndpoint,
+ }
+
+ mac, err := net.ParseMAC(v.MacAddress)
+
+ if err != nil {
+ return nil
+ }
+
+ ep.mac = mac
+ ep.addr = &net.IPNet{
+ IP: v.IPAddress,
+ Mask: net.CIDRMask(32, 32),
+ }
+
+ return ep
+}
+
+func (d *driver) createHnsNetwork(n *network) error {
+
+ subnets := []hcsshim.Subnet{}
+
+ for _, s := range n.subnets {
+ subnet := hcsshim.Subnet{
+ AddressPrefix: s.subnetIP.String(),
+ }
+
+ if s.gwIP != nil {
+ subnet.GatewayAddress = s.gwIP.String()
+ }
+
+ vsidPolicy, err := json.Marshal(hcsshim.VsidPolicy{
+ Type: "VSID",
+ VSID: uint(s.vni),
+ })
+
+ if err != nil {
+ return err
+ }
+
+ subnet.Policies = append(subnet.Policies, vsidPolicy)
+ subnets = append(subnets, subnet)
+ }
+
+ network := &hcsshim.HNSNetwork{
+ Name: n.name,
+ Type: d.Type(),
+ Subnets: subnets,
+ NetworkAdapterName: n.interfaceName,
+ AutomaticDNS: true,
+ }
+
+ configurationb, err := json.Marshal(network)
+ if err != nil {
+ return err
+ }
+
+ configuration := string(configurationb)
+ logrus.Infof("HNSNetwork Request =%v", configuration)
+
+ hnsresponse, err := hcsshim.HNSNetworkRequest("POST", "", configuration)
+ if err != nil {
+ return err
+ }
+
+ n.hnsID = hnsresponse.Id
+ n.providerAddress = hnsresponse.ManagementIP
+
+ return nil
+}
+
+// contains return true if the passed ip belongs to one the network's
+// subnets
+func (n *network) contains(ip net.IP) bool {
+ for _, s := range n.subnets {
+ if s.subnetIP.Contains(ip) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// getSubnetforIP returns the subnet to which the given IP belongs
+func (n *network) getSubnetforIP(ip *net.IPNet) *subnet {
+ for _, s := range n.subnets {
+ // first check if the mask lengths are the same
+ i, _ := s.subnetIP.Mask.Size()
+ j, _ := ip.Mask.Size()
+ if i != j {
+ continue
+ }
+ if s.subnetIP.Contains(ip.IP) {
+ return s
+ }
+ }
+ return nil
+}
+
+// getMatchingSubnet return the network's subnet that matches the input
+func (n *network) getMatchingSubnet(ip *net.IPNet) *subnet {
+ if ip == nil {
+ return nil
+ }
+ for _, s := range n.subnets {
+ // first check if the mask lengths are the same
+ i, _ := s.subnetIP.Mask.Size()
+ j, _ := ip.Mask.Size()
+ if i != j {
+ continue
+ }
+ if s.subnetIP.IP.Equal(ip.IP) {
+ return s
+ }
+ }
+ return nil
+}
--- /dev/null
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: drivers/windows/overlay/overlay.proto
+
+/*
+ Package overlay is a generated protocol buffer package.
+
+ It is generated from these files:
+ drivers/windows/overlay/overlay.proto
+
+ It has these top-level messages:
+ PeerRecord
+*/
+package overlay
+
+import proto "github.com/gogo/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import _ "github.com/gogo/protobuf/gogoproto"
+
+import strings "strings"
+import reflect "reflect"
+
+import io "io"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
+
+// PeerRecord defines the information corresponding to a peer
+// container in the overlay network.
+type PeerRecord struct {
+ // Endpoint IP is the IP of the container attachment on the
+ // given overlay network.
+ EndpointIP string `protobuf:"bytes,1,opt,name=endpoint_ip,json=endpointIp,proto3" json:"endpoint_ip,omitempty"`
+ // Endpoint MAC is the mac address of the container attachment
+ // on the given overlay network.
+ EndpointMAC string `protobuf:"bytes,2,opt,name=endpoint_mac,json=endpointMac,proto3" json:"endpoint_mac,omitempty"`
+ // Tunnel Endpoint IP defines the host IP for the host in
+ // which this container is running and can be reached by
+ // building a tunnel to that host IP.
+ TunnelEndpointIP string `protobuf:"bytes,3,opt,name=tunnel_endpoint_ip,json=tunnelEndpointIp,proto3" json:"tunnel_endpoint_ip,omitempty"`
+}
+
+func (m *PeerRecord) Reset() { *m = PeerRecord{} }
+func (*PeerRecord) ProtoMessage() {}
+func (*PeerRecord) Descriptor() ([]byte, []int) { return fileDescriptorOverlay, []int{0} }
+
+func (m *PeerRecord) GetEndpointIP() string {
+ if m != nil {
+ return m.EndpointIP
+ }
+ return ""
+}
+
+func (m *PeerRecord) GetEndpointMAC() string {
+ if m != nil {
+ return m.EndpointMAC
+ }
+ return ""
+}
+
+func (m *PeerRecord) GetTunnelEndpointIP() string {
+ if m != nil {
+ return m.TunnelEndpointIP
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*PeerRecord)(nil), "overlay.PeerRecord")
+}
+func (this *PeerRecord) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 7)
+ s = append(s, "&overlay.PeerRecord{")
+ s = append(s, "EndpointIP: "+fmt.Sprintf("%#v", this.EndpointIP)+",\n")
+ s = append(s, "EndpointMAC: "+fmt.Sprintf("%#v", this.EndpointMAC)+",\n")
+ s = append(s, "TunnelEndpointIP: "+fmt.Sprintf("%#v", this.TunnelEndpointIP)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func valueToGoStringOverlay(v interface{}, typ string) string {
+ rv := reflect.ValueOf(v)
+ if rv.IsNil() {
+ return "nil"
+ }
+ pv := reflect.Indirect(rv).Interface()
+ return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)
+}
+func (m *PeerRecord) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *PeerRecord) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if len(m.EndpointIP) > 0 {
+ dAtA[i] = 0xa
+ i++
+ i = encodeVarintOverlay(dAtA, i, uint64(len(m.EndpointIP)))
+ i += copy(dAtA[i:], m.EndpointIP)
+ }
+ if len(m.EndpointMAC) > 0 {
+ dAtA[i] = 0x12
+ i++
+ i = encodeVarintOverlay(dAtA, i, uint64(len(m.EndpointMAC)))
+ i += copy(dAtA[i:], m.EndpointMAC)
+ }
+ if len(m.TunnelEndpointIP) > 0 {
+ dAtA[i] = 0x1a
+ i++
+ i = encodeVarintOverlay(dAtA, i, uint64(len(m.TunnelEndpointIP)))
+ i += copy(dAtA[i:], m.TunnelEndpointIP)
+ }
+ return i, nil
+}
+
+func encodeVarintOverlay(dAtA []byte, offset int, v uint64) int {
+ for v >= 1<<7 {
+ dAtA[offset] = uint8(v&0x7f | 0x80)
+ v >>= 7
+ offset++
+ }
+ dAtA[offset] = uint8(v)
+ return offset + 1
+}
+func (m *PeerRecord) Size() (n int) {
+ var l int
+ _ = l
+ l = len(m.EndpointIP)
+ if l > 0 {
+ n += 1 + l + sovOverlay(uint64(l))
+ }
+ l = len(m.EndpointMAC)
+ if l > 0 {
+ n += 1 + l + sovOverlay(uint64(l))
+ }
+ l = len(m.TunnelEndpointIP)
+ if l > 0 {
+ n += 1 + l + sovOverlay(uint64(l))
+ }
+ return n
+}
+
+func sovOverlay(x uint64) (n int) {
+ for {
+ n++
+ x >>= 7
+ if x == 0 {
+ break
+ }
+ }
+ return n
+}
+func sozOverlay(x uint64) (n int) {
+ return sovOverlay(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (this *PeerRecord) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&PeerRecord{`,
+ `EndpointIP:` + fmt.Sprintf("%v", this.EndpointIP) + `,`,
+ `EndpointMAC:` + fmt.Sprintf("%v", this.EndpointMAC) + `,`,
+ `TunnelEndpointIP:` + fmt.Sprintf("%v", this.TunnelEndpointIP) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func valueToStringOverlay(v interface{}) string {
+ rv := reflect.ValueOf(v)
+ if rv.IsNil() {
+ return "nil"
+ }
+ pv := reflect.Indirect(rv).Interface()
+ return fmt.Sprintf("*%v", pv)
+}
+func (m *PeerRecord) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: PeerRecord: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: PeerRecord: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field EndpointIP", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthOverlay
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.EndpointIP = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field EndpointMAC", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthOverlay
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.EndpointMAC = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 3:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field TunnelEndpointIP", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthOverlay
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.TunnelEndpointIP = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipOverlay(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthOverlay
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func skipOverlay(dAtA []byte) (n int, err error) {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ wireType := int(wire & 0x7)
+ switch wireType {
+ case 0:
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ iNdEx++
+ if dAtA[iNdEx-1] < 0x80 {
+ break
+ }
+ }
+ return iNdEx, nil
+ case 1:
+ iNdEx += 8
+ return iNdEx, nil
+ case 2:
+ var length int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ length |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ iNdEx += length
+ if length < 0 {
+ return 0, ErrInvalidLengthOverlay
+ }
+ return iNdEx, nil
+ case 3:
+ for {
+ var innerWire uint64
+ var start int = iNdEx
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowOverlay
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ innerWire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ innerWireType := int(innerWire & 0x7)
+ if innerWireType == 4 {
+ break
+ }
+ next, err := skipOverlay(dAtA[start:])
+ if err != nil {
+ return 0, err
+ }
+ iNdEx = start + next
+ }
+ return iNdEx, nil
+ case 4:
+ return iNdEx, nil
+ case 5:
+ iNdEx += 4
+ return iNdEx, nil
+ default:
+ return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+ }
+ }
+ panic("unreachable")
+}
+
+var (
+ ErrInvalidLengthOverlay = fmt.Errorf("proto: negative length found during unmarshaling")
+ ErrIntOverflowOverlay = fmt.Errorf("proto: integer overflow")
+)
+
+func init() { proto.RegisterFile("drivers/windows/overlay/overlay.proto", fileDescriptorOverlay) }
+
+var fileDescriptorOverlay = []byte{
+ // 220 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4d, 0x29, 0xca, 0x2c,
+ 0x4b, 0x2d, 0x2a, 0xd6, 0x2f, 0xcf, 0xcc, 0x4b, 0xc9, 0x2f, 0x2f, 0xd6, 0xcf, 0x2f, 0x4b, 0x2d,
+ 0xca, 0x49, 0xac, 0x84, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xec, 0x50, 0xae, 0x94,
+ 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x58, 0x4c, 0x1f, 0xc4, 0x82, 0x48, 0x2b, 0x6d, 0x65, 0xe4, 0xe2,
+ 0x0a, 0x48, 0x4d, 0x2d, 0x0a, 0x4a, 0x4d, 0xce, 0x2f, 0x4a, 0x11, 0xd2, 0xe7, 0xe2, 0x4e, 0xcd,
+ 0x4b, 0x29, 0xc8, 0xcf, 0xcc, 0x2b, 0x89, 0xcf, 0x2c, 0x90, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x74,
+ 0xe2, 0x7b, 0x74, 0x4f, 0x9e, 0xcb, 0x15, 0x2a, 0xec, 0x19, 0x10, 0xc4, 0x05, 0x53, 0xe2, 0x59,
+ 0x20, 0x64, 0xc4, 0xc5, 0x03, 0xd7, 0x90, 0x9b, 0x98, 0x2c, 0xc1, 0x04, 0xd6, 0xc1, 0xff, 0xe8,
+ 0x9e, 0x3c, 0x37, 0x4c, 0x87, 0xaf, 0xa3, 0x73, 0x10, 0xdc, 0x54, 0xdf, 0xc4, 0x64, 0x21, 0x27,
+ 0x2e, 0xa1, 0x92, 0xd2, 0xbc, 0xbc, 0xd4, 0x9c, 0x78, 0x64, 0xbb, 0x98, 0xc1, 0x3a, 0x45, 0x1e,
+ 0xdd, 0x93, 0x17, 0x08, 0x01, 0xcb, 0x22, 0xd9, 0x28, 0x50, 0x82, 0x2a, 0x52, 0xe0, 0x24, 0x71,
+ 0xe3, 0xa1, 0x1c, 0xc3, 0x87, 0x87, 0x72, 0x8c, 0x0d, 0x8f, 0xe4, 0x18, 0x4f, 0x3c, 0x92, 0x63,
+ 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x24, 0x36, 0xb0, 0xc7, 0x8c, 0x01, 0x01,
+ 0x00, 0x00, 0xff, 0xff, 0xc0, 0x48, 0xd1, 0xc0, 0x20, 0x01, 0x00, 0x00,
+}
--- /dev/null
+syntax = "proto3";
+
+import "gogoproto/gogo.proto";
+
+package overlay;
+
+option (gogoproto.marshaler_all) = true;
+option (gogoproto.unmarshaler_all) = true;
+option (gogoproto.stringer_all) = true;
+option (gogoproto.gostring_all) = true;
+option (gogoproto.sizer_all) = true;
+option (gogoproto.goproto_stringer_all) = false;
+
+// PeerRecord defines the information corresponding to a peer
+// container in the overlay network.
+message PeerRecord {
+ // Endpoint IP is the IP of the container attachment on the
+ // given overlay network.
+ string endpoint_ip = 1 [(gogoproto.customname) = "EndpointIP"];
+ // Endpoint MAC is the mac address of the container attachment
+ // on the given overlay network.
+ string endpoint_mac = 2 [(gogoproto.customname) = "EndpointMAC"];
+ // Tunnel Endpoint IP defines the host IP for the host in
+ // which this container is running and can be reached by
+ // building a tunnel to that host IP.
+ string tunnel_endpoint_ip = 3 [(gogoproto.customname) = "TunnelEndpointIP"];
+}
\ No newline at end of file
--- /dev/null
+package overlay
+
+//go:generate protoc -I.:../../Godeps/_workspace/src/github.com/gogo/protobuf --gogo_out=import_path=github.com/docker/libnetwork/drivers/overlay,Mgogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto:. overlay.proto
+
+import (
+ "encoding/json"
+ "net"
+ "sync"
+
+ "github.com/Microsoft/hcsshim"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ networkType = "overlay"
+ vethPrefix = "veth"
+ vethLen = 7
+ secureOption = "encrypted"
+)
+
+type driver struct {
+ config map[string]interface{}
+ networks networkTable
+ store datastore.DataStore
+ localStore datastore.DataStore
+ once sync.Once
+ joinOnce sync.Once
+ sync.Mutex
+}
+
+// Init registers a new instance of overlay driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ c := driverapi.Capability{
+ DataScope: datastore.GlobalScope,
+ ConnectivityScope: datastore.GlobalScope,
+ }
+
+ d := &driver{
+ networks: networkTable{},
+ config: config,
+ }
+
+ if data, ok := config[netlabel.GlobalKVClient]; ok {
+ var err error
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+ }
+ d.store, err = datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return types.InternalErrorf("failed to initialize data store: %v", err)
+ }
+ }
+
+ if data, ok := config[netlabel.LocalKVClient]; ok {
+ var err error
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+ }
+ d.localStore, err = datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return types.InternalErrorf("failed to initialize local data store: %v", err)
+ }
+ }
+
+ d.restoreHNSNetworks()
+
+ return dc.RegisterDriver(networkType, d, c)
+}
+
+func (d *driver) restoreHNSNetworks() error {
+ logrus.Infof("Restoring existing overlay networks from HNS into docker")
+
+ hnsresponse, err := hcsshim.HNSListNetworkRequest("GET", "", "")
+ if err != nil {
+ return err
+ }
+
+ for _, v := range hnsresponse {
+ if v.Type != networkType {
+ continue
+ }
+
+ logrus.Infof("Restoring overlay network: %s", v.Name)
+ n := d.convertToOverlayNetwork(&v)
+ d.addNetwork(n)
+
+ //
+ // We assume that any network will be recreated on daemon restart
+ // and therefore don't restore hns endpoints for now
+ //
+ //n.restoreNetworkEndpoints()
+ }
+
+ return nil
+}
+
+func (d *driver) convertToOverlayNetwork(v *hcsshim.HNSNetwork) *network {
+ n := &network{
+ id: v.Name,
+ hnsID: v.Id,
+ driver: d,
+ endpoints: endpointTable{},
+ subnets: []*subnet{},
+ providerAddress: v.ManagementIP,
+ }
+
+ for _, hnsSubnet := range v.Subnets {
+ vsidPolicy := &hcsshim.VsidPolicy{}
+ for _, policy := range hnsSubnet.Policies {
+ if err := json.Unmarshal([]byte(policy), &vsidPolicy); err == nil && vsidPolicy.Type == "VSID" {
+ break
+ }
+ }
+
+ gwIP := net.ParseIP(hnsSubnet.GatewayAddress)
+ localsubnet := &subnet{
+ vni: uint32(vsidPolicy.VSID),
+ gwIP: &gwIP,
+ }
+
+ _, subnetIP, err := net.ParseCIDR(hnsSubnet.AddressPrefix)
+
+ if err != nil {
+ logrus.Errorf("Error parsing subnet address %s ", hnsSubnet.AddressPrefix)
+ continue
+ }
+
+ localsubnet.subnetIP = subnetIP
+
+ n.subnets = append(n.subnets, localsubnet)
+ }
+
+ return n
+}
+
+func (d *driver) Type() string {
+ return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return types.NotImplementedErrorf("not implemented")
+}
--- /dev/null
+package overlay
+
+import (
+ "fmt"
+ "net"
+
+ "encoding/json"
+
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+
+ "github.com/Microsoft/hcsshim"
+)
+
+const ovPeerTable = "overlay_peer_table"
+
+func (d *driver) peerAdd(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
+ peerMac net.HardwareAddr, vtep net.IP, updateDb bool) error {
+
+ logrus.Debugf("WINOVERLAY: Enter peerAdd for ca ip %s with ca mac %s", peerIP.String(), peerMac.String())
+
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return nil
+ }
+
+ if updateDb {
+ logrus.Info("WINOVERLAY: peerAdd: notifying HNS of the REMOTE endpoint")
+
+ hnsEndpoint := &hcsshim.HNSEndpoint{
+ Name: eid,
+ VirtualNetwork: n.hnsID,
+ MacAddress: peerMac.String(),
+ IPAddress: peerIP,
+ IsRemoteEndpoint: true,
+ }
+
+ paPolicy, err := json.Marshal(hcsshim.PaPolicy{
+ Type: "PA",
+ PA: vtep.String(),
+ })
+
+ if err != nil {
+ return err
+ }
+
+ hnsEndpoint.Policies = append(hnsEndpoint.Policies, paPolicy)
+
+ configurationb, err := json.Marshal(hnsEndpoint)
+ if err != nil {
+ return err
+ }
+
+ // Temp: We have to create an endpoint object to keep track of the HNS ID for
+ // this endpoint so that we can retrieve it later when the endpoint is deleted.
+ // This seems unnecessary when we already have dockers EID. See if we can pass
+ // the global EID to HNS to use as it's ID, rather than having each HNS assign
+ // it's own local ID for the endpoint
+
+ addr, err := types.ParseCIDR(peerIP.String() + "/32")
+ if err != nil {
+ return err
+ }
+
+ n.removeEndpointWithAddress(addr)
+ hnsresponse, err := endpointRequest("POST", "", string(configurationb))
+ if err != nil {
+ return err
+ }
+
+ ep := &endpoint{
+ id: eid,
+ nid: nid,
+ addr: addr,
+ mac: peerMac,
+ profileID: hnsresponse.Id,
+ remote: true,
+ }
+
+ n.addEndpoint(ep)
+ }
+
+ return nil
+}
+
+func (d *driver) peerDelete(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
+ peerMac net.HardwareAddr, vtep net.IP, updateDb bool) error {
+
+ logrus.Infof("WINOVERLAY: Enter peerDelete for endpoint %s and peer ip %s", eid, peerIP.String())
+
+ if err := validateID(nid, eid); err != nil {
+ return err
+ }
+
+ n := d.network(nid)
+ if n == nil {
+ return nil
+ }
+
+ ep := n.endpoint(eid)
+ if ep == nil {
+ return fmt.Errorf("could not find endpoint with id %s", eid)
+ }
+
+ if updateDb {
+ _, err := endpointRequest("DELETE", ep.profileID, "")
+ if err != nil {
+ return err
+ }
+
+ n.deleteEndpoint(eid)
+ }
+
+ return nil
+}
--- /dev/null
+// +build windows
+
+package windows
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/portmapper"
+ "github.com/docker/libnetwork/types"
+ "github.com/ishidawataru/sctp"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ maxAllocatePortAttempts = 10
+)
+
+// ErrUnsupportedAddressType is returned when the specified address type is not supported.
+type ErrUnsupportedAddressType string
+
+func (uat ErrUnsupportedAddressType) Error() string {
+ return fmt.Sprintf("unsupported address type: %s", string(uat))
+}
+
+// AllocatePorts allocates ports specified in bindings from the portMapper
+func AllocatePorts(portMapper *portmapper.PortMapper, bindings []types.PortBinding, containerIP net.IP) ([]types.PortBinding, error) {
+ bs := make([]types.PortBinding, 0, len(bindings))
+ for _, c := range bindings {
+ b := c.GetCopy()
+ if err := allocatePort(portMapper, &b, containerIP); err != nil {
+ // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
+ if cuErr := ReleasePorts(portMapper, bs); cuErr != nil {
+ logrus.Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr)
+ }
+ return nil, err
+ }
+ bs = append(bs, b)
+ }
+ return bs, nil
+}
+
+func allocatePort(portMapper *portmapper.PortMapper, bnd *types.PortBinding, containerIP net.IP) error {
+ var (
+ host net.Addr
+ err error
+ )
+
+ // Windows does not support a host ip for port bindings (this is validated in ConvertPortBindings()).
+ // If the HostIP is nil, force it to be 0.0.0.0 for use as the key in portMapper.
+ if bnd.HostIP == nil {
+ bnd.HostIP = net.IPv4zero
+ }
+
+ // Store the container interface address in the operational binding
+ bnd.IP = containerIP
+
+ // Adjust HostPortEnd if this is not a range.
+ if bnd.HostPortEnd == 0 {
+ bnd.HostPortEnd = bnd.HostPort
+ }
+
+ // Construct the container side transport address
+ container, err := bnd.ContainerAddr()
+ if err != nil {
+ return err
+ }
+
+ // Try up to maxAllocatePortAttempts times to get a port that's not already allocated.
+ for i := 0; i < maxAllocatePortAttempts; i++ {
+ if host, err = portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), false); err == nil {
+ break
+ }
+ // There is no point in immediately retrying to map an explicitly chosen port.
+ if bnd.HostPort != 0 {
+ logrus.Warnf("Failed to allocate and map port %d-%d: %s", bnd.HostPort, bnd.HostPortEnd, err)
+ break
+ }
+ logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
+ }
+ if err != nil {
+ return err
+ }
+
+ // Save the host port (regardless it was or not specified in the binding)
+ switch netAddr := host.(type) {
+ case *net.TCPAddr:
+ bnd.HostPort = uint16(host.(*net.TCPAddr).Port)
+ break
+ case *net.UDPAddr:
+ bnd.HostPort = uint16(host.(*net.UDPAddr).Port)
+ break
+ case *sctp.SCTPAddr:
+ bnd.HostPort = uint16(host.(*sctp.SCTPAddr).Port)
+ break
+ default:
+ // For completeness
+ return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
+ }
+ //Windows does not support host port ranges.
+ bnd.HostPortEnd = bnd.HostPort
+ return nil
+}
+
+// ReleasePorts releases ports specified in bindings from the portMapper
+func ReleasePorts(portMapper *portmapper.PortMapper, bindings []types.PortBinding) error {
+ var errorBuf bytes.Buffer
+
+ // Attempt to release all port bindings, do not stop on failure
+ for _, m := range bindings {
+ if err := releasePort(portMapper, m); err != nil {
+ errorBuf.WriteString(fmt.Sprintf("\ncould not release %v because of %v", m, err))
+ }
+ }
+
+ if errorBuf.Len() != 0 {
+ return errors.New(errorBuf.String())
+ }
+ return nil
+}
+
+func releasePort(portMapper *portmapper.PortMapper, bnd types.PortBinding) error {
+ // Construct the host side transport address
+ host, err := bnd.HostAddr()
+ if err != nil {
+ return err
+ }
+ return portMapper.Unmap(host)
+}
--- /dev/null
+// +build windows
+
+// Shim for the Host Network Service (HNS) to manage networking for
+// Windows Server containers and Hyper-V containers. This module
+// is a basic libnetwork driver that passes all the calls to HNS
+// It implements the 4 networking modes supported by HNS L2Bridge,
+// L2Tunnel, NAT and Transparent(DHCP)
+//
+// The network are stored in memory and docker daemon ensures discovering
+// and loading these networks on startup
+
+package windows
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/Microsoft/hcsshim"
+ "github.com/docker/docker/pkg/system"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/portmapper"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// networkConfiguration for network specific configuration
+type networkConfiguration struct {
+ ID string
+ Type string
+ Name string
+ HnsID string
+ RDID string
+ VLAN uint
+ VSID uint
+ DNSServers string
+ MacPools []hcsshim.MacPool
+ DNSSuffix string
+ SourceMac string
+ NetworkAdapterName string
+ dbIndex uint64
+ dbExists bool
+ DisableGatewayDNS bool
+ EnableOutboundNat bool
+ OutboundNatExceptions []string
+}
+
+// endpointConfiguration represents the user specified configuration for the sandbox endpoint
+type endpointOption struct {
+ MacAddress net.HardwareAddr
+ QosPolicies []types.QosPolicy
+ DNSServers []string
+ DisableDNS bool
+ DisableICC bool
+}
+
+// EndpointConnectivity stores the port bindings and exposed ports that the user has specified in epOptions.
+type EndpointConnectivity struct {
+ PortBindings []types.PortBinding
+ ExposedPorts []types.TransportPort
+}
+
+type hnsEndpoint struct {
+ id string
+ nid string
+ profileID string
+ Type string
+ //Note: Currently, the sandboxID is the same as the containerID since windows does
+ //not expose the sandboxID.
+ //In the future, windows will support a proper sandboxID that is different
+ //than the containerID.
+ //Therefore, we are using sandboxID now, so that we won't have to change this code
+ //when windows properly supports a sandboxID.
+ sandboxID string
+ macAddress net.HardwareAddr
+ epOption *endpointOption // User specified parameters
+ epConnectivity *EndpointConnectivity // User specified parameters
+ portMapping []types.PortBinding // Operation port bindings
+ addr *net.IPNet
+ gateway net.IP
+ dbIndex uint64
+ dbExists bool
+}
+
+type hnsNetwork struct {
+ id string
+ created bool
+ config *networkConfiguration
+ endpoints map[string]*hnsEndpoint // key: endpoint id
+ driver *driver // The network's driver
+ portMapper *portmapper.PortMapper
+ sync.Mutex
+}
+
+type driver struct {
+ name string
+ networks map[string]*hnsNetwork
+ store datastore.DataStore
+ sync.Mutex
+}
+
+const (
+ errNotFound = "HNS failed with error : The object identifier does not represent a valid object. "
+)
+
+// IsBuiltinLocalDriver validates if network-type is a builtin local-scoped driver
+func IsBuiltinLocalDriver(networkType string) bool {
+ if "l2bridge" == networkType || "l2tunnel" == networkType || "nat" == networkType || "ics" == networkType || "transparent" == networkType {
+ return true
+ }
+
+ return false
+}
+
+// New constructs a new bridge driver
+func newDriver(networkType string) *driver {
+ return &driver{name: networkType, networks: map[string]*hnsNetwork{}}
+}
+
+// GetInit returns an initializer for the given network type
+func GetInit(networkType string) func(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ return func(dc driverapi.DriverCallback, config map[string]interface{}) error {
+ if !IsBuiltinLocalDriver(networkType) {
+ return types.BadRequestErrorf("Network type not supported: %s", networkType)
+ }
+
+ d := newDriver(networkType)
+
+ err := d.initStore(config)
+ if err != nil {
+ return err
+ }
+
+ return dc.RegisterDriver(networkType, d, driverapi.Capability{
+ DataScope: datastore.LocalScope,
+ ConnectivityScope: datastore.LocalScope,
+ })
+ }
+}
+
+func (d *driver) getNetwork(id string) (*hnsNetwork, error) {
+ d.Lock()
+ defer d.Unlock()
+
+ if nw, ok := d.networks[id]; ok {
+ return nw, nil
+ }
+
+ return nil, types.NotFoundErrorf("network not found: %s", id)
+}
+
+func (n *hnsNetwork) getEndpoint(eid string) (*hnsEndpoint, error) {
+ n.Lock()
+ defer n.Unlock()
+
+ if ep, ok := n.endpoints[eid]; ok {
+ return ep, nil
+ }
+
+ return nil, types.NotFoundErrorf("Endpoint not found: %s", eid)
+}
+
+func (d *driver) parseNetworkOptions(id string, genericOptions map[string]string) (*networkConfiguration, error) {
+ config := &networkConfiguration{Type: d.name}
+
+ for label, value := range genericOptions {
+ switch label {
+ case NetworkName:
+ config.Name = value
+ case HNSID:
+ config.HnsID = value
+ case RoutingDomain:
+ config.RDID = value
+ case Interface:
+ config.NetworkAdapterName = value
+ case DNSSuffix:
+ config.DNSSuffix = value
+ case DNSServers:
+ config.DNSServers = value
+ case DisableGatewayDNS:
+ b, err := strconv.ParseBool(value)
+ if err != nil {
+ return nil, err
+ }
+ config.DisableGatewayDNS = b
+ case MacPool:
+ config.MacPools = make([]hcsshim.MacPool, 0)
+ s := strings.Split(value, ",")
+ if len(s)%2 != 0 {
+ return nil, types.BadRequestErrorf("Invalid mac pool. You must specify both a start range and an end range")
+ }
+ for i := 0; i < len(s)-1; i += 2 {
+ config.MacPools = append(config.MacPools, hcsshim.MacPool{
+ StartMacAddress: s[i],
+ EndMacAddress: s[i+1],
+ })
+ }
+ case VLAN:
+ vlan, err := strconv.ParseUint(value, 10, 32)
+ if err != nil {
+ return nil, err
+ }
+ config.VLAN = uint(vlan)
+ case VSID:
+ vsid, err := strconv.ParseUint(value, 10, 32)
+ if err != nil {
+ return nil, err
+ }
+ config.VSID = uint(vsid)
+ case EnableOutboundNat:
+ if system.GetOSVersion().Build <= 16236 {
+ return nil, fmt.Errorf("Invalid network option. OutboundNat is not supported on this OS version")
+ }
+ b, err := strconv.ParseBool(value)
+ if err != nil {
+ return nil, err
+ }
+ config.EnableOutboundNat = b
+ case OutboundNatExceptions:
+ s := strings.Split(value, ",")
+ config.OutboundNatExceptions = s
+ }
+ }
+
+ config.ID = id
+ config.Type = d.name
+ return config, nil
+}
+
+func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []driverapi.IPAMData) error {
+ if len(ipamV6Data) > 0 {
+ return types.ForbiddenErrorf("windowsshim driver doesn't support v6 subnets")
+ }
+
+ if len(ipamV4Data) == 0 {
+ return types.BadRequestErrorf("network %s requires ipv4 configuration", id)
+ }
+
+ return nil
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+func (d *driver) createNetwork(config *networkConfiguration) error {
+ network := &hnsNetwork{
+ id: config.ID,
+ endpoints: make(map[string]*hnsEndpoint),
+ config: config,
+ driver: d,
+ portMapper: portmapper.New(""),
+ }
+
+ d.Lock()
+ d.networks[config.ID] = network
+ d.Unlock()
+
+ return nil
+}
+
+// Create a new network
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ if _, err := d.getNetwork(id); err == nil {
+ return types.ForbiddenErrorf("network %s exists", id)
+ }
+
+ genData, ok := option[netlabel.GenericData].(map[string]string)
+ if !ok {
+ return fmt.Errorf("Unknown generic data option")
+ }
+
+ // Parse and validate the config. It should not conflict with existing networks' config
+ config, err := d.parseNetworkOptions(id, genData)
+ if err != nil {
+ return err
+ }
+
+ err = config.processIPAM(id, ipV4Data, ipV6Data)
+ if err != nil {
+ return err
+ }
+
+ err = d.createNetwork(config)
+
+ if err != nil {
+ return err
+ }
+
+ // A non blank hnsid indicates that the network was discovered
+ // from HNS. No need to call HNS if this network was discovered
+ // from HNS
+ if config.HnsID == "" {
+ subnets := []hcsshim.Subnet{}
+
+ for _, ipData := range ipV4Data {
+ subnet := hcsshim.Subnet{
+ AddressPrefix: ipData.Pool.String(),
+ }
+
+ if ipData.Gateway != nil {
+ subnet.GatewayAddress = ipData.Gateway.IP.String()
+ }
+
+ subnets = append(subnets, subnet)
+ }
+
+ network := &hcsshim.HNSNetwork{
+ Name: config.Name,
+ Type: d.name,
+ Subnets: subnets,
+ DNSServerList: config.DNSServers,
+ DNSSuffix: config.DNSSuffix,
+ MacPools: config.MacPools,
+ SourceMac: config.SourceMac,
+ NetworkAdapterName: config.NetworkAdapterName,
+ }
+
+ if config.VLAN != 0 {
+ vlanPolicy, err := json.Marshal(hcsshim.VlanPolicy{
+ Type: "VLAN",
+ VLAN: config.VLAN,
+ })
+
+ if err != nil {
+ return err
+ }
+ network.Policies = append(network.Policies, vlanPolicy)
+ }
+
+ if config.VSID != 0 {
+ vsidPolicy, err := json.Marshal(hcsshim.VsidPolicy{
+ Type: "VSID",
+ VSID: config.VSID,
+ })
+
+ if err != nil {
+ return err
+ }
+ network.Policies = append(network.Policies, vsidPolicy)
+ }
+
+ if network.Name == "" {
+ network.Name = id
+ }
+
+ configurationb, err := json.Marshal(network)
+ if err != nil {
+ return err
+ }
+
+ configuration := string(configurationb)
+ logrus.Debugf("HNSNetwork Request =%v Address Space=%v", configuration, subnets)
+
+ hnsresponse, err := hcsshim.HNSNetworkRequest("POST", "", configuration)
+ if err != nil {
+ return err
+ }
+
+ config.HnsID = hnsresponse.Id
+ genData[HNSID] = config.HnsID
+
+ } else {
+ // Delete any stale HNS endpoints for this network.
+ if endpoints, err := hcsshim.HNSListEndpointRequest(); err == nil {
+ for _, ep := range endpoints {
+ if ep.VirtualNetwork == config.HnsID {
+ logrus.Infof("Removing stale HNS endpoint %s", ep.Id)
+ _, err = hcsshim.HNSEndpointRequest("DELETE", ep.Id, "")
+ if err != nil {
+ logrus.Warnf("Error removing HNS endpoint %s", ep.Id)
+ }
+ }
+ }
+ } else {
+ logrus.Warnf("Error listing HNS endpoints for network %s", config.HnsID)
+ }
+ }
+
+ n, err := d.getNetwork(id)
+ if err != nil {
+ return err
+ }
+ n.created = true
+ return d.storeUpdate(config)
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+ n, err := d.getNetwork(nid)
+ if err != nil {
+ return types.InternalMaskableErrorf("%s", err)
+ }
+
+ n.Lock()
+ config := n.config
+ n.Unlock()
+
+ if n.created {
+ _, err = hcsshim.HNSNetworkRequest("DELETE", config.HnsID, "")
+ if err != nil && err.Error() != errNotFound {
+ return types.ForbiddenErrorf(err.Error())
+ }
+ }
+
+ d.Lock()
+ delete(d.networks, nid)
+ d.Unlock()
+
+ // delele endpoints belong to this network
+ for _, ep := range n.endpoints {
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Warnf("Failed to remove bridge endpoint %.7s from store: %v", ep.id, err)
+ }
+ }
+
+ return d.storeDelete(config)
+}
+
+func convertQosPolicies(qosPolicies []types.QosPolicy) ([]json.RawMessage, error) {
+ var qps []json.RawMessage
+
+ // Enumerate through the qos policies specified by the user and convert
+ // them into the internal structure matching the JSON blob that can be
+ // understood by the HCS.
+ for _, elem := range qosPolicies {
+ encodedPolicy, err := json.Marshal(hcsshim.QosPolicy{
+ Type: "QOS",
+ MaximumOutgoingBandwidthInBytes: elem.MaxEgressBandwidth,
+ })
+
+ if err != nil {
+ return nil, err
+ }
+ qps = append(qps, encodedPolicy)
+ }
+ return qps, nil
+}
+
+// ConvertPortBindings converts PortBindings to JSON for HNS request
+func ConvertPortBindings(portBindings []types.PortBinding) ([]json.RawMessage, error) {
+ var pbs []json.RawMessage
+
+ // Enumerate through the port bindings specified by the user and convert
+ // them into the internal structure matching the JSON blob that can be
+ // understood by the HCS.
+ for _, elem := range portBindings {
+ proto := strings.ToUpper(elem.Proto.String())
+ if proto != "TCP" && proto != "UDP" {
+ return nil, fmt.Errorf("invalid protocol %s", elem.Proto.String())
+ }
+
+ if elem.HostPort != elem.HostPortEnd {
+ return nil, fmt.Errorf("Windows does not support more than one host port in NAT settings")
+ }
+
+ if len(elem.HostIP) != 0 && !elem.HostIP.IsUnspecified() {
+ return nil, fmt.Errorf("Windows does not support host IP addresses in NAT settings")
+ }
+
+ encodedPolicy, err := json.Marshal(hcsshim.NatPolicy{
+ Type: "NAT",
+ ExternalPort: elem.HostPort,
+ InternalPort: elem.Port,
+ Protocol: elem.Proto.String(),
+ })
+
+ if err != nil {
+ return nil, err
+ }
+ pbs = append(pbs, encodedPolicy)
+ }
+ return pbs, nil
+}
+
+// ParsePortBindingPolicies parses HNS endpoint response message to PortBindings
+func ParsePortBindingPolicies(policies []json.RawMessage) ([]types.PortBinding, error) {
+ var bindings []types.PortBinding
+ hcsPolicy := &hcsshim.NatPolicy{}
+
+ for _, elem := range policies {
+
+ if err := json.Unmarshal([]byte(elem), &hcsPolicy); err != nil || hcsPolicy.Type != "NAT" {
+ continue
+ }
+
+ binding := types.PortBinding{
+ HostPort: hcsPolicy.ExternalPort,
+ HostPortEnd: hcsPolicy.ExternalPort,
+ Port: hcsPolicy.InternalPort,
+ Proto: types.ParseProtocol(hcsPolicy.Protocol),
+ HostIP: net.IPv4(0, 0, 0, 0),
+ }
+
+ bindings = append(bindings, binding)
+ }
+
+ return bindings, nil
+}
+
+func parseEndpointOptions(epOptions map[string]interface{}) (*endpointOption, error) {
+ if epOptions == nil {
+ return nil, nil
+ }
+
+ ec := &endpointOption{}
+
+ if opt, ok := epOptions[netlabel.MacAddress]; ok {
+ if mac, ok := opt.(net.HardwareAddr); ok {
+ ec.MacAddress = mac
+ } else {
+ return nil, fmt.Errorf("Invalid endpoint configuration")
+ }
+ }
+
+ if opt, ok := epOptions[QosPolicies]; ok {
+ if policies, ok := opt.([]types.QosPolicy); ok {
+ ec.QosPolicies = policies
+ } else {
+ return nil, fmt.Errorf("Invalid endpoint configuration")
+ }
+ }
+
+ if opt, ok := epOptions[netlabel.DNSServers]; ok {
+ if dns, ok := opt.([]string); ok {
+ ec.DNSServers = dns
+ } else {
+ return nil, fmt.Errorf("Invalid endpoint configuration")
+ }
+ }
+
+ if opt, ok := epOptions[DisableICC]; ok {
+ if disableICC, ok := opt.(bool); ok {
+ ec.DisableICC = disableICC
+ } else {
+ return nil, fmt.Errorf("Invalid endpoint configuration")
+ }
+ }
+
+ if opt, ok := epOptions[DisableDNS]; ok {
+ if disableDNS, ok := opt.(bool); ok {
+ ec.DisableDNS = disableDNS
+ } else {
+ return nil, fmt.Errorf("Invalid endpoint configuration")
+ }
+ }
+
+ return ec, nil
+}
+
+// ParseEndpointConnectivity parses options passed to CreateEndpoint, specifically port bindings, and store in a endpointConnectivity object.
+func ParseEndpointConnectivity(epOptions map[string]interface{}) (*EndpointConnectivity, error) {
+ if epOptions == nil {
+ return nil, nil
+ }
+
+ ec := &EndpointConnectivity{}
+
+ if opt, ok := epOptions[netlabel.PortMap]; ok {
+ if bs, ok := opt.([]types.PortBinding); ok {
+ ec.PortBindings = bs
+ } else {
+ return nil, fmt.Errorf("Invalid endpoint configuration")
+ }
+ }
+
+ if opt, ok := epOptions[netlabel.ExposedPorts]; ok {
+ if ports, ok := opt.([]types.TransportPort); ok {
+ ec.ExposedPorts = ports
+ } else {
+ return nil, fmt.Errorf("Invalid endpoint configuration")
+ }
+ }
+ return ec, nil
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+ n, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+ }
+
+ // Check if endpoint id is good and retrieve corresponding endpoint
+ ep, err := n.getEndpoint(eid)
+ if err == nil && ep != nil {
+ return driverapi.ErrEndpointExists(eid)
+ }
+
+ endpointStruct := &hcsshim.HNSEndpoint{
+ VirtualNetwork: n.config.HnsID,
+ }
+
+ epOption, err := parseEndpointOptions(epOptions)
+ if err != nil {
+ return err
+ }
+ epConnectivity, err := ParseEndpointConnectivity(epOptions)
+ if err != nil {
+ return err
+ }
+
+ macAddress := ifInfo.MacAddress()
+ // Use the macaddress if it was provided
+ if macAddress != nil {
+ endpointStruct.MacAddress = strings.Replace(macAddress.String(), ":", "-", -1)
+ }
+
+ portMapping := epConnectivity.PortBindings
+
+ if n.config.Type == "l2bridge" || n.config.Type == "l2tunnel" {
+ ip := net.IPv4(0, 0, 0, 0)
+ if ifInfo.Address() != nil {
+ ip = ifInfo.Address().IP
+ }
+
+ portMapping, err = AllocatePorts(n.portMapper, portMapping, ip)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil {
+ ReleasePorts(n.portMapper, portMapping)
+ }
+ }()
+ }
+
+ endpointStruct.Policies, err = ConvertPortBindings(portMapping)
+ if err != nil {
+ return err
+ }
+
+ qosPolicies, err := convertQosPolicies(epOption.QosPolicies)
+ if err != nil {
+ return err
+ }
+ endpointStruct.Policies = append(endpointStruct.Policies, qosPolicies...)
+
+ if ifInfo.Address() != nil {
+ endpointStruct.IPAddress = ifInfo.Address().IP
+ }
+
+ endpointStruct.DNSServerList = strings.Join(epOption.DNSServers, ",")
+
+ // overwrite the ep DisableDNS option if DisableGatewayDNS was set to true during the network creation option
+ if n.config.DisableGatewayDNS {
+ logrus.Debugf("n.config.DisableGatewayDNS[%v] overwrites epOption.DisableDNS[%v]", n.config.DisableGatewayDNS, epOption.DisableDNS)
+ epOption.DisableDNS = n.config.DisableGatewayDNS
+ }
+
+ if n.driver.name == "nat" && !epOption.DisableDNS {
+ logrus.Debugf("endpointStruct.EnableInternalDNS =[%v]", endpointStruct.EnableInternalDNS)
+ endpointStruct.EnableInternalDNS = true
+ }
+
+ endpointStruct.DisableICC = epOption.DisableICC
+
+ // Inherit OutboundNat policy from the network
+ if n.config.EnableOutboundNat {
+ outboundNatPolicy, err := json.Marshal(hcsshim.OutboundNatPolicy{
+ Policy: hcsshim.Policy{Type: hcsshim.OutboundNat},
+ Exceptions: n.config.OutboundNatExceptions,
+ })
+
+ if err != nil {
+ return err
+ }
+ endpointStruct.Policies = append(endpointStruct.Policies, outboundNatPolicy)
+ }
+
+ configurationb, err := json.Marshal(endpointStruct)
+ if err != nil {
+ return err
+ }
+
+ hnsresponse, err := hcsshim.HNSEndpointRequest("POST", "", string(configurationb))
+ if err != nil {
+ return err
+ }
+
+ mac, err := net.ParseMAC(hnsresponse.MacAddress)
+ if err != nil {
+ return err
+ }
+
+ // TODO For now the ip mask is not in the info generated by HNS
+ endpoint := &hnsEndpoint{
+ id: eid,
+ nid: n.id,
+ Type: d.name,
+ addr: &net.IPNet{IP: hnsresponse.IPAddress, Mask: hnsresponse.IPAddress.DefaultMask()},
+ macAddress: mac,
+ }
+
+ if hnsresponse.GatewayAddress != "" {
+ endpoint.gateway = net.ParseIP(hnsresponse.GatewayAddress)
+ }
+
+ endpoint.profileID = hnsresponse.Id
+ endpoint.epConnectivity = epConnectivity
+ endpoint.epOption = epOption
+ endpoint.portMapping, err = ParsePortBindingPolicies(hnsresponse.Policies)
+
+ if err != nil {
+ hcsshim.HNSEndpointRequest("DELETE", hnsresponse.Id, "")
+ return err
+ }
+
+ n.Lock()
+ n.endpoints[eid] = endpoint
+ n.Unlock()
+
+ if ifInfo.Address() == nil {
+ ifInfo.SetIPAddress(endpoint.addr)
+ }
+
+ if macAddress == nil {
+ ifInfo.SetMacAddress(endpoint.macAddress)
+ }
+
+ if err = d.storeUpdate(endpoint); err != nil {
+ logrus.Errorf("Failed to save endpoint %.7s to store: %v", endpoint.id, err)
+ }
+
+ return nil
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+ n, err := d.getNetwork(nid)
+ if err != nil {
+ return types.InternalMaskableErrorf("%s", err)
+ }
+
+ ep, err := n.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+
+ if n.config.Type == "l2bridge" || n.config.Type == "l2tunnel" {
+ ReleasePorts(n.portMapper, ep.portMapping)
+ }
+
+ n.Lock()
+ delete(n.endpoints, eid)
+ n.Unlock()
+
+ _, err = hcsshim.HNSEndpointRequest("DELETE", ep.profileID, "")
+ if err != nil && err.Error() != errNotFound {
+ return err
+ }
+
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Warnf("Failed to remove bridge endpoint %.7s from store: %v", ep.id, err)
+ }
+ return nil
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return nil, err
+ }
+
+ ep, err := network.getEndpoint(eid)
+ if err != nil {
+ return nil, err
+ }
+
+ data := make(map[string]interface{}, 1)
+ if network.driver.name == "nat" {
+ data["AllowUnqualifiedDNSQuery"] = true
+ }
+
+ data["hnsid"] = ep.profileID
+ if ep.epConnectivity.ExposedPorts != nil {
+ // Return a copy of the config data
+ epc := make([]types.TransportPort, 0, len(ep.epConnectivity.ExposedPorts))
+ for _, tp := range ep.epConnectivity.ExposedPorts {
+ epc = append(epc, tp.GetCopy())
+ }
+ data[netlabel.ExposedPorts] = epc
+ }
+
+ if ep.portMapping != nil {
+ // Return a copy of the operational data
+ pmc := make([]types.PortBinding, 0, len(ep.portMapping))
+ for _, pm := range ep.portMapping {
+ pmc = append(pmc, pm.GetCopy())
+ }
+ data[netlabel.PortMap] = pmc
+ }
+
+ if len(ep.macAddress) != 0 {
+ data[netlabel.MacAddress] = ep.macAddress
+ }
+ return data, nil
+}
+
+// Join method is invoked when a Sandbox is attached to an endpoint.
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+ }
+
+ // Ensure that the endpoint exists
+ endpoint, err := network.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+
+ err = jinfo.SetGateway(endpoint.gateway)
+ if err != nil {
+ return err
+ }
+
+ endpoint.sandboxID = sboxKey
+
+ err = hcsshim.HotAttachEndpoint(endpoint.sandboxID, endpoint.profileID)
+ if err != nil {
+ // If container doesn't exists in hcs, do not throw error for hot add/remove
+ if err != hcsshim.ErrComputeSystemDoesNotExist {
+ return err
+ }
+ }
+
+ jinfo.DisableGatewayService()
+ return nil
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return types.InternalMaskableErrorf("%s", err)
+ }
+
+ // Ensure that the endpoint exists
+ endpoint, err := network.getEndpoint(eid)
+ if err != nil {
+ return err
+ }
+
+ err = hcsshim.HotDetachEndpoint(endpoint.sandboxID, endpoint.profileID)
+ if err != nil {
+ // If container doesn't exists in hcs, do not throw error for hot add/remove
+ if err != hcsshim.ErrComputeSystemDoesNotExist {
+ return err
+ }
+ }
+ return nil
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Type() string {
+ return d.name
+}
+
+func (d *driver) IsBuiltIn() bool {
+ return true
+}
+
+// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
--- /dev/null
+// +build windows
+
+package windows
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ windowsPrefix = "windows"
+ windowsEndpointPrefix = "windows-endpoint"
+)
+
+func (d *driver) initStore(option map[string]interface{}) error {
+ if data, ok := option[netlabel.LocalKVClient]; ok {
+ var err error
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+ }
+ d.store, err = datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return types.InternalErrorf("windows driver failed to initialize data store: %v", err)
+ }
+
+ err = d.populateNetworks()
+ if err != nil {
+ return err
+ }
+
+ err = d.populateEndpoints()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (d *driver) populateNetworks() error {
+ kvol, err := d.store.List(datastore.Key(windowsPrefix), &networkConfiguration{Type: d.name})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return fmt.Errorf("failed to get windows network configurations from store: %v", err)
+ }
+
+ // It's normal for network configuration state to be empty. Just return.
+ if err == datastore.ErrKeyNotFound {
+ return nil
+ }
+
+ for _, kvo := range kvol {
+ ncfg := kvo.(*networkConfiguration)
+ if ncfg.Type != d.name {
+ continue
+ }
+ if err = d.createNetwork(ncfg); err != nil {
+ logrus.Warnf("could not create windows network for id %s hnsid %s while booting up from persistent state: %v", ncfg.ID, ncfg.HnsID, err)
+ }
+ logrus.Debugf("Network %v (%.7s) restored", d.name, ncfg.ID)
+ }
+
+ return nil
+}
+
+func (d *driver) populateEndpoints() error {
+ kvol, err := d.store.List(datastore.Key(windowsEndpointPrefix), &hnsEndpoint{Type: d.name})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return fmt.Errorf("failed to get endpoints from store: %v", err)
+ }
+
+ if err == datastore.ErrKeyNotFound {
+ return nil
+ }
+
+ for _, kvo := range kvol {
+ ep := kvo.(*hnsEndpoint)
+ if ep.Type != d.name {
+ continue
+ }
+ n, ok := d.networks[ep.nid]
+ if !ok {
+ logrus.Debugf("Network (%.7s) not found for restored endpoint (%.7s)", ep.nid, ep.id)
+ logrus.Debugf("Deleting stale endpoint (%.7s) from store", ep.id)
+ if err := d.storeDelete(ep); err != nil {
+ logrus.Debugf("Failed to delete stale endpoint (%.7s) from store", ep.id)
+ }
+ continue
+ }
+ n.endpoints[ep.id] = ep
+ logrus.Debugf("Endpoint (%.7s) restored to network (%.7s)", ep.id, ep.nid)
+ }
+
+ return nil
+}
+
+func (d *driver) storeUpdate(kvObject datastore.KVObject) error {
+ if d.store == nil {
+ logrus.Warnf("store not initialized. kv object %s is not added to the store", datastore.Key(kvObject.Key()...))
+ return nil
+ }
+
+ if err := d.store.PutObjectAtomic(kvObject); err != nil {
+ return fmt.Errorf("failed to update store for object type %T: %v", kvObject, err)
+ }
+
+ return nil
+}
+
+func (d *driver) storeDelete(kvObject datastore.KVObject) error {
+ if d.store == nil {
+ logrus.Debugf("store not initialized. kv object %s is not deleted from store", datastore.Key(kvObject.Key()...))
+ return nil
+ }
+
+retry:
+ if err := d.store.DeleteObjectAtomic(kvObject); err != nil {
+ if err == datastore.ErrKeyModified {
+ if err := d.store.GetObject(datastore.Key(kvObject.Key()...), kvObject); err != nil {
+ return fmt.Errorf("could not update the kvobject to latest when trying to delete: %v", err)
+ }
+ goto retry
+ }
+ return err
+ }
+
+ return nil
+}
+
+func (ncfg *networkConfiguration) MarshalJSON() ([]byte, error) {
+ nMap := make(map[string]interface{})
+
+ nMap["ID"] = ncfg.ID
+ nMap["Type"] = ncfg.Type
+ nMap["Name"] = ncfg.Name
+ nMap["HnsID"] = ncfg.HnsID
+ nMap["VLAN"] = ncfg.VLAN
+ nMap["VSID"] = ncfg.VSID
+ nMap["DNSServers"] = ncfg.DNSServers
+ nMap["DNSSuffix"] = ncfg.DNSSuffix
+ nMap["SourceMac"] = ncfg.SourceMac
+ nMap["NetworkAdapterName"] = ncfg.NetworkAdapterName
+
+ return json.Marshal(nMap)
+}
+
+func (ncfg *networkConfiguration) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ nMap map[string]interface{}
+ )
+
+ if err = json.Unmarshal(b, &nMap); err != nil {
+ return err
+ }
+
+ ncfg.ID = nMap["ID"].(string)
+ ncfg.Type = nMap["Type"].(string)
+ ncfg.Name = nMap["Name"].(string)
+ ncfg.HnsID = nMap["HnsID"].(string)
+ ncfg.VLAN = uint(nMap["VLAN"].(float64))
+ ncfg.VSID = uint(nMap["VSID"].(float64))
+ ncfg.DNSServers = nMap["DNSServers"].(string)
+ ncfg.DNSSuffix = nMap["DNSSuffix"].(string)
+ ncfg.SourceMac = nMap["SourceMac"].(string)
+ ncfg.NetworkAdapterName = nMap["NetworkAdapterName"].(string)
+ return nil
+}
+
+func (ncfg *networkConfiguration) Key() []string {
+ return []string{windowsPrefix + ncfg.Type, ncfg.ID}
+}
+
+func (ncfg *networkConfiguration) KeyPrefix() []string {
+ return []string{windowsPrefix + ncfg.Type}
+}
+
+func (ncfg *networkConfiguration) Value() []byte {
+ b, err := json.Marshal(ncfg)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (ncfg *networkConfiguration) SetValue(value []byte) error {
+ return json.Unmarshal(value, ncfg)
+}
+
+func (ncfg *networkConfiguration) Index() uint64 {
+ return ncfg.dbIndex
+}
+
+func (ncfg *networkConfiguration) SetIndex(index uint64) {
+ ncfg.dbIndex = index
+ ncfg.dbExists = true
+}
+
+func (ncfg *networkConfiguration) Exists() bool {
+ return ncfg.dbExists
+}
+
+func (ncfg *networkConfiguration) Skip() bool {
+ return false
+}
+
+func (ncfg *networkConfiguration) New() datastore.KVObject {
+ return &networkConfiguration{Type: ncfg.Type}
+}
+
+func (ncfg *networkConfiguration) CopyTo(o datastore.KVObject) error {
+ dstNcfg := o.(*networkConfiguration)
+ *dstNcfg = *ncfg
+ return nil
+}
+
+func (ncfg *networkConfiguration) DataScope() string {
+ return datastore.LocalScope
+}
+
+func (ep *hnsEndpoint) MarshalJSON() ([]byte, error) {
+ epMap := make(map[string]interface{})
+ epMap["id"] = ep.id
+ epMap["nid"] = ep.nid
+ epMap["Type"] = ep.Type
+ epMap["profileID"] = ep.profileID
+ epMap["MacAddress"] = ep.macAddress.String()
+ if ep.addr.IP != nil {
+ epMap["Addr"] = ep.addr.String()
+ }
+ if ep.gateway != nil {
+ epMap["gateway"] = ep.gateway.String()
+ }
+ epMap["epOption"] = ep.epOption
+ epMap["epConnectivity"] = ep.epConnectivity
+ epMap["PortMapping"] = ep.portMapping
+
+ return json.Marshal(epMap)
+}
+
+func (ep *hnsEndpoint) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ epMap map[string]interface{}
+ )
+
+ if err = json.Unmarshal(b, &epMap); err != nil {
+ return fmt.Errorf("Failed to unmarshal to endpoint: %v", err)
+ }
+ if v, ok := epMap["MacAddress"]; ok {
+ if ep.macAddress, err = net.ParseMAC(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode endpoint MAC address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ if v, ok := epMap["Addr"]; ok {
+ if ep.addr, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode endpoint IPv4 address (%s) after json unmarshal: %v", v.(string), err)
+ }
+ }
+ if v, ok := epMap["gateway"]; ok {
+ ep.gateway = net.ParseIP(v.(string))
+ }
+ ep.id = epMap["id"].(string)
+ ep.Type = epMap["Type"].(string)
+ ep.nid = epMap["nid"].(string)
+ ep.profileID = epMap["profileID"].(string)
+ d, _ := json.Marshal(epMap["epOption"])
+ if err := json.Unmarshal(d, &ep.epOption); err != nil {
+ logrus.Warnf("Failed to decode endpoint container config %v", err)
+ }
+ d, _ = json.Marshal(epMap["epConnectivity"])
+ if err := json.Unmarshal(d, &ep.epConnectivity); err != nil {
+ logrus.Warnf("Failed to decode endpoint external connectivity configuration %v", err)
+ }
+ d, _ = json.Marshal(epMap["PortMapping"])
+ if err := json.Unmarshal(d, &ep.portMapping); err != nil {
+ logrus.Warnf("Failed to decode endpoint port mapping %v", err)
+ }
+
+ return nil
+}
+
+func (ep *hnsEndpoint) Key() []string {
+ return []string{windowsEndpointPrefix + ep.Type, ep.id}
+}
+
+func (ep *hnsEndpoint) KeyPrefix() []string {
+ return []string{windowsEndpointPrefix + ep.Type}
+}
+
+func (ep *hnsEndpoint) Value() []byte {
+ b, err := json.Marshal(ep)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (ep *hnsEndpoint) SetValue(value []byte) error {
+ return json.Unmarshal(value, ep)
+}
+
+func (ep *hnsEndpoint) Index() uint64 {
+ return ep.dbIndex
+}
+
+func (ep *hnsEndpoint) SetIndex(index uint64) {
+ ep.dbIndex = index
+ ep.dbExists = true
+}
+
+func (ep *hnsEndpoint) Exists() bool {
+ return ep.dbExists
+}
+
+func (ep *hnsEndpoint) Skip() bool {
+ return false
+}
+
+func (ep *hnsEndpoint) New() datastore.KVObject {
+ return &hnsEndpoint{Type: ep.Type}
+}
+
+func (ep *hnsEndpoint) CopyTo(o datastore.KVObject) error {
+ dstEp := o.(*hnsEndpoint)
+ *dstEp = *ep
+ return nil
+}
+
+func (ep *hnsEndpoint) DataScope() string {
+ return datastore.LocalScope
+}
--- /dev/null
+// +build windows
+
+package windows
+
+import (
+ "net"
+ "testing"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/types"
+)
+
+func testNetwork(networkType string, t *testing.T) {
+ d := newDriver(networkType)
+ bnw, _ := types.ParseCIDR("172.16.0.0/24")
+ br, _ := types.ParseCIDR("172.16.0.1/16")
+
+ netOption := make(map[string]interface{})
+ networkOptions := map[string]string{
+ NetworkName: "TestNetwork",
+ }
+
+ netOption[netlabel.GenericData] = networkOptions
+ ipdList := []driverapi.IPAMData{
+ {
+ Pool: bnw,
+ Gateway: br,
+ },
+ }
+
+ err := d.CreateNetwork("dummy", netOption, nil, ipdList, nil)
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+ defer func() {
+ err = d.DeleteNetwork("dummy")
+ if err != nil {
+ t.Fatalf("Failed to create bridge: %v", err)
+ }
+ }()
+
+ epOptions := make(map[string]interface{})
+ te := &testEndpoint{}
+ err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
+ if err != nil {
+ t.Fatalf("Failed to create an endpoint : %s", err.Error())
+ }
+
+ err = d.DeleteEndpoint("dummy", "ep1")
+ if err != nil {
+ t.Fatalf("Failed to delete an endpoint : %s", err.Error())
+ }
+}
+
+func TestNAT(t *testing.T) {
+ testNetwork("nat", t)
+}
+
+func TestTransparent(t *testing.T) {
+ testNetwork("transparent", t)
+}
+
+type testEndpoint struct {
+ t *testing.T
+ src string
+ dst string
+ address string
+ macAddress string
+ gateway string
+ disableGatewayService bool
+}
+
+func (test *testEndpoint) Interface() driverapi.InterfaceInfo {
+ return test
+}
+
+func (test *testEndpoint) Address() *net.IPNet {
+ if test.address == "" {
+ return nil
+ }
+ nw, _ := types.ParseCIDR(test.address)
+ return nw
+}
+
+func (test *testEndpoint) AddressIPv6() *net.IPNet {
+ return nil
+}
+
+func (test *testEndpoint) MacAddress() net.HardwareAddr {
+ if test.macAddress == "" {
+ return nil
+ }
+ mac, _ := net.ParseMAC(test.macAddress)
+ return mac
+}
+
+func (test *testEndpoint) SetMacAddress(mac net.HardwareAddr) error {
+ if test.macAddress != "" {
+ return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", test.macAddress, mac)
+ }
+
+ if mac == nil {
+ return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
+ }
+ test.macAddress = mac.String()
+ return nil
+}
+
+func (test *testEndpoint) SetIPAddress(address *net.IPNet) error {
+ if address.IP == nil {
+ return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
+ }
+
+ test.address = address.String()
+ return nil
+}
+
+func (test *testEndpoint) InterfaceName() driverapi.InterfaceNameInfo {
+ return test
+}
+
+func (test *testEndpoint) SetGateway(ipv4 net.IP) error {
+ return nil
+}
+
+func (test *testEndpoint) SetGatewayIPv6(ipv6 net.IP) error {
+ return nil
+}
+
+func (test *testEndpoint) SetNames(src string, dst string) error {
+ return nil
+}
+
+func (test *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP) error {
+ return nil
+}
+
+func (test *testEndpoint) DisableGatewayService() {
+ test.disableGatewayService = true
+}
--- /dev/null
+package libnetwork
+
+import "github.com/docker/libnetwork/drivers/ipvlan"
+
+func additionalDrivers() []initializer {
+ return []initializer{
+ {ipvlan.Init, "ipvlan"},
+ }
+}
--- /dev/null
+package libnetwork
+
+import (
+ "github.com/docker/libnetwork/drivers/null"
+ "github.com/docker/libnetwork/drivers/remote"
+)
+
+func getInitializers(experimental bool) []initializer {
+ return []initializer{
+ {null.Init, "null"},
+ {remote.Init, "remote"},
+ }
+}
--- /dev/null
+package libnetwork
+
+import (
+ "github.com/docker/libnetwork/drvregistry"
+ "github.com/docker/libnetwork/ipamapi"
+ builtinIpam "github.com/docker/libnetwork/ipams/builtin"
+ nullIpam "github.com/docker/libnetwork/ipams/null"
+ remoteIpam "github.com/docker/libnetwork/ipams/remote"
+ "github.com/docker/libnetwork/ipamutils"
+)
+
+func initIPAMDrivers(r *drvregistry.DrvRegistry, lDs, gDs interface{}, addressPool []*ipamutils.NetworkToSplit) error {
+ builtinIpam.SetDefaultIPAddressPool(addressPool)
+ for _, fn := range [](func(ipamapi.Callback, interface{}, interface{}) error){
+ builtinIpam.Init,
+ remoteIpam.Init,
+ nullIpam.Init,
+ } {
+ if err := fn(r, lDs, gDs); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
--- /dev/null
+package libnetwork
+
+import (
+ "github.com/docker/libnetwork/drivers/bridge"
+ "github.com/docker/libnetwork/drivers/host"
+ "github.com/docker/libnetwork/drivers/macvlan"
+ "github.com/docker/libnetwork/drivers/null"
+ "github.com/docker/libnetwork/drivers/overlay"
+ "github.com/docker/libnetwork/drivers/remote"
+)
+
+func getInitializers(experimental bool) []initializer {
+ in := []initializer{
+ {bridge.Init, "bridge"},
+ {host.Init, "host"},
+ {macvlan.Init, "macvlan"},
+ {null.Init, "null"},
+ {remote.Init, "remote"},
+ {overlay.Init, "overlay"},
+ }
+
+ if experimental {
+ in = append(in, additionalDrivers()...)
+ }
+ return in
+}
--- /dev/null
+package libnetwork
+
+import (
+ "github.com/docker/libnetwork/drivers/null"
+ "github.com/docker/libnetwork/drivers/remote"
+ "github.com/docker/libnetwork/drivers/windows"
+ "github.com/docker/libnetwork/drivers/windows/overlay"
+)
+
+func getInitializers(experimental bool) []initializer {
+ return []initializer{
+ {null.Init, "null"},
+ {overlay.Init, "overlay"},
+ {remote.Init, "remote"},
+ {windows.GetInit("transparent"), "transparent"},
+ {windows.GetInit("l2bridge"), "l2bridge"},
+ {windows.GetInit("l2tunnel"), "l2tunnel"},
+ {windows.GetInit("nat"), "nat"},
+ {windows.GetInit("ics"), "ics"},
+ }
+}
--- /dev/null
+package drvregistry
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/types"
+)
+
+type driverData struct {
+ driver driverapi.Driver
+ capability driverapi.Capability
+}
+
+type ipamData struct {
+ driver ipamapi.Ipam
+ capability *ipamapi.Capability
+ // default address spaces are provided by ipam driver at registration time
+ defaultLocalAddressSpace, defaultGlobalAddressSpace string
+}
+
+type driverTable map[string]*driverData
+type ipamTable map[string]*ipamData
+
+// DrvRegistry holds the registry of all network drivers and IPAM drivers that it knows about.
+type DrvRegistry struct {
+ sync.Mutex
+ drivers driverTable
+ ipamDrivers ipamTable
+ dfn DriverNotifyFunc
+ ifn IPAMNotifyFunc
+ pluginGetter plugingetter.PluginGetter
+}
+
+// Functors definition
+
+// InitFunc defines the driver initialization function signature.
+type InitFunc func(driverapi.DriverCallback, map[string]interface{}) error
+
+// IPAMWalkFunc defines the IPAM driver table walker function signature.
+type IPAMWalkFunc func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool
+
+// DriverWalkFunc defines the network driver table walker function signature.
+type DriverWalkFunc func(name string, driver driverapi.Driver, capability driverapi.Capability) bool
+
+// IPAMNotifyFunc defines the notify function signature when a new IPAM driver gets registered.
+type IPAMNotifyFunc func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) error
+
+// DriverNotifyFunc defines the notify function signature when a new network driver gets registered.
+type DriverNotifyFunc func(name string, driver driverapi.Driver, capability driverapi.Capability) error
+
+// New returns a new driver registry handle.
+func New(lDs, gDs interface{}, dfn DriverNotifyFunc, ifn IPAMNotifyFunc, pg plugingetter.PluginGetter) (*DrvRegistry, error) {
+ r := &DrvRegistry{
+ drivers: make(driverTable),
+ ipamDrivers: make(ipamTable),
+ dfn: dfn,
+ ifn: ifn,
+ pluginGetter: pg,
+ }
+
+ return r, nil
+}
+
+// AddDriver adds a network driver to the registry.
+func (r *DrvRegistry) AddDriver(ntype string, fn InitFunc, config map[string]interface{}) error {
+ return fn(r, config)
+}
+
+// WalkIPAMs walks the IPAM drivers registered in the registry and invokes the passed walk function and each one of them.
+func (r *DrvRegistry) WalkIPAMs(ifn IPAMWalkFunc) {
+ type ipamVal struct {
+ name string
+ data *ipamData
+ }
+
+ r.Lock()
+ ivl := make([]ipamVal, 0, len(r.ipamDrivers))
+ for k, v := range r.ipamDrivers {
+ ivl = append(ivl, ipamVal{name: k, data: v})
+ }
+ r.Unlock()
+
+ for _, iv := range ivl {
+ if ifn(iv.name, iv.data.driver, iv.data.capability) {
+ break
+ }
+ }
+}
+
+// WalkDrivers walks the network drivers registered in the registry and invokes the passed walk function and each one of them.
+func (r *DrvRegistry) WalkDrivers(dfn DriverWalkFunc) {
+ type driverVal struct {
+ name string
+ data *driverData
+ }
+
+ r.Lock()
+ dvl := make([]driverVal, 0, len(r.drivers))
+ for k, v := range r.drivers {
+ dvl = append(dvl, driverVal{name: k, data: v})
+ }
+ r.Unlock()
+
+ for _, dv := range dvl {
+ if dfn(dv.name, dv.data.driver, dv.data.capability) {
+ break
+ }
+ }
+}
+
+// Driver returns the actual network driver instance and its capability which registered with the passed name.
+func (r *DrvRegistry) Driver(name string) (driverapi.Driver, *driverapi.Capability) {
+ r.Lock()
+ defer r.Unlock()
+
+ d, ok := r.drivers[name]
+ if !ok {
+ return nil, nil
+ }
+
+ return d.driver, &d.capability
+}
+
+// IPAM returns the actual IPAM driver instance and its capability which registered with the passed name.
+func (r *DrvRegistry) IPAM(name string) (ipamapi.Ipam, *ipamapi.Capability) {
+ r.Lock()
+ defer r.Unlock()
+
+ i, ok := r.ipamDrivers[name]
+ if !ok {
+ return nil, nil
+ }
+
+ return i.driver, i.capability
+}
+
+// IPAMDefaultAddressSpaces returns the default address space strings for the passed IPAM driver name.
+func (r *DrvRegistry) IPAMDefaultAddressSpaces(name string) (string, string, error) {
+ r.Lock()
+ defer r.Unlock()
+
+ i, ok := r.ipamDrivers[name]
+ if !ok {
+ return "", "", fmt.Errorf("ipam %s not found", name)
+ }
+
+ return i.defaultLocalAddressSpace, i.defaultGlobalAddressSpace, nil
+}
+
+// GetPluginGetter returns the plugingetter
+func (r *DrvRegistry) GetPluginGetter() plugingetter.PluginGetter {
+ return r.pluginGetter
+}
+
+// RegisterDriver registers the network driver when it gets discovered.
+func (r *DrvRegistry) RegisterDriver(ntype string, driver driverapi.Driver, capability driverapi.Capability) error {
+ if strings.TrimSpace(ntype) == "" {
+ return errors.New("network type string cannot be empty")
+ }
+
+ r.Lock()
+ dd, ok := r.drivers[ntype]
+ r.Unlock()
+
+ if ok && dd.driver.IsBuiltIn() {
+ return driverapi.ErrActiveRegistration(ntype)
+ }
+
+ if r.dfn != nil {
+ if err := r.dfn(ntype, driver, capability); err != nil {
+ return err
+ }
+ }
+
+ dData := &driverData{driver, capability}
+
+ r.Lock()
+ r.drivers[ntype] = dData
+ r.Unlock()
+
+ return nil
+}
+
+func (r *DrvRegistry) registerIpamDriver(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
+ if strings.TrimSpace(name) == "" {
+ return errors.New("ipam driver name string cannot be empty")
+ }
+
+ r.Lock()
+ dd, ok := r.ipamDrivers[name]
+ r.Unlock()
+ if ok && dd.driver.IsBuiltIn() {
+ return types.ForbiddenErrorf("ipam driver %q already registered", name)
+ }
+
+ locAS, glbAS, err := driver.GetDefaultAddressSpaces()
+ if err != nil {
+ return types.InternalErrorf("ipam driver %q failed to return default address spaces: %v", name, err)
+ }
+
+ if r.ifn != nil {
+ if err := r.ifn(name, driver, caps); err != nil {
+ return err
+ }
+ }
+
+ r.Lock()
+ r.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS, capability: caps}
+ r.Unlock()
+
+ return nil
+}
+
+// RegisterIpamDriver registers the IPAM driver discovered with default capabilities.
+func (r *DrvRegistry) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
+ return r.registerIpamDriver(name, driver, &ipamapi.Capability{})
+}
+
+// RegisterIpamDriverWithCapabilities registers the IPAM driver discovered with specified capabilities.
+func (r *DrvRegistry) RegisterIpamDriverWithCapabilities(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
+ return r.registerIpamDriver(name, driver, caps)
+}
--- /dev/null
+package drvregistry
+
+import (
+ "sort"
+ "testing"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/ipamapi"
+ builtinIpam "github.com/docker/libnetwork/ipams/builtin"
+ nullIpam "github.com/docker/libnetwork/ipams/null"
+ remoteIpam "github.com/docker/libnetwork/ipams/remote"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+
+ // this takes care of the incontainer flag
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+const mockDriverName = "mock-driver"
+
+type mockDriver struct{}
+
+var md = mockDriver{}
+
+func mockDriverInit(reg driverapi.DriverCallback, opt map[string]interface{}) error {
+ return reg.RegisterDriver(mockDriverName, &md, driverapi.Capability{DataScope: datastore.LocalScope})
+}
+
+func (m *mockDriver) CreateNetwork(nid string, options map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ return nil
+}
+
+func (m *mockDriver) DeleteNetwork(nid string) error {
+ return nil
+}
+
+func (m *mockDriver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, options map[string]interface{}) error {
+ return nil
+}
+
+func (m *mockDriver) DeleteEndpoint(nid, eid string) error {
+ return nil
+}
+
+func (m *mockDriver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return nil, nil
+}
+
+func (m *mockDriver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ return nil
+}
+
+func (m *mockDriver) Leave(nid, eid string) error {
+ return nil
+}
+
+func (m *mockDriver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+func (m *mockDriver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+func (m *mockDriver) Type() string {
+ return mockDriverName
+}
+
+func (m *mockDriver) IsBuiltIn() bool {
+ return true
+}
+
+func (m *mockDriver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return nil
+}
+
+func (m *mockDriver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+}
+
+func (m *mockDriver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, nil
+}
+
+func (m *mockDriver) NetworkFree(id string) error {
+ return nil
+}
+
+func (m *mockDriver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (m *mockDriver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
+
+func getNew(t *testing.T) *DrvRegistry {
+ reg, err := New(nil, nil, nil, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = initIPAMDrivers(reg, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return reg
+}
+
+func initIPAMDrivers(r *DrvRegistry, lDs, gDs interface{}) error {
+ for _, fn := range [](func(ipamapi.Callback, interface{}, interface{}) error){
+ builtinIpam.Init,
+ remoteIpam.Init,
+ nullIpam.Init,
+ } {
+ if err := fn(r, lDs, gDs); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+func TestNew(t *testing.T) {
+ getNew(t)
+}
+
+func TestAddDriver(t *testing.T) {
+ reg := getNew(t)
+
+ err := reg.AddDriver(mockDriverName, mockDriverInit, nil)
+ assert.NilError(t, err)
+}
+
+func TestAddDuplicateDriver(t *testing.T) {
+ reg := getNew(t)
+
+ err := reg.AddDriver(mockDriverName, mockDriverInit, nil)
+ assert.NilError(t, err)
+
+ // Try adding the same driver
+ err = reg.AddDriver(mockDriverName, mockDriverInit, nil)
+ assert.Check(t, is.ErrorContains(err, ""))
+}
+
+func TestIPAMDefaultAddressSpaces(t *testing.T) {
+ reg := getNew(t)
+
+ as1, as2, err := reg.IPAMDefaultAddressSpaces("default")
+ assert.NilError(t, err)
+ assert.Check(t, as1 != "")
+ assert.Check(t, as2 != "")
+}
+
+func TestDriver(t *testing.T) {
+ reg := getNew(t)
+
+ err := reg.AddDriver(mockDriverName, mockDriverInit, nil)
+ assert.NilError(t, err)
+
+ d, cap := reg.Driver(mockDriverName)
+ assert.Check(t, d != nil)
+ assert.Check(t, cap != nil)
+}
+
+func TestIPAM(t *testing.T) {
+ reg := getNew(t)
+
+ i, cap := reg.IPAM("default")
+ assert.Check(t, i != nil)
+ assert.Check(t, cap != nil)
+}
+
+func TestWalkIPAMs(t *testing.T) {
+ reg := getNew(t)
+
+ ipams := make([]string, 0, 2)
+ reg.WalkIPAMs(func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool {
+ ipams = append(ipams, name)
+ return false
+ })
+
+ sort.Strings(ipams)
+ assert.Check(t, is.DeepEqual(ipams, []string{"default", "null"}))
+}
+
+func TestWalkDrivers(t *testing.T) {
+ reg := getNew(t)
+
+ err := reg.AddDriver(mockDriverName, mockDriverInit, nil)
+ assert.NilError(t, err)
+
+ var driverName string
+ reg.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
+ driverName = name
+ return false
+ })
+
+ assert.Check(t, is.Equal(driverName, mockDriverName))
+}
--- /dev/null
+package libnetwork
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "strings"
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// Endpoint represents a logical connection between a network and a sandbox.
+type Endpoint interface {
+ // A system generated id for this endpoint.
+ ID() string
+
+ // Name returns the name of this endpoint.
+ Name() string
+
+ // Network returns the name of the network to which this endpoint is attached.
+ Network() string
+
+ // Join joins the sandbox to the endpoint and populates into the sandbox
+ // the network resources allocated for the endpoint.
+ Join(sandbox Sandbox, options ...EndpointOption) error
+
+ // Leave detaches the network resources populated in the sandbox.
+ Leave(sandbox Sandbox, options ...EndpointOption) error
+
+ // Return certain operational data belonging to this endpoint
+ Info() EndpointInfo
+
+ // DriverInfo returns a collection of driver operational data related to this endpoint retrieved from the driver
+ DriverInfo() (map[string]interface{}, error)
+
+ // Delete and detaches this endpoint from the network.
+ Delete(force bool) error
+}
+
+// EndpointOption is an option setter function type used to pass various options to Network
+// and Endpoint interfaces methods. The various setter functions of type EndpointOption are
+// provided by libnetwork, they look like <Create|Join|Leave>Option[...](...)
+type EndpointOption func(ep *endpoint)
+
+type endpoint struct {
+ name string
+ id string
+ network *network
+ iface *endpointInterface
+ joinInfo *endpointJoinInfo
+ sandboxID string
+ locator string
+ exposedPorts []types.TransportPort
+ anonymous bool
+ disableResolution bool
+ generic map[string]interface{}
+ joinLeaveDone chan struct{}
+ prefAddress net.IP
+ prefAddressV6 net.IP
+ ipamOptions map[string]string
+ aliases map[string]string
+ myAliases []string
+ svcID string
+ svcName string
+ virtualIP net.IP
+ svcAliases []string
+ ingressPorts []*PortConfig
+ dbIndex uint64
+ dbExists bool
+ serviceEnabled bool
+ loadBalancer bool
+ sync.Mutex
+}
+
+func (ep *endpoint) MarshalJSON() ([]byte, error) {
+ ep.Lock()
+ defer ep.Unlock()
+
+ epMap := make(map[string]interface{})
+ epMap["name"] = ep.name
+ epMap["id"] = ep.id
+ epMap["ep_iface"] = ep.iface
+ epMap["joinInfo"] = ep.joinInfo
+ epMap["exposed_ports"] = ep.exposedPorts
+ if ep.generic != nil {
+ epMap["generic"] = ep.generic
+ }
+ epMap["sandbox"] = ep.sandboxID
+ epMap["locator"] = ep.locator
+ epMap["anonymous"] = ep.anonymous
+ epMap["disableResolution"] = ep.disableResolution
+ epMap["myAliases"] = ep.myAliases
+ epMap["svcName"] = ep.svcName
+ epMap["svcID"] = ep.svcID
+ epMap["virtualIP"] = ep.virtualIP.String()
+ epMap["ingressPorts"] = ep.ingressPorts
+ epMap["svcAliases"] = ep.svcAliases
+ epMap["loadBalancer"] = ep.loadBalancer
+
+ return json.Marshal(epMap)
+}
+
+func (ep *endpoint) UnmarshalJSON(b []byte) (err error) {
+ ep.Lock()
+ defer ep.Unlock()
+
+ var epMap map[string]interface{}
+ if err := json.Unmarshal(b, &epMap); err != nil {
+ return err
+ }
+ ep.name = epMap["name"].(string)
+ ep.id = epMap["id"].(string)
+
+ ib, _ := json.Marshal(epMap["ep_iface"])
+ json.Unmarshal(ib, &ep.iface)
+
+ jb, _ := json.Marshal(epMap["joinInfo"])
+ json.Unmarshal(jb, &ep.joinInfo)
+
+ tb, _ := json.Marshal(epMap["exposed_ports"])
+ var tPorts []types.TransportPort
+ json.Unmarshal(tb, &tPorts)
+ ep.exposedPorts = tPorts
+
+ cb, _ := json.Marshal(epMap["sandbox"])
+ json.Unmarshal(cb, &ep.sandboxID)
+
+ if v, ok := epMap["generic"]; ok {
+ ep.generic = v.(map[string]interface{})
+
+ if opt, ok := ep.generic[netlabel.PortMap]; ok {
+ pblist := []types.PortBinding{}
+
+ for i := 0; i < len(opt.([]interface{})); i++ {
+ pb := types.PortBinding{}
+ tmp := opt.([]interface{})[i].(map[string]interface{})
+
+ bytes, err := json.Marshal(tmp)
+ if err != nil {
+ logrus.Error(err)
+ break
+ }
+ err = json.Unmarshal(bytes, &pb)
+ if err != nil {
+ logrus.Error(err)
+ break
+ }
+ pblist = append(pblist, pb)
+ }
+ ep.generic[netlabel.PortMap] = pblist
+ }
+
+ if opt, ok := ep.generic[netlabel.ExposedPorts]; ok {
+ tplist := []types.TransportPort{}
+
+ for i := 0; i < len(opt.([]interface{})); i++ {
+ tp := types.TransportPort{}
+ tmp := opt.([]interface{})[i].(map[string]interface{})
+
+ bytes, err := json.Marshal(tmp)
+ if err != nil {
+ logrus.Error(err)
+ break
+ }
+ err = json.Unmarshal(bytes, &tp)
+ if err != nil {
+ logrus.Error(err)
+ break
+ }
+ tplist = append(tplist, tp)
+ }
+ ep.generic[netlabel.ExposedPorts] = tplist
+
+ }
+ }
+
+ if v, ok := epMap["anonymous"]; ok {
+ ep.anonymous = v.(bool)
+ }
+ if v, ok := epMap["disableResolution"]; ok {
+ ep.disableResolution = v.(bool)
+ }
+ if l, ok := epMap["locator"]; ok {
+ ep.locator = l.(string)
+ }
+
+ if sn, ok := epMap["svcName"]; ok {
+ ep.svcName = sn.(string)
+ }
+
+ if si, ok := epMap["svcID"]; ok {
+ ep.svcID = si.(string)
+ }
+
+ if vip, ok := epMap["virtualIP"]; ok {
+ ep.virtualIP = net.ParseIP(vip.(string))
+ }
+
+ if v, ok := epMap["loadBalancer"]; ok {
+ ep.loadBalancer = v.(bool)
+ }
+
+ sal, _ := json.Marshal(epMap["svcAliases"])
+ var svcAliases []string
+ json.Unmarshal(sal, &svcAliases)
+ ep.svcAliases = svcAliases
+
+ pc, _ := json.Marshal(epMap["ingressPorts"])
+ var ingressPorts []*PortConfig
+ json.Unmarshal(pc, &ingressPorts)
+ ep.ingressPorts = ingressPorts
+
+ ma, _ := json.Marshal(epMap["myAliases"])
+ var myAliases []string
+ json.Unmarshal(ma, &myAliases)
+ ep.myAliases = myAliases
+ return nil
+}
+
+func (ep *endpoint) New() datastore.KVObject {
+ return &endpoint{network: ep.getNetwork()}
+}
+
+func (ep *endpoint) CopyTo(o datastore.KVObject) error {
+ ep.Lock()
+ defer ep.Unlock()
+
+ dstEp := o.(*endpoint)
+ dstEp.name = ep.name
+ dstEp.id = ep.id
+ dstEp.sandboxID = ep.sandboxID
+ dstEp.locator = ep.locator
+ dstEp.dbIndex = ep.dbIndex
+ dstEp.dbExists = ep.dbExists
+ dstEp.anonymous = ep.anonymous
+ dstEp.disableResolution = ep.disableResolution
+ dstEp.svcName = ep.svcName
+ dstEp.svcID = ep.svcID
+ dstEp.virtualIP = ep.virtualIP
+ dstEp.loadBalancer = ep.loadBalancer
+
+ dstEp.svcAliases = make([]string, len(ep.svcAliases))
+ copy(dstEp.svcAliases, ep.svcAliases)
+
+ dstEp.ingressPorts = make([]*PortConfig, len(ep.ingressPorts))
+ copy(dstEp.ingressPorts, ep.ingressPorts)
+
+ if ep.iface != nil {
+ dstEp.iface = &endpointInterface{}
+ ep.iface.CopyTo(dstEp.iface)
+ }
+
+ if ep.joinInfo != nil {
+ dstEp.joinInfo = &endpointJoinInfo{}
+ ep.joinInfo.CopyTo(dstEp.joinInfo)
+ }
+
+ dstEp.exposedPorts = make([]types.TransportPort, len(ep.exposedPorts))
+ copy(dstEp.exposedPorts, ep.exposedPorts)
+
+ dstEp.myAliases = make([]string, len(ep.myAliases))
+ copy(dstEp.myAliases, ep.myAliases)
+
+ dstEp.generic = options.Generic{}
+ for k, v := range ep.generic {
+ dstEp.generic[k] = v
+ }
+
+ return nil
+}
+
+func (ep *endpoint) ID() string {
+ ep.Lock()
+ defer ep.Unlock()
+
+ return ep.id
+}
+
+func (ep *endpoint) Name() string {
+ ep.Lock()
+ defer ep.Unlock()
+
+ return ep.name
+}
+
+func (ep *endpoint) MyAliases() []string {
+ ep.Lock()
+ defer ep.Unlock()
+
+ return ep.myAliases
+}
+
+func (ep *endpoint) Network() string {
+ if ep.network == nil {
+ return ""
+ }
+
+ return ep.network.name
+}
+
+func (ep *endpoint) isAnonymous() bool {
+ ep.Lock()
+ defer ep.Unlock()
+ return ep.anonymous
+}
+
+// isServiceEnabled check if service is enabled on the endpoint
+func (ep *endpoint) isServiceEnabled() bool {
+ ep.Lock()
+ defer ep.Unlock()
+ return ep.serviceEnabled
+}
+
+// enableService sets service enabled on the endpoint
+func (ep *endpoint) enableService() {
+ ep.Lock()
+ defer ep.Unlock()
+ ep.serviceEnabled = true
+}
+
+// disableService disables service on the endpoint
+func (ep *endpoint) disableService() {
+ ep.Lock()
+ defer ep.Unlock()
+ ep.serviceEnabled = false
+}
+
+func (ep *endpoint) needResolver() bool {
+ ep.Lock()
+ defer ep.Unlock()
+ return !ep.disableResolution
+}
+
+// endpoint Key structure : endpoint/network-id/endpoint-id
+func (ep *endpoint) Key() []string {
+ if ep.network == nil {
+ return nil
+ }
+
+ return []string{datastore.EndpointKeyPrefix, ep.network.id, ep.id}
+}
+
+func (ep *endpoint) KeyPrefix() []string {
+ if ep.network == nil {
+ return nil
+ }
+
+ return []string{datastore.EndpointKeyPrefix, ep.network.id}
+}
+
+func (ep *endpoint) networkIDFromKey(key string) (string, error) {
+ // endpoint Key structure : docker/libnetwork/endpoint/${network-id}/${endpoint-id}
+ // it's an invalid key if the key doesn't have all the 5 key elements above
+ keyElements := strings.Split(key, "/")
+ if !strings.HasPrefix(key, datastore.Key(datastore.EndpointKeyPrefix)) || len(keyElements) < 5 {
+ return "", fmt.Errorf("invalid endpoint key : %v", key)
+ }
+ // network-id is placed at index=3. pls refer to endpoint.Key() method
+ return strings.Split(key, "/")[3], nil
+}
+
+func (ep *endpoint) Value() []byte {
+ b, err := json.Marshal(ep)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (ep *endpoint) SetValue(value []byte) error {
+ return json.Unmarshal(value, ep)
+}
+
+func (ep *endpoint) Index() uint64 {
+ ep.Lock()
+ defer ep.Unlock()
+ return ep.dbIndex
+}
+
+func (ep *endpoint) SetIndex(index uint64) {
+ ep.Lock()
+ defer ep.Unlock()
+ ep.dbIndex = index
+ ep.dbExists = true
+}
+
+func (ep *endpoint) Exists() bool {
+ ep.Lock()
+ defer ep.Unlock()
+ return ep.dbExists
+}
+
+func (ep *endpoint) Skip() bool {
+ return ep.getNetwork().Skip()
+}
+
+func (ep *endpoint) processOptions(options ...EndpointOption) {
+ ep.Lock()
+ defer ep.Unlock()
+
+ for _, opt := range options {
+ if opt != nil {
+ opt(ep)
+ }
+ }
+}
+
+func (ep *endpoint) getNetwork() *network {
+ ep.Lock()
+ defer ep.Unlock()
+
+ return ep.network
+}
+
+func (ep *endpoint) getNetworkFromStore() (*network, error) {
+ if ep.network == nil {
+ return nil, fmt.Errorf("invalid network object in endpoint %s", ep.Name())
+ }
+
+ return ep.network.getController().getNetworkFromStore(ep.network.id)
+}
+
+func (ep *endpoint) Join(sbox Sandbox, options ...EndpointOption) error {
+ if sbox == nil {
+ return types.BadRequestErrorf("endpoint cannot be joined by nil container")
+ }
+
+ sb, ok := sbox.(*sandbox)
+ if !ok {
+ return types.BadRequestErrorf("not a valid Sandbox interface")
+ }
+
+ sb.joinLeaveStart()
+ defer sb.joinLeaveEnd()
+
+ return ep.sbJoin(sb, options...)
+}
+
+func (ep *endpoint) sbJoin(sb *sandbox, options ...EndpointOption) (err error) {
+ n, err := ep.getNetworkFromStore()
+ if err != nil {
+ return fmt.Errorf("failed to get network from store during join: %v", err)
+ }
+
+ ep, err = n.getEndpointFromStore(ep.ID())
+ if err != nil {
+ return fmt.Errorf("failed to get endpoint from store during join: %v", err)
+ }
+
+ ep.Lock()
+ if ep.sandboxID != "" {
+ ep.Unlock()
+ return types.ForbiddenErrorf("another container is attached to the same network endpoint")
+ }
+ ep.network = n
+ ep.sandboxID = sb.ID()
+ ep.joinInfo = &endpointJoinInfo{}
+ epid := ep.id
+ ep.Unlock()
+ defer func() {
+ if err != nil {
+ ep.Lock()
+ ep.sandboxID = ""
+ ep.Unlock()
+ }
+ }()
+
+ nid := n.ID()
+
+ ep.processOptions(options...)
+
+ d, err := n.driver(true)
+ if err != nil {
+ return fmt.Errorf("failed to get driver during join: %v", err)
+ }
+
+ err = d.Join(nid, epid, sb.Key(), ep, sb.Labels())
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ if e := d.Leave(nid, epid); e != nil {
+ logrus.Warnf("driver leave failed while rolling back join: %v", e)
+ }
+ }
+ }()
+
+ // Watch for service records
+ if !n.getController().isAgent() {
+ n.getController().watchSvcRecord(ep)
+ }
+
+ if doUpdateHostsFile(n, sb) {
+ address := ""
+ if ip := ep.getFirstInterfaceAddress(); ip != nil {
+ address = ip.String()
+ }
+ if err = sb.updateHostsFile(address); err != nil {
+ return err
+ }
+ }
+ if err = sb.updateDNS(n.enableIPv6); err != nil {
+ return err
+ }
+
+ // Current endpoint providing external connectivity for the sandbox
+ extEp := sb.getGatewayEndpoint()
+
+ sb.addEndpoint(ep)
+ defer func() {
+ if err != nil {
+ sb.removeEndpoint(ep)
+ }
+ }()
+
+ if err = sb.populateNetworkResources(ep); err != nil {
+ return err
+ }
+
+ if err = n.getController().updateToStore(ep); err != nil {
+ return err
+ }
+
+ if err = ep.addDriverInfoToCluster(); err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil {
+ if e := ep.deleteDriverInfoFromCluster(); e != nil {
+ logrus.Errorf("Could not delete endpoint state for endpoint %s from cluster on join failure: %v", ep.Name(), e)
+ }
+ }
+ }()
+
+ // Load balancing endpoints should never have a default gateway nor
+ // should they alter the status of a network's default gateway
+ if ep.loadBalancer && !sb.ingress {
+ return nil
+ }
+
+ if sb.needDefaultGW() && sb.getEndpointInGWNetwork() == nil {
+ return sb.setupDefaultGW()
+ }
+
+ moveExtConn := sb.getGatewayEndpoint() != extEp
+
+ if moveExtConn {
+ if extEp != nil {
+ logrus.Debugf("Revoking external connectivity on endpoint %s (%s)", extEp.Name(), extEp.ID())
+ extN, err := extEp.getNetworkFromStore()
+ if err != nil {
+ return fmt.Errorf("failed to get network from store for revoking external connectivity during join: %v", err)
+ }
+ extD, err := extN.driver(true)
+ if err != nil {
+ return fmt.Errorf("failed to get driver for revoking external connectivity during join: %v", err)
+ }
+ if err = extD.RevokeExternalConnectivity(extEp.network.ID(), extEp.ID()); err != nil {
+ return types.InternalErrorf(
+ "driver failed revoking external connectivity on endpoint %s (%s): %v",
+ extEp.Name(), extEp.ID(), err)
+ }
+ defer func() {
+ if err != nil {
+ if e := extD.ProgramExternalConnectivity(extEp.network.ID(), extEp.ID(), sb.Labels()); e != nil {
+ logrus.Warnf("Failed to roll-back external connectivity on endpoint %s (%s): %v",
+ extEp.Name(), extEp.ID(), e)
+ }
+ }
+ }()
+ }
+ if !n.internal {
+ logrus.Debugf("Programming external connectivity on endpoint %s (%s)", ep.Name(), ep.ID())
+ if err = d.ProgramExternalConnectivity(n.ID(), ep.ID(), sb.Labels()); err != nil {
+ return types.InternalErrorf(
+ "driver failed programming external connectivity on endpoint %s (%s): %v",
+ ep.Name(), ep.ID(), err)
+ }
+ }
+
+ }
+
+ if !sb.needDefaultGW() {
+ if e := sb.clearDefaultGW(); e != nil {
+ logrus.Warnf("Failure while disconnecting sandbox %s (%s) from gateway network: %v",
+ sb.ID(), sb.ContainerID(), e)
+ }
+ }
+
+ return nil
+}
+
+func doUpdateHostsFile(n *network, sb *sandbox) bool {
+ return !n.ingress && n.Name() != libnGWNetwork
+}
+
+func (ep *endpoint) rename(name string) error {
+ var (
+ err error
+ netWatch *netWatch
+ ok bool
+ )
+
+ n := ep.getNetwork()
+ if n == nil {
+ return fmt.Errorf("network not connected for ep %q", ep.name)
+ }
+
+ c := n.getController()
+
+ sb, ok := ep.getSandbox()
+ if !ok {
+ logrus.Warnf("rename for %s aborted, sandbox %s is not anymore present", ep.ID(), ep.sandboxID)
+ return nil
+ }
+
+ if c.isAgent() {
+ if err = ep.deleteServiceInfoFromCluster(sb, true, "rename"); err != nil {
+ return types.InternalErrorf("Could not delete service state for endpoint %s from cluster on rename: %v", ep.Name(), err)
+ }
+ } else {
+ c.Lock()
+ netWatch, ok = c.nmap[n.ID()]
+ c.Unlock()
+ if !ok {
+ return fmt.Errorf("watch null for network %q", n.Name())
+ }
+ n.updateSvcRecord(ep, c.getLocalEps(netWatch), false)
+ }
+
+ oldName := ep.name
+ oldAnonymous := ep.anonymous
+ ep.name = name
+ ep.anonymous = false
+
+ if c.isAgent() {
+ if err = ep.addServiceInfoToCluster(sb); err != nil {
+ return types.InternalErrorf("Could not add service state for endpoint %s to cluster on rename: %v", ep.Name(), err)
+ }
+ defer func() {
+ if err != nil {
+ ep.deleteServiceInfoFromCluster(sb, true, "rename")
+ ep.name = oldName
+ ep.anonymous = oldAnonymous
+ ep.addServiceInfoToCluster(sb)
+ }
+ }()
+ } else {
+ n.updateSvcRecord(ep, c.getLocalEps(netWatch), true)
+ defer func() {
+ if err != nil {
+ n.updateSvcRecord(ep, c.getLocalEps(netWatch), false)
+ ep.name = oldName
+ ep.anonymous = oldAnonymous
+ n.updateSvcRecord(ep, c.getLocalEps(netWatch), true)
+ }
+ }()
+ }
+
+ // Update the store with the updated name
+ if err = c.updateToStore(ep); err != nil {
+ return err
+ }
+ // After the name change do a dummy endpoint count update to
+ // trigger the service record update in the peer nodes
+
+ // Ignore the error because updateStore fail for EpCnt is a
+ // benign error. Besides there is no meaningful recovery that
+ // we can do. When the cluster recovers subsequent EpCnt update
+ // will force the peers to get the correct EP name.
+ n.getEpCnt().updateStore()
+
+ return err
+}
+
+func (ep *endpoint) hasInterface(iName string) bool {
+ ep.Lock()
+ defer ep.Unlock()
+
+ return ep.iface != nil && ep.iface.srcName == iName
+}
+
+func (ep *endpoint) Leave(sbox Sandbox, options ...EndpointOption) error {
+ if sbox == nil || sbox.ID() == "" || sbox.Key() == "" {
+ return types.BadRequestErrorf("invalid Sandbox passed to endpoint leave: %v", sbox)
+ }
+
+ sb, ok := sbox.(*sandbox)
+ if !ok {
+ return types.BadRequestErrorf("not a valid Sandbox interface")
+ }
+
+ sb.joinLeaveStart()
+ defer sb.joinLeaveEnd()
+
+ return ep.sbLeave(sb, false, options...)
+}
+
+func (ep *endpoint) sbLeave(sb *sandbox, force bool, options ...EndpointOption) error {
+ n, err := ep.getNetworkFromStore()
+ if err != nil {
+ return fmt.Errorf("failed to get network from store during leave: %v", err)
+ }
+
+ ep, err = n.getEndpointFromStore(ep.ID())
+ if err != nil {
+ return fmt.Errorf("failed to get endpoint from store during leave: %v", err)
+ }
+
+ ep.Lock()
+ sid := ep.sandboxID
+ ep.Unlock()
+
+ if sid == "" {
+ return types.ForbiddenErrorf("cannot leave endpoint with no attached sandbox")
+ }
+ if sid != sb.ID() {
+ return types.ForbiddenErrorf("unexpected sandbox ID in leave request. Expected %s. Got %s", ep.sandboxID, sb.ID())
+ }
+
+ ep.processOptions(options...)
+
+ d, err := n.driver(!force)
+ if err != nil {
+ return fmt.Errorf("failed to get driver during endpoint leave: %v", err)
+ }
+
+ ep.Lock()
+ ep.sandboxID = ""
+ ep.network = n
+ ep.Unlock()
+
+ // Current endpoint providing external connectivity to the sandbox
+ extEp := sb.getGatewayEndpoint()
+ moveExtConn := extEp != nil && (extEp.ID() == ep.ID())
+
+ if d != nil {
+ if moveExtConn {
+ logrus.Debugf("Revoking external connectivity on endpoint %s (%s)", ep.Name(), ep.ID())
+ if err := d.RevokeExternalConnectivity(n.id, ep.id); err != nil {
+ logrus.Warnf("driver failed revoking external connectivity on endpoint %s (%s): %v",
+ ep.Name(), ep.ID(), err)
+ }
+ }
+
+ if err := d.Leave(n.id, ep.id); err != nil {
+ if _, ok := err.(types.MaskableError); !ok {
+ logrus.Warnf("driver error disconnecting container %s : %v", ep.name, err)
+ }
+ }
+ }
+
+ if err := ep.deleteServiceInfoFromCluster(sb, true, "sbLeave"); err != nil {
+ logrus.Warnf("Failed to clean up service info on container %s disconnect: %v", ep.name, err)
+ }
+
+ if err := sb.clearNetworkResources(ep); err != nil {
+ logrus.Warnf("Failed to clean up network resources on container %s disconnect: %v", ep.name, err)
+ }
+
+ // Update the store about the sandbox detach only after we
+ // have completed sb.clearNetworkresources above to avoid
+ // spurious logs when cleaning up the sandbox when the daemon
+ // ungracefully exits and restarts before completing sandbox
+ // detach but after store has been updated.
+ if err := n.getController().updateToStore(ep); err != nil {
+ return err
+ }
+
+ if e := ep.deleteDriverInfoFromCluster(); e != nil {
+ logrus.Errorf("Failed to delete endpoint state for endpoint %s from cluster: %v", ep.Name(), e)
+ }
+
+ sb.deleteHostsEntries(n.getSvcRecords(ep))
+ if !sb.inDelete && sb.needDefaultGW() && sb.getEndpointInGWNetwork() == nil {
+ return sb.setupDefaultGW()
+ }
+
+ // New endpoint providing external connectivity for the sandbox
+ extEp = sb.getGatewayEndpoint()
+ if moveExtConn && extEp != nil {
+ logrus.Debugf("Programming external connectivity on endpoint %s (%s)", extEp.Name(), extEp.ID())
+ extN, err := extEp.getNetworkFromStore()
+ if err != nil {
+ return fmt.Errorf("failed to get network from store for programming external connectivity during leave: %v", err)
+ }
+ extD, err := extN.driver(true)
+ if err != nil {
+ return fmt.Errorf("failed to get driver for programming external connectivity during leave: %v", err)
+ }
+ if err := extD.ProgramExternalConnectivity(extEp.network.ID(), extEp.ID(), sb.Labels()); err != nil {
+ logrus.Warnf("driver failed programming external connectivity on endpoint %s: (%s) %v",
+ extEp.Name(), extEp.ID(), err)
+ }
+ }
+
+ if !sb.needDefaultGW() {
+ if err := sb.clearDefaultGW(); err != nil {
+ logrus.Warnf("Failure while disconnecting sandbox %s (%s) from gateway network: %v",
+ sb.ID(), sb.ContainerID(), err)
+ }
+ }
+
+ return nil
+}
+
+func (ep *endpoint) Delete(force bool) error {
+ var err error
+ n, err := ep.getNetworkFromStore()
+ if err != nil {
+ return fmt.Errorf("failed to get network during Delete: %v", err)
+ }
+
+ ep, err = n.getEndpointFromStore(ep.ID())
+ if err != nil {
+ return fmt.Errorf("failed to get endpoint from store during Delete: %v", err)
+ }
+
+ ep.Lock()
+ epid := ep.id
+ name := ep.name
+ sbid := ep.sandboxID
+ ep.Unlock()
+
+ sb, _ := n.getController().SandboxByID(sbid)
+ if sb != nil && !force {
+ return &ActiveContainerError{name: name, id: epid}
+ }
+
+ if sb != nil {
+ if e := ep.sbLeave(sb.(*sandbox), force); e != nil {
+ logrus.Warnf("failed to leave sandbox for endpoint %s : %v", name, e)
+ }
+ }
+
+ if err = n.getController().deleteFromStore(ep); err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil && !force {
+ ep.dbExists = false
+ if e := n.getController().updateToStore(ep); e != nil {
+ logrus.Warnf("failed to recreate endpoint in store %s : %v", name, e)
+ }
+ }
+ }()
+
+ // unwatch for service records
+ n.getController().unWatchSvcRecord(ep)
+
+ if err = ep.deleteEndpoint(force); err != nil && !force {
+ return err
+ }
+
+ ep.releaseAddress()
+
+ if err := n.getEpCnt().DecEndpointCnt(); err != nil {
+ logrus.Warnf("failed to decrement endpoint count for ep %s: %v", ep.ID(), err)
+ }
+
+ return nil
+}
+
+func (ep *endpoint) deleteEndpoint(force bool) error {
+ ep.Lock()
+ n := ep.network
+ name := ep.name
+ epid := ep.id
+ ep.Unlock()
+
+ driver, err := n.driver(!force)
+ if err != nil {
+ return fmt.Errorf("failed to delete endpoint: %v", err)
+ }
+
+ if driver == nil {
+ return nil
+ }
+
+ if err := driver.DeleteEndpoint(n.id, epid); err != nil {
+ if _, ok := err.(types.ForbiddenError); ok {
+ return err
+ }
+
+ if _, ok := err.(types.MaskableError); !ok {
+ logrus.Warnf("driver error deleting endpoint %s : %v", name, err)
+ }
+ }
+
+ return nil
+}
+
+func (ep *endpoint) getSandbox() (*sandbox, bool) {
+ c := ep.network.getController()
+ ep.Lock()
+ sid := ep.sandboxID
+ ep.Unlock()
+
+ c.Lock()
+ ps, ok := c.sandboxes[sid]
+ c.Unlock()
+
+ return ps, ok
+}
+
+func (ep *endpoint) getFirstInterfaceAddress() net.IP {
+ ep.Lock()
+ defer ep.Unlock()
+
+ if ep.iface.addr != nil {
+ return ep.iface.addr.IP
+ }
+
+ return nil
+}
+
+// EndpointOptionGeneric function returns an option setter for a Generic option defined
+// in a Dictionary of Key-Value pair
+func EndpointOptionGeneric(generic map[string]interface{}) EndpointOption {
+ return func(ep *endpoint) {
+ for k, v := range generic {
+ ep.generic[k] = v
+ }
+ }
+}
+
+var (
+ linkLocalMask = net.CIDRMask(16, 32)
+ linkLocalMaskIPv6 = net.CIDRMask(64, 128)
+)
+
+// CreateOptionIpam function returns an option setter for the ipam configuration for this endpoint
+func CreateOptionIpam(ipV4, ipV6 net.IP, llIPs []net.IP, ipamOptions map[string]string) EndpointOption {
+ return func(ep *endpoint) {
+ ep.prefAddress = ipV4
+ ep.prefAddressV6 = ipV6
+ if len(llIPs) != 0 {
+ for _, ip := range llIPs {
+ nw := &net.IPNet{IP: ip, Mask: linkLocalMask}
+ if ip.To4() == nil {
+ nw.Mask = linkLocalMaskIPv6
+ }
+ ep.iface.llAddrs = append(ep.iface.llAddrs, nw)
+ }
+ }
+ ep.ipamOptions = ipamOptions
+ }
+}
+
+// CreateOptionExposedPorts function returns an option setter for the container exposed
+// ports option to be passed to network.CreateEndpoint() method.
+func CreateOptionExposedPorts(exposedPorts []types.TransportPort) EndpointOption {
+ return func(ep *endpoint) {
+ // Defensive copy
+ eps := make([]types.TransportPort, len(exposedPorts))
+ copy(eps, exposedPorts)
+ // Store endpoint label and in generic because driver needs it
+ ep.exposedPorts = eps
+ ep.generic[netlabel.ExposedPorts] = eps
+ }
+}
+
+// CreateOptionPortMapping function returns an option setter for the mapping
+// ports option to be passed to network.CreateEndpoint() method.
+func CreateOptionPortMapping(portBindings []types.PortBinding) EndpointOption {
+ return func(ep *endpoint) {
+ // Store a copy of the bindings as generic data to pass to the driver
+ pbs := make([]types.PortBinding, len(portBindings))
+ copy(pbs, portBindings)
+ ep.generic[netlabel.PortMap] = pbs
+ }
+}
+
+// CreateOptionDNS function returns an option setter for dns entry option to
+// be passed to container Create method.
+func CreateOptionDNS(dns []string) EndpointOption {
+ return func(ep *endpoint) {
+ ep.generic[netlabel.DNSServers] = dns
+ }
+}
+
+// CreateOptionAnonymous function returns an option setter for setting
+// this endpoint as anonymous
+func CreateOptionAnonymous() EndpointOption {
+ return func(ep *endpoint) {
+ ep.anonymous = true
+ }
+}
+
+// CreateOptionDisableResolution function returns an option setter to indicate
+// this endpoint doesn't want embedded DNS server functionality
+func CreateOptionDisableResolution() EndpointOption {
+ return func(ep *endpoint) {
+ ep.disableResolution = true
+ }
+}
+
+// CreateOptionAlias function returns an option setter for setting endpoint alias
+func CreateOptionAlias(name string, alias string) EndpointOption {
+ return func(ep *endpoint) {
+ if ep.aliases == nil {
+ ep.aliases = make(map[string]string)
+ }
+ ep.aliases[alias] = name
+ }
+}
+
+// CreateOptionService function returns an option setter for setting service binding configuration
+func CreateOptionService(name, id string, vip net.IP, ingressPorts []*PortConfig, aliases []string) EndpointOption {
+ return func(ep *endpoint) {
+ ep.svcName = name
+ ep.svcID = id
+ ep.virtualIP = vip
+ ep.ingressPorts = ingressPorts
+ ep.svcAliases = aliases
+ }
+}
+
+// CreateOptionMyAlias function returns an option setter for setting endpoint's self alias
+func CreateOptionMyAlias(alias string) EndpointOption {
+ return func(ep *endpoint) {
+ ep.myAliases = append(ep.myAliases, alias)
+ }
+}
+
+// CreateOptionLoadBalancer function returns an option setter for denoting the endpoint is a load balancer for a network
+func CreateOptionLoadBalancer() EndpointOption {
+ return func(ep *endpoint) {
+ ep.loadBalancer = true
+ }
+}
+
+// JoinOptionPriority function returns an option setter for priority option to
+// be passed to the endpoint.Join() method.
+func JoinOptionPriority(ep Endpoint, prio int) EndpointOption {
+ return func(ep *endpoint) {
+ // ep lock already acquired
+ c := ep.network.getController()
+ c.Lock()
+ sb, ok := c.sandboxes[ep.sandboxID]
+ c.Unlock()
+ if !ok {
+ logrus.Errorf("Could not set endpoint priority value during Join to endpoint %s: No sandbox id present in endpoint", ep.id)
+ return
+ }
+ sb.epPriority[ep.id] = prio
+ }
+}
+
+func (ep *endpoint) DataScope() string {
+ return ep.getNetwork().DataScope()
+}
+
+func (ep *endpoint) assignAddress(ipam ipamapi.Ipam, assignIPv4, assignIPv6 bool) error {
+ var err error
+
+ n := ep.getNetwork()
+ if n.hasSpecialDriver() {
+ return nil
+ }
+
+ logrus.Debugf("Assigning addresses for endpoint %s's interface on network %s", ep.Name(), n.Name())
+
+ if assignIPv4 {
+ if err = ep.assignAddressVersion(4, ipam); err != nil {
+ return err
+ }
+ }
+
+ if assignIPv6 {
+ err = ep.assignAddressVersion(6, ipam)
+ }
+
+ return err
+}
+
+func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
+ var (
+ poolID *string
+ address **net.IPNet
+ prefAdd net.IP
+ progAdd net.IP
+ )
+
+ n := ep.getNetwork()
+ switch ipVer {
+ case 4:
+ poolID = &ep.iface.v4PoolID
+ address = &ep.iface.addr
+ prefAdd = ep.prefAddress
+ case 6:
+ poolID = &ep.iface.v6PoolID
+ address = &ep.iface.addrv6
+ prefAdd = ep.prefAddressV6
+ default:
+ return types.InternalErrorf("incorrect ip version number passed: %d", ipVer)
+ }
+
+ ipInfo := n.getIPInfo(ipVer)
+
+ // ipv6 address is not mandatory
+ if len(ipInfo) == 0 && ipVer == 6 {
+ return nil
+ }
+
+ // The address to program may be chosen by the user or by the network driver in one specific
+ // case to support backward compatibility with `docker daemon --fixed-cidrv6` use case
+ if prefAdd != nil {
+ progAdd = prefAdd
+ } else if *address != nil {
+ progAdd = (*address).IP
+ }
+
+ for _, d := range ipInfo {
+ if progAdd != nil && !d.Pool.Contains(progAdd) {
+ continue
+ }
+ addr, _, err := ipam.RequestAddress(d.PoolID, progAdd, ep.ipamOptions)
+ if err == nil {
+ ep.Lock()
+ *address = addr
+ *poolID = d.PoolID
+ ep.Unlock()
+ return nil
+ }
+ if err != ipamapi.ErrNoAvailableIPs || progAdd != nil {
+ return err
+ }
+ }
+ if progAdd != nil {
+ return types.BadRequestErrorf("Invalid address %s: It does not belong to any of this network's subnets", prefAdd)
+ }
+ return fmt.Errorf("no available IPv%d addresses on this network's address pools: %s (%s)", ipVer, n.Name(), n.ID())
+}
+
+func (ep *endpoint) releaseAddress() {
+ n := ep.getNetwork()
+ if n.hasSpecialDriver() {
+ return
+ }
+
+ logrus.Debugf("Releasing addresses for endpoint %s's interface on network %s", ep.Name(), n.Name())
+
+ ipam, _, err := n.getController().getIPAMDriver(n.ipamType)
+ if err != nil {
+ logrus.Warnf("Failed to retrieve ipam driver to release interface address on delete of endpoint %s (%s): %v", ep.Name(), ep.ID(), err)
+ return
+ }
+
+ if ep.iface.addr != nil {
+ if err := ipam.ReleaseAddress(ep.iface.v4PoolID, ep.iface.addr.IP); err != nil {
+ logrus.Warnf("Failed to release ip address %s on delete of endpoint %s (%s): %v", ep.iface.addr.IP, ep.Name(), ep.ID(), err)
+ }
+ }
+
+ if ep.iface.addrv6 != nil && ep.iface.addrv6.IP.IsGlobalUnicast() {
+ if err := ipam.ReleaseAddress(ep.iface.v6PoolID, ep.iface.addrv6.IP); err != nil {
+ logrus.Warnf("Failed to release ip address %s on delete of endpoint %s (%s): %v", ep.iface.addrv6.IP, ep.Name(), ep.ID(), err)
+ }
+ }
+}
+
+func (c *controller) cleanupLocalEndpoints() {
+ // Get used endpoints
+ eps := make(map[string]interface{})
+ for _, sb := range c.sandboxes {
+ for _, ep := range sb.endpoints {
+ eps[ep.id] = true
+ }
+ }
+ nl, err := c.getNetworksForScope(datastore.LocalScope)
+ if err != nil {
+ logrus.Warnf("Could not get list of networks during endpoint cleanup: %v", err)
+ return
+ }
+
+ for _, n := range nl {
+ if n.ConfigOnly() {
+ continue
+ }
+ epl, err := n.getEndpointsFromStore()
+ if err != nil {
+ logrus.Warnf("Could not get list of endpoints in network %s during endpoint cleanup: %v", n.name, err)
+ continue
+ }
+
+ for _, ep := range epl {
+ if _, ok := eps[ep.id]; ok {
+ continue
+ }
+ logrus.Infof("Removing stale endpoint %s (%s)", ep.name, ep.id)
+ if err := ep.Delete(true); err != nil {
+ logrus.Warnf("Could not delete local endpoint %s during endpoint cleanup: %v", ep.name, err)
+ }
+ }
+
+ epl, err = n.getEndpointsFromStore()
+ if err != nil {
+ logrus.Warnf("Could not get list of endpoints in network %s for count update: %v", n.name, err)
+ continue
+ }
+
+ epCnt := n.getEpCnt().EndpointCnt()
+ if epCnt != uint64(len(epl)) {
+ logrus.Infof("Fixing inconsistent endpoint_cnt for network %s. Expected=%d, Actual=%d", n.name, len(epl), epCnt)
+ n.getEpCnt().setCnt(uint64(len(epl)))
+ }
+ }
+}
--- /dev/null
+package libnetwork
+
+import (
+ "encoding/json"
+ "fmt"
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+)
+
+type endpointCnt struct {
+ n *network
+ Count uint64
+ dbIndex uint64
+ dbExists bool
+ sync.Mutex
+}
+
+const epCntKeyPrefix = "endpoint_count"
+
+func (ec *endpointCnt) Key() []string {
+ ec.Lock()
+ defer ec.Unlock()
+
+ return []string{epCntKeyPrefix, ec.n.id}
+}
+
+func (ec *endpointCnt) KeyPrefix() []string {
+ ec.Lock()
+ defer ec.Unlock()
+
+ return []string{epCntKeyPrefix, ec.n.id}
+}
+
+func (ec *endpointCnt) Value() []byte {
+ ec.Lock()
+ defer ec.Unlock()
+
+ b, err := json.Marshal(ec)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (ec *endpointCnt) SetValue(value []byte) error {
+ ec.Lock()
+ defer ec.Unlock()
+
+ return json.Unmarshal(value, &ec)
+}
+
+func (ec *endpointCnt) Index() uint64 {
+ ec.Lock()
+ defer ec.Unlock()
+ return ec.dbIndex
+}
+
+func (ec *endpointCnt) SetIndex(index uint64) {
+ ec.Lock()
+ ec.dbIndex = index
+ ec.dbExists = true
+ ec.Unlock()
+}
+
+func (ec *endpointCnt) Exists() bool {
+ ec.Lock()
+ defer ec.Unlock()
+ return ec.dbExists
+}
+
+func (ec *endpointCnt) Skip() bool {
+ ec.Lock()
+ defer ec.Unlock()
+ return !ec.n.persist
+}
+
+func (ec *endpointCnt) New() datastore.KVObject {
+ ec.Lock()
+ defer ec.Unlock()
+
+ return &endpointCnt{
+ n: ec.n,
+ }
+}
+
+func (ec *endpointCnt) CopyTo(o datastore.KVObject) error {
+ ec.Lock()
+ defer ec.Unlock()
+
+ dstEc := o.(*endpointCnt)
+ dstEc.n = ec.n
+ dstEc.Count = ec.Count
+ dstEc.dbExists = ec.dbExists
+ dstEc.dbIndex = ec.dbIndex
+
+ return nil
+}
+
+func (ec *endpointCnt) DataScope() string {
+ return ec.n.DataScope()
+}
+
+func (ec *endpointCnt) EndpointCnt() uint64 {
+ ec.Lock()
+ defer ec.Unlock()
+
+ return ec.Count
+}
+
+func (ec *endpointCnt) updateStore() error {
+ store := ec.n.getController().getStore(ec.DataScope())
+ if store == nil {
+ return fmt.Errorf("store not found for scope %s on endpoint count update", ec.DataScope())
+ }
+ // make a copy of count and n to avoid being overwritten by store.GetObject
+ count := ec.EndpointCnt()
+ n := ec.n
+ for {
+ if err := ec.n.getController().updateToStore(ec); err == nil || err != datastore.ErrKeyModified {
+ return err
+ }
+ if err := store.GetObject(datastore.Key(ec.Key()...), ec); err != nil {
+ return fmt.Errorf("could not update the kvobject to latest on endpoint count update: %v", err)
+ }
+ ec.Lock()
+ ec.Count = count
+ ec.n = n
+ ec.Unlock()
+ }
+}
+
+func (ec *endpointCnt) setCnt(cnt uint64) error {
+ ec.Lock()
+ ec.Count = cnt
+ ec.Unlock()
+ return ec.updateStore()
+}
+
+func (ec *endpointCnt) atomicIncDecEpCnt(inc bool) error {
+ store := ec.n.getController().getStore(ec.DataScope())
+ if store == nil {
+ return fmt.Errorf("store not found for scope %s", ec.DataScope())
+ }
+
+ tmp := &endpointCnt{n: ec.n}
+ if err := store.GetObject(datastore.Key(ec.Key()...), tmp); err != nil {
+ return err
+ }
+retry:
+ ec.Lock()
+ if inc {
+ ec.Count++
+ } else {
+ if ec.Count > 0 {
+ ec.Count--
+ }
+ }
+ ec.Unlock()
+
+ if err := ec.n.getController().updateToStore(ec); err != nil {
+ if err == datastore.ErrKeyModified {
+ if err := store.GetObject(datastore.Key(ec.Key()...), ec); err != nil {
+ return fmt.Errorf("could not update the kvobject to latest when trying to atomic add endpoint count: %v", err)
+ }
+
+ goto retry
+ }
+
+ return err
+ }
+
+ return nil
+}
+
+func (ec *endpointCnt) IncEndpointCnt() error {
+ return ec.atomicIncDecEpCnt(true)
+}
+
+func (ec *endpointCnt) DecEndpointCnt() error {
+ return ec.atomicIncDecEpCnt(false)
+}
--- /dev/null
+package libnetwork
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/types"
+)
+
+// EndpointInfo provides an interface to retrieve network resources bound to the endpoint.
+type EndpointInfo interface {
+ // Iface returns InterfaceInfo, go interface that can be used
+ // to get more information on the interface which was assigned to
+ // the endpoint by the driver. This can be used after the
+ // endpoint has been created.
+ Iface() InterfaceInfo
+
+ // Gateway returns the IPv4 gateway assigned by the driver.
+ // This will only return a valid value if a container has joined the endpoint.
+ Gateway() net.IP
+
+ // GatewayIPv6 returns the IPv6 gateway assigned by the driver.
+ // This will only return a valid value if a container has joined the endpoint.
+ GatewayIPv6() net.IP
+
+ // StaticRoutes returns the list of static routes configured by the network
+ // driver when the container joins a network
+ StaticRoutes() []*types.StaticRoute
+
+ // Sandbox returns the attached sandbox if there, nil otherwise.
+ Sandbox() Sandbox
+
+ // LoadBalancer returns whether the endpoint is the load balancer endpoint for the network.
+ LoadBalancer() bool
+}
+
+// InterfaceInfo provides an interface to retrieve interface addresses bound to the endpoint.
+type InterfaceInfo interface {
+ // MacAddress returns the MAC address assigned to the endpoint.
+ MacAddress() net.HardwareAddr
+
+ // Address returns the IPv4 address assigned to the endpoint.
+ Address() *net.IPNet
+
+ // AddressIPv6 returns the IPv6 address assigned to the endpoint.
+ AddressIPv6() *net.IPNet
+
+ // LinkLocalAddresses returns the list of link-local (IPv4/IPv6) addresses assigned to the endpoint.
+ LinkLocalAddresses() []*net.IPNet
+
+ // SrcName returns the name of the interface w/in the container
+ SrcName() string
+}
+
+type endpointInterface struct {
+ mac net.HardwareAddr
+ addr *net.IPNet
+ addrv6 *net.IPNet
+ llAddrs []*net.IPNet
+ srcName string
+ dstPrefix string
+ routes []*net.IPNet
+ v4PoolID string
+ v6PoolID string
+}
+
+func (epi *endpointInterface) MarshalJSON() ([]byte, error) {
+ epMap := make(map[string]interface{})
+ if epi.mac != nil {
+ epMap["mac"] = epi.mac.String()
+ }
+ if epi.addr != nil {
+ epMap["addr"] = epi.addr.String()
+ }
+ if epi.addrv6 != nil {
+ epMap["addrv6"] = epi.addrv6.String()
+ }
+ if len(epi.llAddrs) != 0 {
+ list := make([]string, 0, len(epi.llAddrs))
+ for _, ll := range epi.llAddrs {
+ list = append(list, ll.String())
+ }
+ epMap["llAddrs"] = list
+ }
+ epMap["srcName"] = epi.srcName
+ epMap["dstPrefix"] = epi.dstPrefix
+ var routes []string
+ for _, route := range epi.routes {
+ routes = append(routes, route.String())
+ }
+ epMap["routes"] = routes
+ epMap["v4PoolID"] = epi.v4PoolID
+ epMap["v6PoolID"] = epi.v6PoolID
+ return json.Marshal(epMap)
+}
+
+func (epi *endpointInterface) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ epMap map[string]interface{}
+ )
+ if err = json.Unmarshal(b, &epMap); err != nil {
+ return err
+ }
+ if v, ok := epMap["mac"]; ok {
+ if epi.mac, err = net.ParseMAC(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode endpoint interface mac address after json unmarshal: %s", v.(string))
+ }
+ }
+ if v, ok := epMap["addr"]; ok {
+ if epi.addr, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode endpoint interface ipv4 address after json unmarshal: %v", err)
+ }
+ }
+ if v, ok := epMap["addrv6"]; ok {
+ if epi.addrv6, err = types.ParseCIDR(v.(string)); err != nil {
+ return types.InternalErrorf("failed to decode endpoint interface ipv6 address after json unmarshal: %v", err)
+ }
+ }
+ if v, ok := epMap["llAddrs"]; ok {
+ list := v.([]interface{})
+ epi.llAddrs = make([]*net.IPNet, 0, len(list))
+ for _, llS := range list {
+ ll, err := types.ParseCIDR(llS.(string))
+ if err != nil {
+ return types.InternalErrorf("failed to decode endpoint interface link-local address (%v) after json unmarshal: %v", llS, err)
+ }
+ epi.llAddrs = append(epi.llAddrs, ll)
+ }
+ }
+ epi.srcName = epMap["srcName"].(string)
+ epi.dstPrefix = epMap["dstPrefix"].(string)
+
+ rb, _ := json.Marshal(epMap["routes"])
+ var routes []string
+ json.Unmarshal(rb, &routes)
+ epi.routes = make([]*net.IPNet, 0)
+ for _, route := range routes {
+ ip, ipr, err := net.ParseCIDR(route)
+ if err == nil {
+ ipr.IP = ip
+ epi.routes = append(epi.routes, ipr)
+ }
+ }
+ epi.v4PoolID = epMap["v4PoolID"].(string)
+ epi.v6PoolID = epMap["v6PoolID"].(string)
+
+ return nil
+}
+
+func (epi *endpointInterface) CopyTo(dstEpi *endpointInterface) error {
+ dstEpi.mac = types.GetMacCopy(epi.mac)
+ dstEpi.addr = types.GetIPNetCopy(epi.addr)
+ dstEpi.addrv6 = types.GetIPNetCopy(epi.addrv6)
+ dstEpi.srcName = epi.srcName
+ dstEpi.dstPrefix = epi.dstPrefix
+ dstEpi.v4PoolID = epi.v4PoolID
+ dstEpi.v6PoolID = epi.v6PoolID
+ if len(epi.llAddrs) != 0 {
+ dstEpi.llAddrs = make([]*net.IPNet, 0, len(epi.llAddrs))
+ dstEpi.llAddrs = append(dstEpi.llAddrs, epi.llAddrs...)
+ }
+
+ for _, route := range epi.routes {
+ dstEpi.routes = append(dstEpi.routes, types.GetIPNetCopy(route))
+ }
+
+ return nil
+}
+
+type endpointJoinInfo struct {
+ gw net.IP
+ gw6 net.IP
+ StaticRoutes []*types.StaticRoute
+ driverTableEntries []*tableEntry
+ disableGatewayService bool
+}
+
+type tableEntry struct {
+ tableName string
+ key string
+ value []byte
+}
+
+func (ep *endpoint) Info() EndpointInfo {
+ if ep.sandboxID != "" {
+ return ep
+ }
+ n, err := ep.getNetworkFromStore()
+ if err != nil {
+ return nil
+ }
+
+ ep, err = n.getEndpointFromStore(ep.ID())
+ if err != nil {
+ return nil
+ }
+
+ sb, ok := ep.getSandbox()
+ if !ok {
+ // endpoint hasn't joined any sandbox.
+ // Just return the endpoint
+ return ep
+ }
+
+ return sb.getEndpoint(ep.ID())
+}
+
+func (ep *endpoint) Iface() InterfaceInfo {
+ ep.Lock()
+ defer ep.Unlock()
+
+ if ep.iface != nil {
+ return ep.iface
+ }
+
+ return nil
+}
+
+func (ep *endpoint) Interface() driverapi.InterfaceInfo {
+ ep.Lock()
+ defer ep.Unlock()
+
+ if ep.iface != nil {
+ return ep.iface
+ }
+
+ return nil
+}
+
+func (epi *endpointInterface) SetMacAddress(mac net.HardwareAddr) error {
+ if epi.mac != nil {
+ return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", epi.mac, mac)
+ }
+ if mac == nil {
+ return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
+ }
+ epi.mac = types.GetMacCopy(mac)
+ return nil
+}
+
+func (epi *endpointInterface) SetIPAddress(address *net.IPNet) error {
+ if address.IP == nil {
+ return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
+ }
+ if address.IP.To4() == nil {
+ return setAddress(&epi.addrv6, address)
+ }
+ return setAddress(&epi.addr, address)
+}
+
+func setAddress(ifaceAddr **net.IPNet, address *net.IPNet) error {
+ if *ifaceAddr != nil {
+ return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
+ }
+ *ifaceAddr = types.GetIPNetCopy(address)
+ return nil
+}
+
+func (epi *endpointInterface) MacAddress() net.HardwareAddr {
+ return types.GetMacCopy(epi.mac)
+}
+
+func (epi *endpointInterface) Address() *net.IPNet {
+ return types.GetIPNetCopy(epi.addr)
+}
+
+func (epi *endpointInterface) AddressIPv6() *net.IPNet {
+ return types.GetIPNetCopy(epi.addrv6)
+}
+
+func (epi *endpointInterface) LinkLocalAddresses() []*net.IPNet {
+ return epi.llAddrs
+}
+
+func (epi *endpointInterface) SrcName() string {
+ return epi.srcName
+}
+
+func (epi *endpointInterface) SetNames(srcName string, dstPrefix string) error {
+ epi.srcName = srcName
+ epi.dstPrefix = dstPrefix
+ return nil
+}
+
+func (ep *endpoint) InterfaceName() driverapi.InterfaceNameInfo {
+ ep.Lock()
+ defer ep.Unlock()
+
+ if ep.iface != nil {
+ return ep.iface
+ }
+
+ return nil
+}
+
+func (ep *endpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP) error {
+ ep.Lock()
+ defer ep.Unlock()
+
+ r := types.StaticRoute{Destination: destination, RouteType: routeType, NextHop: nextHop}
+
+ if routeType == types.NEXTHOP {
+ // If the route specifies a next-hop, then it's loosely routed (i.e. not bound to a particular interface).
+ ep.joinInfo.StaticRoutes = append(ep.joinInfo.StaticRoutes, &r)
+ } else {
+ // If the route doesn't specify a next-hop, it must be a connected route, bound to an interface.
+ ep.iface.routes = append(ep.iface.routes, r.Destination)
+ }
+ return nil
+}
+
+func (ep *endpoint) AddTableEntry(tableName, key string, value []byte) error {
+ ep.Lock()
+ defer ep.Unlock()
+
+ ep.joinInfo.driverTableEntries = append(ep.joinInfo.driverTableEntries, &tableEntry{
+ tableName: tableName,
+ key: key,
+ value: value,
+ })
+
+ return nil
+}
+
+func (ep *endpoint) Sandbox() Sandbox {
+ cnt, ok := ep.getSandbox()
+ if !ok {
+ return nil
+ }
+ return cnt
+}
+
+func (ep *endpoint) LoadBalancer() bool {
+ ep.Lock()
+ defer ep.Unlock()
+ return ep.loadBalancer
+}
+
+func (ep *endpoint) StaticRoutes() []*types.StaticRoute {
+ ep.Lock()
+ defer ep.Unlock()
+
+ if ep.joinInfo == nil {
+ return nil
+ }
+
+ return ep.joinInfo.StaticRoutes
+}
+
+func (ep *endpoint) Gateway() net.IP {
+ ep.Lock()
+ defer ep.Unlock()
+
+ if ep.joinInfo == nil {
+ return net.IP{}
+ }
+
+ return types.GetIPCopy(ep.joinInfo.gw)
+}
+
+func (ep *endpoint) GatewayIPv6() net.IP {
+ ep.Lock()
+ defer ep.Unlock()
+
+ if ep.joinInfo == nil {
+ return net.IP{}
+ }
+
+ return types.GetIPCopy(ep.joinInfo.gw6)
+}
+
+func (ep *endpoint) SetGateway(gw net.IP) error {
+ ep.Lock()
+ defer ep.Unlock()
+
+ ep.joinInfo.gw = types.GetIPCopy(gw)
+ return nil
+}
+
+func (ep *endpoint) SetGatewayIPv6(gw6 net.IP) error {
+ ep.Lock()
+ defer ep.Unlock()
+
+ ep.joinInfo.gw6 = types.GetIPCopy(gw6)
+ return nil
+}
+
+func (ep *endpoint) retrieveFromStore() (*endpoint, error) {
+ n, err := ep.getNetworkFromStore()
+ if err != nil {
+ return nil, fmt.Errorf("could not find network in store to get latest endpoint %s: %v", ep.Name(), err)
+ }
+ return n.getEndpointFromStore(ep.ID())
+}
+
+func (ep *endpoint) DisableGatewayService() {
+ ep.Lock()
+ defer ep.Unlock()
+
+ ep.joinInfo.disableGatewayService = true
+}
+
+func (epj *endpointJoinInfo) MarshalJSON() ([]byte, error) {
+ epMap := make(map[string]interface{})
+ if epj.gw != nil {
+ epMap["gw"] = epj.gw.String()
+ }
+ if epj.gw6 != nil {
+ epMap["gw6"] = epj.gw6.String()
+ }
+ epMap["disableGatewayService"] = epj.disableGatewayService
+ epMap["StaticRoutes"] = epj.StaticRoutes
+ return json.Marshal(epMap)
+}
+
+func (epj *endpointJoinInfo) UnmarshalJSON(b []byte) error {
+ var (
+ err error
+ epMap map[string]interface{}
+ )
+ if err = json.Unmarshal(b, &epMap); err != nil {
+ return err
+ }
+ if v, ok := epMap["gw"]; ok {
+ epj.gw = net.ParseIP(v.(string))
+ }
+ if v, ok := epMap["gw6"]; ok {
+ epj.gw6 = net.ParseIP(v.(string))
+ }
+ epj.disableGatewayService = epMap["disableGatewayService"].(bool)
+
+ var tStaticRoute []types.StaticRoute
+ if v, ok := epMap["StaticRoutes"]; ok {
+ tb, _ := json.Marshal(v)
+ var tStaticRoute []types.StaticRoute
+ json.Unmarshal(tb, &tStaticRoute)
+ }
+ var StaticRoutes []*types.StaticRoute
+ for _, r := range tStaticRoute {
+ StaticRoutes = append(StaticRoutes, &r)
+ }
+ epj.StaticRoutes = StaticRoutes
+
+ return nil
+}
+
+func (epj *endpointJoinInfo) CopyTo(dstEpj *endpointJoinInfo) error {
+ dstEpj.disableGatewayService = epj.disableGatewayService
+ dstEpj.StaticRoutes = make([]*types.StaticRoute, len(epj.StaticRoutes))
+ copy(dstEpj.StaticRoutes, epj.StaticRoutes)
+ dstEpj.driverTableEntries = make([]*tableEntry, len(epj.driverTableEntries))
+ copy(dstEpj.driverTableEntries, epj.driverTableEntries)
+ dstEpj.gw = types.GetIPCopy(epj.gw)
+ dstEpj.gw6 = types.GetIPCopy(epj.gw6)
+ return nil
+}
--- /dev/null
+// +build !windows
+
+package libnetwork
+
+import "fmt"
+
+func (ep *endpoint) DriverInfo() (map[string]interface{}, error) {
+ ep, err := ep.retrieveFromStore()
+ if err != nil {
+ return nil, err
+ }
+
+ if sb, ok := ep.getSandbox(); ok {
+ if gwep := sb.getEndpointInGWNetwork(); gwep != nil && gwep.ID() != ep.ID() {
+ return gwep.DriverInfo()
+ }
+ }
+
+ n, err := ep.getNetworkFromStore()
+ if err != nil {
+ return nil, fmt.Errorf("could not find network in store for driver info: %v", err)
+ }
+
+ driver, err := n.driver(true)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get driver info: %v", err)
+ }
+
+ return driver.EndpointOperInfo(n.ID(), ep.ID())
+}
--- /dev/null
+// +build windows
+
+package libnetwork
+
+import "fmt"
+
+func (ep *endpoint) DriverInfo() (map[string]interface{}, error) {
+ ep, err := ep.retrieveFromStore()
+ if err != nil {
+ return nil, err
+ }
+
+ var gwDriverInfo map[string]interface{}
+ if sb, ok := ep.getSandbox(); ok {
+ if gwep := sb.getEndpointInGWNetwork(); gwep != nil && gwep.ID() != ep.ID() {
+
+ gwDriverInfo, err = gwep.DriverInfo()
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ n, err := ep.getNetworkFromStore()
+ if err != nil {
+ return nil, fmt.Errorf("could not find network in store for driver info: %v", err)
+ }
+
+ driver, err := n.driver(true)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get driver info: %v", err)
+ }
+
+ epInfo, err := driver.EndpointOperInfo(n.ID(), ep.ID())
+ if err != nil {
+ return nil, err
+ }
+
+ if epInfo != nil {
+ epInfo["GW_INFO"] = gwDriverInfo
+ return epInfo, nil
+ }
+
+ return gwDriverInfo, nil
+}
--- /dev/null
+package libnetwork
+
+import (
+ "fmt"
+)
+
+// ErrNoSuchNetwork is returned when a network query finds no result
+type ErrNoSuchNetwork string
+
+func (nsn ErrNoSuchNetwork) Error() string {
+ return fmt.Sprintf("network %s not found", string(nsn))
+}
+
+// NotFound denotes the type of this error
+func (nsn ErrNoSuchNetwork) NotFound() {}
+
+// ErrNoSuchEndpoint is returned when an endpoint query finds no result
+type ErrNoSuchEndpoint string
+
+func (nse ErrNoSuchEndpoint) Error() string {
+ return fmt.Sprintf("endpoint %s not found", string(nse))
+}
+
+// NotFound denotes the type of this error
+func (nse ErrNoSuchEndpoint) NotFound() {}
+
+// ErrInvalidNetworkDriver is returned if an invalid driver
+// name is passed.
+type ErrInvalidNetworkDriver string
+
+func (ind ErrInvalidNetworkDriver) Error() string {
+ return fmt.Sprintf("invalid driver bound to network: %s", string(ind))
+}
+
+// BadRequest denotes the type of this error
+func (ind ErrInvalidNetworkDriver) BadRequest() {}
+
+// ErrInvalidJoin is returned if a join is attempted on an endpoint
+// which already has a container joined.
+type ErrInvalidJoin struct{}
+
+func (ij ErrInvalidJoin) Error() string {
+ return "a container has already joined the endpoint"
+}
+
+// BadRequest denotes the type of this error
+func (ij ErrInvalidJoin) BadRequest() {}
+
+// ErrNoContainer is returned when the endpoint has no container
+// attached to it.
+type ErrNoContainer struct{}
+
+func (nc ErrNoContainer) Error() string {
+ return "no container is attached to the endpoint"
+}
+
+// Maskable denotes the type of this error
+func (nc ErrNoContainer) Maskable() {}
+
+// ErrInvalidID is returned when a query-by-id method is being invoked
+// with an empty id parameter
+type ErrInvalidID string
+
+func (ii ErrInvalidID) Error() string {
+ return fmt.Sprintf("invalid id: %s", string(ii))
+}
+
+// BadRequest denotes the type of this error
+func (ii ErrInvalidID) BadRequest() {}
+
+// ErrInvalidName is returned when a query-by-name or resource create method is
+// invoked with an empty name parameter
+type ErrInvalidName string
+
+func (in ErrInvalidName) Error() string {
+ return fmt.Sprintf("invalid name: %s", string(in))
+}
+
+// BadRequest denotes the type of this error
+func (in ErrInvalidName) BadRequest() {}
+
+// ErrInvalidConfigFile type is returned when an invalid LibNetwork config file is detected
+type ErrInvalidConfigFile string
+
+func (cf ErrInvalidConfigFile) Error() string {
+ return fmt.Sprintf("Invalid Config file %q", string(cf))
+}
+
+// NetworkTypeError type is returned when the network type string is not
+// known to libnetwork.
+type NetworkTypeError string
+
+func (nt NetworkTypeError) Error() string {
+ return fmt.Sprintf("unknown driver %q", string(nt))
+}
+
+// NotFound denotes the type of this error
+func (nt NetworkTypeError) NotFound() {}
+
+// NetworkNameError is returned when a network with the same name already exists.
+type NetworkNameError string
+
+func (nnr NetworkNameError) Error() string {
+ return fmt.Sprintf("network with name %s already exists", string(nnr))
+}
+
+// Forbidden denotes the type of this error
+func (nnr NetworkNameError) Forbidden() {}
+
+// UnknownNetworkError is returned when libnetwork could not find in its database
+// a network with the same name and id.
+type UnknownNetworkError struct {
+ name string
+ id string
+}
+
+func (une *UnknownNetworkError) Error() string {
+ return fmt.Sprintf("unknown network %s id %s", une.name, une.id)
+}
+
+// NotFound denotes the type of this error
+func (une *UnknownNetworkError) NotFound() {}
+
+// ActiveEndpointsError is returned when a network is deleted which has active
+// endpoints in it.
+type ActiveEndpointsError struct {
+ name string
+ id string
+}
+
+func (aee *ActiveEndpointsError) Error() string {
+ return fmt.Sprintf("network %s id %s has active endpoints", aee.name, aee.id)
+}
+
+// Forbidden denotes the type of this error
+func (aee *ActiveEndpointsError) Forbidden() {}
+
+// UnknownEndpointError is returned when libnetwork could not find in its database
+// an endpoint with the same name and id.
+type UnknownEndpointError struct {
+ name string
+ id string
+}
+
+func (uee *UnknownEndpointError) Error() string {
+ return fmt.Sprintf("unknown endpoint %s id %s", uee.name, uee.id)
+}
+
+// NotFound denotes the type of this error
+func (uee *UnknownEndpointError) NotFound() {}
+
+// ActiveContainerError is returned when an endpoint is deleted which has active
+// containers attached to it.
+type ActiveContainerError struct {
+ name string
+ id string
+}
+
+func (ace *ActiveContainerError) Error() string {
+ return fmt.Sprintf("endpoint with name %s id %s has active containers", ace.name, ace.id)
+}
+
+// Forbidden denotes the type of this error
+func (ace *ActiveContainerError) Forbidden() {}
+
+// InvalidContainerIDError is returned when an invalid container id is passed
+// in Join/Leave
+type InvalidContainerIDError string
+
+func (id InvalidContainerIDError) Error() string {
+ return fmt.Sprintf("invalid container id %s", string(id))
+}
+
+// BadRequest denotes the type of this error
+func (id InvalidContainerIDError) BadRequest() {}
+
+// ManagerRedirectError is returned when the request should be redirected to Manager
+type ManagerRedirectError string
+
+func (mr ManagerRedirectError) Error() string {
+ return "Redirect the request to the manager"
+}
+
+// Maskable denotes the type of this error
+func (mr ManagerRedirectError) Maskable() {}
+
+// ErrDataStoreNotInitialized is returned if an invalid data scope is passed
+// for getting data store
+type ErrDataStoreNotInitialized string
+
+func (dsni ErrDataStoreNotInitialized) Error() string {
+ return fmt.Sprintf("datastore for scope %q is not initialized", string(dsni))
+}
--- /dev/null
+package libnetwork
+
+import (
+ "testing"
+
+ "github.com/docker/libnetwork/types"
+)
+
+func TestErrorInterfaces(t *testing.T) {
+
+ badRequestErrorList := []error{ErrInvalidID(""), ErrInvalidName(""), ErrInvalidJoin{}, ErrInvalidNetworkDriver(""), InvalidContainerIDError(""), ErrNoSuchNetwork(""), ErrNoSuchEndpoint("")}
+ for _, err := range badRequestErrorList {
+ switch u := err.(type) {
+ case types.BadRequestError:
+ return
+ default:
+ t.Fatalf("Failed to detect err %v is of type BadRequestError. Got type: %T", err, u)
+ }
+ }
+
+ maskableErrorList := []error{ErrNoContainer{}}
+ for _, err := range maskableErrorList {
+ switch u := err.(type) {
+ case types.MaskableError:
+ return
+ default:
+ t.Fatalf("Failed to detect err %v is of type MaskableError. Got type: %T", err, u)
+ }
+ }
+
+ notFoundErrorList := []error{NetworkTypeError(""), &UnknownNetworkError{}, &UnknownEndpointError{}}
+ for _, err := range notFoundErrorList {
+ switch u := err.(type) {
+ case types.NotFoundError:
+ return
+ default:
+ t.Fatalf("Failed to detect err %v is of type NotFoundError. Got type: %T", err, u)
+ }
+ }
+
+ forbiddenErrorList := []error{NetworkTypeError(""), &UnknownNetworkError{}, &UnknownEndpointError{}}
+ for _, err := range forbiddenErrorList {
+ switch u := err.(type) {
+ case types.ForbiddenError:
+ return
+ default:
+ t.Fatalf("Failed to detect err %v is of type ForbiddenError. Got type: %T", err, u)
+ }
+ }
+
+}
--- /dev/null
+package etchosts
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "regexp"
+ "strings"
+ "sync"
+)
+
+// Record Structure for a single host record
+type Record struct {
+ Hosts string
+ IP string
+}
+
+// WriteTo writes record to file and returns bytes written or error
+func (r Record) WriteTo(w io.Writer) (int64, error) {
+ n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts)
+ return int64(n), err
+}
+
+var (
+ // Default hosts config records slice
+ defaultContent = []Record{
+ {Hosts: "localhost", IP: "127.0.0.1"},
+ {Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"},
+ {Hosts: "ip6-localnet", IP: "fe00::0"},
+ {Hosts: "ip6-mcastprefix", IP: "ff00::0"},
+ {Hosts: "ip6-allnodes", IP: "ff02::1"},
+ {Hosts: "ip6-allrouters", IP: "ff02::2"},
+ }
+
+ // A cache of path level locks for synchronizing /etc/hosts
+ // updates on a file level
+ pathMap = make(map[string]*sync.Mutex)
+
+ // A package level mutex to synchronize the cache itself
+ pathMutex sync.Mutex
+)
+
+func pathLock(path string) func() {
+ pathMutex.Lock()
+ defer pathMutex.Unlock()
+
+ pl, ok := pathMap[path]
+ if !ok {
+ pl = &sync.Mutex{}
+ pathMap[path] = pl
+ }
+
+ pl.Lock()
+ return func() {
+ pl.Unlock()
+ }
+}
+
+// Drop drops the path string from the path cache
+func Drop(path string) {
+ pathMutex.Lock()
+ defer pathMutex.Unlock()
+
+ delete(pathMap, path)
+}
+
+// Build function
+// path is path to host file string required
+// IP, hostname, and domainname set main record leave empty for no master record
+// extraContent is an array of extra host records.
+func Build(path, IP, hostname, domainname string, extraContent []Record) error {
+ defer pathLock(path)()
+
+ content := bytes.NewBuffer(nil)
+ if IP != "" {
+ //set main record
+ var mainRec Record
+ mainRec.IP = IP
+ // User might have provided a FQDN in hostname or split it across hostname
+ // and domainname. We want the FQDN and the bare hostname.
+ fqdn := hostname
+ if domainname != "" {
+ fqdn = fmt.Sprintf("%s.%s", fqdn, domainname)
+ }
+ parts := strings.SplitN(fqdn, ".", 2)
+ if len(parts) == 2 {
+ mainRec.Hosts = fmt.Sprintf("%s %s", fqdn, parts[0])
+ } else {
+ mainRec.Hosts = fqdn
+ }
+ if _, err := mainRec.WriteTo(content); err != nil {
+ return err
+ }
+ }
+ // Write defaultContent slice to buffer
+ for _, r := range defaultContent {
+ if _, err := r.WriteTo(content); err != nil {
+ return err
+ }
+ }
+ // Write extra content from function arguments
+ for _, r := range extraContent {
+ if _, err := r.WriteTo(content); err != nil {
+ return err
+ }
+ }
+
+ return ioutil.WriteFile(path, content.Bytes(), 0644)
+}
+
+// Add adds an arbitrary number of Records to an already existing /etc/hosts file
+func Add(path string, recs []Record) error {
+ defer pathLock(path)()
+
+ if len(recs) == 0 {
+ return nil
+ }
+
+ b, err := mergeRecords(path, recs)
+ if err != nil {
+ return err
+ }
+
+ return ioutil.WriteFile(path, b, 0644)
+}
+
+func mergeRecords(path string, recs []Record) ([]byte, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ content := bytes.NewBuffer(nil)
+
+ if _, err := content.ReadFrom(f); err != nil {
+ return nil, err
+ }
+
+ for _, r := range recs {
+ if _, err := r.WriteTo(content); err != nil {
+ return nil, err
+ }
+ }
+
+ return content.Bytes(), nil
+}
+
+// Delete deletes an arbitrary number of Records already existing in /etc/hosts file
+func Delete(path string, recs []Record) error {
+ defer pathLock(path)()
+
+ if len(recs) == 0 {
+ return nil
+ }
+ old, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+
+ s := bufio.NewScanner(old)
+ eol := []byte{'\n'}
+loop:
+ for s.Scan() {
+ b := s.Bytes()
+ if len(b) == 0 {
+ continue
+ }
+
+ if b[0] == '#' {
+ buf.Write(b)
+ buf.Write(eol)
+ continue
+ }
+ for _, r := range recs {
+ if bytes.HasSuffix(b, []byte("\t"+r.Hosts)) {
+ continue loop
+ }
+ }
+ buf.Write(b)
+ buf.Write(eol)
+ }
+ old.Close()
+ if err := s.Err(); err != nil {
+ return err
+ }
+ return ioutil.WriteFile(path, buf.Bytes(), 0644)
+}
+
+// Update all IP addresses where hostname matches.
+// path is path to host file
+// IP is new IP address
+// hostname is hostname to search for to replace IP
+func Update(path, IP, hostname string) error {
+ defer pathLock(path)()
+
+ old, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ var re = regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)(\\s|\\.)", regexp.QuoteMeta(hostname)))
+ return ioutil.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2"+"$3")), 0644)
+}
--- /dev/null
+package etchosts
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "sync"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func TestBuildDefault(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ // check that /etc/hosts has consistent ordering
+ for i := 0; i <= 5; i++ {
+ err = Build(file.Name(), "", "", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n"
+
+ if expected != string(content) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+ }
+}
+
+func TestBuildHostnameDomainname(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+}
+
+func TestBuildHostname(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "10.11.12.13", "testhostname", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "10.11.12.13\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+}
+
+func TestBuildHostnameFQDN(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "10.11.12.13", "testhostname.testdomainname.com", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "10.11.12.13\ttesthostname.testdomainname.com testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+}
+
+func TestBuildNoIP(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "", "testhostname", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := ""; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+}
+
+func TestUpdate(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil); err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+
+ if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil {
+ t.Fatal(err)
+ }
+
+ content, err = ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+}
+
+// This regression test ensures that when a host is given a new IP
+// via the Update function that other hosts which start with the
+// same name as the targeted host are not erroneously updated as well.
+// In the test example, if updating a host called "prefix", unrelated
+// hosts named "prefixAndMore" or "prefix2" or anything else starting
+// with "prefix" should not be changed. For more information see
+// GitHub issue #603.
+func TestUpdateIgnoresPrefixedHostname(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", []Record{
+ {
+ Hosts: "prefix",
+ IP: "2.2.2.2",
+ },
+ {
+ Hosts: "prefixAndMore",
+ IP: "3.3.3.3",
+ },
+ {
+ Hosts: "unaffectedHost",
+ IP: "4.4.4.4",
+ },
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "2.2.2.2\tprefix\n3.3.3.3\tprefixAndMore\n4.4.4.4\tunaffectedHost\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+
+ if err := Update(file.Name(), "5.5.5.5", "prefix"); err != nil {
+ t.Fatal(err)
+ }
+
+ content, err = ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "5.5.5.5\tprefix\n3.3.3.3\tprefixAndMore\n4.4.4.4\tunaffectedHost\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+
+}
+
+// This regression test covers the host prefix issue for the
+// Delete function. In the test example, if deleting a host called
+// "prefix", an unrelated host called "prefixAndMore" should not
+// be deleted. For more information see GitHub issue #603.
+func TestDeleteIgnoresPrefixedHostname(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "", "", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := Add(file.Name(), []Record{
+ {
+ Hosts: "prefix",
+ IP: "1.1.1.1",
+ },
+ {
+ Hosts: "prefixAndMore",
+ IP: "2.2.2.2",
+ },
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := Delete(file.Name(), []Record{
+ {
+ Hosts: "prefix",
+ IP: "1.1.1.1",
+ },
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "2.2.2.2\tprefixAndMore\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+
+ if expected := "1.1.1.1\tprefix\n"; bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Did not expect to find '%s' got '%s'", expected, content)
+ }
+}
+
+func TestAddEmpty(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "", "", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := Add(file.Name(), []Record{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestAdd(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "", "", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := Add(file.Name(), []Record{
+ {
+ Hosts: "testhostname",
+ IP: "2.2.2.2",
+ },
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "2.2.2.2\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+}
+
+func TestDeleteEmpty(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "", "", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := Delete(file.Name(), []Record{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestDeleteNewline(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ b := []byte("\n")
+ if _, err := file.Write(b); err != nil {
+ t.Fatal(err)
+ }
+
+ rec := []Record{
+ {
+ Hosts: "prefix",
+ IP: "2.2.2.2",
+ },
+ }
+ if err := Delete(file.Name(), rec); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestDelete(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "", "", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := Add(file.Name(), []Record{
+ {
+ Hosts: "testhostname1",
+ IP: "1.1.1.1",
+ },
+ {
+ Hosts: "testhostname2",
+ IP: "2.2.2.2",
+ },
+ {
+ Hosts: "testhostname3",
+ IP: "3.3.3.3",
+ },
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := Delete(file.Name(), []Record{
+ {
+ Hosts: "testhostname1",
+ IP: "1.1.1.1",
+ },
+ {
+ Hosts: "testhostname3",
+ IP: "3.3.3.3",
+ },
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "2.2.2.2\ttesthostname2\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+
+ if expected := "1.1.1.1\ttesthostname1\n"; bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Did not expect to find '%s' got '%s'", expected, content)
+ }
+}
+
+func TestConcurrentWrites(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ err = Build(file.Name(), "", "", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := Add(file.Name(), []Record{
+ {
+ Hosts: "inithostname",
+ IP: "172.17.0.1",
+ },
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+
+ rec := []Record{
+ {
+ IP: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i),
+ Hosts: fmt.Sprintf("testhostname%d", i),
+ },
+ }
+
+ for j := 0; j < 25; j++ {
+ if err := Add(file.Name(), rec); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := Delete(file.Name(), rec); err != nil {
+ t.Fatal(err)
+ }
+ }
+ }(i)
+ }
+
+ wg.Wait()
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "172.17.0.1\tinithostname\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+}
+
+func benchDelete(b *testing.B) {
+ b.StopTimer()
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer func() {
+ b.StopTimer()
+ file.Close()
+ os.Remove(file.Name())
+ b.StartTimer()
+ }()
+
+ err = Build(file.Name(), "", "", "", nil)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ var records []Record
+ var toDelete []Record
+ for i := 0; i < 255; i++ {
+ record := Record{
+ Hosts: fmt.Sprintf("testhostname%d", i),
+ IP: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i),
+ }
+ records = append(records, record)
+ if i%2 == 0 {
+ toDelete = append(records, record)
+ }
+ }
+
+ if err := Add(file.Name(), records); err != nil {
+ b.Fatal(err)
+ }
+
+ b.StartTimer()
+ if err := Delete(file.Name(), toDelete); err != nil {
+ b.Fatal(err)
+ }
+}
+
+func BenchmarkDelete(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ benchDelete(b)
+ }
+}
--- /dev/null
+package libnetwork
+
+import (
+ "github.com/docker/libnetwork/iptables"
+ "github.com/sirupsen/logrus"
+)
+
+const userChain = "DOCKER-USER"
+
+func (c *controller) arrangeUserFilterRule() {
+ c.Lock()
+ arrangeUserFilterRule()
+ c.Unlock()
+ iptables.OnReloaded(func() {
+ c.Lock()
+ arrangeUserFilterRule()
+ c.Unlock()
+ })
+}
+
+// This chain allow users to configure firewall policies in a way that persists
+// docker operations/restarts. Docker will not delete or modify any pre-existing
+// rules from the DOCKER-USER filter chain.
+func arrangeUserFilterRule() {
+ _, err := iptables.NewChain(userChain, iptables.Filter, false)
+ if err != nil {
+ logrus.Warnf("Failed to create %s chain: %v", userChain, err)
+ return
+ }
+
+ if err = iptables.AddReturnRule(userChain); err != nil {
+ logrus.Warnf("Failed to add the RETURN rule for %s: %v", userChain, err)
+ return
+ }
+
+ err = iptables.EnsureJumpRule("FORWARD", userChain)
+ if err != nil {
+ logrus.Warnf("Failed to ensure the jump rule for %s: %v", userChain, err)
+ }
+}
--- /dev/null
+// +build !linux
+
+package libnetwork
+
+func (c *controller) arrangeUserFilterRule() {
+}
--- /dev/null
+package hostdiscovery
+
+import (
+ "net"
+ "sync"
+
+ "github.com/sirupsen/logrus"
+
+ mapset "github.com/deckarep/golang-set"
+ "github.com/docker/docker/pkg/discovery"
+ // Including KV
+ _ "github.com/docker/docker/pkg/discovery/kv"
+ "github.com/docker/libkv/store/consul"
+ "github.com/docker/libkv/store/etcd"
+ "github.com/docker/libkv/store/zookeeper"
+ "github.com/docker/libnetwork/types"
+)
+
+type hostDiscovery struct {
+ watcher discovery.Watcher
+ nodes mapset.Set
+ stopChan chan struct{}
+ sync.Mutex
+}
+
+func init() {
+ consul.Register()
+ etcd.Register()
+ zookeeper.Register()
+}
+
+// NewHostDiscovery function creates a host discovery object
+func NewHostDiscovery(watcher discovery.Watcher) HostDiscovery {
+ return &hostDiscovery{watcher: watcher, nodes: mapset.NewSet(), stopChan: make(chan struct{})}
+}
+
+func (h *hostDiscovery) Watch(activeCallback ActiveCallback, joinCallback JoinCallback, leaveCallback LeaveCallback) error {
+ h.Lock()
+ d := h.watcher
+ h.Unlock()
+ if d == nil {
+ return types.BadRequestErrorf("invalid discovery watcher")
+ }
+ discoveryCh, errCh := d.Watch(h.stopChan)
+ go h.monitorDiscovery(discoveryCh, errCh, activeCallback, joinCallback, leaveCallback)
+ return nil
+}
+
+func (h *hostDiscovery) monitorDiscovery(ch <-chan discovery.Entries, errCh <-chan error,
+ activeCallback ActiveCallback, joinCallback JoinCallback, leaveCallback LeaveCallback) {
+ for {
+ select {
+ case entries := <-ch:
+ h.processCallback(entries, activeCallback, joinCallback, leaveCallback)
+ case err := <-errCh:
+ if err != nil {
+ logrus.Errorf("discovery error: %v", err)
+ }
+ case <-h.stopChan:
+ return
+ }
+ }
+}
+
+func (h *hostDiscovery) StopDiscovery() error {
+ h.Lock()
+ stopChan := h.stopChan
+ h.watcher = nil
+ h.Unlock()
+
+ close(stopChan)
+ return nil
+}
+
+func (h *hostDiscovery) processCallback(entries discovery.Entries,
+ activeCallback ActiveCallback, joinCallback JoinCallback, leaveCallback LeaveCallback) {
+ updated := hosts(entries)
+ h.Lock()
+ existing := h.nodes
+ added, removed := diff(existing, updated)
+ h.nodes = updated
+ h.Unlock()
+
+ activeCallback()
+ if len(added) > 0 {
+ joinCallback(added)
+ }
+ if len(removed) > 0 {
+ leaveCallback(removed)
+ }
+}
+
+func diff(existing mapset.Set, updated mapset.Set) (added []net.IP, removed []net.IP) {
+ addSlice := updated.Difference(existing).ToSlice()
+ removeSlice := existing.Difference(updated).ToSlice()
+ for _, ip := range addSlice {
+ added = append(added, net.ParseIP(ip.(string)))
+ }
+ for _, ip := range removeSlice {
+ removed = append(removed, net.ParseIP(ip.(string)))
+ }
+ return
+}
+
+func (h *hostDiscovery) Fetch() []net.IP {
+ h.Lock()
+ defer h.Unlock()
+ ips := []net.IP{}
+ for _, ipstr := range h.nodes.ToSlice() {
+ ips = append(ips, net.ParseIP(ipstr.(string)))
+ }
+ return ips
+}
+
+func hosts(entries discovery.Entries) mapset.Set {
+ hosts := mapset.NewSet()
+ for _, entry := range entries {
+ hosts.Add(entry.Host)
+ }
+ return hosts
+}
--- /dev/null
+package hostdiscovery
+
+import "net"
+
+// JoinCallback provides a callback event for new node joining the cluster
+type JoinCallback func(entries []net.IP)
+
+// ActiveCallback provides a callback event for active discovery event
+type ActiveCallback func()
+
+// LeaveCallback provides a callback event for node leaving the cluster
+type LeaveCallback func(entries []net.IP)
+
+// HostDiscovery primary interface
+type HostDiscovery interface {
+ //Watch Node join and leave cluster events
+ Watch(activeCallback ActiveCallback, joinCallback JoinCallback, leaveCallback LeaveCallback) error
+ // StopDiscovery stops the discovery process
+ StopDiscovery() error
+ // Fetch returns a list of host IPs that are currently discovered
+ Fetch() []net.IP
+}
--- /dev/null
+package hostdiscovery
+
+import (
+ "net"
+ "testing"
+
+ mapset "github.com/deckarep/golang-set"
+ _ "github.com/docker/libnetwork/testutils"
+
+ "github.com/docker/docker/pkg/discovery"
+)
+
+func TestDiff(t *testing.T) {
+ existing := mapset.NewSetFromSlice([]interface{}{"1.1.1.1", "2.2.2.2"})
+ addedIP := "3.3.3.3"
+ updated := existing.Clone()
+ updated.Add(addedIP)
+
+ added, removed := diff(existing, updated)
+ if len(added) != 1 {
+ t.Fatalf("Diff failed for an Add update. Expecting 1 element, but got %d elements", len(added))
+ }
+ if added[0].String() != addedIP {
+ t.Fatalf("Expecting : %v, Got : %v", addedIP, added[0])
+ }
+ if len(removed) > 0 {
+ t.Fatalf("Diff failed for remove use-case. Expecting 0 element, but got %d elements", len(removed))
+ }
+
+ updated = mapset.NewSetFromSlice([]interface{}{addedIP})
+ added, removed = diff(existing, updated)
+ if len(removed) != 2 {
+ t.Fatalf("Diff failed for a remove update. Expecting 2 element, but got %d elements", len(removed))
+ }
+ if len(added) != 1 {
+ t.Fatalf("Diff failed for add use-case. Expecting 1 element, but got %d elements", len(added))
+ }
+}
+
+func TestAddedCallback(t *testing.T) {
+ hd := hostDiscovery{}
+ hd.nodes = mapset.NewSetFromSlice([]interface{}{"1.1.1.1"})
+ update := []*discovery.Entry{{Host: "1.1.1.1", Port: "0"}, {Host: "2.2.2.2", Port: "0"}}
+
+ added := false
+ removed := false
+ hd.processCallback(update, func() {}, func(hosts []net.IP) { added = true }, func(hosts []net.IP) { removed = true })
+ if !added {
+ t.Fatal("Expecting an Added callback notification. But none received")
+ }
+ if removed {
+ t.Fatal("Not expecting a Removed callback notification. But received a callback")
+ }
+}
+
+func TestRemovedCallback(t *testing.T) {
+ hd := hostDiscovery{}
+ hd.nodes = mapset.NewSetFromSlice([]interface{}{"1.1.1.1", "2.2.2.2"})
+ update := []*discovery.Entry{{Host: "1.1.1.1", Port: "0"}}
+
+ added := false
+ removed := false
+ hd.processCallback(update, func() {}, func(hosts []net.IP) { added = true }, func(hosts []net.IP) { removed = true })
+ if added {
+ t.Fatal("Not expecting an Added callback notification. But received a callback")
+ }
+ if !removed {
+ t.Fatal("Expecting a Removed callback notification. But none received")
+ }
+}
+
+func TestNoCallback(t *testing.T) {
+ hd := hostDiscovery{}
+ hd.nodes = mapset.NewSetFromSlice([]interface{}{"1.1.1.1", "2.2.2.2"})
+ update := []*discovery.Entry{{Host: "1.1.1.1", Port: "0"}, {Host: "2.2.2.2", Port: "0"}}
+
+ added := false
+ removed := false
+ hd.processCallback(update, func() {}, func(hosts []net.IP) { added = true }, func(hosts []net.IP) { removed = true })
+ if added || removed {
+ t.Fatal("Not expecting any callback notification. But received a callback")
+ }
+}
--- /dev/null
+title = "LibNetwork Configuration file"
+
+[cluster]
+ discovery = "consul://localhost:8500"
+ Address = "6.5.5.5"
+ Heartbeat = 3
--- /dev/null
+// Package idm manages reservation/release of numerical ids from a configured set of contiguous ids
+package idm
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/docker/libnetwork/bitseq"
+ "github.com/docker/libnetwork/datastore"
+)
+
+// Idm manages the reservation/release of numerical ids from a contiguous set
+type Idm struct {
+ start uint64
+ end uint64
+ handle *bitseq.Handle
+}
+
+// New returns an instance of id manager for a [start,end] set of numerical ids
+func New(ds datastore.DataStore, id string, start, end uint64) (*Idm, error) {
+ if id == "" {
+ return nil, errors.New("Invalid id")
+ }
+ if end <= start {
+ return nil, fmt.Errorf("Invalid set range: [%d, %d]", start, end)
+ }
+
+ h, err := bitseq.NewHandle("idm", ds, id, 1+end-start)
+ if err != nil {
+ return nil, fmt.Errorf("failed to initialize bit sequence handler: %s", err.Error())
+ }
+
+ return &Idm{start: start, end: end, handle: h}, nil
+}
+
+// GetID returns the first available id in the set
+func (i *Idm) GetID(serial bool) (uint64, error) {
+ if i.handle == nil {
+ return 0, errors.New("ID set is not initialized")
+ }
+ ordinal, err := i.handle.SetAny(serial)
+ return i.start + ordinal, err
+}
+
+// GetSpecificID tries to reserve the specified id
+func (i *Idm) GetSpecificID(id uint64) error {
+ if i.handle == nil {
+ return errors.New("ID set is not initialized")
+ }
+
+ if id < i.start || id > i.end {
+ return errors.New("Requested id does not belong to the set")
+ }
+
+ return i.handle.Set(id - i.start)
+}
+
+// GetIDInRange returns the first available id in the set within a [start,end] range
+func (i *Idm) GetIDInRange(start, end uint64, serial bool) (uint64, error) {
+ if i.handle == nil {
+ return 0, errors.New("ID set is not initialized")
+ }
+
+ if start < i.start || end > i.end {
+ return 0, errors.New("Requested range does not belong to the set")
+ }
+
+ ordinal, err := i.handle.SetAnyInRange(start-i.start, end-i.start, serial)
+
+ return i.start + ordinal, err
+}
+
+// Release releases the specified id
+func (i *Idm) Release(id uint64) {
+ i.handle.Unset(id - i.start)
+}
--- /dev/null
+package idm
+
+import (
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func TestNew(t *testing.T) {
+ _, err := New(nil, "", 0, 1)
+ if err == nil {
+ t.Fatal("Expected failure, but succeeded")
+ }
+
+ _, err = New(nil, "myset", 1<<10, 0)
+ if err == nil {
+ t.Fatal("Expected failure, but succeeded")
+ }
+
+ i, err := New(nil, "myset", 0, 10)
+ if err != nil {
+ t.Fatalf("Unexpected failure: %v", err)
+ }
+ if i.handle == nil {
+ t.Fatal("set is not initialized")
+ }
+ if i.start != 0 {
+ t.Fatal("unexpected start")
+ }
+ if i.end != 10 {
+ t.Fatal("unexpected end")
+ }
+}
+
+func TestAllocate(t *testing.T) {
+ i, err := New(nil, "myids", 50, 52)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err = i.GetSpecificID(49); err == nil {
+ t.Fatal("Expected failure but succeeded")
+ }
+
+ if err = i.GetSpecificID(53); err == nil {
+ t.Fatal("Expected failure but succeeded")
+ }
+
+ o, err := i.GetID(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 50 {
+ t.Fatalf("Unexpected first id returned: %d", o)
+ }
+
+ err = i.GetSpecificID(50)
+ if err == nil {
+ t.Fatal(err)
+ }
+
+ o, err = i.GetID(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 51 {
+ t.Fatalf("Unexpected id returned: %d", o)
+ }
+
+ o, err = i.GetID(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 52 {
+ t.Fatalf("Unexpected id returned: %d", o)
+ }
+
+ o, err = i.GetID(false)
+ if err == nil {
+ t.Fatalf("Expected failure but succeeded: %d", o)
+ }
+
+ i.Release(50)
+
+ o, err = i.GetID(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 50 {
+ t.Fatal("Unexpected id returned")
+ }
+
+ i.Release(52)
+ err = i.GetSpecificID(52)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestUninitialized(t *testing.T) {
+ i := &Idm{}
+
+ if _, err := i.GetID(false); err == nil {
+ t.Fatal("Expected failure but succeeded")
+ }
+
+ if err := i.GetSpecificID(44); err == nil {
+ t.Fatal("Expected failure but succeeded")
+ }
+}
+
+func TestAllocateInRange(t *testing.T) {
+ i, err := New(nil, "myset", 5, 10)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ o, err := i.GetIDInRange(6, 6, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 6 {
+ t.Fatalf("Unexpected id returned. Expected: 6. Got: %d", o)
+ }
+
+ if err = i.GetSpecificID(6); err == nil {
+ t.Fatalf("Expected failure but succeeded")
+ }
+
+ o, err = i.GetID(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 5 {
+ t.Fatalf("Unexpected id returned. Expected: 5. Got: %d", o)
+ }
+
+ i.Release(6)
+
+ o, err = i.GetID(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 6 {
+ t.Fatalf("Unexpected id returned. Expected: 6. Got: %d", o)
+ }
+
+ for n := 7; n <= 10; n++ {
+ o, err := i.GetIDInRange(7, 10, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != uint64(n) {
+ t.Fatalf("Unexpected id returned. Expected: %d. Got: %d", n, o)
+ }
+ }
+
+ if err = i.GetSpecificID(7); err == nil {
+ t.Fatalf("Expected failure but succeeded")
+ }
+
+ if err = i.GetSpecificID(10); err == nil {
+ t.Fatalf("Expected failure but succeeded")
+ }
+
+ i.Release(10)
+
+ o, err = i.GetIDInRange(5, 10, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 10 {
+ t.Fatalf("Unexpected id returned. Expected: 10. Got: %d", o)
+ }
+
+ i.Release(5)
+
+ o, err = i.GetIDInRange(5, 10, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 5 {
+ t.Fatalf("Unexpected id returned. Expected: 5. Got: %d", o)
+ }
+
+ for n := 5; n <= 10; n++ {
+ i.Release(uint64(n))
+ }
+
+ for n := 5; n <= 10; n++ {
+ o, err := i.GetIDInRange(5, 10, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != uint64(n) {
+ t.Fatalf("Unexpected id returned. Expected: %d. Got: %d", n, o)
+ }
+ }
+
+ for n := 5; n <= 10; n++ {
+ if err = i.GetSpecificID(uint64(n)); err == nil {
+ t.Fatalf("Expected failure but succeeded for id: %d", n)
+ }
+ }
+
+ // New larger set
+ ul := uint64((1 << 24) - 1)
+ i, err = New(nil, "newset", 0, ul)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ o, err = i.GetIDInRange(4096, ul, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 4096 {
+ t.Fatalf("Unexpected id returned. Expected: 4096. Got: %d", o)
+ }
+
+ o, err = i.GetIDInRange(4096, ul, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 4097 {
+ t.Fatalf("Unexpected id returned. Expected: 4097. Got: %d", o)
+ }
+
+ o, err = i.GetIDInRange(4096, ul, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 4098 {
+ t.Fatalf("Unexpected id returned. Expected: 4098. Got: %d", o)
+ }
+}
+
+func TestAllocateSerial(t *testing.T) {
+ i, err := New(nil, "myids", 50, 55)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err = i.GetSpecificID(49); err == nil {
+ t.Fatal("Expected failure but succeeded")
+ }
+
+ if err = i.GetSpecificID(56); err == nil {
+ t.Fatal("Expected failure but succeeded")
+ }
+
+ o, err := i.GetID(true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 50 {
+ t.Fatalf("Unexpected first id returned: %d", o)
+ }
+
+ err = i.GetSpecificID(50)
+ if err == nil {
+ t.Fatal(err)
+ }
+
+ o, err = i.GetID(true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 51 {
+ t.Fatalf("Unexpected id returned: %d", o)
+ }
+
+ o, err = i.GetID(true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 52 {
+ t.Fatalf("Unexpected id returned: %d", o)
+ }
+
+ i.Release(50)
+
+ o, err = i.GetID(true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if o != 53 {
+ t.Fatal("Unexpected id returned")
+ }
+
+ i.Release(52)
+ err = i.GetSpecificID(52)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
--- /dev/null
+package caller
+
+import (
+ "runtime"
+ "strings"
+)
+
+func callerInfo(i int) string {
+ ptr, _, _, ok := runtime.Caller(i)
+ fName := "unknown"
+ if ok {
+ f := runtime.FuncForPC(ptr)
+ if f != nil {
+ // f.Name() is like: github.com/docker/libnetwork/caller.MethodName
+ tmp := strings.Split(f.Name(), ".")
+ if len(tmp) > 0 {
+ fName = tmp[len(tmp)-1]
+ }
+ }
+ }
+
+ return fName
+}
+
+// Name returns the name of the function at the specified level
+// level == 0 means current method name
+func Name(level int) string {
+ return callerInfo(2 + level)
+}
--- /dev/null
+package caller
+
+import (
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func fun1() string {
+ return Name(0)
+}
+
+func fun2() string {
+ return Name(1)
+}
+
+func fun3() string {
+ return fun4()
+}
+
+func fun4() string {
+ return Name(0)
+}
+
+func fun5() string {
+ return fun6()
+}
+
+func fun6() string {
+ return Name(1)
+}
+
+func TestCaller(t *testing.T) {
+ funName := fun1()
+ if funName != "fun1" {
+ t.Fatalf("error on fun1 caller %s", funName)
+ }
+
+ funName = fun2()
+ if funName != "TestCaller" {
+ t.Fatalf("error on fun2 caller %s", funName)
+ }
+
+ funName = fun3()
+ if funName != "fun4" {
+ t.Fatalf("error on fun2 caller %s", funName)
+ }
+
+ funName = fun5()
+ if funName != "fun5" {
+ t.Fatalf("error on fun5 caller %s", funName)
+ }
+}
--- /dev/null
+package setmatrix
+
+import (
+ "sync"
+
+ mapset "github.com/deckarep/golang-set"
+)
+
+// SetMatrix is a map of Sets
+type SetMatrix interface {
+ // Get returns the members of the set for a specific key as a slice.
+ Get(key string) ([]interface{}, bool)
+ // Contains is used to verify if an element is in a set for a specific key
+ // returns true if the element is in the set
+ // returns true if there is a set for the key
+ Contains(key string, value interface{}) (bool, bool)
+ // Insert inserts the value in the set of a key
+ // returns true if the value is inserted (was not already in the set), false otherwise
+ // returns also the length of the set for the key
+ Insert(key string, value interface{}) (bool, int)
+ // Remove removes the value in the set for a specific key
+ // returns true if the value is deleted, false otherwise
+ // returns also the length of the set for the key
+ Remove(key string, value interface{}) (bool, int)
+ // Cardinality returns the number of elements in the set for a key
+ // returns false if the set is not present
+ Cardinality(key string) (int, bool)
+ // String returns the string version of the set, empty otherwise
+ // returns false if the set is not present
+ String(key string) (string, bool)
+ // Returns all the keys in the map
+ Keys() []string
+}
+
+type setMatrix struct {
+ matrix map[string]mapset.Set
+
+ sync.Mutex
+}
+
+// NewSetMatrix creates a new set matrix object
+func NewSetMatrix() SetMatrix {
+ s := &setMatrix{}
+ s.init()
+ return s
+}
+
+func (s *setMatrix) init() {
+ s.matrix = make(map[string]mapset.Set)
+}
+
+func (s *setMatrix) Get(key string) ([]interface{}, bool) {
+ s.Lock()
+ defer s.Unlock()
+ set, ok := s.matrix[key]
+ if !ok {
+ return nil, ok
+ }
+ return set.ToSlice(), ok
+}
+
+func (s *setMatrix) Contains(key string, value interface{}) (bool, bool) {
+ s.Lock()
+ defer s.Unlock()
+ set, ok := s.matrix[key]
+ if !ok {
+ return false, ok
+ }
+ return set.Contains(value), ok
+}
+
+func (s *setMatrix) Insert(key string, value interface{}) (bool, int) {
+ s.Lock()
+ defer s.Unlock()
+ set, ok := s.matrix[key]
+ if !ok {
+ s.matrix[key] = mapset.NewSet()
+ s.matrix[key].Add(value)
+ return true, 1
+ }
+
+ return set.Add(value), set.Cardinality()
+}
+
+func (s *setMatrix) Remove(key string, value interface{}) (bool, int) {
+ s.Lock()
+ defer s.Unlock()
+ set, ok := s.matrix[key]
+ if !ok {
+ return false, 0
+ }
+
+ var removed bool
+ if set.Contains(value) {
+ set.Remove(value)
+ removed = true
+ // If the set is empty remove it from the matrix
+ if set.Cardinality() == 0 {
+ delete(s.matrix, key)
+ }
+ }
+
+ return removed, set.Cardinality()
+}
+
+func (s *setMatrix) Cardinality(key string) (int, bool) {
+ s.Lock()
+ defer s.Unlock()
+ set, ok := s.matrix[key]
+ if !ok {
+ return 0, ok
+ }
+
+ return set.Cardinality(), ok
+}
+
+func (s *setMatrix) String(key string) (string, bool) {
+ s.Lock()
+ defer s.Unlock()
+ set, ok := s.matrix[key]
+ if !ok {
+ return "", ok
+ }
+ return set.String(), ok
+}
+
+func (s *setMatrix) Keys() []string {
+ s.Lock()
+ defer s.Unlock()
+ keys := make([]string, 0, len(s.matrix))
+ for k := range s.matrix {
+ keys = append(keys, k)
+ }
+ return keys
+}
--- /dev/null
+package setmatrix
+
+import (
+ "context"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func TestSetSerialInsertDelete(t *testing.T) {
+ s := NewSetMatrix()
+
+ b, i := s.Insert("a", "1")
+ if !b || i != 1 {
+ t.Fatalf("error in insert %t %d", b, i)
+ }
+ b, i = s.Insert("a", "1")
+ if b || i != 1 {
+ t.Fatalf("error in insert %t %d", b, i)
+ }
+ b, i = s.Insert("a", "2")
+ if !b || i != 2 {
+ t.Fatalf("error in insert %t %d", b, i)
+ }
+ b, i = s.Insert("a", "1")
+ if b || i != 2 {
+ t.Fatalf("error in insert %t %d", b, i)
+ }
+ b, i = s.Insert("a", "3")
+ if !b || i != 3 {
+ t.Fatalf("error in insert %t %d", b, i)
+ }
+ b, i = s.Insert("a", "2")
+ if b || i != 3 {
+ t.Fatalf("error in insert %t %d", b, i)
+ }
+ b, i = s.Insert("a", "3")
+ if b || i != 3 {
+ t.Fatalf("error in insert %t %d", b, i)
+ }
+ b, i = s.Insert("a", "4")
+ if !b || i != 4 {
+ t.Fatalf("error in insert %t %d", b, i)
+ }
+
+ b, p := s.Contains("a", "1")
+ if !b || !p {
+ t.Fatalf("error in contains %t %t", b, p)
+ }
+ b, p = s.Contains("a", "2")
+ if !b || !p {
+ t.Fatalf("error in contains %t %t", b, p)
+ }
+ b, p = s.Contains("a", "3")
+ if !b || !p {
+ t.Fatalf("error in contains %t %t", b, p)
+ }
+ b, p = s.Contains("a", "4")
+ if !b || !p {
+ t.Fatalf("error in contains %t %t", b, p)
+ }
+
+ i, b = s.Cardinality("a")
+ if !b || i != 4 {
+ t.Fatalf("error in cardinality count %t %d", b, i)
+ }
+ keys := s.Keys()
+ if len(keys) != 1 {
+ t.Fatalf("error in keys %v", keys)
+ }
+ str, b := s.String("a")
+ if !b ||
+ !strings.Contains(str, "1") ||
+ !strings.Contains(str, "2") ||
+ !strings.Contains(str, "3") ||
+ !strings.Contains(str, "4") {
+ t.Fatalf("error in string %t %s", b, str)
+ }
+
+ _, b = s.Get("a")
+ if !b {
+ t.Fatalf("error in get %t", b)
+ }
+
+ b, i = s.Remove("a", "1")
+ if !b || i != 3 {
+ t.Fatalf("error in remove %t %d", b, i)
+ }
+ b, i = s.Remove("a", "3")
+ if !b || i != 2 {
+ t.Fatalf("error in remove %t %d", b, i)
+ }
+ b, i = s.Remove("a", "1")
+ if b || i != 2 {
+ t.Fatalf("error in remove %t %d", b, i)
+ }
+ b, i = s.Remove("a", "4")
+ if !b || i != 1 {
+ t.Fatalf("error in remove %t %d", b, i)
+ }
+ b, i = s.Remove("a", "2")
+ if !b || i != 0 {
+ t.Fatalf("error in remove %t %d", b, i)
+ }
+ b, i = s.Remove("a", "2")
+ if b || i != 0 {
+ t.Fatalf("error in remove %t %d", b, i)
+ }
+
+ i, b = s.Cardinality("a")
+ if b || i != 0 {
+ t.Fatalf("error in cardinality count %t %d", b, i)
+ }
+
+ str, b = s.String("a")
+ if b || str != "" {
+ t.Fatalf("error in string %t %s", b, str)
+ }
+
+ keys = s.Keys()
+ if len(keys) > 0 {
+ t.Fatalf("error in keys %v", keys)
+ }
+
+ // Negative tests
+ _, b = s.Get("not exists")
+ if b {
+ t.Fatalf("error should not happen %t", b)
+ }
+
+ b1, b := s.Contains("not exists", "a")
+ if b1 || b {
+ t.Fatalf("error should not happen %t %t", b1, b)
+ }
+}
+
+func insertDeleteRotuine(ctx context.Context, endCh chan int, s SetMatrix, key, value string) {
+ for {
+ select {
+ case <-ctx.Done():
+ endCh <- 0
+ return
+ default:
+ b, _ := s.Insert(key, value)
+ if !b {
+ endCh <- 1
+ return
+ }
+
+ b, _ = s.Remove(key, value)
+ if !b {
+ endCh <- 2
+ return
+ }
+ }
+ }
+}
+
+func TestSetParallelInsertDelete(t *testing.T) {
+ s := NewSetMatrix()
+ parallelRoutines := 6
+ endCh := make(chan int)
+ // Let the routines running and competing for 10s
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ for i := 0; i < parallelRoutines; i++ {
+ go insertDeleteRotuine(ctx, endCh, s, "key-"+strconv.Itoa(i%3), strconv.Itoa(i))
+ }
+ for parallelRoutines > 0 {
+ v := <-endCh
+ if v == 1 {
+ t.Fatalf("error one goroutine failed on the insert")
+ }
+ if v == 2 {
+ t.Fatalf("error one goroutine failed on the remove")
+ }
+ parallelRoutines--
+ }
+ if i, b := s.Cardinality("key"); b || i > 0 {
+ t.Fatalf("error the set should be empty %t %d", b, i)
+ }
+}
--- /dev/null
+package ipam
+
+import (
+ "fmt"
+ "net"
+ "sort"
+ "sync"
+
+ "github.com/docker/libnetwork/bitseq"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/ipamutils"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ localAddressSpace = "LocalDefault"
+ globalAddressSpace = "GlobalDefault"
+ // The biggest configurable host subnets
+ minNetSize = 8
+ minNetSizeV6 = 64
+ // datastore keyes for ipam objects
+ dsConfigKey = "ipam/" + ipamapi.DefaultIPAM + "/config"
+ dsDataKey = "ipam/" + ipamapi.DefaultIPAM + "/data"
+)
+
+// Allocator provides per address space ipv4/ipv6 book keeping
+type Allocator struct {
+ // Predefined pools for default address spaces
+ // Separate from the addrSpace because they should not be serialized
+ predefined map[string][]*net.IPNet
+ predefinedStartIndices map[string]int
+ // The (potentially serialized) address spaces
+ addrSpaces map[string]*addrSpace
+ // stores []datastore.Datastore
+ // Allocated addresses in each address space's subnet
+ addresses map[SubnetKey]*bitseq.Handle
+ sync.Mutex
+}
+
+// NewAllocator returns an instance of libnetwork ipam
+func NewAllocator(lcDs, glDs datastore.DataStore) (*Allocator, error) {
+ a := &Allocator{}
+
+ // Load predefined subnet pools
+
+ a.predefined = map[string][]*net.IPNet{
+ localAddressSpace: ipamutils.GetLocalScopeDefaultNetworks(),
+ globalAddressSpace: ipamutils.GetGlobalScopeDefaultNetworks(),
+ }
+
+ // Initialize asIndices map
+ a.predefinedStartIndices = make(map[string]int)
+
+ // Initialize bitseq map
+ a.addresses = make(map[SubnetKey]*bitseq.Handle)
+
+ // Initialize address spaces
+ a.addrSpaces = make(map[string]*addrSpace)
+ for _, aspc := range []struct {
+ as string
+ ds datastore.DataStore
+ }{
+ {localAddressSpace, lcDs},
+ {globalAddressSpace, glDs},
+ } {
+ a.initializeAddressSpace(aspc.as, aspc.ds)
+ }
+
+ return a, nil
+}
+
+func (a *Allocator) refresh(as string) error {
+ aSpace, err := a.getAddressSpaceFromStore(as)
+ if err != nil {
+ return types.InternalErrorf("error getting pools config from store: %v", err)
+ }
+
+ if aSpace == nil {
+ return nil
+ }
+
+ a.Lock()
+ a.addrSpaces[as] = aSpace
+ a.Unlock()
+
+ return nil
+}
+
+func (a *Allocator) updateBitMasks(aSpace *addrSpace) error {
+ var inserterList []func() error
+
+ aSpace.Lock()
+ for k, v := range aSpace.subnets {
+ if v.Range == nil {
+ kk := k
+ vv := v
+ inserterList = append(inserterList, func() error { return a.insertBitMask(kk, vv.Pool) })
+ }
+ }
+ aSpace.Unlock()
+
+ // Add the bitmasks (data could come from datastore)
+ if inserterList != nil {
+ for _, f := range inserterList {
+ if err := f(); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+// Checks for and fixes damaged bitmask.
+func (a *Allocator) checkConsistency(as string) {
+ var sKeyList []SubnetKey
+
+ // Retrieve this address space's configuration and bitmasks from the datastore
+ a.refresh(as)
+ a.Lock()
+ aSpace, ok := a.addrSpaces[as]
+ a.Unlock()
+ if !ok {
+ return
+ }
+ a.updateBitMasks(aSpace)
+
+ aSpace.Lock()
+ for sk, pd := range aSpace.subnets {
+ if pd.Range != nil {
+ continue
+ }
+ sKeyList = append(sKeyList, sk)
+ }
+ aSpace.Unlock()
+
+ for _, sk := range sKeyList {
+ a.Lock()
+ bm := a.addresses[sk]
+ a.Unlock()
+ if err := bm.CheckConsistency(); err != nil {
+ logrus.Warnf("Error while running consistency check for %s: %v", sk, err)
+ }
+ }
+}
+
+func (a *Allocator) initializeAddressSpace(as string, ds datastore.DataStore) error {
+ scope := ""
+ if ds != nil {
+ scope = ds.Scope()
+ }
+
+ a.Lock()
+ if currAS, ok := a.addrSpaces[as]; ok {
+ if currAS.ds != nil {
+ a.Unlock()
+ return types.ForbiddenErrorf("a datastore is already configured for the address space %s", as)
+ }
+ }
+ a.addrSpaces[as] = &addrSpace{
+ subnets: map[SubnetKey]*PoolData{},
+ id: dsConfigKey + "/" + as,
+ scope: scope,
+ ds: ds,
+ alloc: a,
+ }
+ a.Unlock()
+
+ a.checkConsistency(as)
+
+ return nil
+}
+
+// DiscoverNew informs the allocator about a new global scope datastore
+func (a *Allocator) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ if dType != discoverapi.DatastoreConfig {
+ return nil
+ }
+
+ dsc, ok := data.(discoverapi.DatastoreConfigData)
+ if !ok {
+ return types.InternalErrorf("incorrect data in datastore update notification: %v", data)
+ }
+
+ ds, err := datastore.NewDataStoreFromConfig(dsc)
+ if err != nil {
+ return err
+ }
+
+ return a.initializeAddressSpace(globalAddressSpace, ds)
+}
+
+// DiscoverDelete is a notification of no interest for the allocator
+func (a *Allocator) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+// GetDefaultAddressSpaces returns the local and global default address spaces
+func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) {
+ return localAddressSpace, globalAddressSpace, nil
+}
+
+// RequestPool returns an address pool along with its unique id.
+// addressSpace must be a valid address space name and must not be the empty string.
+// If pool is the empty string then the default predefined pool for addressSpace will be used, otherwise pool must be a valid IP address and length in CIDR notation.
+// If subPool is not empty, it must be a valid IP address and length in CIDR notation which is a sub-range of pool.
+// subPool must be empty if pool is empty.
+func (a *Allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
+ logrus.Debugf("RequestPool(%s, %s, %s, %v, %t)", addressSpace, pool, subPool, options, v6)
+
+ k, nw, ipr, err := a.parsePoolRequest(addressSpace, pool, subPool, v6)
+ if err != nil {
+ return "", nil, nil, types.InternalErrorf("failed to parse pool request for address space %q pool %q subpool %q: %v", addressSpace, pool, subPool, err)
+ }
+
+ pdf := k == nil
+
+retry:
+ if pdf {
+ if nw, err = a.getPredefinedPool(addressSpace, v6); err != nil {
+ return "", nil, nil, err
+ }
+ k = &SubnetKey{AddressSpace: addressSpace, Subnet: nw.String()}
+ }
+
+ if err := a.refresh(addressSpace); err != nil {
+ return "", nil, nil, err
+ }
+
+ aSpace, err := a.getAddrSpace(addressSpace)
+ if err != nil {
+ return "", nil, nil, err
+ }
+
+ insert, err := aSpace.updatePoolDBOnAdd(*k, nw, ipr, pdf)
+ if err != nil {
+ if _, ok := err.(types.MaskableError); ok {
+ logrus.Debugf("Retrying predefined pool search: %v", err)
+ goto retry
+ }
+ return "", nil, nil, err
+ }
+
+ if err := a.writeToStore(aSpace); err != nil {
+ if _, ok := err.(types.RetryError); !ok {
+ return "", nil, nil, types.InternalErrorf("pool configuration failed because of %s", err.Error())
+ }
+
+ goto retry
+ }
+
+ return k.String(), nw, nil, insert()
+}
+
+// ReleasePool releases the address pool identified by the passed id
+func (a *Allocator) ReleasePool(poolID string) error {
+ logrus.Debugf("ReleasePool(%s)", poolID)
+ k := SubnetKey{}
+ if err := k.FromString(poolID); err != nil {
+ return types.BadRequestErrorf("invalid pool id: %s", poolID)
+ }
+
+retry:
+ if err := a.refresh(k.AddressSpace); err != nil {
+ return err
+ }
+
+ aSpace, err := a.getAddrSpace(k.AddressSpace)
+ if err != nil {
+ return err
+ }
+
+ remove, err := aSpace.updatePoolDBOnRemoval(k)
+ if err != nil {
+ return err
+ }
+
+ if err = a.writeToStore(aSpace); err != nil {
+ if _, ok := err.(types.RetryError); !ok {
+ return types.InternalErrorf("pool (%s) removal failed because of %v", poolID, err)
+ }
+ goto retry
+ }
+
+ return remove()
+}
+
+// Given the address space, returns the local or global PoolConfig based on whether the
+// address space is local or global. AddressSpace locality is registered with IPAM out of band.
+func (a *Allocator) getAddrSpace(as string) (*addrSpace, error) {
+ a.Lock()
+ defer a.Unlock()
+ aSpace, ok := a.addrSpaces[as]
+ if !ok {
+ return nil, types.BadRequestErrorf("cannot find address space %s (most likely the backing datastore is not configured)", as)
+ }
+ return aSpace, nil
+}
+
+// parsePoolRequest parses and validates a request to create a new pool under addressSpace and returns
+// a SubnetKey, network and range describing the request.
+func (a *Allocator) parsePoolRequest(addressSpace, pool, subPool string, v6 bool) (*SubnetKey, *net.IPNet, *AddressRange, error) {
+ var (
+ nw *net.IPNet
+ ipr *AddressRange
+ err error
+ )
+
+ if addressSpace == "" {
+ return nil, nil, nil, ipamapi.ErrInvalidAddressSpace
+ }
+
+ if pool == "" && subPool != "" {
+ return nil, nil, nil, ipamapi.ErrInvalidSubPool
+ }
+
+ if pool == "" {
+ return nil, nil, nil, nil
+ }
+
+ if _, nw, err = net.ParseCIDR(pool); err != nil {
+ return nil, nil, nil, ipamapi.ErrInvalidPool
+ }
+
+ if subPool != "" {
+ if ipr, err = getAddressRange(subPool, nw); err != nil {
+ return nil, nil, nil, err
+ }
+ }
+
+ return &SubnetKey{AddressSpace: addressSpace, Subnet: nw.String(), ChildSubnet: subPool}, nw, ipr, nil
+}
+
+func (a *Allocator) insertBitMask(key SubnetKey, pool *net.IPNet) error {
+ //logrus.Debugf("Inserting bitmask (%s, %s)", key.String(), pool.String())
+
+ store := a.getStore(key.AddressSpace)
+ ipVer := getAddressVersion(pool.IP)
+ ones, bits := pool.Mask.Size()
+ numAddresses := uint64(1 << uint(bits-ones))
+
+ // Allow /64 subnet
+ if ipVer == v6 && numAddresses == 0 {
+ numAddresses--
+ }
+
+ // Generate the new address masks. AddressMask content may come from datastore
+ h, err := bitseq.NewHandle(dsDataKey, store, key.String(), numAddresses)
+ if err != nil {
+ return err
+ }
+
+ // Do not let network identifier address be reserved
+ // Do the same for IPv6 so that bridge ip starts with XXXX...::1
+ h.Set(0)
+
+ // Do not let broadcast address be reserved
+ if ipVer == v4 {
+ h.Set(numAddresses - 1)
+ }
+
+ a.Lock()
+ a.addresses[key] = h
+ a.Unlock()
+ return nil
+}
+
+func (a *Allocator) retrieveBitmask(k SubnetKey, n *net.IPNet) (*bitseq.Handle, error) {
+ a.Lock()
+ bm, ok := a.addresses[k]
+ a.Unlock()
+ if !ok {
+ logrus.Debugf("Retrieving bitmask (%s, %s)", k.String(), n.String())
+ if err := a.insertBitMask(k, n); err != nil {
+ return nil, types.InternalErrorf("could not find bitmask in datastore for %s", k.String())
+ }
+ a.Lock()
+ bm = a.addresses[k]
+ a.Unlock()
+ }
+ return bm, nil
+}
+
+func (a *Allocator) getPredefineds(as string) []*net.IPNet {
+ a.Lock()
+ defer a.Unlock()
+
+ p := a.predefined[as]
+ i := a.predefinedStartIndices[as]
+ // defensive in case the list changed since last update
+ if i >= len(p) {
+ i = 0
+ }
+ return append(p[i:], p[:i]...)
+}
+
+func (a *Allocator) updateStartIndex(as string, amt int) {
+ a.Lock()
+ i := a.predefinedStartIndices[as] + amt
+ if i < 0 || i >= len(a.predefined[as]) {
+ i = 0
+ }
+ a.predefinedStartIndices[as] = i
+ a.Unlock()
+}
+
+func (a *Allocator) getPredefinedPool(as string, ipV6 bool) (*net.IPNet, error) {
+ var v ipVersion
+ v = v4
+ if ipV6 {
+ v = v6
+ }
+
+ if as != localAddressSpace && as != globalAddressSpace {
+ return nil, types.NotImplementedErrorf("no default pool available for non-default address spaces")
+ }
+
+ aSpace, err := a.getAddrSpace(as)
+ if err != nil {
+ return nil, err
+ }
+
+ predefined := a.getPredefineds(as)
+
+ aSpace.Lock()
+ for i, nw := range predefined {
+ if v != getAddressVersion(nw.IP) {
+ continue
+ }
+ // Checks whether pool has already been allocated
+ if _, ok := aSpace.subnets[SubnetKey{AddressSpace: as, Subnet: nw.String()}]; ok {
+ continue
+ }
+ // Shouldn't be necessary, but check prevents IP collisions should
+ // predefined pools overlap for any reason.
+ if !aSpace.contains(as, nw) {
+ aSpace.Unlock()
+ a.updateStartIndex(as, i+1)
+ return nw, nil
+ }
+ }
+ aSpace.Unlock()
+
+ return nil, types.NotFoundErrorf("could not find an available, non-overlapping IPv%d address pool among the defaults to assign to the network", v)
+}
+
+// RequestAddress returns an address from the specified pool ID
+func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) {
+ logrus.Debugf("RequestAddress(%s, %v, %v)", poolID, prefAddress, opts)
+ k := SubnetKey{}
+ if err := k.FromString(poolID); err != nil {
+ return nil, nil, types.BadRequestErrorf("invalid pool id: %s", poolID)
+ }
+
+ if err := a.refresh(k.AddressSpace); err != nil {
+ return nil, nil, err
+ }
+
+ aSpace, err := a.getAddrSpace(k.AddressSpace)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ aSpace.Lock()
+ p, ok := aSpace.subnets[k]
+ if !ok {
+ aSpace.Unlock()
+ return nil, nil, types.NotFoundErrorf("cannot find address pool for poolID:%s", poolID)
+ }
+
+ if prefAddress != nil && !p.Pool.Contains(prefAddress) {
+ aSpace.Unlock()
+ return nil, nil, ipamapi.ErrIPOutOfRange
+ }
+
+ c := p
+ for c.Range != nil {
+ k = c.ParentKey
+ c = aSpace.subnets[k]
+ }
+ aSpace.Unlock()
+
+ bm, err := a.retrieveBitmask(k, c.Pool)
+ if err != nil {
+ return nil, nil, types.InternalErrorf("could not find bitmask in datastore for %s on address %v request from pool %s: %v",
+ k.String(), prefAddress, poolID, err)
+ }
+ // In order to request for a serial ip address allocation, callers can pass in the option to request
+ // IP allocation serially or first available IP in the subnet
+ var serial bool
+ if opts != nil {
+ if val, ok := opts[ipamapi.AllocSerialPrefix]; ok {
+ serial = (val == "true")
+ }
+ }
+ ip, err := a.getAddress(p.Pool, bm, prefAddress, p.Range, serial)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return &net.IPNet{IP: ip, Mask: p.Pool.Mask}, nil, nil
+}
+
+// ReleaseAddress releases the address from the specified pool ID
+func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
+ logrus.Debugf("ReleaseAddress(%s, %v)", poolID, address)
+ k := SubnetKey{}
+ if err := k.FromString(poolID); err != nil {
+ return types.BadRequestErrorf("invalid pool id: %s", poolID)
+ }
+
+ if err := a.refresh(k.AddressSpace); err != nil {
+ return err
+ }
+
+ aSpace, err := a.getAddrSpace(k.AddressSpace)
+ if err != nil {
+ return err
+ }
+
+ aSpace.Lock()
+ p, ok := aSpace.subnets[k]
+ if !ok {
+ aSpace.Unlock()
+ return types.NotFoundErrorf("cannot find address pool for poolID:%s", poolID)
+ }
+
+ if address == nil {
+ aSpace.Unlock()
+ return types.BadRequestErrorf("invalid address: nil")
+ }
+
+ if !p.Pool.Contains(address) {
+ aSpace.Unlock()
+ return ipamapi.ErrIPOutOfRange
+ }
+
+ c := p
+ for c.Range != nil {
+ k = c.ParentKey
+ c = aSpace.subnets[k]
+ }
+ aSpace.Unlock()
+
+ mask := p.Pool.Mask
+
+ h, err := types.GetHostPartIP(address, mask)
+ if err != nil {
+ return types.InternalErrorf("failed to release address %s: %v", address.String(), err)
+ }
+
+ bm, err := a.retrieveBitmask(k, c.Pool)
+ if err != nil {
+ return types.InternalErrorf("could not find bitmask in datastore for %s on address %v release from pool %s: %v",
+ k.String(), address, poolID, err)
+ }
+ defer logrus.Debugf("Released address PoolID:%s, Address:%v Sequence:%s", poolID, address, bm.String())
+
+ return bm.Unset(ipToUint64(h))
+}
+
+func (a *Allocator) getAddress(nw *net.IPNet, bitmask *bitseq.Handle, prefAddress net.IP, ipr *AddressRange, serial bool) (net.IP, error) {
+ var (
+ ordinal uint64
+ err error
+ base *net.IPNet
+ )
+
+ logrus.Debugf("Request address PoolID:%v %s Serial:%v PrefAddress:%v ", nw, bitmask.String(), serial, prefAddress)
+ base = types.GetIPNetCopy(nw)
+
+ if bitmask.Unselected() <= 0 {
+ return nil, ipamapi.ErrNoAvailableIPs
+ }
+ if ipr == nil && prefAddress == nil {
+ ordinal, err = bitmask.SetAny(serial)
+ } else if prefAddress != nil {
+ hostPart, e := types.GetHostPartIP(prefAddress, base.Mask)
+ if e != nil {
+ return nil, types.InternalErrorf("failed to allocate requested address %s: %v", prefAddress.String(), e)
+ }
+ ordinal = ipToUint64(types.GetMinimalIP(hostPart))
+ err = bitmask.Set(ordinal)
+ } else {
+ ordinal, err = bitmask.SetAnyInRange(ipr.Start, ipr.End, serial)
+ }
+
+ switch err {
+ case nil:
+ // Convert IP ordinal for this subnet into IP address
+ return generateAddress(ordinal, base), nil
+ case bitseq.ErrBitAllocated:
+ return nil, ipamapi.ErrIPAlreadyAllocated
+ case bitseq.ErrNoBitAvailable:
+ return nil, ipamapi.ErrNoAvailableIPs
+ default:
+ return nil, err
+ }
+}
+
+// DumpDatabase dumps the internal info
+func (a *Allocator) DumpDatabase() string {
+ a.Lock()
+ aspaces := make(map[string]*addrSpace, len(a.addrSpaces))
+ orderedAS := make([]string, 0, len(a.addrSpaces))
+ for as, aSpace := range a.addrSpaces {
+ orderedAS = append(orderedAS, as)
+ aspaces[as] = aSpace
+ }
+ a.Unlock()
+
+ sort.Strings(orderedAS)
+
+ var s string
+ for _, as := range orderedAS {
+ aSpace := aspaces[as]
+ s = fmt.Sprintf("\n\n%s Config", as)
+ aSpace.Lock()
+ for k, config := range aSpace.subnets {
+ s += fmt.Sprintf("\n%v: %v", k, config)
+ if config.Range == nil {
+ a.retrieveBitmask(k, config.Pool)
+ }
+ }
+ aSpace.Unlock()
+ }
+
+ s = fmt.Sprintf("%s\n\nBitmasks", s)
+ for k, bm := range a.addresses {
+ s += fmt.Sprintf("\n%s: %s", k, bm)
+ }
+
+ return s
+}
+
+// IsBuiltIn returns true for builtin drivers
+func (a *Allocator) IsBuiltIn() bool {
+ return true
+}
--- /dev/null
+package ipam
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "math/rand"
+ "net"
+ "strconv"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/docker/libkv/store"
+ "github.com/docker/libkv/store/boltdb"
+ "github.com/docker/libnetwork/bitseq"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/ipamapi"
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+)
+
+const (
+ defaultPrefix = "/tmp/libnetwork/test/ipam"
+)
+
+func init() {
+ boltdb.Register()
+}
+
+// OptionBoltdbWithRandomDBFile function returns a random dir for local store backend
+func randomLocalStore(needStore bool) (datastore.DataStore, error) {
+ if !needStore {
+ return nil, nil
+ }
+ tmp, err := ioutil.TempFile("", "libnetwork-")
+ if err != nil {
+ return nil, fmt.Errorf("Error creating temp file: %v", err)
+ }
+ if err := tmp.Close(); err != nil {
+ return nil, fmt.Errorf("Error closing temp file: %v", err)
+ }
+ return datastore.NewDataStore(datastore.LocalScope, &datastore.ScopeCfg{
+ Client: datastore.ScopeClientCfg{
+ Provider: "boltdb",
+ Address: defaultPrefix + tmp.Name(),
+ Config: &store.Config{
+ Bucket: "libnetwork",
+ ConnectionTimeout: 3 * time.Second,
+ },
+ },
+ })
+}
+
+func getAllocator(store bool) (*Allocator, error) {
+ ds, err := randomLocalStore(store)
+ if err != nil {
+ return nil, err
+ }
+ return NewAllocator(ds, nil)
+}
+
+func TestInt2IP2IntConversion(t *testing.T) {
+ for i := uint64(0); i < 256*256*256; i++ {
+ var array [4]byte // new array at each cycle
+ addIntToIP(array[:], i)
+ j := ipToUint64(array[:])
+ if j != i {
+ t.Fatalf("Failed to convert ordinal %d to IP % x and back to ordinal. Got %d", i, array, j)
+ }
+ }
+}
+
+func TestGetAddressVersion(t *testing.T) {
+ if v4 != getAddressVersion(net.ParseIP("172.28.30.112")) {
+ t.Fatal("Failed to detect IPv4 version")
+ }
+ if v4 != getAddressVersion(net.ParseIP("0.0.0.1")) {
+ t.Fatal("Failed to detect IPv4 version")
+ }
+ if v6 != getAddressVersion(net.ParseIP("ff01::1")) {
+ t.Fatal("Failed to detect IPv6 version")
+ }
+ if v6 != getAddressVersion(net.ParseIP("2001:db8::76:51")) {
+ t.Fatal("Failed to detect IPv6 version")
+ }
+}
+
+func TestKeyString(t *testing.T) {
+ k := &SubnetKey{AddressSpace: "default", Subnet: "172.27.0.0/16"}
+ expected := "default/172.27.0.0/16"
+ if expected != k.String() {
+ t.Fatalf("Unexpected key string: %s", k.String())
+ }
+
+ k2 := &SubnetKey{}
+ err := k2.FromString(expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if k2.AddressSpace != k.AddressSpace || k2.Subnet != k.Subnet {
+ t.Fatalf("SubnetKey.FromString() failed. Expected %v. Got %v", k, k2)
+ }
+
+ expected = fmt.Sprintf("%s/%s", expected, "172.27.3.0/24")
+ k.ChildSubnet = "172.27.3.0/24"
+ if expected != k.String() {
+ t.Fatalf("Unexpected key string: %s", k.String())
+ }
+
+ err = k2.FromString(expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if k2.AddressSpace != k.AddressSpace || k2.Subnet != k.Subnet || k2.ChildSubnet != k.ChildSubnet {
+ t.Fatalf("SubnetKey.FromString() failed. Expected %v. Got %v", k, k2)
+ }
+}
+
+func TestPoolDataMarshal(t *testing.T) {
+ _, nw, err := net.ParseCIDR("172.28.30.1/24")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ p := &PoolData{
+ ParentKey: SubnetKey{AddressSpace: "Blue", Subnet: "172.28.0.0/16"},
+ Pool: nw,
+ Range: &AddressRange{Sub: &net.IPNet{IP: net.IP{172, 28, 20, 0}, Mask: net.IPMask{255, 255, 255, 0}}, Start: 0, End: 255},
+ RefCount: 4,
+ }
+
+ ba, err := json.Marshal(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var q PoolData
+ err = json.Unmarshal(ba, &q)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if p.ParentKey != q.ParentKey || !types.CompareIPNet(p.Range.Sub, q.Range.Sub) ||
+ p.Range.Start != q.Range.Start || p.Range.End != q.Range.End || p.RefCount != q.RefCount ||
+ !types.CompareIPNet(p.Pool, q.Pool) {
+ t.Fatalf("\n%#v\n%#v", p, &q)
+ }
+
+ p = &PoolData{
+ ParentKey: SubnetKey{AddressSpace: "Blue", Subnet: "172.28.0.0/16"},
+ Pool: nw,
+ RefCount: 4,
+ }
+
+ ba, err = json.Marshal(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = json.Unmarshal(ba, &q)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if q.Range != nil {
+ t.Fatal("Unexpected Range")
+ }
+}
+
+func TestSubnetsMarshal(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ if err != nil {
+ t.Fatal(err)
+ }
+ pid0, _, _, err := a.RequestPool(localAddressSpace, "192.168.0.0/16", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ pid1, _, _, err := a.RequestPool(localAddressSpace, "192.169.0.0/16", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, _, err = a.RequestAddress(pid0, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cfg, err := a.getAddrSpace(localAddressSpace)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ba := cfg.Value()
+ if err := cfg.SetValue(ba); err != nil {
+ t.Fatal(err)
+ }
+
+ expIP := &net.IPNet{IP: net.IP{192, 168, 0, 2}, Mask: net.IPMask{255, 255, 0, 0}}
+ ip, _, err := a.RequestAddress(pid0, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(expIP, ip) {
+ t.Fatalf("Got unexpected ip after pool config restore: %s", ip)
+ }
+
+ expIP = &net.IPNet{IP: net.IP{192, 169, 0, 1}, Mask: net.IPMask{255, 255, 0, 0}}
+ ip, _, err = a.RequestAddress(pid1, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(expIP, ip) {
+ t.Fatalf("Got unexpected ip after pool config restore: %s", ip)
+ }
+ }
+}
+
+func TestAddSubnets(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ if err != nil {
+ t.Fatal(err)
+ }
+ a.addrSpaces["abc"] = a.addrSpaces[localAddressSpace]
+
+ pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
+ if err != nil {
+ t.Fatal("Unexpected failure in adding subnet")
+ }
+
+ pid1, _, _, err := a.RequestPool("abc", "10.0.0.0/8", "", nil, false)
+ if err != nil {
+ t.Fatalf("Unexpected failure in adding overlapping subnets to different address spaces: %v", err)
+ }
+
+ if pid0 == pid1 {
+ t.Fatal("returned same pool id for same subnets in different namespaces")
+ }
+
+ _, _, _, err = a.RequestPool("abc", "10.0.0.0/8", "", nil, false)
+ if err == nil {
+ t.Fatalf("Expected failure requesting existing subnet")
+ }
+
+ _, _, _, err = a.RequestPool("abc", "10.128.0.0/9", "", nil, false)
+ if err == nil {
+ t.Fatal("Expected failure on adding overlapping base subnet")
+ }
+
+ _, _, _, err = a.RequestPool("abc", "10.0.0.0/8", "10.128.0.0/9", nil, false)
+ if err != nil {
+ t.Fatalf("Unexpected failure on adding sub pool: %v", err)
+ }
+ _, _, _, err = a.RequestPool("abc", "10.0.0.0/8", "10.128.0.0/9", nil, false)
+ if err == nil {
+ t.Fatalf("Expected failure on adding overlapping sub pool")
+ }
+
+ _, _, _, err = a.RequestPool(localAddressSpace, "10.20.2.0/24", "", nil, false)
+ if err == nil {
+ t.Fatal("Failed to detect overlapping subnets")
+ }
+
+ _, _, _, err = a.RequestPool(localAddressSpace, "10.128.0.0/9", "", nil, false)
+ if err == nil {
+ t.Fatal("Failed to detect overlapping subnets")
+ }
+
+ _, _, _, err = a.RequestPool(localAddressSpace, "1003:1:2:3:4:5:6::/112", "", nil, false)
+ if err != nil {
+ t.Fatalf("Failed to add v6 subnet: %s", err.Error())
+ }
+
+ _, _, _, err = a.RequestPool(localAddressSpace, "1003:1:2:3::/64", "", nil, false)
+ if err == nil {
+ t.Fatal("Failed to detect overlapping v6 subnet")
+ }
+ }
+}
+
+// TestDoublePoolRelease tests that releasing a pool which has already
+// been released raises an error.
+func TestDoublePoolRelease(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
+ assert.NilError(t, err)
+
+ err = a.ReleasePool(pid0)
+ assert.NilError(t, err)
+
+ err = a.ReleasePool(pid0)
+ assert.Check(t, is.ErrorContains(err, ""))
+ }
+}
+
+func TestAddReleasePoolID(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ var k0, k1 SubnetKey
+ aSpace, err := a.getAddrSpace(localAddressSpace)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
+ if err != nil {
+ t.Fatal("Unexpected failure in adding pool")
+ }
+ if err := k0.FromString(pid0); err != nil {
+ t.Fatal(err)
+ }
+
+ aSpace, err = a.getAddrSpace(localAddressSpace)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ subnets := aSpace.subnets
+
+ if subnets[k0].RefCount != 1 {
+ t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
+ }
+
+ pid1, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
+ if err != nil {
+ t.Fatal("Unexpected failure in adding sub pool")
+ }
+ if err := k1.FromString(pid1); err != nil {
+ t.Fatal(err)
+ }
+
+ if pid0 == pid1 {
+ t.Fatalf("Incorrect poolIDs returned %s, %s", pid0, pid1)
+ }
+
+ aSpace, err = a.getAddrSpace(localAddressSpace)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ subnets = aSpace.subnets
+ if subnets[k1].RefCount != 1 {
+ t.Fatalf("Unexpected ref count for %s: %d", k1, subnets[k1].RefCount)
+ }
+
+ _, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
+ if err == nil {
+ t.Fatal("Expected failure in adding sub pool")
+ }
+
+ aSpace, err = a.getAddrSpace(localAddressSpace)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ subnets = aSpace.subnets
+
+ if subnets[k0].RefCount != 2 {
+ t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
+ }
+
+ if err := a.ReleasePool(pid1); err != nil {
+ t.Fatal(err)
+ }
+
+ aSpace, err = a.getAddrSpace(localAddressSpace)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ subnets = aSpace.subnets
+ if subnets[k0].RefCount != 1 {
+ t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
+ }
+ if err := a.ReleasePool(pid0); err != nil {
+ t.Fatal(err)
+ }
+
+ pid00, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
+ if err != nil {
+ t.Fatal("Unexpected failure in adding pool")
+ }
+ if pid00 != pid0 {
+ t.Fatal("main pool should still exist")
+ }
+
+ aSpace, err = a.getAddrSpace(localAddressSpace)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ subnets = aSpace.subnets
+ if subnets[k0].RefCount != 1 {
+ t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
+ }
+
+ if err := a.ReleasePool(pid00); err != nil {
+ t.Fatal(err)
+ }
+
+ aSpace, err = a.getAddrSpace(localAddressSpace)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ subnets = aSpace.subnets
+ if bp, ok := subnets[k0]; ok {
+ t.Fatalf("Base pool %s is still present: %v", k0, bp)
+ }
+
+ _, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
+ if err != nil {
+ t.Fatal("Unexpected failure in adding pool")
+ }
+
+ aSpace, err = a.getAddrSpace(localAddressSpace)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ subnets = aSpace.subnets
+ if subnets[k0].RefCount != 1 {
+ t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
+ }
+ }
+}
+
+func TestPredefinedPool(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ if _, err := a.getPredefinedPool("blue", false); err == nil {
+ t.Fatal("Expected failure for non default addr space")
+ }
+
+ pid, nw, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ nw2, err := a.getPredefinedPool(localAddressSpace, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if types.CompareIPNet(nw, nw2) {
+ t.Fatalf("Unexpected default network returned: %s = %s", nw2, nw)
+ }
+
+ if err := a.ReleasePool(pid); err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+func TestRemoveSubnet(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ a.addrSpaces["splane"] = &addrSpace{
+ id: dsConfigKey + "/" + "splane",
+ ds: a.addrSpaces[localAddressSpace].ds,
+ alloc: a.addrSpaces[localAddressSpace].alloc,
+ scope: a.addrSpaces[localAddressSpace].scope,
+ subnets: map[SubnetKey]*PoolData{},
+ }
+
+ input := []struct {
+ addrSpace string
+ subnet string
+ v6 bool
+ }{
+ {localAddressSpace, "192.168.0.0/16", false},
+ {localAddressSpace, "172.17.0.0/16", false},
+ {localAddressSpace, "10.0.0.0/8", false},
+ {localAddressSpace, "2001:db8:1:2:3:4:ffff::/112", false},
+ {"splane", "172.17.0.0/16", false},
+ {"splane", "10.0.0.0/8", false},
+ {"splane", "2001:db8:1:2:3:4:5::/112", true},
+ {"splane", "2001:db8:1:2:3:4:ffff::/112", true},
+ }
+
+ poolIDs := make([]string, len(input))
+
+ for ind, i := range input {
+ if poolIDs[ind], _, _, err = a.RequestPool(i.addrSpace, i.subnet, "", nil, i.v6); err != nil {
+ t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error())
+ }
+ }
+
+ for ind, id := range poolIDs {
+ if err := a.ReleasePool(id); err != nil {
+ t.Fatalf("Failed to release poolID %s (%d)", id, ind)
+ }
+ }
+ }
+}
+
+func TestGetSameAddress(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ a.addrSpaces["giallo"] = &addrSpace{
+ id: dsConfigKey + "/" + "giallo",
+ ds: a.addrSpaces[localAddressSpace].ds,
+ alloc: a.addrSpaces[localAddressSpace].alloc,
+ scope: a.addrSpaces[localAddressSpace].scope,
+ subnets: map[SubnetKey]*PoolData{},
+ }
+
+ pid, _, _, err := a.RequestPool("giallo", "192.168.100.0/24", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ip := net.ParseIP("192.168.100.250")
+ _, _, err = a.RequestAddress(pid, ip, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, _, err = a.RequestAddress(pid, ip, nil)
+ if err == nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+func TestPoolAllocationReuse(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ // First get all pools until they are exhausted to
+ pList := []string{}
+ pool, _, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
+ for err == nil {
+ pList = append(pList, pool)
+ pool, _, _, err = a.RequestPool(localAddressSpace, "", "", nil, false)
+ }
+ nPools := len(pList)
+ for _, pool := range pList {
+ if err := a.ReleasePool(pool); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Now try to allocate then free nPool pools sequentially.
+ // Verify that we don't see any repeat networks even though
+ // we have freed them.
+ seen := map[string]bool{}
+ for i := 0; i < nPools; i++ {
+ pool, nw, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := seen[nw.String()]; ok {
+ t.Fatalf("Network %s was reused before exhausing the pool list", nw.String())
+ }
+ seen[nw.String()] = true
+ if err := a.ReleasePool(pool); err != nil {
+ t.Fatal(err)
+ }
+ }
+ }
+}
+
+func TestGetAddressSubPoolEqualPool(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ // Requesting a subpool of same size of the master pool should not cause any problem on ip allocation
+ pid, _, _, err := a.RequestPool(localAddressSpace, "172.18.0.0/16", "172.18.0.0/16", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, _, err = a.RequestAddress(pid, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+func TestRequestReleaseAddressFromSubPool(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ a.addrSpaces["rosso"] = &addrSpace{
+ id: dsConfigKey + "/" + "rosso",
+ ds: a.addrSpaces[localAddressSpace].ds,
+ alloc: a.addrSpaces[localAddressSpace].alloc,
+ scope: a.addrSpaces[localAddressSpace].scope,
+ subnets: map[SubnetKey]*PoolData{},
+ }
+
+ poolID, _, _, err := a.RequestPool("rosso", "172.28.0.0/16", "172.28.30.0/24", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var ip *net.IPNet
+ expected := &net.IPNet{IP: net.IP{172, 28, 30, 255}, Mask: net.IPMask{255, 255, 0, 0}}
+ for err == nil {
+ var c *net.IPNet
+ if c, _, err = a.RequestAddress(poolID, nil, nil); err == nil {
+ ip = c
+ }
+ }
+ if err != ipamapi.ErrNoAvailableIPs {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(expected, ip) {
+ t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
+ }
+ rp := &net.IPNet{IP: net.IP{172, 28, 30, 97}, Mask: net.IPMask{255, 255, 0, 0}}
+ if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
+ t.Fatal(err)
+ }
+ if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(rp, ip) {
+ t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
+ }
+
+ _, _, _, err = a.RequestPool("rosso", "10.0.0.0/8", "10.0.0.0/16", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ poolID, _, _, err = a.RequestPool("rosso", "10.0.0.0/16", "10.0.0.0/24", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected = &net.IPNet{IP: net.IP{10, 0, 0, 255}, Mask: net.IPMask{255, 255, 0, 0}}
+ for err == nil {
+ var c *net.IPNet
+ if c, _, err = a.RequestAddress(poolID, nil, nil); err == nil {
+ ip = c
+ }
+ }
+ if err != ipamapi.ErrNoAvailableIPs {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(expected, ip) {
+ t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
+ }
+ rp = &net.IPNet{IP: net.IP{10, 0, 0, 79}, Mask: net.IPMask{255, 255, 0, 0}}
+ if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
+ t.Fatal(err)
+ }
+ if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(rp, ip) {
+ t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
+ }
+
+ // Request any addresses from subpool after explicit address request
+ unoExp, _ := types.ParseCIDR("10.2.2.0/16")
+ dueExp, _ := types.ParseCIDR("10.2.2.2/16")
+ treExp, _ := types.ParseCIDR("10.2.2.1/16")
+
+ if poolID, _, _, err = a.RequestPool("rosso", "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
+ t.Fatal(err)
+ }
+ tre, _, err := a.RequestAddress(poolID, treExp.IP, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(tre, treExp) {
+ t.Fatalf("Unexpected address: %v", tre)
+ }
+
+ uno, _, err := a.RequestAddress(poolID, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(uno, unoExp) {
+ t.Fatalf("Unexpected address: %v", uno)
+ }
+
+ due, _, err := a.RequestAddress(poolID, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(due, dueExp) {
+ t.Fatalf("Unexpected address: %v", due)
+ }
+
+ if err = a.ReleaseAddress(poolID, uno.IP); err != nil {
+ t.Fatal(err)
+ }
+ uno, _, err = a.RequestAddress(poolID, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(uno, unoExp) {
+ t.Fatalf("Unexpected address: %v", uno)
+ }
+
+ if err = a.ReleaseAddress(poolID, tre.IP); err != nil {
+ t.Fatal(err)
+ }
+ tre, _, err = a.RequestAddress(poolID, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(tre, treExp) {
+ t.Fatalf("Unexpected address: %v", tre)
+ }
+ }
+}
+
+func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
+ opts := map[string]string{
+ ipamapi.AllocSerialPrefix: "true"}
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ a.addrSpaces["rosso"] = &addrSpace{
+ id: dsConfigKey + "/" + "rosso",
+ ds: a.addrSpaces[localAddressSpace].ds,
+ alloc: a.addrSpaces[localAddressSpace].alloc,
+ scope: a.addrSpaces[localAddressSpace].scope,
+ subnets: map[SubnetKey]*PoolData{},
+ }
+
+ poolID, _, _, err := a.RequestPool("rosso", "172.28.0.0/16", "172.28.30.0/24", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var ip *net.IPNet
+ expected := &net.IPNet{IP: net.IP{172, 28, 30, 255}, Mask: net.IPMask{255, 255, 0, 0}}
+ for err == nil {
+ var c *net.IPNet
+ if c, _, err = a.RequestAddress(poolID, nil, opts); err == nil {
+ ip = c
+ }
+ }
+ if err != ipamapi.ErrNoAvailableIPs {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(expected, ip) {
+ t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
+ }
+ rp := &net.IPNet{IP: net.IP{172, 28, 30, 97}, Mask: net.IPMask{255, 255, 0, 0}}
+ if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
+ t.Fatal(err)
+ }
+ if ip, _, err = a.RequestAddress(poolID, nil, opts); err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(rp, ip) {
+ t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
+ }
+
+ _, _, _, err = a.RequestPool("rosso", "10.0.0.0/8", "10.0.0.0/16", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ poolID, _, _, err = a.RequestPool("rosso", "10.0.0.0/16", "10.0.0.0/24", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected = &net.IPNet{IP: net.IP{10, 0, 0, 255}, Mask: net.IPMask{255, 255, 0, 0}}
+ for err == nil {
+ var c *net.IPNet
+ if c, _, err = a.RequestAddress(poolID, nil, opts); err == nil {
+ ip = c
+ }
+ }
+ if err != ipamapi.ErrNoAvailableIPs {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(expected, ip) {
+ t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
+ }
+ rp = &net.IPNet{IP: net.IP{10, 0, 0, 79}, Mask: net.IPMask{255, 255, 0, 0}}
+ if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
+ t.Fatal(err)
+ }
+ if ip, _, err = a.RequestAddress(poolID, nil, opts); err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(rp, ip) {
+ t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
+ }
+
+ // Request any addresses from subpool after explicit address request
+ unoExp, _ := types.ParseCIDR("10.2.2.0/16")
+ dueExp, _ := types.ParseCIDR("10.2.2.2/16")
+ treExp, _ := types.ParseCIDR("10.2.2.1/16")
+ quaExp, _ := types.ParseCIDR("10.2.2.3/16")
+ fivExp, _ := types.ParseCIDR("10.2.2.4/16")
+ if poolID, _, _, err = a.RequestPool("rosso", "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
+ t.Fatal(err)
+ }
+ tre, _, err := a.RequestAddress(poolID, treExp.IP, opts)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(tre, treExp) {
+ t.Fatalf("Unexpected address: %v", tre)
+ }
+
+ uno, _, err := a.RequestAddress(poolID, nil, opts)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(uno, unoExp) {
+ t.Fatalf("Unexpected address: %v", uno)
+ }
+
+ due, _, err := a.RequestAddress(poolID, nil, opts)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(due, dueExp) {
+ t.Fatalf("Unexpected address: %v", due)
+ }
+
+ if err = a.ReleaseAddress(poolID, uno.IP); err != nil {
+ t.Fatal(err)
+ }
+ uno, _, err = a.RequestAddress(poolID, nil, opts)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(uno, quaExp) {
+ t.Fatalf("Unexpected address: %v", uno)
+ }
+
+ if err = a.ReleaseAddress(poolID, tre.IP); err != nil {
+ t.Fatal(err)
+ }
+ tre, _, err = a.RequestAddress(poolID, nil, opts)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(tre, fivExp) {
+ t.Fatalf("Unexpected address: %v", tre)
+ }
+ }
+}
+
+func TestGetAddress(t *testing.T) {
+ input := []string{
+ /*"10.0.0.0/8", "10.0.0.0/9", "10.0.0.0/10",*/ "10.0.0.0/11", "10.0.0.0/12", "10.0.0.0/13", "10.0.0.0/14",
+ "10.0.0.0/15", "10.0.0.0/16", "10.0.0.0/17", "10.0.0.0/18", "10.0.0.0/19", "10.0.0.0/20", "10.0.0.0/21",
+ "10.0.0.0/22", "10.0.0.0/23", "10.0.0.0/24", "10.0.0.0/25", "10.0.0.0/26", "10.0.0.0/27", "10.0.0.0/28",
+ "10.0.0.0/29", "10.0.0.0/30", "10.0.0.0/31"}
+
+ for _, subnet := range input {
+ assertGetAddress(t, subnet)
+ }
+}
+
+func TestRequestSyntaxCheck(t *testing.T) {
+ var (
+ pool = "192.168.0.0/16"
+ subPool = "192.168.0.0/24"
+ as = "green"
+ )
+
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ a.addrSpaces[as] = &addrSpace{
+ id: dsConfigKey + "/" + as,
+ ds: a.addrSpaces[localAddressSpace].ds,
+ alloc: a.addrSpaces[localAddressSpace].alloc,
+ scope: a.addrSpaces[localAddressSpace].scope,
+ subnets: map[SubnetKey]*PoolData{},
+ }
+
+ _, _, _, err = a.RequestPool("", pool, "", nil, false)
+ if err == nil {
+ t.Fatal("Failed to detect wrong request: empty address space")
+ }
+
+ _, _, _, err = a.RequestPool("", pool, subPool, nil, false)
+ if err == nil {
+ t.Fatal("Failed to detect wrong request: empty address space")
+ }
+
+ _, _, _, err = a.RequestPool(as, "", subPool, nil, false)
+ if err == nil {
+ t.Fatal("Failed to detect wrong request: subPool specified and no pool")
+ }
+
+ pid, _, _, err := a.RequestPool(as, pool, subPool, nil, false)
+ if err != nil {
+ t.Fatalf("Unexpected failure: %v", err)
+ }
+
+ _, _, err = a.RequestAddress("", nil, nil)
+ if err == nil {
+ t.Fatal("Failed to detect wrong request: no pool id specified")
+ }
+
+ ip := net.ParseIP("172.17.0.23")
+ _, _, err = a.RequestAddress(pid, ip, nil)
+ if err == nil {
+ t.Fatal("Failed to detect wrong request: requested IP from different subnet")
+ }
+
+ ip = net.ParseIP("192.168.0.50")
+ _, _, err = a.RequestAddress(pid, ip, nil)
+ if err != nil {
+ t.Fatalf("Unexpected failure: %v", err)
+ }
+
+ err = a.ReleaseAddress("", ip)
+ if err == nil {
+ t.Fatal("Failed to detect wrong request: no pool id specified")
+ }
+
+ err = a.ReleaseAddress(pid, nil)
+ if err == nil {
+ t.Fatal("Failed to detect wrong request: no pool id specified")
+ }
+
+ err = a.ReleaseAddress(pid, ip)
+ if err != nil {
+ t.Fatalf("Unexpected failure: %v: %s, %s", err, pid, ip)
+ }
+ }
+}
+
+func TestRequest(t *testing.T) {
+ // Request N addresses from different size subnets, verifying last request
+ // returns expected address. Internal subnet host size is Allocator's default, 16
+ input := []struct {
+ subnet string
+ numReq int
+ lastIP string
+ }{
+ {"192.168.59.0/24", 254, "192.168.59.254"},
+ {"192.168.240.0/20", 255, "192.168.240.255"},
+ {"192.168.0.0/16", 255, "192.168.0.255"},
+ {"192.168.0.0/16", 256, "192.168.1.0"},
+ {"10.16.0.0/16", 255, "10.16.0.255"},
+ {"10.128.0.0/12", 255, "10.128.0.255"},
+ {"10.0.0.0/8", 256, "10.0.1.0"},
+
+ {"192.168.128.0/18", 4*256 - 1, "192.168.131.255"},
+ /*
+ {"192.168.240.0/20", 16*256 - 2, "192.168.255.254"},
+
+ {"192.168.0.0/16", 256*256 - 2, "192.168.255.254"},
+ {"10.0.0.0/8", 2 * 256, "10.0.2.0"},
+ {"10.0.0.0/8", 5 * 256, "10.0.5.0"},
+ {"10.0.0.0/8", 100 * 256 * 254, "10.99.255.254"},
+ */
+ }
+
+ for _, d := range input {
+ assertNRequests(t, d.subnet, d.numReq, d.lastIP)
+ }
+}
+
+// TestOverlappingRequests tests that overlapping subnets cannot be allocated.
+// Requests for subnets which are supersets or subsets of existing allocations,
+// or which overlap at the beginning or end, should not be permitted.
+func TestOverlappingRequests(t *testing.T) {
+ input := []struct {
+ environment []string
+ subnet string
+ ok bool
+ }{
+ // IPv4
+ // Previously allocated network does not overlap with request
+ {[]string{"10.0.0.0/8"}, "11.0.0.0/8", true},
+ {[]string{"74.0.0.0/7"}, "9.111.99.72/30", true},
+ {[]string{"110.192.0.0/10"}, "16.0.0.0/10", true},
+
+ // Previously allocated network entirely contains request
+ {[]string{"10.0.0.0/8"}, "10.0.0.0/8", false}, // exact overlap
+ {[]string{"0.0.0.0/1"}, "16.182.0.0/15", false},
+ {[]string{"16.0.0.0/4"}, "17.11.66.0/23", false},
+
+ // Previously allocated network overlaps beginning of request
+ {[]string{"0.0.0.0/1"}, "0.0.0.0/0", false},
+ {[]string{"64.0.0.0/6"}, "64.0.0.0/3", false},
+ {[]string{"112.0.0.0/6"}, "112.0.0.0/4", false},
+
+ // Previously allocated network overlaps end of request
+ {[]string{"96.0.0.0/3"}, "0.0.0.0/1", false},
+ {[]string{"192.0.0.0/2"}, "128.0.0.0/1", false},
+ {[]string{"95.0.0.0/8"}, "92.0.0.0/6", false},
+
+ // Previously allocated network entirely contained within request
+ {[]string{"10.0.0.0/8"}, "10.0.0.0/6", false}, // non-canonical
+ {[]string{"10.0.0.0/8"}, "8.0.0.0/6", false}, // canonical
+ {[]string{"25.173.144.0/20"}, "0.0.0.0/0", false},
+
+ // IPv6
+ // Previously allocated network entirely contains request
+ {[]string{"::/0"}, "f656:3484:c878:a05:e540:a6ed:4d70:3740/123", false},
+ {[]string{"8000::/1"}, "8fe8:e7c4:5779::/49", false},
+ {[]string{"f000::/4"}, "ffc7:6000::/19", false},
+
+ // Previously allocated network overlaps beginning of request
+ {[]string{"::/2"}, "::/0", false},
+ {[]string{"::/3"}, "::/1", false},
+ {[]string{"::/6"}, "::/5", false},
+
+ // Previously allocated network overlaps end of request
+ {[]string{"c000::/2"}, "8000::/1", false},
+ {[]string{"7c00::/6"}, "::/1", false},
+ {[]string{"cf80::/9"}, "c000::/4", false},
+
+ // Previously allocated network entirely contained within request
+ {[]string{"ff77:93f8::/29"}, "::/0", false},
+ {[]string{"9287:2e20:5134:fab6:9061:a0c6:bfe3:9400/119"}, "8000::/1", false},
+ {[]string{"3ea1:bfa9:8691:d1c6:8c46:519b:db6d:e700/120"}, "3000::/4", false},
+ }
+
+ for _, store := range []bool{false, true} {
+ for _, tc := range input {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ // Set up some existing allocations. This should always succeed.
+ for _, env := range tc.environment {
+ _, _, _, err = a.RequestPool(localAddressSpace, env, "", nil, false)
+ assert.NilError(t, err)
+ }
+
+ // Make the test allocation.
+ _, _, _, err = a.RequestPool(localAddressSpace, tc.subnet, "", nil, false)
+ if tc.ok {
+ assert.NilError(t, err)
+ } else {
+ assert.Check(t, is.ErrorContains(err, ""))
+ }
+ }
+ }
+}
+
+func TestRelease(t *testing.T) {
+ var (
+ subnet = "192.168.0.0/23"
+ )
+
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ pid, _, _, err := a.RequestPool(localAddressSpace, subnet, "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ bm := a.addresses[SubnetKey{localAddressSpace, subnet, ""}]
+
+ // Allocate all addresses
+ for err != ipamapi.ErrNoAvailableIPs {
+ _, _, err = a.RequestAddress(pid, nil, nil)
+ }
+
+ toRelease := []struct {
+ address string
+ }{
+ {"192.168.0.1"},
+ {"192.168.0.2"},
+ {"192.168.0.3"},
+ {"192.168.0.4"},
+ {"192.168.0.5"},
+ {"192.168.0.6"},
+ {"192.168.0.7"},
+ {"192.168.0.8"},
+ {"192.168.0.9"},
+ {"192.168.0.10"},
+ {"192.168.0.30"},
+ {"192.168.0.31"},
+ {"192.168.1.32"},
+
+ {"192.168.0.254"},
+ {"192.168.1.1"},
+ {"192.168.1.2"},
+
+ {"192.168.1.3"},
+
+ {"192.168.1.253"},
+ {"192.168.1.254"},
+ }
+
+ // One by one, release the address and request again. We should get the same IP
+ for i, inp := range toRelease {
+ ip0 := net.ParseIP(inp.address)
+ a.ReleaseAddress(pid, ip0)
+ bm = a.addresses[SubnetKey{localAddressSpace, subnet, ""}]
+ if bm.Unselected() != 1 {
+ t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.Unselected())
+ }
+
+ nw, _, err := a.RequestAddress(pid, nil, nil)
+ if err != nil {
+ t.Fatalf("Failed to obtain the address: %s", err.Error())
+ }
+ ip := nw.IP
+ if !ip0.Equal(ip) {
+ t.Fatalf("Failed to obtain the same address. Expected: %s, Got: %s", ip0, ip)
+ }
+ }
+ }
+}
+
+func assertGetAddress(t *testing.T, subnet string) {
+ var (
+ err error
+ printTime = false
+ a = &Allocator{}
+ )
+
+ _, sub, _ := net.ParseCIDR(subnet)
+ ones, bits := sub.Mask.Size()
+ zeroes := bits - ones
+ numAddresses := 1 << uint(zeroes)
+
+ bm, err := bitseq.NewHandle("ipam_test", nil, "default/"+subnet, uint64(numAddresses))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ start := time.Now()
+ run := 0
+ for err != ipamapi.ErrNoAvailableIPs {
+ _, err = a.getAddress(sub, bm, nil, nil, false)
+ run++
+ }
+ if printTime {
+ fmt.Printf("\nTaken %v, to allocate all addresses on %s. (nemAddresses: %d. Runs: %d)", time.Since(start), subnet, numAddresses, run)
+ }
+ if bm.Unselected() != 0 {
+ t.Fatalf("Unexpected free count after reserving all addresses: %d", bm.Unselected())
+ }
+ /*
+ if bm.Head.Block != expectedMax || bm.Head.Count != numBlocks {
+ t.Fatalf("Failed to effectively reserve all addresses on %s. Expected (0x%x, %d) as first sequence. Found (0x%x,%d)",
+ subnet, expectedMax, numBlocks, bm.Head.Block, bm.Head.Count)
+ }
+ */
+}
+
+func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP string) {
+ var (
+ nw *net.IPNet
+ printTime = false
+ )
+
+ lastIP := net.ParseIP(lastExpectedIP)
+ for _, store := range []bool{false, true} {
+ a, err := getAllocator(store)
+ assert.NilError(t, err)
+
+ pid, _, _, err := a.RequestPool(localAddressSpace, subnet, "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ i := 0
+ start := time.Now()
+ for ; i < numReq; i++ {
+ nw, _, err = a.RequestAddress(pid, nil, nil)
+ }
+ if printTime {
+ fmt.Printf("\nTaken %v, to allocate %d addresses on %s\n", time.Since(start), numReq, subnet)
+ }
+
+ if !lastIP.Equal(nw.IP) {
+ t.Fatalf("Wrong last IP. Expected %s. Got: %s (err: %v, ind: %d)", lastExpectedIP, nw.IP.String(), err, i)
+ }
+ }
+}
+
+func benchmarkRequest(b *testing.B, a *Allocator, subnet string) {
+ pid, _, _, err := a.RequestPool(localAddressSpace, subnet, "", nil, false)
+ for err != ipamapi.ErrNoAvailableIPs {
+ _, _, err = a.RequestAddress(pid, nil, nil)
+ }
+}
+
+func benchMarkRequest(subnet string, b *testing.B) {
+ a, _ := getAllocator(true)
+ for n := 0; n < b.N; n++ {
+ benchmarkRequest(b, a, subnet)
+ }
+}
+
+func BenchmarkRequest(b *testing.B) {
+
+ subnets := []string{
+ "10.0.0.0/24",
+ "10.0.0.0/16",
+ "10.0.0.0/8",
+ }
+
+ for _, subnet := range subnets {
+ name := fmt.Sprintf("%vSubnet", subnet)
+ b.Run(name, func(b *testing.B) {
+ a, _ := getAllocator(true)
+ benchmarkRequest(b, a, subnet)
+ })
+ }
+}
+
+func TestAllocateRandomDeallocate(t *testing.T) {
+ for _, store := range []bool{false, true} {
+ testAllocateRandomDeallocate(t, "172.25.0.0/16", "", 384, store)
+ testAllocateRandomDeallocate(t, "172.25.0.0/16", "172.25.252.0/22", 384, store)
+ }
+}
+
+func testAllocateRandomDeallocate(t *testing.T, pool, subPool string, num int, store bool) {
+ ds, err := randomLocalStore(store)
+ assert.NilError(t, err)
+
+ a, err := NewAllocator(ds, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ pid, _, _, err := a.RequestPool(localAddressSpace, pool, subPool, nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Allocate num ip addresses
+ indices := make(map[int]*net.IPNet, num)
+ allocated := make(map[string]bool, num)
+ for i := 0; i < num; i++ {
+ ip, _, err := a.RequestAddress(pid, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ips := ip.String()
+ if _, ok := allocated[ips]; ok {
+ t.Fatalf("Address %s is already allocated", ips)
+ }
+ allocated[ips] = true
+ indices[i] = ip
+ }
+ if len(indices) != len(allocated) || len(indices) != num {
+ t.Fatalf("Unexpected number of allocated addresses: (%d,%d).", len(indices), len(allocated))
+ }
+
+ seed := time.Now().Unix()
+ rand.Seed(seed)
+
+ // Deallocate half of the allocated addresses following a random pattern
+ pattern := rand.Perm(num)
+ for i := 0; i < num/2; i++ {
+ idx := pattern[i]
+ ip := indices[idx]
+ err := a.ReleaseAddress(pid, ip.IP)
+ if err != nil {
+ t.Fatalf("Unexpected failure on deallocation of %s: %v.\nSeed: %d.", ip, err, seed)
+ }
+ delete(indices, idx)
+ delete(allocated, ip.String())
+ }
+
+ // Request a quarter of addresses
+ for i := 0; i < num/2; i++ {
+ ip, _, err := a.RequestAddress(pid, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ips := ip.String()
+ if _, ok := allocated[ips]; ok {
+ t.Fatalf("\nAddress %s is already allocated.\nSeed: %d.", ips, seed)
+ }
+ allocated[ips] = true
+ }
+ if len(allocated) != num {
+ t.Fatalf("Unexpected number of allocated addresses: %d.\nSeed: %d.", len(allocated), seed)
+ }
+}
+
+func TestRetrieveFromStore(t *testing.T) {
+ num := 200
+ ds, err := randomLocalStore(true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ a, err := NewAllocator(ds, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ pid, _, _, err := a.RequestPool(localAddressSpace, "172.25.0.0/16", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i < num; i++ {
+ if _, _, err := a.RequestAddress(pid, nil, nil); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Restore
+ a1, err := NewAllocator(ds, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ a1.refresh(localAddressSpace)
+ db := a.DumpDatabase()
+ db1 := a1.DumpDatabase()
+ if db != db1 {
+ t.Fatalf("Unexpected db change.\nExpected:%s\nGot:%s", db, db1)
+ }
+ checkDBEquality(a, a1, t)
+ pid, _, _, err = a1.RequestPool(localAddressSpace, "172.25.0.0/16", "172.25.1.0/24", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i < num/2; i++ {
+ if _, _, err := a1.RequestAddress(pid, nil, nil); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Restore
+ a2, err := NewAllocator(ds, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ a2.refresh(localAddressSpace)
+ checkDBEquality(a1, a2, t)
+ pid, _, _, err = a2.RequestPool(localAddressSpace, "172.25.0.0/16", "172.25.2.0/24", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i < num/2; i++ {
+ if _, _, err := a2.RequestAddress(pid, nil, nil); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Restore
+ a3, err := NewAllocator(ds, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ a3.refresh(localAddressSpace)
+ checkDBEquality(a2, a3, t)
+ pid, _, _, err = a3.RequestPool(localAddressSpace, "172.26.0.0/16", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i < num/2; i++ {
+ if _, _, err := a3.RequestAddress(pid, nil, nil); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Restore
+ a4, err := NewAllocator(ds, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ a4.refresh(localAddressSpace)
+ checkDBEquality(a3, a4, t)
+}
+
+func checkDBEquality(a1, a2 *Allocator, t *testing.T) {
+ for k, cnf1 := range a1.addrSpaces[localAddressSpace].subnets {
+ cnf2 := a2.addrSpaces[localAddressSpace].subnets[k]
+ if cnf1.String() != cnf2.String() {
+ t.Fatalf("%s\n%s", cnf1, cnf2)
+ }
+ if cnf1.Range == nil {
+ a2.retrieveBitmask(k, cnf1.Pool)
+ }
+ }
+
+ for k, bm1 := range a1.addresses {
+ bm2 := a2.addresses[k]
+ if bm1.String() != bm2.String() {
+ t.Fatalf("%s\n%s", bm1, bm2)
+ }
+ }
+}
+
+const (
+ numInstances = 5
+ first = 0
+ last = numInstances - 1
+)
+
+var (
+ allocator *Allocator
+ start = make(chan struct{})
+ done = make(chan chan struct{}, numInstances-1)
+ pools = make([]*net.IPNet, numInstances)
+)
+
+func runParallelTests(t *testing.T, instance int) {
+ var err error
+
+ t.Parallel()
+
+ pTest := flag.Lookup("test.parallel")
+ if pTest == nil {
+ t.Skip("Skipped because test.parallel flag not set;")
+ }
+ numParallel, err := strconv.Atoi(pTest.Value.String())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if numParallel < numInstances {
+ t.Skip("Skipped because t.parallel was less than ", numInstances)
+ }
+
+ // The first instance creates the allocator, gives the start
+ // and finally checks the pools each instance was assigned
+ if instance == first {
+ allocator, err = getAllocator(true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ close(start)
+ }
+
+ if instance != first {
+ select {
+ case <-start:
+ }
+
+ instDone := make(chan struct{})
+ done <- instDone
+ defer close(instDone)
+
+ if instance == last {
+ defer close(done)
+ }
+ }
+
+ _, pools[instance], _, err = allocator.RequestPool(localAddressSpace, "", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if instance == first {
+ for instDone := range done {
+ select {
+ case <-instDone:
+ }
+ }
+ // Now check each instance got a different pool
+ for i := 0; i < numInstances; i++ {
+ for j := i + 1; j < numInstances; j++ {
+ if types.CompareIPNet(pools[i], pools[j]) {
+ t.Fatalf("Instance %d and %d were given the same predefined pool: %v", i, j, pools)
+ }
+ }
+ }
+ }
+}
+
+func TestRequestReleaseAddressDuplicate(t *testing.T) {
+ a, err := getAllocator(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ type IP struct {
+ ip *net.IPNet
+ ref int
+ }
+ ips := []IP{}
+ allocatedIPs := []*net.IPNet{}
+ a.addrSpaces["rosso"] = &addrSpace{
+ id: dsConfigKey + "/" + "rosso",
+ ds: a.addrSpaces[localAddressSpace].ds,
+ alloc: a.addrSpaces[localAddressSpace].alloc,
+ scope: a.addrSpaces[localAddressSpace].scope,
+ subnets: map[SubnetKey]*PoolData{},
+ }
+ var wg sync.WaitGroup
+ opts := map[string]string{
+ ipamapi.AllocSerialPrefix: "true",
+ }
+ var l sync.Mutex
+
+ poolID, _, _, err := a.RequestPool("rosso", "198.168.0.0/23", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for err == nil {
+ var c *net.IPNet
+ if c, _, err = a.RequestAddress(poolID, nil, opts); err == nil {
+ l.Lock()
+ ips = append(ips, IP{c, 1})
+ l.Unlock()
+ allocatedIPs = append(allocatedIPs, c)
+ if len(allocatedIPs) > 500 {
+ i := rand.Intn(len(allocatedIPs) - 1)
+ wg.Add(1)
+ go func(ip *net.IPNet) {
+ if err = a.ReleaseAddress(poolID, ip.IP); err != nil {
+ t.Fatal(err)
+ }
+ l.Lock()
+ ips = append(ips, IP{ip, -1})
+ l.Unlock()
+ wg.Done()
+ }(allocatedIPs[i])
+ allocatedIPs = append(allocatedIPs[:i], allocatedIPs[i+1:]...)
+ }
+ }
+ }
+ wg.Wait()
+ refMap := make(map[string]int)
+ for _, ip := range ips {
+ refMap[ip.ip.String()] = refMap[ip.ip.String()] + ip.ref
+ if refMap[ip.ip.String()] < 0 {
+ t.Fatalf("IP %s was previously released", ip.ip.String())
+ }
+ if refMap[ip.ip.String()] > 1 {
+ t.Fatalf("IP %s was previously allocated", ip.ip.String())
+ }
+ }
+}
+
+func TestParallelPredefinedRequest1(t *testing.T) {
+ runParallelTests(t, 0)
+}
+
+func TestParallelPredefinedRequest2(t *testing.T) {
+ runParallelTests(t, 1)
+}
+
+func TestParallelPredefinedRequest3(t *testing.T) {
+ runParallelTests(t, 2)
+}
+
+func TestParallelPredefinedRequest4(t *testing.T) {
+ runParallelTests(t, 3)
+}
+
+func TestParallelPredefinedRequest5(t *testing.T) {
+ runParallelTests(t, 4)
+}
--- /dev/null
+package ipam
+
+import (
+ "context"
+ "fmt"
+ "math/rand"
+ "net"
+ "sort"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "github.com/docker/libnetwork/ipamapi"
+ "golang.org/x/sync/semaphore"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+)
+
+const (
+ all = iota
+ even
+ odd
+)
+
+type releaseMode uint
+
+type testContext struct {
+ a *Allocator
+ opts map[string]string
+ ipList []*net.IPNet
+ ipMap map[string]bool
+ pid string
+ maxIP int
+}
+
+func newTestContext(t *testing.T, mask int, options map[string]string) *testContext {
+ a, err := getAllocator(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ a.addrSpaces["giallo"] = &addrSpace{
+ id: dsConfigKey + "/" + "giallo",
+ ds: a.addrSpaces[localAddressSpace].ds,
+ alloc: a.addrSpaces[localAddressSpace].alloc,
+ scope: a.addrSpaces[localAddressSpace].scope,
+ subnets: map[SubnetKey]*PoolData{},
+ }
+
+ network := fmt.Sprintf("192.168.100.0/%d", mask)
+ // total ips 2^(32-mask) - 2 (network and broadcast)
+ totalIps := 1<<uint(32-mask) - 2
+
+ pid, _, _, err := a.RequestPool("giallo", network, "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return &testContext{
+ a: a,
+ opts: options,
+ ipList: make([]*net.IPNet, 0, totalIps),
+ ipMap: make(map[string]bool),
+ pid: pid,
+ maxIP: totalIps,
+ }
+}
+
+func TestDebug(t *testing.T) {
+ tctx := newTestContext(t, 23, map[string]string{ipamapi.AllocSerialPrefix: "true"})
+ tctx.a.RequestAddress(tctx.pid, nil, map[string]string{ipamapi.AllocSerialPrefix: "true"})
+ tctx.a.RequestAddress(tctx.pid, nil, map[string]string{ipamapi.AllocSerialPrefix: "true"})
+}
+
+type op struct {
+ id int32
+ add bool
+ name string
+}
+
+func (o *op) String() string {
+ return fmt.Sprintf("%+v", *o)
+}
+
+func TestRequestPoolParallel(t *testing.T) {
+ a, err := getAllocator(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var operationIndex int32
+ ch := make(chan *op, 240)
+ for i := 0; i < 120; i++ {
+ go func(t *testing.T, a *Allocator, ch chan *op) {
+ name, _, _, err := a.RequestPool("GlobalDefault", "", "", nil, false)
+ if err != nil {
+ t.Fatalf("request error %v", err)
+ }
+ idx := atomic.AddInt32(&operationIndex, 1)
+ ch <- &op{idx, true, name}
+ time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
+ idx = atomic.AddInt32(&operationIndex, 1)
+ err = a.ReleasePool(name)
+ if err != nil {
+ t.Fatalf("relase error %v", err)
+ }
+ ch <- &op{idx, false, name}
+ }(t, a, ch)
+ }
+
+ // map of events
+ m := make(map[string][]*op)
+ for i := 0; i < 240; i++ {
+ x := <-ch
+ ops, ok := m[x.name]
+ if !ok {
+ ops = make([]*op, 0, 10)
+ }
+ ops = append(ops, x)
+ m[x.name] = ops
+ }
+
+ // Post processing to avoid event reordering on the channel
+ for pool, ops := range m {
+ sort.Slice(ops[:], func(i, j int) bool {
+ return ops[i].id < ops[j].id
+ })
+ expected := true
+ for _, op := range ops {
+ if op.add != expected {
+ t.Fatalf("Operations for %v not valid %v, operations %v", pool, op, ops)
+ }
+ expected = !expected
+ }
+ }
+}
+
+func TestFullAllocateRelease(t *testing.T) {
+ for _, parallelism := range []int64{2, 4, 8} {
+ for _, mask := range []int{29, 25, 24, 21} {
+ tctx := newTestContext(t, mask, map[string]string{ipamapi.AllocSerialPrefix: "true"})
+ allocate(t, tctx, parallelism)
+ release(t, tctx, all, parallelism)
+ }
+ }
+}
+
+func TestOddAllocateRelease(t *testing.T) {
+ for _, parallelism := range []int64{2, 4, 8} {
+ for _, mask := range []int{29, 25, 24, 21} {
+ tctx := newTestContext(t, mask, map[string]string{ipamapi.AllocSerialPrefix: "true"})
+ allocate(t, tctx, parallelism)
+ release(t, tctx, odd, parallelism)
+ }
+ }
+}
+
+func TestFullAllocateSerialReleaseParallel(t *testing.T) {
+ for _, parallelism := range []int64{1, 4, 8} {
+ tctx := newTestContext(t, 23, map[string]string{ipamapi.AllocSerialPrefix: "true"})
+ allocate(t, tctx, 1)
+ release(t, tctx, all, parallelism)
+ }
+}
+
+func TestOddAllocateSerialReleaseParallel(t *testing.T) {
+ for _, parallelism := range []int64{1, 4, 8} {
+ tctx := newTestContext(t, 23, map[string]string{ipamapi.AllocSerialPrefix: "true"})
+ allocate(t, tctx, 1)
+ release(t, tctx, odd, parallelism)
+ }
+}
+
+func TestEvenAllocateSerialReleaseParallel(t *testing.T) {
+ for _, parallelism := range []int64{1, 4, 8} {
+ tctx := newTestContext(t, 23, map[string]string{ipamapi.AllocSerialPrefix: "true"})
+ allocate(t, tctx, 1)
+ release(t, tctx, even, parallelism)
+ }
+}
+
+func allocate(t *testing.T, tctx *testContext, parallel int64) {
+ // Allocate the whole space
+ parallelExec := semaphore.NewWeighted(parallel)
+ routineNum := tctx.maxIP + 10
+ ch := make(chan *net.IPNet, routineNum)
+ var id int
+ var wg sync.WaitGroup
+ // routine loop
+ for {
+ wg.Add(1)
+ go func(id int) {
+ parallelExec.Acquire(context.Background(), 1)
+ ip, _, _ := tctx.a.RequestAddress(tctx.pid, nil, tctx.opts)
+ ch <- ip
+ parallelExec.Release(1)
+ wg.Done()
+ }(id)
+ id++
+ if id == routineNum {
+ break
+ }
+ }
+
+ // give time to all the go routines to finish
+ wg.Wait()
+
+ // process results
+ for i := 0; i < routineNum; i++ {
+ ip := <-ch
+ if ip == nil {
+ continue
+ }
+ if there, ok := tctx.ipMap[ip.String()]; ok && there {
+ t.Fatalf("Got duplicate IP %s", ip.String())
+ break
+ }
+ tctx.ipList = append(tctx.ipList, ip)
+ tctx.ipMap[ip.String()] = true
+ }
+
+ assert.Check(t, is.Len(tctx.ipList, tctx.maxIP))
+ if len(tctx.ipList) != tctx.maxIP {
+ t.Fatal("missmatch number allocation")
+ }
+}
+
+func release(t *testing.T, tctx *testContext, mode releaseMode, parallel int64) {
+ var startIndex, increment, stopIndex, length int
+ switch mode {
+ case all:
+ startIndex = 0
+ increment = 1
+ stopIndex = tctx.maxIP - 1
+ length = tctx.maxIP
+ case odd, even:
+ if mode == odd {
+ startIndex = 1
+ }
+ increment = 2
+ stopIndex = tctx.maxIP - 1
+ length = tctx.maxIP / 2
+ if tctx.maxIP%2 > 0 {
+ length++
+ }
+ default:
+ t.Fatal("unsupported mode yet")
+ }
+
+ ipIndex := make([]int, 0, length)
+ // calculate the index to release from the ipList
+ for i := startIndex; ; i += increment {
+ ipIndex = append(ipIndex, i)
+ if i+increment > stopIndex {
+ break
+ }
+ }
+
+ var id int
+ parallelExec := semaphore.NewWeighted(parallel)
+ ch := make(chan *net.IPNet, len(ipIndex))
+ wg := sync.WaitGroup{}
+ for index := range ipIndex {
+ wg.Add(1)
+ go func(id, index int) {
+ parallelExec.Acquire(context.Background(), 1)
+ // logrus.Errorf("index %v", index)
+ // logrus.Errorf("list %v", tctx.ipList)
+ err := tctx.a.ReleaseAddress(tctx.pid, tctx.ipList[index].IP)
+ if err != nil {
+ t.Fatalf("routine %d got %v", id, err)
+ }
+ ch <- tctx.ipList[index]
+ parallelExec.Release(1)
+ wg.Done()
+ }(id, index)
+ id++
+ }
+ wg.Wait()
+
+ for i := 0; i < len(ipIndex); i++ {
+ ip := <-ch
+
+ // check if it is really free
+ _, _, err := tctx.a.RequestAddress(tctx.pid, ip.IP, nil)
+ assert.Check(t, err, "ip %v not properly released", ip)
+ if err != nil {
+ t.Fatalf("ip %v not properly released, error:%v", ip, err)
+ }
+ err = tctx.a.ReleaseAddress(tctx.pid, ip.IP)
+ assert.NilError(t, err)
+
+ if there, ok := tctx.ipMap[ip.String()]; !ok || !there {
+ t.Fatalf("ip %v got double deallocated", ip)
+ }
+ tctx.ipMap[ip.String()] = false
+ for j, v := range tctx.ipList {
+ if v == ip {
+ tctx.ipList = append(tctx.ipList[:j], tctx.ipList[j+1:]...)
+ break
+ }
+ }
+ }
+
+ assert.Check(t, is.Len(tctx.ipList, tctx.maxIP-length))
+}
--- /dev/null
+package ipam
+
+import (
+ "encoding/json"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// Key provides the Key to be used in KV Store
+func (aSpace *addrSpace) Key() []string {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+ return []string{aSpace.id}
+}
+
+// KeyPrefix returns the immediate parent key that can be used for tree walk
+func (aSpace *addrSpace) KeyPrefix() []string {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+ return []string{dsConfigKey}
+}
+
+// Value marshals the data to be stored in the KV store
+func (aSpace *addrSpace) Value() []byte {
+ b, err := json.Marshal(aSpace)
+ if err != nil {
+ logrus.Warnf("Failed to marshal ipam configured pools: %v", err)
+ return nil
+ }
+ return b
+}
+
+// SetValue unmarshalls the data from the KV store.
+func (aSpace *addrSpace) SetValue(value []byte) error {
+ rc := &addrSpace{subnets: make(map[SubnetKey]*PoolData)}
+ if err := json.Unmarshal(value, rc); err != nil {
+ return err
+ }
+ aSpace.subnets = rc.subnets
+ return nil
+}
+
+// Index returns the latest DB Index as seen by this object
+func (aSpace *addrSpace) Index() uint64 {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+ return aSpace.dbIndex
+}
+
+// SetIndex method allows the datastore to store the latest DB Index into this object
+func (aSpace *addrSpace) SetIndex(index uint64) {
+ aSpace.Lock()
+ aSpace.dbIndex = index
+ aSpace.dbExists = true
+ aSpace.Unlock()
+}
+
+// Exists method is true if this object has been stored in the DB.
+func (aSpace *addrSpace) Exists() bool {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+ return aSpace.dbExists
+}
+
+// Skip provides a way for a KV Object to avoid persisting it in the KV Store
+func (aSpace *addrSpace) Skip() bool {
+ return false
+}
+
+func (a *Allocator) getStore(as string) datastore.DataStore {
+ a.Lock()
+ defer a.Unlock()
+
+ if aSpace, ok := a.addrSpaces[as]; ok {
+ return aSpace.ds
+ }
+
+ return nil
+}
+
+func (a *Allocator) getAddressSpaceFromStore(as string) (*addrSpace, error) {
+ store := a.getStore(as)
+
+ // IPAM may not have a valid store. In such cases it is just in-memory state.
+ if store == nil {
+ return nil, nil
+ }
+
+ pc := &addrSpace{id: dsConfigKey + "/" + as, ds: store, alloc: a}
+ if err := store.GetObject(datastore.Key(pc.Key()...), pc); err != nil {
+ if err == datastore.ErrKeyNotFound {
+ return nil, nil
+ }
+
+ return nil, types.InternalErrorf("could not get pools config from store: %v", err)
+ }
+
+ return pc, nil
+}
+
+func (a *Allocator) writeToStore(aSpace *addrSpace) error {
+ store := aSpace.store()
+
+ // IPAM may not have a valid store. In such cases it is just in-memory state.
+ if store == nil {
+ return nil
+ }
+
+ err := store.PutObjectAtomic(aSpace)
+ if err == datastore.ErrKeyModified {
+ return types.RetryErrorf("failed to perform atomic write (%v). retry might fix the error", err)
+ }
+
+ return err
+}
+
+func (a *Allocator) deleteFromStore(aSpace *addrSpace) error {
+ store := aSpace.store()
+
+ // IPAM may not have a valid store. In such cases it is just in-memory state.
+ if store == nil {
+ return nil
+ }
+
+ return store.DeleteObjectAtomic(aSpace)
+}
+
+// DataScope method returns the storage scope of the datastore
+func (aSpace *addrSpace) DataScope() string {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+
+ return aSpace.scope
+}
--- /dev/null
+package ipam
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "strings"
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/types"
+)
+
+// SubnetKey is the pointer to the configured pools in each address space
+type SubnetKey struct {
+ AddressSpace string
+ Subnet string
+ ChildSubnet string
+}
+
+// PoolData contains the configured pool data
+type PoolData struct {
+ ParentKey SubnetKey
+ Pool *net.IPNet
+ Range *AddressRange `json:",omitempty"`
+ RefCount int
+}
+
+// addrSpace contains the pool configurations for the address space
+type addrSpace struct {
+ subnets map[SubnetKey]*PoolData
+ dbIndex uint64
+ dbExists bool
+ id string
+ scope string
+ ds datastore.DataStore
+ alloc *Allocator
+ sync.Mutex
+}
+
+// AddressRange specifies first and last ip ordinal which
+// identifies a range in a pool of addresses
+type AddressRange struct {
+ Sub *net.IPNet
+ Start, End uint64
+}
+
+// String returns the string form of the AddressRange object
+func (r *AddressRange) String() string {
+ return fmt.Sprintf("Sub: %s, range [%d, %d]", r.Sub, r.Start, r.End)
+}
+
+// MarshalJSON returns the JSON encoding of the Range object
+func (r *AddressRange) MarshalJSON() ([]byte, error) {
+ m := map[string]interface{}{
+ "Sub": r.Sub.String(),
+ "Start": r.Start,
+ "End": r.End,
+ }
+ return json.Marshal(m)
+}
+
+// UnmarshalJSON decodes data into the Range object
+func (r *AddressRange) UnmarshalJSON(data []byte) error {
+ m := map[string]interface{}{}
+ err := json.Unmarshal(data, &m)
+ if err != nil {
+ return err
+ }
+ if r.Sub, err = types.ParseCIDR(m["Sub"].(string)); err != nil {
+ return err
+ }
+ r.Start = uint64(m["Start"].(float64))
+ r.End = uint64(m["End"].(float64))
+ return nil
+}
+
+// String returns the string form of the SubnetKey object
+func (s *SubnetKey) String() string {
+ k := fmt.Sprintf("%s/%s", s.AddressSpace, s.Subnet)
+ if s.ChildSubnet != "" {
+ k = fmt.Sprintf("%s/%s", k, s.ChildSubnet)
+ }
+ return k
+}
+
+// FromString populates the SubnetKey object reading it from string
+func (s *SubnetKey) FromString(str string) error {
+ if str == "" || !strings.Contains(str, "/") {
+ return types.BadRequestErrorf("invalid string form for subnetkey: %s", str)
+ }
+
+ p := strings.Split(str, "/")
+ if len(p) != 3 && len(p) != 5 {
+ return types.BadRequestErrorf("invalid string form for subnetkey: %s", str)
+ }
+ s.AddressSpace = p[0]
+ s.Subnet = fmt.Sprintf("%s/%s", p[1], p[2])
+ if len(p) == 5 {
+ s.ChildSubnet = fmt.Sprintf("%s/%s", p[3], p[4])
+ }
+
+ return nil
+}
+
+// String returns the string form of the PoolData object
+func (p *PoolData) String() string {
+ return fmt.Sprintf("ParentKey: %s, Pool: %s, Range: %s, RefCount: %d",
+ p.ParentKey.String(), p.Pool.String(), p.Range, p.RefCount)
+}
+
+// MarshalJSON returns the JSON encoding of the PoolData object
+func (p *PoolData) MarshalJSON() ([]byte, error) {
+ m := map[string]interface{}{
+ "ParentKey": p.ParentKey,
+ "RefCount": p.RefCount,
+ }
+ if p.Pool != nil {
+ m["Pool"] = p.Pool.String()
+ }
+ if p.Range != nil {
+ m["Range"] = p.Range
+ }
+ return json.Marshal(m)
+}
+
+// UnmarshalJSON decodes data into the PoolData object
+func (p *PoolData) UnmarshalJSON(data []byte) error {
+ var (
+ err error
+ t struct {
+ ParentKey SubnetKey
+ Pool string
+ Range *AddressRange `json:",omitempty"`
+ RefCount int
+ }
+ )
+
+ if err = json.Unmarshal(data, &t); err != nil {
+ return err
+ }
+
+ p.ParentKey = t.ParentKey
+ p.Range = t.Range
+ p.RefCount = t.RefCount
+ if t.Pool != "" {
+ if p.Pool, err = types.ParseCIDR(t.Pool); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// MarshalJSON returns the JSON encoding of the addrSpace object
+func (aSpace *addrSpace) MarshalJSON() ([]byte, error) {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+
+ m := map[string]interface{}{
+ "Scope": string(aSpace.scope),
+ }
+
+ if aSpace.subnets != nil {
+ s := map[string]*PoolData{}
+ for k, v := range aSpace.subnets {
+ s[k.String()] = v
+ }
+ m["Subnets"] = s
+ }
+
+ return json.Marshal(m)
+}
+
+// UnmarshalJSON decodes data into the addrSpace object
+func (aSpace *addrSpace) UnmarshalJSON(data []byte) error {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+
+ m := map[string]interface{}{}
+ err := json.Unmarshal(data, &m)
+ if err != nil {
+ return err
+ }
+
+ aSpace.scope = datastore.LocalScope
+ s := m["Scope"].(string)
+ if s == string(datastore.GlobalScope) {
+ aSpace.scope = datastore.GlobalScope
+ }
+
+ if v, ok := m["Subnets"]; ok {
+ sb, _ := json.Marshal(v)
+ var s map[string]*PoolData
+ err := json.Unmarshal(sb, &s)
+ if err != nil {
+ return err
+ }
+ for ks, v := range s {
+ k := SubnetKey{}
+ k.FromString(ks)
+ aSpace.subnets[k] = v
+ }
+ }
+
+ return nil
+}
+
+// CopyTo deep copies the pool data to the destination pooldata
+func (p *PoolData) CopyTo(dstP *PoolData) error {
+ dstP.ParentKey = p.ParentKey
+ dstP.Pool = types.GetIPNetCopy(p.Pool)
+
+ if p.Range != nil {
+ dstP.Range = &AddressRange{}
+ dstP.Range.Sub = types.GetIPNetCopy(p.Range.Sub)
+ dstP.Range.Start = p.Range.Start
+ dstP.Range.End = p.Range.End
+ }
+
+ dstP.RefCount = p.RefCount
+ return nil
+}
+
+func (aSpace *addrSpace) CopyTo(o datastore.KVObject) error {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+
+ dstAspace := o.(*addrSpace)
+
+ dstAspace.id = aSpace.id
+ dstAspace.ds = aSpace.ds
+ dstAspace.alloc = aSpace.alloc
+ dstAspace.scope = aSpace.scope
+ dstAspace.dbIndex = aSpace.dbIndex
+ dstAspace.dbExists = aSpace.dbExists
+
+ dstAspace.subnets = make(map[SubnetKey]*PoolData)
+ for k, v := range aSpace.subnets {
+ dstAspace.subnets[k] = &PoolData{}
+ v.CopyTo(dstAspace.subnets[k])
+ }
+
+ return nil
+}
+
+func (aSpace *addrSpace) New() datastore.KVObject {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+
+ return &addrSpace{
+ id: aSpace.id,
+ ds: aSpace.ds,
+ alloc: aSpace.alloc,
+ scope: aSpace.scope,
+ }
+}
+
+// updatePoolDBOnAdd returns a closure which will add the subnet k to the address space when executed.
+func (aSpace *addrSpace) updatePoolDBOnAdd(k SubnetKey, nw *net.IPNet, ipr *AddressRange, pdf bool) (func() error, error) {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+
+ // Check if already allocated
+ if _, ok := aSpace.subnets[k]; ok {
+ if pdf {
+ return nil, types.InternalMaskableErrorf("predefined pool %s is already reserved", nw)
+ }
+ // This means the same pool is already allocated. updatePoolDBOnAdd is called when there
+ // is request for a pool/subpool. It should ensure there is no overlap with existing pools
+ return nil, ipamapi.ErrPoolOverlap
+ }
+
+ // If master pool, check for overlap
+ if ipr == nil {
+ if aSpace.contains(k.AddressSpace, nw) {
+ return nil, ipamapi.ErrPoolOverlap
+ }
+ // This is a new master pool, add it along with corresponding bitmask
+ aSpace.subnets[k] = &PoolData{Pool: nw, RefCount: 1}
+ return func() error { return aSpace.alloc.insertBitMask(k, nw) }, nil
+ }
+
+ // This is a new non-master pool (subPool)
+ p := &PoolData{
+ ParentKey: SubnetKey{AddressSpace: k.AddressSpace, Subnet: k.Subnet},
+ Pool: nw,
+ Range: ipr,
+ RefCount: 1,
+ }
+ aSpace.subnets[k] = p
+
+ // Look for parent pool
+ pp, ok := aSpace.subnets[p.ParentKey]
+ if ok {
+ aSpace.incRefCount(pp, 1)
+ return func() error { return nil }, nil
+ }
+
+ // Parent pool does not exist, add it along with corresponding bitmask
+ aSpace.subnets[p.ParentKey] = &PoolData{Pool: nw, RefCount: 1}
+ return func() error { return aSpace.alloc.insertBitMask(p.ParentKey, nw) }, nil
+}
+
+func (aSpace *addrSpace) updatePoolDBOnRemoval(k SubnetKey) (func() error, error) {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+
+ p, ok := aSpace.subnets[k]
+ if !ok {
+ return nil, ipamapi.ErrBadPool
+ }
+
+ aSpace.incRefCount(p, -1)
+
+ c := p
+ for ok {
+ if c.RefCount == 0 {
+ delete(aSpace.subnets, k)
+ if c.Range == nil {
+ return func() error {
+ bm, err := aSpace.alloc.retrieveBitmask(k, c.Pool)
+ if err != nil {
+ return types.InternalErrorf("could not find bitmask in datastore for pool %s removal: %v", k.String(), err)
+ }
+ return bm.Destroy()
+ }, nil
+ }
+ }
+ k = c.ParentKey
+ c, ok = aSpace.subnets[k]
+ }
+
+ return func() error { return nil }, nil
+}
+
+func (aSpace *addrSpace) incRefCount(p *PoolData, delta int) {
+ c := p
+ ok := true
+ for ok {
+ c.RefCount += delta
+ c, ok = aSpace.subnets[c.ParentKey]
+ }
+}
+
+// Checks whether the passed subnet is a superset or subset of any of the subset in this config db
+func (aSpace *addrSpace) contains(space string, nw *net.IPNet) bool {
+ for k, v := range aSpace.subnets {
+ if space == k.AddressSpace && k.ChildSubnet == "" {
+ if nw.Contains(v.Pool.IP) || v.Pool.Contains(nw.IP) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func (aSpace *addrSpace) store() datastore.DataStore {
+ aSpace.Lock()
+ defer aSpace.Unlock()
+
+ return aSpace.ds
+}
--- /dev/null
+package ipam
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/types"
+)
+
+type ipVersion int
+
+const (
+ v4 = 4
+ v6 = 6
+)
+
+func getAddressRange(pool string, masterNw *net.IPNet) (*AddressRange, error) {
+ ip, nw, err := net.ParseCIDR(pool)
+ if err != nil {
+ return nil, ipamapi.ErrInvalidSubPool
+ }
+ lIP, e := types.GetHostPartIP(nw.IP, masterNw.Mask)
+ if e != nil {
+ return nil, fmt.Errorf("failed to compute range's lowest ip address: %v", e)
+ }
+ bIP, e := types.GetBroadcastIP(nw.IP, nw.Mask)
+ if e != nil {
+ return nil, fmt.Errorf("failed to compute range's broadcast ip address: %v", e)
+ }
+ hIP, e := types.GetHostPartIP(bIP, masterNw.Mask)
+ if e != nil {
+ return nil, fmt.Errorf("failed to compute range's highest ip address: %v", e)
+ }
+ nw.IP = ip
+ return &AddressRange{nw, ipToUint64(types.GetMinimalIP(lIP)), ipToUint64(types.GetMinimalIP(hIP))}, nil
+}
+
+// It generates the ip address in the passed subnet specified by
+// the passed host address ordinal
+func generateAddress(ordinal uint64, network *net.IPNet) net.IP {
+ var address [16]byte
+
+ // Get network portion of IP
+ if getAddressVersion(network.IP) == v4 {
+ copy(address[:], network.IP.To4())
+ } else {
+ copy(address[:], network.IP)
+ }
+
+ end := len(network.Mask)
+ addIntToIP(address[:end], ordinal)
+
+ return net.IP(address[:end])
+}
+
+func getAddressVersion(ip net.IP) ipVersion {
+ if ip.To4() == nil {
+ return v6
+ }
+ return v4
+}
+
+// Adds the ordinal IP to the current array
+// 192.168.0.0 + 53 => 192.168.0.53
+func addIntToIP(array []byte, ordinal uint64) {
+ for i := len(array) - 1; i >= 0; i-- {
+ array[i] |= (byte)(ordinal & 0xff)
+ ordinal >>= 8
+ }
+}
+
+// Convert an ordinal to the respective IP address
+func ipToUint64(ip []byte) (value uint64) {
+ cip := types.GetMinimalIP(ip)
+ for i := 0; i < len(cip); i++ {
+ j := len(cip) - 1 - i
+ value += uint64(cip[i]) << uint(j*8)
+ }
+ return value
+}
--- /dev/null
+// Package ipamapi specifies the contract the IPAM service (built-in or remote) needs to satisfy.
+package ipamapi
+
+import (
+ "net"
+
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/types"
+)
+
+/********************
+ * IPAM plugin types
+ ********************/
+
+const (
+ // DefaultIPAM is the name of the built-in default ipam driver
+ DefaultIPAM = "default"
+ // NullIPAM is the name of the built-in null ipam driver
+ NullIPAM = "null"
+ // PluginEndpointType represents the Endpoint Type used by Plugin system
+ PluginEndpointType = "IpamDriver"
+ // RequestAddressType represents the Address Type used when requesting an address
+ RequestAddressType = "RequestAddressType"
+)
+
+// Callback provides a Callback interface for registering an IPAM instance into LibNetwork
+type Callback interface {
+ // GetPluginGetter returns the pluginv2 getter.
+ GetPluginGetter() plugingetter.PluginGetter
+ // RegisterIpamDriver provides a way for Remote drivers to dynamically register with libnetwork
+ RegisterIpamDriver(name string, driver Ipam) error
+ // RegisterIpamDriverWithCapabilities provides a way for Remote drivers to dynamically register with libnetwork and specify capabilities
+ RegisterIpamDriverWithCapabilities(name string, driver Ipam, capability *Capability) error
+}
+
+/**************
+ * IPAM Errors
+ **************/
+
+// Well-known errors returned by IPAM
+var (
+ ErrIpamInternalError = types.InternalErrorf("IPAM Internal Error")
+ ErrInvalidAddressSpace = types.BadRequestErrorf("Invalid Address Space")
+ ErrInvalidPool = types.BadRequestErrorf("Invalid Address Pool")
+ ErrInvalidSubPool = types.BadRequestErrorf("Invalid Address SubPool")
+ ErrInvalidRequest = types.BadRequestErrorf("Invalid Request")
+ ErrPoolNotFound = types.BadRequestErrorf("Address Pool not found")
+ ErrOverlapPool = types.ForbiddenErrorf("Address pool overlaps with existing pool on this address space")
+ ErrNoAvailablePool = types.NoServiceErrorf("No available pool")
+ ErrNoAvailableIPs = types.NoServiceErrorf("No available addresses on this pool")
+ ErrNoIPReturned = types.NoServiceErrorf("No address returned")
+ ErrIPAlreadyAllocated = types.ForbiddenErrorf("Address already in use")
+ ErrIPOutOfRange = types.BadRequestErrorf("Requested address is out of range")
+ ErrPoolOverlap = types.ForbiddenErrorf("Pool overlaps with other one on this address space")
+ ErrBadPool = types.BadRequestErrorf("Address space does not contain specified address pool")
+)
+
+/*******************************
+ * IPAM Service Interface
+ *******************************/
+
+// Ipam represents the interface the IPAM service plugins must implement
+// in order to allow injection/modification of IPAM database.
+type Ipam interface {
+ discoverapi.Discover
+
+ // GetDefaultAddressSpaces returns the default local and global address spaces for this ipam
+ GetDefaultAddressSpaces() (string, string, error)
+ // RequestPool returns an address pool along with its unique id. Address space is a mandatory field
+ // which denotes a set of non-overlapping pools. pool describes the pool of addresses in CIDR notation.
+ // subpool indicates a smaller range of addresses from the pool, for now it is specified in CIDR notation.
+ // Both pool and subpool are non mandatory fields. When they are not specified, Ipam driver may choose to
+ // return a self chosen pool for this request. In such case the v6 flag needs to be set appropriately so
+ // that the driver would return the expected ip version pool.
+ RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error)
+ // ReleasePool releases the address pool identified by the passed id
+ ReleasePool(poolID string) error
+ // Request address from the specified pool ID. Input options or required IP can be passed.
+ RequestAddress(string, net.IP, map[string]string) (*net.IPNet, map[string]string, error)
+ // Release the address from the specified pool ID
+ ReleaseAddress(string, net.IP) error
+
+ //IsBuiltIn returns true if it is a built-in driver.
+ IsBuiltIn() bool
+}
+
+// Capability represents the requirements and capabilities of the IPAM driver
+type Capability struct {
+ // Whether on address request, libnetwork must
+ // specify the endpoint MAC address
+ RequiresMACAddress bool
+ // Whether of daemon start, libnetwork must replay the pool
+ // request and the address request for current local networks
+ RequiresRequestReplay bool
+}
--- /dev/null
+package ipamapi
+
+const (
+ // Prefix constant marks the reserved label space for libnetwork
+ Prefix = "com.docker.network"
+
+ // AllocSerialPrefix constant marks the reserved label space for libnetwork ipam
+ // allocation ordering.(serial/first available)
+ AllocSerialPrefix = Prefix + ".ipam.serial"
+)
--- /dev/null
+// +build linux freebsd darwin
+
+package builtin
+
+import (
+ "errors"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/ipam"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/ipamutils"
+)
+
+var (
+ // defaultAddressPool Stores user configured subnet list
+ defaultAddressPool []*ipamutils.NetworkToSplit
+)
+
+// Init registers the built-in ipam service with libnetwork
+func Init(ic ipamapi.Callback, l, g interface{}) error {
+ var (
+ ok bool
+ localDs, globalDs datastore.DataStore
+ )
+
+ if l != nil {
+ if localDs, ok = l.(datastore.DataStore); !ok {
+ return errors.New("incorrect local datastore passed to built-in ipam init")
+ }
+ }
+
+ if g != nil {
+ if globalDs, ok = g.(datastore.DataStore); !ok {
+ return errors.New("incorrect global datastore passed to built-in ipam init")
+ }
+ }
+
+ ipamutils.ConfigLocalScopeDefaultNetworks(GetDefaultIPAddressPool())
+
+ a, err := ipam.NewAllocator(localDs, globalDs)
+ if err != nil {
+ return err
+ }
+
+ cps := &ipamapi.Capability{RequiresRequestReplay: true}
+
+ return ic.RegisterIpamDriverWithCapabilities(ipamapi.DefaultIPAM, a, cps)
+}
+
+// SetDefaultIPAddressPool stores default address pool.
+func SetDefaultIPAddressPool(addressPool []*ipamutils.NetworkToSplit) {
+ defaultAddressPool = addressPool
+}
+
+// GetDefaultIPAddressPool returns default address pool.
+func GetDefaultIPAddressPool() []*ipamutils.NetworkToSplit {
+ return defaultAddressPool
+}
--- /dev/null
+// +build windows
+
+package builtin
+
+import (
+ "errors"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/ipam"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/ipamutils"
+
+ windowsipam "github.com/docker/libnetwork/ipams/windowsipam"
+)
+
+var (
+ // defaultAddressPool Stores user configured subnet list
+ defaultAddressPool []*ipamutils.NetworkToSplit
+)
+
+// InitDockerDefault registers the built-in ipam service with libnetwork
+func InitDockerDefault(ic ipamapi.Callback, l, g interface{}) error {
+ var (
+ ok bool
+ localDs, globalDs datastore.DataStore
+ )
+
+ if l != nil {
+ if localDs, ok = l.(datastore.DataStore); !ok {
+ return errors.New("incorrect local datastore passed to built-in ipam init")
+ }
+ }
+
+ if g != nil {
+ if globalDs, ok = g.(datastore.DataStore); !ok {
+ return errors.New("incorrect global datastore passed to built-in ipam init")
+ }
+ }
+
+ ipamutils.ConfigLocalScopeDefaultNetworks(nil)
+
+ a, err := ipam.NewAllocator(localDs, globalDs)
+ if err != nil {
+ return err
+ }
+
+ cps := &ipamapi.Capability{RequiresRequestReplay: true}
+
+ return ic.RegisterIpamDriverWithCapabilities(ipamapi.DefaultIPAM, a, cps)
+}
+
+// Init registers the built-in ipam service with libnetwork
+func Init(ic ipamapi.Callback, l, g interface{}) error {
+ initFunc := windowsipam.GetInit(windowsipam.DefaultIPAM)
+
+ err := InitDockerDefault(ic, l, g)
+ if err != nil {
+ return err
+ }
+
+ return initFunc(ic, l, g)
+}
+
+// SetDefaultIPAddressPool stores default address pool .
+func SetDefaultIPAddressPool(addressPool []*ipamutils.NetworkToSplit) {
+ defaultAddressPool = addressPool
+}
+
+// GetDefaultIPAddressPool returns default address pool .
+func GetDefaultIPAddressPool() []*ipamutils.NetworkToSplit {
+ return defaultAddressPool
+}
--- /dev/null
+// Package null implements the null ipam driver. Null ipam driver satisfies ipamapi contract,
+// but does not effectively reserve/allocate any address pool or address
+package null
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/types"
+)
+
+var (
+ defaultAS = "null"
+ defaultPool, _ = types.ParseCIDR("0.0.0.0/0")
+ defaultPoolID = fmt.Sprintf("%s/%s", defaultAS, defaultPool.String())
+)
+
+type allocator struct{}
+
+func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
+ return defaultAS, defaultAS, nil
+}
+
+func (a *allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
+ if addressSpace != defaultAS {
+ return "", nil, nil, types.BadRequestErrorf("unknown address space: %s", addressSpace)
+ }
+ if pool != "" {
+ return "", nil, nil, types.BadRequestErrorf("null ipam driver does not handle specific address pool requests")
+ }
+ if subPool != "" {
+ return "", nil, nil, types.BadRequestErrorf("null ipam driver does not handle specific address subpool requests")
+ }
+ if v6 {
+ return "", nil, nil, types.BadRequestErrorf("null ipam driver does not handle IPv6 address pool pool requests")
+ }
+ return defaultPoolID, defaultPool, nil, nil
+}
+
+func (a *allocator) ReleasePool(poolID string) error {
+ return nil
+}
+
+func (a *allocator) RequestAddress(poolID string, ip net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) {
+ if poolID != defaultPoolID {
+ return nil, nil, types.BadRequestErrorf("unknown pool id: %s", poolID)
+ }
+ return nil, nil, nil
+}
+
+func (a *allocator) ReleaseAddress(poolID string, ip net.IP) error {
+ if poolID != defaultPoolID {
+ return types.BadRequestErrorf("unknown pool id: %s", poolID)
+ }
+ return nil
+}
+
+func (a *allocator) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+func (a *allocator) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+func (a *allocator) IsBuiltIn() bool {
+ return true
+}
+
+// Init registers a remote ipam when its plugin is activated
+func Init(ic ipamapi.Callback, l, g interface{}) error {
+ return ic.RegisterIpamDriver(ipamapi.NullIPAM, &allocator{})
+}
--- /dev/null
+package null
+
+import (
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+func TestPoolRequest(t *testing.T) {
+ a := allocator{}
+
+ pid, pool, _, err := a.RequestPool(defaultAS, "", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(defaultPool, pool) {
+ t.Fatalf("Unexpected pool returned. Expected %v. Got: %v", defaultPool, pool)
+ }
+ if pid != defaultPoolID {
+ t.Fatalf("Unexpected pool id returned. Expected: %s. Got: %s", defaultPoolID, pid)
+ }
+
+ _, _, _, err = a.RequestPool("default", "", "", nil, false)
+ if err == nil {
+ t.Fatal("Unexpected success")
+ }
+
+ _, _, _, err = a.RequestPool(defaultAS, "192.168.0.0/16", "", nil, false)
+ if err == nil {
+ t.Fatal("Unexpected success")
+ }
+
+ _, _, _, err = a.RequestPool(defaultAS, "", "192.168.0.0/24", nil, false)
+ if err == nil {
+ t.Fatal("Unexpected success")
+ }
+
+ _, _, _, err = a.RequestPool(defaultAS, "", "", nil, true)
+ if err == nil {
+ t.Fatal("Unexpected success")
+ }
+}
+
+func TestOtherRequests(t *testing.T) {
+ a := allocator{}
+
+ ip, _, err := a.RequestAddress(defaultPoolID, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ip != nil {
+ t.Fatalf("Unexpected address returned: %v", ip)
+ }
+
+ _, _, err = a.RequestAddress("anypid", nil, nil)
+ if err == nil {
+ t.Fatal("Unexpected success")
+ }
+
+}
--- /dev/null
+// Package api defines the data structure to be used in the request/response
+// messages between libnetwork and the remote ipam plugin
+package api
+
+import "github.com/docker/libnetwork/ipamapi"
+
+// Response is the basic response structure used in all responses
+type Response struct {
+ Error string
+}
+
+// IsSuccess returns whether the plugin response is successful
+func (r *Response) IsSuccess() bool {
+ return r.Error == ""
+}
+
+// GetError returns the error from the response, if any.
+func (r *Response) GetError() string {
+ return r.Error
+}
+
+// GetCapabilityResponse is the response of GetCapability request
+type GetCapabilityResponse struct {
+ Response
+ RequiresMACAddress bool
+ RequiresRequestReplay bool
+}
+
+// ToCapability converts the capability response into the internal ipam driver capability structure
+func (capRes GetCapabilityResponse) ToCapability() *ipamapi.Capability {
+ return &ipamapi.Capability{
+ RequiresMACAddress: capRes.RequiresMACAddress,
+ RequiresRequestReplay: capRes.RequiresRequestReplay,
+ }
+}
+
+// GetAddressSpacesResponse is the response to the ``get default address spaces`` request message
+type GetAddressSpacesResponse struct {
+ Response
+ LocalDefaultAddressSpace string
+ GlobalDefaultAddressSpace string
+}
+
+// RequestPoolRequest represents the expected data in a ``request address pool`` request message
+type RequestPoolRequest struct {
+ AddressSpace string
+ Pool string
+ SubPool string
+ Options map[string]string
+ V6 bool
+}
+
+// RequestPoolResponse represents the response message to a ``request address pool`` request
+type RequestPoolResponse struct {
+ Response
+ PoolID string
+ Pool string // CIDR format
+ Data map[string]string
+}
+
+// ReleasePoolRequest represents the expected data in a ``release address pool`` request message
+type ReleasePoolRequest struct {
+ PoolID string
+}
+
+// ReleasePoolResponse represents the response message to a ``release address pool`` request
+type ReleasePoolResponse struct {
+ Response
+}
+
+// RequestAddressRequest represents the expected data in a ``request address`` request message
+type RequestAddressRequest struct {
+ PoolID string
+ Address string
+ Options map[string]string
+}
+
+// RequestAddressResponse represents the expected data in the response message to a ``request address`` request
+type RequestAddressResponse struct {
+ Response
+ Address string // in CIDR format
+ Data map[string]string
+}
+
+// ReleaseAddressRequest represents the expected data in a ``release address`` request message
+type ReleaseAddressRequest struct {
+ PoolID string
+ Address string
+}
+
+// ReleaseAddressResponse represents the response message to a ``release address`` request
+type ReleaseAddressResponse struct {
+ Response
+}
--- /dev/null
+package remote
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/docker/pkg/plugins"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/ipams/remote/api"
+ "github.com/docker/libnetwork/types"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+type allocator struct {
+ endpoint *plugins.Client
+ name string
+}
+
+// PluginResponse is the interface for the plugin request responses
+type PluginResponse interface {
+ IsSuccess() bool
+ GetError() string
+}
+
+func newAllocator(name string, client *plugins.Client) ipamapi.Ipam {
+ a := &allocator{name: name, endpoint: client}
+ return a
+}
+
+// Init registers a remote ipam when its plugin is activated
+func Init(cb ipamapi.Callback, l, g interface{}) error {
+
+ newPluginHandler := func(name string, client *plugins.Client) {
+ a := newAllocator(name, client)
+ if cps, err := a.(*allocator).getCapabilities(); err == nil {
+ if err := cb.RegisterIpamDriverWithCapabilities(name, a, cps); err != nil {
+ logrus.Errorf("error registering remote ipam driver %s due to %v", name, err)
+ }
+ } else {
+ logrus.Infof("remote ipam driver %s does not support capabilities", name)
+ logrus.Debug(err)
+ if err := cb.RegisterIpamDriver(name, a); err != nil {
+ logrus.Errorf("error registering remote ipam driver %s due to %v", name, err)
+ }
+ }
+ }
+
+ // Unit test code is unaware of a true PluginStore. So we fall back to v1 plugins.
+ handleFunc := plugins.Handle
+ if pg := cb.GetPluginGetter(); pg != nil {
+ handleFunc = pg.Handle
+ activePlugins := pg.GetAllManagedPluginsByCap(ipamapi.PluginEndpointType)
+ for _, ap := range activePlugins {
+ client, err := getPluginClient(ap)
+ if err != nil {
+ return err
+ }
+ newPluginHandler(ap.Name(), client)
+ }
+ }
+ handleFunc(ipamapi.PluginEndpointType, newPluginHandler)
+ return nil
+}
+
+func getPluginClient(p plugingetter.CompatPlugin) (*plugins.Client, error) {
+ if v1, ok := p.(plugingetter.PluginWithV1Client); ok {
+ return v1.Client(), nil
+ }
+
+ pa, ok := p.(plugingetter.PluginAddr)
+ if !ok {
+ return nil, errors.Errorf("unknown plugin type %T", p)
+ }
+
+ if pa.Protocol() != plugins.ProtocolSchemeHTTPV1 {
+ return nil, errors.Errorf("unsupported plugin protocol %s", pa.Protocol())
+ }
+
+ addr := pa.Addr()
+ client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, pa.Timeout())
+ if err != nil {
+ return nil, errors.Wrap(err, "error creating plugin client")
+ }
+ return client, nil
+}
+
+func (a *allocator) call(methodName string, arg interface{}, retVal PluginResponse) error {
+ method := ipamapi.PluginEndpointType + "." + methodName
+ err := a.endpoint.Call(method, arg, retVal)
+ if err != nil {
+ return err
+ }
+ if !retVal.IsSuccess() {
+ return fmt.Errorf("remote: %s", retVal.GetError())
+ }
+ return nil
+}
+
+func (a *allocator) getCapabilities() (*ipamapi.Capability, error) {
+ var res api.GetCapabilityResponse
+ if err := a.call("GetCapabilities", nil, &res); err != nil {
+ return nil, err
+ }
+ return res.ToCapability(), nil
+}
+
+// GetDefaultAddressSpaces returns the local and global default address spaces
+func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
+ res := &api.GetAddressSpacesResponse{}
+ if err := a.call("GetDefaultAddressSpaces", nil, res); err != nil {
+ return "", "", err
+ }
+ return res.LocalDefaultAddressSpace, res.GlobalDefaultAddressSpace, nil
+}
+
+// RequestPool requests an address pool in the specified address space
+func (a *allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
+ req := &api.RequestPoolRequest{AddressSpace: addressSpace, Pool: pool, SubPool: subPool, Options: options, V6: v6}
+ res := &api.RequestPoolResponse{}
+ if err := a.call("RequestPool", req, res); err != nil {
+ return "", nil, nil, err
+ }
+ retPool, err := types.ParseCIDR(res.Pool)
+ return res.PoolID, retPool, res.Data, err
+}
+
+// ReleasePool removes an address pool from the specified address space
+func (a *allocator) ReleasePool(poolID string) error {
+ req := &api.ReleasePoolRequest{PoolID: poolID}
+ res := &api.ReleasePoolResponse{}
+ return a.call("ReleasePool", req, res)
+}
+
+// RequestAddress requests an address from the address pool
+func (a *allocator) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) {
+ var (
+ prefAddress string
+ retAddress *net.IPNet
+ err error
+ )
+ if address != nil {
+ prefAddress = address.String()
+ }
+ req := &api.RequestAddressRequest{PoolID: poolID, Address: prefAddress, Options: options}
+ res := &api.RequestAddressResponse{}
+ if err := a.call("RequestAddress", req, res); err != nil {
+ return nil, nil, err
+ }
+ if res.Address != "" {
+ retAddress, err = types.ParseCIDR(res.Address)
+ } else {
+ return nil, nil, ipamapi.ErrNoIPReturned
+ }
+ return retAddress, res.Data, err
+}
+
+// ReleaseAddress releases the address from the specified address pool
+func (a *allocator) ReleaseAddress(poolID string, address net.IP) error {
+ var relAddress string
+ if address != nil {
+ relAddress = address.String()
+ }
+ req := &api.ReleaseAddressRequest{PoolID: poolID, Address: relAddress}
+ res := &api.ReleaseAddressResponse{}
+ return a.call("ReleaseAddress", req, res)
+}
+
+// DiscoverNew is a notification for a new discovery event, such as a new global datastore
+func (a *allocator) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
+func (a *allocator) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+func (a *allocator) IsBuiltIn() bool {
+ return false
+}
--- /dev/null
+package remote
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ "github.com/docker/docker/pkg/plugins"
+ "github.com/docker/libnetwork/ipamapi"
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func decodeToMap(r *http.Request) (res map[string]interface{}, err error) {
+ err = json.NewDecoder(r.Body).Decode(&res)
+ return
+}
+
+func handle(t *testing.T, mux *http.ServeMux, method string, h func(map[string]interface{}) interface{}) {
+ mux.HandleFunc(fmt.Sprintf("/%s.%s", ipamapi.PluginEndpointType, method), func(w http.ResponseWriter, r *http.Request) {
+ ask, err := decodeToMap(r)
+ if err != nil && err != io.EOF {
+ t.Fatal(err)
+ }
+ answer := h(ask)
+ err = json.NewEncoder(w).Encode(&answer)
+ if err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+
+func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() {
+ if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ server := httptest.NewServer(mux)
+ if server == nil {
+ t.Fatal("Failed to start an HTTP Server")
+ }
+
+ if err := ioutil.WriteFile(fmt.Sprintf("/etc/docker/plugins/%s.spec", name), []byte(server.URL), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprintf(w, `{"Implements": ["%s"]}`, ipamapi.PluginEndpointType)
+ })
+
+ return func() {
+ if err := os.RemoveAll("/etc/docker/plugins"); err != nil {
+ t.Fatal(err)
+ }
+ server.Close()
+ }
+}
+
+func TestGetCapabilities(t *testing.T) {
+ var plugin = "test-ipam-driver-capabilities"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{
+ "RequiresMACAddress": true,
+ }
+ })
+
+ p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ d := newAllocator(plugin, client)
+
+ caps, err := d.(*allocator).getCapabilities()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !caps.RequiresMACAddress || caps.RequiresRequestReplay {
+ t.Fatalf("Unexpected capability: %v", caps)
+ }
+}
+
+func TestGetCapabilitiesFromLegacyDriver(t *testing.T) {
+ var plugin = "test-ipam-legacy-driver"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ d := newAllocator(plugin, client)
+
+ if _, err := d.(*allocator).getCapabilities(); err == nil {
+ t.Fatalf("Expected error, but got Success %v", err)
+ }
+}
+
+func TestGetDefaultAddressSpaces(t *testing.T) {
+ var plugin = "test-ipam-driver-addr-spaces"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ handle(t, mux, "GetDefaultAddressSpaces", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{
+ "LocalDefaultAddressSpace": "white",
+ "GlobalDefaultAddressSpace": "blue",
+ }
+ })
+
+ p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ d := newAllocator(plugin, client)
+
+ l, g, err := d.(*allocator).GetDefaultAddressSpaces()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if l != "white" || g != "blue" {
+ t.Fatalf("Unexpected default local and global address spaces: %s, %s", l, g)
+ }
+}
+
+func TestRemoteDriver(t *testing.T) {
+ var plugin = "test-ipam-driver"
+
+ mux := http.NewServeMux()
+ defer setupPlugin(t, plugin, mux)()
+
+ handle(t, mux, "GetDefaultAddressSpaces", func(msg map[string]interface{}) interface{} {
+ return map[string]interface{}{
+ "LocalDefaultAddressSpace": "white",
+ "GlobalDefaultAddressSpace": "blue",
+ }
+ })
+
+ handle(t, mux, "RequestPool", func(msg map[string]interface{}) interface{} {
+ as := "white"
+ if v, ok := msg["AddressSpace"]; ok && v.(string) != "" {
+ as = v.(string)
+ }
+
+ pl := "172.18.0.0/16"
+ sp := ""
+ if v, ok := msg["Pool"]; ok && v.(string) != "" {
+ pl = v.(string)
+ }
+ if v, ok := msg["SubPool"]; ok && v.(string) != "" {
+ sp = v.(string)
+ }
+ pid := fmt.Sprintf("%s/%s", as, pl)
+ if sp != "" {
+ pid = fmt.Sprintf("%s/%s", pid, sp)
+ }
+ return map[string]interface{}{
+ "PoolID": pid,
+ "Pool": pl,
+ "Data": map[string]string{"DNS": "8.8.8.8"},
+ }
+ })
+
+ handle(t, mux, "ReleasePool", func(msg map[string]interface{}) interface{} {
+ if _, ok := msg["PoolID"]; !ok {
+ t.Fatal("Missing PoolID in Release request")
+ }
+ return map[string]interface{}{}
+ })
+
+ handle(t, mux, "RequestAddress", func(msg map[string]interface{}) interface{} {
+ if _, ok := msg["PoolID"]; !ok {
+ t.Fatal("Missing PoolID in address request")
+ }
+ prefAddr := ""
+ if v, ok := msg["Address"]; ok {
+ prefAddr = v.(string)
+ }
+ ip := prefAddr
+ if ip == "" {
+ ip = "172.20.0.34"
+ }
+ ip = fmt.Sprintf("%s/16", ip)
+ return map[string]interface{}{
+ "Address": ip,
+ }
+ })
+
+ handle(t, mux, "ReleaseAddress", func(msg map[string]interface{}) interface{} {
+ if _, ok := msg["PoolID"]; !ok {
+ t.Fatal("Missing PoolID in address request")
+ }
+ if _, ok := msg["Address"]; !ok {
+ t.Fatal("Missing Address in release address request")
+ }
+ return map[string]interface{}{}
+ })
+
+ p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client, err := getPluginClient(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ d := newAllocator(plugin, client)
+
+ l, g, err := d.(*allocator).GetDefaultAddressSpaces()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if l != "white" || g != "blue" {
+ t.Fatalf("Unexpected default local/global address spaces: %s, %s", l, g)
+ }
+
+ // Request any pool
+ poolID, pool, _, err := d.RequestPool("white", "", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if poolID != "white/172.18.0.0/16" {
+ t.Fatalf("Unexpected pool id: %s", poolID)
+ }
+ if pool == nil || pool.String() != "172.18.0.0/16" {
+ t.Fatalf("Unexpected pool: %s", pool)
+ }
+
+ // Request specific pool
+ poolID2, pool2, ops, err := d.RequestPool("white", "172.20.0.0/16", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if poolID2 != "white/172.20.0.0/16" {
+ t.Fatalf("Unexpected pool id: %s", poolID2)
+ }
+ if pool2 == nil || pool2.String() != "172.20.0.0/16" {
+ t.Fatalf("Unexpected pool: %s", pool2)
+ }
+ if dns, ok := ops["DNS"]; !ok || dns != "8.8.8.8" {
+ t.Fatal("Missing options")
+ }
+
+ // Request specific pool and subpool
+ poolID3, pool3, _, err := d.RequestPool("white", "172.20.0.0/16", "172.20.3.0/24" /*nil*/, map[string]string{"culo": "yes"}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if poolID3 != "white/172.20.0.0/16/172.20.3.0/24" {
+ t.Fatalf("Unexpected pool id: %s", poolID3)
+ }
+ if pool3 == nil || pool3.String() != "172.20.0.0/16" {
+ t.Fatalf("Unexpected pool: %s", pool3)
+ }
+
+ // Request any address
+ addr, _, err := d.RequestAddress(poolID2, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if addr == nil || addr.String() != "172.20.0.34/16" {
+ t.Fatalf("Unexpected address: %s", addr)
+ }
+
+ // Request specific address
+ addr2, _, err := d.RequestAddress(poolID2, net.ParseIP("172.20.1.45"), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if addr2 == nil || addr2.String() != "172.20.1.45/16" {
+ t.Fatalf("Unexpected address: %s", addr2)
+ }
+
+ // Release address
+ err = d.ReleaseAddress(poolID, net.ParseIP("172.18.1.45"))
+ if err != nil {
+ t.Fatal(err)
+ }
+}
--- /dev/null
+package windowsipam
+
+import (
+ "net"
+
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ localAddressSpace = "LocalDefault"
+ globalAddressSpace = "GlobalDefault"
+)
+
+// DefaultIPAM defines the default ipam-driver for local-scoped windows networks
+const DefaultIPAM = "windows"
+
+var (
+ defaultPool, _ = types.ParseCIDR("0.0.0.0/0")
+)
+
+type allocator struct {
+}
+
+// GetInit registers the built-in ipam service with libnetwork
+func GetInit(ipamName string) func(ic ipamapi.Callback, l, g interface{}) error {
+ return func(ic ipamapi.Callback, l, g interface{}) error {
+ return ic.RegisterIpamDriver(ipamName, &allocator{})
+ }
+}
+
+func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
+ return localAddressSpace, globalAddressSpace, nil
+}
+
+// RequestPool returns an address pool along with its unique id. This is a null ipam driver. It allocates the
+// subnet user asked and does not validate anything. Doesn't support subpool allocation
+func (a *allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
+ logrus.Debugf("RequestPool(%s, %s, %s, %v, %t)", addressSpace, pool, subPool, options, v6)
+ if subPool != "" || v6 {
+ return "", nil, nil, types.InternalErrorf("This request is not supported by null ipam driver")
+ }
+
+ var ipNet *net.IPNet
+ var err error
+
+ if pool != "" {
+ _, ipNet, err = net.ParseCIDR(pool)
+ if err != nil {
+ return "", nil, nil, err
+ }
+ } else {
+ ipNet = defaultPool
+ }
+
+ return ipNet.String(), ipNet, nil, nil
+}
+
+// ReleasePool releases the address pool - always succeeds
+func (a *allocator) ReleasePool(poolID string) error {
+ logrus.Debugf("ReleasePool(%s)", poolID)
+ return nil
+}
+
+// RequestAddress returns an address from the specified pool ID.
+// Always allocate the 0.0.0.0/32 ip if no preferred address was specified
+func (a *allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) {
+ logrus.Debugf("RequestAddress(%s, %v, %v)", poolID, prefAddress, opts)
+ _, ipNet, err := net.ParseCIDR(poolID)
+
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if prefAddress != nil {
+ return &net.IPNet{IP: prefAddress, Mask: ipNet.Mask}, nil, nil
+ }
+
+ return nil, nil, nil
+}
+
+// ReleaseAddress releases the address - always succeeds
+func (a *allocator) ReleaseAddress(poolID string, address net.IP) error {
+ logrus.Debugf("ReleaseAddress(%s, %v)", poolID, address)
+ return nil
+}
+
+// DiscoverNew informs the allocator about a new global scope datastore
+func (a *allocator) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+// DiscoverDelete is a notification of no interest for the allocator
+func (a *allocator) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+
+func (a *allocator) IsBuiltIn() bool {
+ return true
+}
--- /dev/null
+package windowsipam
+
+import (
+ "net"
+ "testing"
+
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/netlabel"
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+func TestWindowsIPAM(t *testing.T) {
+ a := &allocator{}
+ requestPool, _ := types.ParseCIDR("192.168.0.0/16")
+ requestAddress := net.ParseIP("192.168.1.1")
+
+ pid, pool, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(defaultPool, pool) ||
+ pid != pool.String() {
+ t.Fatalf("Unexpected data returned. Expected %v : %s. Got: %v : %s", defaultPool, pid, pool, pool.String())
+ }
+
+ pid, pool, _, err = a.RequestPool(localAddressSpace, requestPool.String(), "", nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(requestPool, pool) ||
+ pid != requestPool.String() {
+ t.Fatalf("Unexpected data returned. Expected %v : %s. Got: %v : %s", requestPool, requestPool.String(), pool, pool.String())
+ }
+
+ _, _, _, err = a.RequestPool(localAddressSpace, requestPool.String(), requestPool.String(), nil, false)
+ if err == nil {
+ t.Fatal("Unexpected success for subpool request")
+ }
+
+ _, _, _, err = a.RequestPool(localAddressSpace, requestPool.String(), "", nil, true)
+ if err == nil {
+ t.Fatal("Unexpected success for v6 request")
+ }
+
+ err = a.ReleasePool(requestPool.String())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ip, _, err := a.RequestAddress(requestPool.String(), nil, map[string]string{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if ip != nil {
+ t.Fatalf("Unexpected data returned. Expected %v . Got: %v ", requestPool, ip)
+ }
+
+ ip, _, err = a.RequestAddress(requestPool.String(), requestAddress, map[string]string{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !ip.IP.Equal(requestAddress) {
+ t.Fatalf("Unexpected data returned. Expected %v . Got: %v ", requestAddress, ip.IP)
+ }
+
+ requestOptions := map[string]string{}
+ requestOptions[ipamapi.RequestAddressType] = netlabel.Gateway
+ ip, _, err = a.RequestAddress(requestPool.String(), requestAddress, requestOptions)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !ip.IP.Equal(requestAddress) {
+ t.Fatalf("Unexpected data returned. Expected %v . Got: %v ", requestAddress, ip.IP)
+ }
+
+ err = a.ReleaseAddress(requestPool.String(), requestAddress)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
--- /dev/null
+// Package ipamutils provides utility functions for ipam management
+package ipamutils
+
+import (
+ "fmt"
+ "net"
+ "sync"
+)
+
+var (
+ // PredefinedLocalScopeDefaultNetworks contains a list of 31 IPv4 private networks with host size 16 and 12
+ // (172.17-31.x.x/16, 192.168.x.x/20) which do not overlap with the networks in `PredefinedGlobalScopeDefaultNetworks`
+ PredefinedLocalScopeDefaultNetworks []*net.IPNet
+ // PredefinedGlobalScopeDefaultNetworks contains a list of 64K IPv4 private networks with host size 8
+ // (10.x.x.x/24) which do not overlap with the networks in `PredefinedLocalScopeDefaultNetworks`
+ PredefinedGlobalScopeDefaultNetworks []*net.IPNet
+ mutex sync.Mutex
+ localScopeDefaultNetworks = []*NetworkToSplit{{"172.17.0.0/16", 16}, {"172.18.0.0/16", 16}, {"172.19.0.0/16", 16},
+ {"172.20.0.0/14", 16}, {"172.24.0.0/14", 16}, {"172.28.0.0/14", 16},
+ {"192.168.0.0/16", 20}}
+ globalScopeDefaultNetworks = []*NetworkToSplit{{"10.0.0.0/8", 24}}
+)
+
+// NetworkToSplit represent a network that has to be split in chunks with mask length Size.
+// Each subnet in the set is derived from the Base pool. Base is to be passed
+// in CIDR format.
+// Example: a Base "10.10.0.0/16 with Size 24 will define the set of 256
+// 10.10.[0-255].0/24 address pools
+type NetworkToSplit struct {
+ Base string `json:"base"`
+ Size int `json:"size"`
+}
+
+func init() {
+ var err error
+ if PredefinedGlobalScopeDefaultNetworks, err = splitNetworks(globalScopeDefaultNetworks); err != nil {
+ //we are going to panic in case of error as we should never get into this state
+ panic("InitAddressPools failed to initialize the global scope default address pool")
+ }
+
+ if PredefinedLocalScopeDefaultNetworks, err = splitNetworks(localScopeDefaultNetworks); err != nil {
+ //we are going to panic in case of error as we should never get into this state
+ panic("InitAddressPools failed to initialize the local scope default address pool")
+ }
+}
+
+// configDefaultNetworks configures local as well global default pool based on input
+func configDefaultNetworks(defaultAddressPool []*NetworkToSplit, result *[]*net.IPNet) error {
+ mutex.Lock()
+ defer mutex.Unlock()
+ defaultNetworks, err := splitNetworks(defaultAddressPool)
+ if err != nil {
+ return err
+ }
+ *result = defaultNetworks
+ return nil
+}
+
+// GetGlobalScopeDefaultNetworks returns PredefinedGlobalScopeDefaultNetworks
+func GetGlobalScopeDefaultNetworks() []*net.IPNet {
+ mutex.Lock()
+ defer mutex.Unlock()
+ return PredefinedGlobalScopeDefaultNetworks
+}
+
+// GetLocalScopeDefaultNetworks returns PredefinedLocalScopeDefaultNetworks
+func GetLocalScopeDefaultNetworks() []*net.IPNet {
+ mutex.Lock()
+ defer mutex.Unlock()
+ return PredefinedLocalScopeDefaultNetworks
+}
+
+// ConfigGlobalScopeDefaultNetworks configures global default pool.
+// Ideally this will be called from SwarmKit as part of swarm init
+func ConfigGlobalScopeDefaultNetworks(defaultAddressPool []*NetworkToSplit) error {
+ if defaultAddressPool == nil {
+ defaultAddressPool = globalScopeDefaultNetworks
+ }
+ return configDefaultNetworks(defaultAddressPool, &PredefinedGlobalScopeDefaultNetworks)
+}
+
+// ConfigLocalScopeDefaultNetworks configures local default pool.
+// Ideally this will be called during libnetwork init
+func ConfigLocalScopeDefaultNetworks(defaultAddressPool []*NetworkToSplit) error {
+ if defaultAddressPool == nil {
+ return nil
+ }
+ return configDefaultNetworks(defaultAddressPool, &PredefinedLocalScopeDefaultNetworks)
+}
+
+// splitNetworks takes a slice of networks, split them accordingly and returns them
+func splitNetworks(list []*NetworkToSplit) ([]*net.IPNet, error) {
+ localPools := make([]*net.IPNet, 0, len(list))
+
+ for _, p := range list {
+ _, b, err := net.ParseCIDR(p.Base)
+ if err != nil {
+ return nil, fmt.Errorf("invalid base pool %q: %v", p.Base, err)
+ }
+ ones, _ := b.Mask.Size()
+ if p.Size <= 0 || p.Size < ones {
+ return nil, fmt.Errorf("invalid pools size: %d", p.Size)
+ }
+ localPools = append(localPools, splitNetwork(p.Size, b)...)
+ }
+ return localPools, nil
+}
+
+func splitNetwork(size int, base *net.IPNet) []*net.IPNet {
+ one, bits := base.Mask.Size()
+ mask := net.CIDRMask(size, bits)
+ n := 1 << uint(size-one)
+ s := uint(bits - size)
+ list := make([]*net.IPNet, 0, n)
+
+ for i := 0; i < n; i++ {
+ ip := copyIP(base.IP)
+ addIntToIP(ip, uint(i<<s))
+ list = append(list, &net.IPNet{IP: ip, Mask: mask})
+ }
+ return list
+}
+
+func copyIP(from net.IP) net.IP {
+ ip := make([]byte, len(from))
+ copy(ip, from)
+ return ip
+}
+
+func addIntToIP(array net.IP, ordinal uint) {
+ for i := len(array) - 1; i >= 0; i-- {
+ array[i] |= (byte)(ordinal & 0xff)
+ ordinal >>= 8
+ }
+}
--- /dev/null
+package ipamutils
+
+import (
+ "net"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+)
+
+func initBroadPredefinedNetworks() []*net.IPNet {
+ pl := make([]*net.IPNet, 0, 31)
+ mask := []byte{255, 255, 0, 0}
+ for i := 17; i < 32; i++ {
+ pl = append(pl, &net.IPNet{IP: []byte{172, byte(i), 0, 0}, Mask: mask})
+ }
+ mask20 := []byte{255, 255, 240, 0}
+ for i := 0; i < 16; i++ {
+ pl = append(pl, &net.IPNet{IP: []byte{192, 168, byte(i << 4), 0}, Mask: mask20})
+ }
+ return pl
+}
+
+func initGranularPredefinedNetworks() []*net.IPNet {
+ pl := make([]*net.IPNet, 0, 256*256)
+ mask := []byte{255, 255, 255, 0}
+ for i := 0; i < 256; i++ {
+ for j := 0; j < 256; j++ {
+ pl = append(pl, &net.IPNet{IP: []byte{10, byte(i), byte(j), 0}, Mask: mask})
+ }
+ }
+ return pl
+}
+
+func initGlobalScopeNetworks() []*net.IPNet {
+ pl := make([]*net.IPNet, 0, 256*256)
+ mask := []byte{255, 255, 255, 0}
+ for i := 0; i < 256; i++ {
+ for j := 0; j < 256; j++ {
+ pl = append(pl, &net.IPNet{IP: []byte{30, byte(i), byte(j), 0}, Mask: mask})
+ }
+ }
+ return pl
+}
+
+func TestDefaultNetwork(t *testing.T) {
+ for _, nw := range PredefinedGlobalScopeDefaultNetworks {
+ if ones, bits := nw.Mask.Size(); bits != 32 || ones != 24 {
+ t.Fatalf("Unexpected size for network in granular list: %v", nw)
+ }
+ }
+
+ for _, nw := range PredefinedLocalScopeDefaultNetworks {
+ if ones, bits := nw.Mask.Size(); bits != 32 || (ones != 20 && ones != 16) {
+ t.Fatalf("Unexpected size for network in broad list: %v", nw)
+ }
+ }
+
+ originalBroadNets := initBroadPredefinedNetworks()
+ m := make(map[string]bool)
+ for _, v := range originalBroadNets {
+ m[v.String()] = true
+ }
+ for _, nw := range PredefinedLocalScopeDefaultNetworks {
+ _, ok := m[nw.String()]
+ assert.Check(t, ok)
+ delete(m, nw.String())
+ }
+
+ assert.Check(t, is.Len(m, 0))
+
+ originalGranularNets := initGranularPredefinedNetworks()
+
+ m = make(map[string]bool)
+ for _, v := range originalGranularNets {
+ m[v.String()] = true
+ }
+ for _, nw := range PredefinedGlobalScopeDefaultNetworks {
+ _, ok := m[nw.String()]
+ assert.Check(t, ok)
+ delete(m, nw.String())
+ }
+
+ assert.Check(t, is.Len(m, 0))
+}
+
+func TestConfigGlobalScopeDefaultNetworks(t *testing.T) {
+ err := ConfigGlobalScopeDefaultNetworks([]*NetworkToSplit{{"30.0.0.0/8", 24}})
+ assert.NilError(t, err)
+
+ originalGlobalScopeNetworks := initGlobalScopeNetworks()
+ m := make(map[string]bool)
+ for _, v := range originalGlobalScopeNetworks {
+ m[v.String()] = true
+ }
+ for _, nw := range PredefinedGlobalScopeDefaultNetworks {
+ _, ok := m[nw.String()]
+ assert.Check(t, ok)
+ delete(m, nw.String())
+ }
+
+ assert.Check(t, is.Len(m, 0))
+}
+
+func TestInitAddressPools(t *testing.T) {
+ err := ConfigLocalScopeDefaultNetworks([]*NetworkToSplit{{"172.80.0.0/16", 24}, {"172.90.0.0/16", 24}})
+ assert.NilError(t, err)
+
+ // Check for Random IPAddresses in PredefinedLocalScopeDefaultNetworks ex: first , last and middle
+ assert.Check(t, is.Len(PredefinedLocalScopeDefaultNetworks, 512), "Failed to find PredefinedLocalScopeDefaultNetworks")
+ assert.Check(t, is.Equal(PredefinedLocalScopeDefaultNetworks[0].String(), "172.80.0.0/24"))
+ assert.Check(t, is.Equal(PredefinedLocalScopeDefaultNetworks[127].String(), "172.80.127.0/24"))
+ assert.Check(t, is.Equal(PredefinedLocalScopeDefaultNetworks[255].String(), "172.80.255.0/24"))
+ assert.Check(t, is.Equal(PredefinedLocalScopeDefaultNetworks[256].String(), "172.90.0.0/24"))
+ assert.Check(t, is.Equal(PredefinedLocalScopeDefaultNetworks[383].String(), "172.90.127.0/24"))
+ assert.Check(t, is.Equal(PredefinedLocalScopeDefaultNetworks[511].String(), "172.90.255.0/24"))
+}
--- /dev/null
+package iptables
+
+import (
+ "errors"
+ "net"
+ "syscall"
+
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+var (
+ // ErrConntrackNotConfigurable means that conntrack module is not loaded or does not have the netlink module loaded
+ ErrConntrackNotConfigurable = errors.New("conntrack is not available")
+)
+
+// IsConntrackProgrammable returns true if the handle supports the NETLINK_NETFILTER and the base modules are loaded
+func IsConntrackProgrammable(nlh *netlink.Handle) bool {
+ return nlh.SupportsNetlinkFamily(syscall.NETLINK_NETFILTER)
+}
+
+// DeleteConntrackEntries deletes all the conntrack connections on the host for the specified IP
+// Returns the number of flows deleted for IPv4, IPv6 else error
+func DeleteConntrackEntries(nlh *netlink.Handle, ipv4List []net.IP, ipv6List []net.IP) (uint, uint, error) {
+ if !IsConntrackProgrammable(nlh) {
+ return 0, 0, ErrConntrackNotConfigurable
+ }
+
+ var totalIPv4FlowPurged uint
+ for _, ipAddress := range ipv4List {
+ flowPurged, err := purgeConntrackState(nlh, syscall.AF_INET, ipAddress)
+ if err != nil {
+ logrus.Warnf("Failed to delete conntrack state for %s: %v", ipAddress, err)
+ continue
+ }
+ totalIPv4FlowPurged += flowPurged
+ }
+
+ var totalIPv6FlowPurged uint
+ for _, ipAddress := range ipv6List {
+ flowPurged, err := purgeConntrackState(nlh, syscall.AF_INET6, ipAddress)
+ if err != nil {
+ logrus.Warnf("Failed to delete conntrack state for %s: %v", ipAddress, err)
+ continue
+ }
+ totalIPv6FlowPurged += flowPurged
+ }
+
+ logrus.Debugf("DeleteConntrackEntries purged ipv4:%d, ipv6:%d", totalIPv4FlowPurged, totalIPv6FlowPurged)
+ return totalIPv4FlowPurged, totalIPv6FlowPurged, nil
+}
+
+func purgeConntrackState(nlh *netlink.Handle, family netlink.InetFamily, ipAddress net.IP) (uint, error) {
+ filter := &netlink.ConntrackFilter{}
+ // NOTE: doing the flush using the ipAddress is safe because today there cannot be multiple networks with the same subnet
+ // so it will not be possible to flush flows that are of other containers
+ filter.AddIP(netlink.ConntrackNatAnyIP, ipAddress)
+ return nlh.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
+}
--- /dev/null
+package iptables
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/godbus/dbus"
+ "github.com/sirupsen/logrus"
+)
+
+// IPV defines the table string
+type IPV string
+
+const (
+ // Iptables point ipv4 table
+ Iptables IPV = "ipv4"
+ // IP6Tables point to ipv6 table
+ IP6Tables IPV = "ipv6"
+ // Ebtables point to bridge table
+ Ebtables IPV = "eb"
+)
+const (
+ dbusInterface = "org.fedoraproject.FirewallD1"
+ dbusPath = "/org/fedoraproject/FirewallD1"
+)
+
+// Conn is a connection to firewalld dbus endpoint.
+type Conn struct {
+ sysconn *dbus.Conn
+ sysobj dbus.BusObject
+ signal chan *dbus.Signal
+}
+
+var (
+ connection *Conn
+ firewalldRunning bool // is Firewalld service running
+ onReloaded []*func() // callbacks when Firewalld has been reloaded
+)
+
+// FirewalldInit initializes firewalld management code.
+func FirewalldInit() error {
+ var err error
+
+ if connection, err = newConnection(); err != nil {
+ return fmt.Errorf("Failed to connect to D-Bus system bus: %v", err)
+ }
+ firewalldRunning = checkRunning()
+ if !firewalldRunning {
+ connection.sysconn.Close()
+ connection = nil
+ }
+ if connection != nil {
+ go signalHandler()
+ }
+
+ return nil
+}
+
+// New() establishes a connection to the system bus.
+func newConnection() (*Conn, error) {
+ c := new(Conn)
+ if err := c.initConnection(); err != nil {
+ return nil, err
+ }
+
+ return c, nil
+}
+
+// Initialize D-Bus connection.
+func (c *Conn) initConnection() error {
+ var err error
+
+ c.sysconn, err = dbus.SystemBus()
+ if err != nil {
+ return err
+ }
+
+ // This never fails, even if the service is not running atm.
+ c.sysobj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath))
+
+ rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'",
+ dbusPath, dbusInterface, dbusInterface)
+ c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
+
+ rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'",
+ dbusInterface)
+ c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
+
+ c.signal = make(chan *dbus.Signal, 10)
+ c.sysconn.Signal(c.signal)
+
+ return nil
+}
+
+func signalHandler() {
+ for signal := range connection.signal {
+ if strings.Contains(signal.Name, "NameOwnerChanged") {
+ firewalldRunning = checkRunning()
+ dbusConnectionChanged(signal.Body)
+ } else if strings.Contains(signal.Name, "Reloaded") {
+ reloaded()
+ }
+ }
+}
+
+func dbusConnectionChanged(args []interface{}) {
+ name := args[0].(string)
+ oldOwner := args[1].(string)
+ newOwner := args[2].(string)
+
+ if name != dbusInterface {
+ return
+ }
+
+ if len(newOwner) > 0 {
+ connectionEstablished()
+ } else if len(oldOwner) > 0 {
+ connectionLost()
+ }
+}
+
+func connectionEstablished() {
+ reloaded()
+}
+
+func connectionLost() {
+ // Doesn't do anything for now. Libvirt also doesn't react to this.
+}
+
+// call all callbacks
+func reloaded() {
+ for _, pf := range onReloaded {
+ (*pf)()
+ }
+}
+
+// OnReloaded add callback
+func OnReloaded(callback func()) {
+ for _, pf := range onReloaded {
+ if pf == &callback {
+ return
+ }
+ }
+ onReloaded = append(onReloaded, &callback)
+}
+
+// Call some remote method to see whether the service is actually running.
+func checkRunning() bool {
+ var zone string
+ var err error
+
+ if connection != nil {
+ err = connection.sysobj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone)
+ return err == nil
+ }
+ return false
+}
+
+// Passthrough method simply passes args through to iptables/ip6tables
+func Passthrough(ipv IPV, args ...string) ([]byte, error) {
+ var output string
+ logrus.Debugf("Firewalld passthrough: %s, %s", ipv, args)
+ if err := connection.sysobj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output); err != nil {
+ return nil, err
+ }
+ return []byte(output), nil
+}
--- /dev/null
+package iptables
+
+import (
+ "net"
+ "strconv"
+ "testing"
+)
+
+func TestFirewalldInit(t *testing.T) {
+ if !checkRunning() {
+ t.Skip("firewalld is not running")
+ }
+ if err := FirewalldInit(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestReloaded(t *testing.T) {
+ var err error
+ var fwdChain *ChainInfo
+
+ fwdChain, err = NewChain("FWD", Filter, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ bridgeName := "lo"
+
+ err = ProgramChain(fwdChain, bridgeName, false, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer fwdChain.Remove()
+
+ // copy-pasted from iptables_test:TestLink
+ ip1 := net.ParseIP("192.168.1.1")
+ ip2 := net.ParseIP("192.168.1.2")
+ port := 1234
+ proto := "tcp"
+
+ err = fwdChain.Link(Append, ip1, ip2, port, proto, bridgeName)
+ if err != nil {
+ t.Fatal(err)
+ } else {
+ // to be re-called again later
+ OnReloaded(func() { fwdChain.Link(Append, ip1, ip2, port, proto, bridgeName) })
+ }
+
+ rule1 := []string{
+ "-i", bridgeName,
+ "-o", bridgeName,
+ "-p", proto,
+ "-s", ip1.String(),
+ "-d", ip2.String(),
+ "--dport", strconv.Itoa(port),
+ "-j", "ACCEPT"}
+
+ if !Exists(fwdChain.Table, fwdChain.Name, rule1...) {
+ t.Fatal("rule1 does not exist")
+ }
+
+ // flush all rules
+ fwdChain.Remove()
+
+ reloaded()
+
+ // make sure the rules have been recreated
+ if !Exists(fwdChain.Table, fwdChain.Name, rule1...) {
+ t.Fatal("rule1 hasn't been recreated")
+ }
+}
+
+func TestPassthrough(t *testing.T) {
+ rule1 := []string{
+ "-i", "lo",
+ "-p", "udp",
+ "--dport", "123",
+ "-j", "ACCEPT"}
+
+ if firewalldRunning {
+ _, err := Passthrough(Iptables, append([]string{"-A"}, rule1...)...)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !Exists(Filter, "INPUT", rule1...) {
+ t.Fatal("rule1 does not exist")
+ }
+ }
+
+}
--- /dev/null
+package iptables
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "os/exec"
+ "regexp"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/sirupsen/logrus"
+)
+
+// Action signifies the iptable action.
+type Action string
+
+// Policy is the default iptable policies
+type Policy string
+
+// Table refers to Nat, Filter or Mangle.
+type Table string
+
+const (
+ // Append appends the rule at the end of the chain.
+ Append Action = "-A"
+ // Delete deletes the rule from the chain.
+ Delete Action = "-D"
+ // Insert inserts the rule at the top of the chain.
+ Insert Action = "-I"
+ // Nat table is used for nat translation rules.
+ Nat Table = "nat"
+ // Filter table is used for filter rules.
+ Filter Table = "filter"
+ // Mangle table is used for mangling the packet.
+ Mangle Table = "mangle"
+ // Drop is the default iptables DROP policy
+ Drop Policy = "DROP"
+ // Accept is the default iptables ACCEPT policy
+ Accept Policy = "ACCEPT"
+)
+
+var (
+ iptablesPath string
+ supportsXlock = false
+ supportsCOpt = false
+ xLockWaitMsg = "Another app is currently holding the xtables lock"
+ // used to lock iptables commands if xtables lock is not supported
+ bestEffortLock sync.Mutex
+ // ErrIptablesNotFound is returned when the rule is not found.
+ ErrIptablesNotFound = errors.New("Iptables not found")
+ initOnce sync.Once
+)
+
+// ChainInfo defines the iptables chain.
+type ChainInfo struct {
+ Name string
+ Table Table
+ HairpinMode bool
+}
+
+// ChainError is returned to represent errors during ip table operation.
+type ChainError struct {
+ Chain string
+ Output []byte
+}
+
+func (e ChainError) Error() string {
+ return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
+}
+
+func probe() {
+ if out, err := exec.Command("modprobe", "-va", "nf_nat").CombinedOutput(); err != nil {
+ logrus.Warnf("Running modprobe nf_nat failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
+ }
+ if out, err := exec.Command("modprobe", "-va", "xt_conntrack").CombinedOutput(); err != nil {
+ logrus.Warnf("Running modprobe xt_conntrack failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
+ }
+}
+
+func initFirewalld() {
+ if err := FirewalldInit(); err != nil {
+ logrus.Debugf("Fail to initialize firewalld: %v, using raw iptables instead", err)
+ }
+}
+
+func detectIptables() {
+ path, err := exec.LookPath("iptables")
+ if err != nil {
+ return
+ }
+ iptablesPath = path
+ supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
+ mj, mn, mc, err := GetVersion()
+ if err != nil {
+ logrus.Warnf("Failed to read iptables version: %v", err)
+ return
+ }
+ supportsCOpt = supportsCOption(mj, mn, mc)
+}
+
+func initDependencies() {
+ probe()
+ initFirewalld()
+ detectIptables()
+}
+
+func initCheck() error {
+ initOnce.Do(initDependencies)
+
+ if iptablesPath == "" {
+ return ErrIptablesNotFound
+ }
+ return nil
+}
+
+// NewChain adds a new chain to ip table.
+func NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) {
+ c := &ChainInfo{
+ Name: name,
+ Table: table,
+ HairpinMode: hairpinMode,
+ }
+ if string(c.Table) == "" {
+ c.Table = Filter
+ }
+
+ // Add chain if it doesn't exist
+ if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
+ if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil {
+ return nil, err
+ } else if len(output) != 0 {
+ return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
+ }
+ }
+ return c, nil
+}
+
+// ProgramChain is used to add rules to a chain
+func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error {
+ if c.Name == "" {
+ return errors.New("Could not program chain, missing chain name")
+ }
+
+ switch c.Table {
+ case Nat:
+ preroute := []string{
+ "-m", "addrtype",
+ "--dst-type", "LOCAL",
+ "-j", c.Name}
+ if !Exists(Nat, "PREROUTING", preroute...) && enable {
+ if err := c.Prerouting(Append, preroute...); err != nil {
+ return fmt.Errorf("Failed to inject %s in PREROUTING chain: %s", c.Name, err)
+ }
+ } else if Exists(Nat, "PREROUTING", preroute...) && !enable {
+ if err := c.Prerouting(Delete, preroute...); err != nil {
+ return fmt.Errorf("Failed to remove %s in PREROUTING chain: %s", c.Name, err)
+ }
+ }
+ output := []string{
+ "-m", "addrtype",
+ "--dst-type", "LOCAL",
+ "-j", c.Name}
+ if !hairpinMode {
+ output = append(output, "!", "--dst", "127.0.0.0/8")
+ }
+ if !Exists(Nat, "OUTPUT", output...) && enable {
+ if err := c.Output(Append, output...); err != nil {
+ return fmt.Errorf("Failed to inject %s in OUTPUT chain: %s", c.Name, err)
+ }
+ } else if Exists(Nat, "OUTPUT", output...) && !enable {
+ if err := c.Output(Delete, output...); err != nil {
+ return fmt.Errorf("Failed to inject %s in OUTPUT chain: %s", c.Name, err)
+ }
+ }
+ case Filter:
+ if bridgeName == "" {
+ return fmt.Errorf("Could not program chain %s/%s, missing bridge name",
+ c.Table, c.Name)
+ }
+ link := []string{
+ "-o", bridgeName,
+ "-j", c.Name}
+ 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)
+ }
+
+ }
+ establish := []string{
+ "-o", bridgeName,
+ "-m", "conntrack",
+ "--ctstate", "RELATED,ESTABLISHED",
+ "-j", "ACCEPT"}
+ if !Exists(Filter, "FORWARD", establish...) && enable {
+ insert := append([]string{string(Insert), "FORWARD"}, establish...)
+ if output, err := Raw(insert...); err != nil {
+ return err
+ } else if len(output) != 0 {
+ return fmt.Errorf("Could not create establish rule to %s: %s", c.Table, output)
+ }
+ } else if Exists(Filter, "FORWARD", establish...) && !enable {
+ del := append([]string{string(Delete), "FORWARD"}, establish...)
+ if output, err := Raw(del...); err != nil {
+ return err
+ } else if len(output) != 0 {
+ return fmt.Errorf("Could not delete establish rule from %s: %s", c.Table, output)
+ }
+ }
+ }
+ return nil
+}
+
+// RemoveExistingChain removes existing chain from the table.
+func RemoveExistingChain(name string, table Table) error {
+ c := &ChainInfo{
+ Name: name,
+ Table: table,
+ }
+ if string(c.Table) == "" {
+ c.Table = Filter
+ }
+ return c.Remove()
+}
+
+// Forward adds forwarding rule to 'filter' table and corresponding nat rule to 'nat' table.
+func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int, bridgeName string) error {
+ daddr := ip.String()
+ if ip.IsUnspecified() {
+ // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
+ // want "0.0.0.0/0". "0/0" is correctly interpreted as "any
+ // value" by both iptables and ip6tables.
+ daddr = "0/0"
+ }
+
+ args := []string{
+ "-p", proto,
+ "-d", daddr,
+ "--dport", strconv.Itoa(port),
+ "-j", "DNAT",
+ "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))}
+ if !c.HairpinMode {
+ args = append(args, "!", "-i", bridgeName)
+ }
+ if err := ProgramRule(Nat, c.Name, action, args); err != nil {
+ return err
+ }
+
+ args = []string{
+ "!", "-i", bridgeName,
+ "-o", bridgeName,
+ "-p", proto,
+ "-d", destAddr,
+ "--dport", strconv.Itoa(destPort),
+ "-j", "ACCEPT",
+ }
+ if err := ProgramRule(Filter, c.Name, action, args); err != nil {
+ return err
+ }
+
+ args = []string{
+ "-p", proto,
+ "-s", destAddr,
+ "-d", destAddr,
+ "--dport", strconv.Itoa(destPort),
+ "-j", "MASQUERADE",
+ }
+
+ if err := ProgramRule(Nat, "POSTROUTING", action, args); err != nil {
+ return err
+ }
+
+ if proto == "sctp" {
+ // Linux kernel v4.9 and below enables NETIF_F_SCTP_CRC for veth by
+ // the following commit.
+ // This introduces a problem when conbined with a physical NIC without
+ // NETIF_F_SCTP_CRC. As for a workaround, here we add an iptables entry
+ // to fill the checksum.
+ //
+ // https://github.com/torvalds/linux/commit/c80fafbbb59ef9924962f83aac85531039395b18
+ args = []string{
+ "-p", proto,
+ "--sport", strconv.Itoa(destPort),
+ "-j", "CHECKSUM",
+ "--checksum-fill",
+ }
+ if err := ProgramRule(Mangle, "POSTROUTING", action, args); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Link adds reciprocal ACCEPT rule for two supplied IP addresses.
+// Traffic is allowed from ip1 to ip2 and vice-versa
+func (c *ChainInfo) Link(action Action, ip1, ip2 net.IP, port int, proto string, bridgeName string) error {
+ // forward
+ args := []string{
+ "-i", bridgeName, "-o", bridgeName,
+ "-p", proto,
+ "-s", ip1.String(),
+ "-d", ip2.String(),
+ "--dport", strconv.Itoa(port),
+ "-j", "ACCEPT",
+ }
+ if err := ProgramRule(Filter, c.Name, action, args); err != nil {
+ return err
+ }
+ // reverse
+ args[7], args[9] = args[9], args[7]
+ args[10] = "--sport"
+ return ProgramRule(Filter, c.Name, action, args)
+}
+
+// ProgramRule adds the rule specified by args only if the
+// rule is not already present in the chain. Reciprocally,
+// it removes the rule only if present.
+func ProgramRule(table Table, chain string, action Action, args []string) error {
+ if Exists(table, chain, args...) != (action == Delete) {
+ return nil
+ }
+ return RawCombinedOutput(append([]string{"-t", string(table), string(action), chain}, args...)...)
+}
+
+// Prerouting adds linking rule to nat/PREROUTING chain.
+func (c *ChainInfo) Prerouting(action Action, args ...string) error {
+ a := []string{"-t", string(Nat), string(action), "PREROUTING"}
+ if len(args) > 0 {
+ a = append(a, args...)
+ }
+ if output, err := Raw(a...); err != nil {
+ return err
+ } else if len(output) != 0 {
+ return ChainError{Chain: "PREROUTING", Output: output}
+ }
+ return nil
+}
+
+// Output adds linking rule to an OUTPUT chain.
+func (c *ChainInfo) Output(action Action, args ...string) error {
+ a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
+ if len(args) > 0 {
+ a = append(a, args...)
+ }
+ if output, err := Raw(a...); err != nil {
+ return err
+ } else if len(output) != 0 {
+ return ChainError{Chain: "OUTPUT", Output: output}
+ }
+ return nil
+}
+
+// Remove removes the chain.
+func (c *ChainInfo) Remove() error {
+ // Ignore errors - This could mean the chains were never set up
+ if c.Table == Nat {
+ c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name)
+ c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", c.Name)
+ c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) // Created in versions <= 0.1.6
+
+ c.Prerouting(Delete)
+ c.Output(Delete)
+ }
+ Raw("-t", string(c.Table), "-F", c.Name)
+ Raw("-t", string(c.Table), "-X", c.Name)
+ return nil
+}
+
+// Exists checks if a rule exists
+func Exists(table Table, chain string, rule ...string) bool {
+ return exists(false, table, chain, rule...)
+}
+
+// ExistsNative behaves as Exists with the difference it
+// will always invoke `iptables` binary.
+func ExistsNative(table Table, chain string, rule ...string) bool {
+ return exists(true, table, chain, rule...)
+}
+
+func exists(native bool, table Table, chain string, rule ...string) bool {
+ f := Raw
+ if native {
+ f = raw
+ }
+
+ if string(table) == "" {
+ table = Filter
+ }
+
+ if err := initCheck(); err != nil {
+ // The exists() signature does not allow us to return an error, but at least
+ // we can skip the (likely invalid) exec invocation.
+ return false
+ }
+
+ if supportsCOpt {
+ // if exit status is 0 then return true, the rule exists
+ _, err := f(append([]string{"-t", string(table), "-C", chain}, rule...)...)
+ return err == nil
+ }
+
+ // parse "iptables -S" for the rule (it checks rules in a specific chain
+ // in a specific table and it is very unreliable)
+ return existsRaw(table, chain, rule...)
+}
+
+func existsRaw(table Table, chain string, rule ...string) bool {
+ ruleString := fmt.Sprintf("%s %s\n", chain, strings.Join(rule, " "))
+ existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
+
+ return strings.Contains(string(existingRules), ruleString)
+}
+
+// Maximum duration that an iptables operation can take
+// before flagging a warning.
+const opWarnTime = 2 * time.Second
+
+func filterOutput(start time.Time, output []byte, args ...string) []byte {
+ // Flag operations that have taken a long time to complete
+ opTime := time.Since(start)
+ if opTime > opWarnTime {
+ logrus.Warnf("xtables contention detected while running [%s]: Waited for %.2f seconds and received %q", strings.Join(args, " "), float64(opTime)/float64(time.Second), string(output))
+ }
+ // ignore iptables' message about xtables lock:
+ // it is a warning, not an error.
+ if strings.Contains(string(output), xLockWaitMsg) {
+ output = []byte("")
+ }
+ // Put further filters here if desired
+ return output
+}
+
+// Raw calls 'iptables' system command, passing supplied arguments.
+func Raw(args ...string) ([]byte, error) {
+ if firewalldRunning {
+ startTime := time.Now()
+ output, err := Passthrough(Iptables, args...)
+ if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
+ return filterOutput(startTime, output, args...), err
+ }
+ }
+ return raw(args...)
+}
+
+func raw(args ...string) ([]byte, error) {
+ if err := initCheck(); err != nil {
+ return nil, err
+ }
+ if supportsXlock {
+ args = append([]string{"--wait"}, args...)
+ } else {
+ bestEffortLock.Lock()
+ defer bestEffortLock.Unlock()
+ }
+
+ logrus.Debugf("%s, %v", iptablesPath, args)
+
+ startTime := time.Now()
+ output, err := exec.Command(iptablesPath, args...).CombinedOutput()
+ if err != nil {
+ return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
+ }
+
+ return filterOutput(startTime, output, args...), err
+}
+
+// RawCombinedOutput internally calls the Raw function and returns a non nil
+// error if Raw returned a non nil error or a non empty output
+func RawCombinedOutput(args ...string) error {
+ if output, err := Raw(args...); err != nil || len(output) != 0 {
+ return fmt.Errorf("%s (%v)", string(output), err)
+ }
+ return nil
+}
+
+// RawCombinedOutputNative behave as RawCombinedOutput with the difference it
+// will always invoke `iptables` binary
+func RawCombinedOutputNative(args ...string) error {
+ if output, err := raw(args...); err != nil || len(output) != 0 {
+ return fmt.Errorf("%s (%v)", string(output), err)
+ }
+ return nil
+}
+
+// ExistChain checks if a chain exists
+func ExistChain(chain string, table Table) bool {
+ if _, err := Raw("-t", string(table), "-nL", chain); err == nil {
+ return true
+ }
+ return false
+}
+
+// GetVersion reads the iptables version numbers during initialization
+func GetVersion() (major, minor, micro int, err error) {
+ out, err := exec.Command(iptablesPath, "--version").CombinedOutput()
+ if err == nil {
+ major, minor, micro = parseVersionNumbers(string(out))
+ }
+ return
+}
+
+// SetDefaultPolicy sets the passed default policy for the table/chain
+func SetDefaultPolicy(table Table, chain string, policy Policy) error {
+ if err := RawCombinedOutput("-t", string(table), "-P", chain, string(policy)); err != nil {
+ return fmt.Errorf("setting default policy to %v in %v chain failed: %v", policy, chain, err)
+ }
+ return nil
+}
+
+func parseVersionNumbers(input string) (major, minor, micro int) {
+ re := regexp.MustCompile(`v\d*.\d*.\d*`)
+ line := re.FindString(input)
+ fmt.Sscanf(line, "v%d.%d.%d", &major, &minor, µ)
+ return
+}
+
+// iptables -C, --check option was added in v.1.4.11
+// http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
+func supportsCOption(mj, mn, mc int) bool {
+ return mj > 1 || (mj == 1 && (mn > 4 || (mn == 4 && mc >= 11)))
+}
+
+// AddReturnRule adds a return rule for the chain in the filter table
+func AddReturnRule(chain string) error {
+ var (
+ table = Filter
+ args = []string{"-j", "RETURN"}
+ )
+
+ if Exists(table, chain, args...) {
+ return nil
+ }
+
+ err := RawCombinedOutput(append([]string{"-A", chain}, args...)...)
+ if err != nil {
+ return fmt.Errorf("unable to add return rule in %s chain: %s", chain, err.Error())
+ }
+
+ return nil
+}
+
+// EnsureJumpRule ensures the jump rule is on top
+func EnsureJumpRule(fromChain, toChain string) error {
+ var (
+ table = Filter
+ args = []string{"-j", toChain}
+ )
+
+ if Exists(table, fromChain, args...) {
+ err := RawCombinedOutput(append([]string{"-D", fromChain}, args...)...)
+ if err != nil {
+ return fmt.Errorf("unable to remove jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
+ }
+ }
+
+ err := RawCombinedOutput(append([]string{"-I", fromChain}, args...)...)
+ if err != nil {
+ return fmt.Errorf("unable to insert jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
+ }
+
+ return nil
+}
--- /dev/null
+package iptables
+
+import (
+ "net"
+ "os/exec"
+ "strconv"
+ "strings"
+ "sync"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+const chainName = "DOCKEREST"
+
+var natChain *ChainInfo
+var filterChain *ChainInfo
+var bridgeName string
+
+func TestNewChain(t *testing.T) {
+ var err error
+
+ bridgeName = "lo"
+ natChain, err = NewChain(chainName, Nat, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = ProgramChain(natChain, bridgeName, false, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ filterChain, err = NewChain(chainName, Filter, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = ProgramChain(filterChain, bridgeName, false, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestForward(t *testing.T) {
+ ip := net.ParseIP("192.168.1.1")
+ port := 1234
+ dstAddr := "172.17.0.1"
+ dstPort := 4321
+ proto := "tcp"
+
+ bridgeName := "lo"
+ err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort, bridgeName)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ dnatRule := []string{
+ "-d", ip.String(),
+ "-p", proto,
+ "--dport", strconv.Itoa(port),
+ "-j", "DNAT",
+ "--to-destination", dstAddr + ":" + strconv.Itoa(dstPort),
+ "!", "-i", bridgeName,
+ }
+
+ if !Exists(natChain.Table, natChain.Name, dnatRule...) {
+ t.Fatal("DNAT rule does not exist")
+ }
+
+ filterRule := []string{
+ "!", "-i", bridgeName,
+ "-o", bridgeName,
+ "-d", dstAddr,
+ "-p", proto,
+ "--dport", strconv.Itoa(dstPort),
+ "-j", "ACCEPT",
+ }
+
+ if !Exists(filterChain.Table, filterChain.Name, filterRule...) {
+ t.Fatal("filter rule does not exist")
+ }
+
+ masqRule := []string{
+ "-d", dstAddr,
+ "-s", dstAddr,
+ "-p", proto,
+ "--dport", strconv.Itoa(dstPort),
+ "-j", "MASQUERADE",
+ }
+
+ if !Exists(natChain.Table, "POSTROUTING", masqRule...) {
+ t.Fatal("MASQUERADE rule does not exist")
+ }
+}
+
+func TestLink(t *testing.T) {
+ var err error
+
+ bridgeName := "lo"
+ ip1 := net.ParseIP("192.168.1.1")
+ ip2 := net.ParseIP("192.168.1.2")
+ port := 1234
+ proto := "tcp"
+
+ err = filterChain.Link(Append, ip1, ip2, port, proto, bridgeName)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rule1 := []string{
+ "-i", bridgeName,
+ "-o", bridgeName,
+ "-p", proto,
+ "-s", ip1.String(),
+ "-d", ip2.String(),
+ "--dport", strconv.Itoa(port),
+ "-j", "ACCEPT"}
+
+ if !Exists(filterChain.Table, filterChain.Name, rule1...) {
+ t.Fatal("rule1 does not exist")
+ }
+
+ rule2 := []string{
+ "-i", bridgeName,
+ "-o", bridgeName,
+ "-p", proto,
+ "-s", ip2.String(),
+ "-d", ip1.String(),
+ "--sport", strconv.Itoa(port),
+ "-j", "ACCEPT"}
+
+ if !Exists(filterChain.Table, filterChain.Name, rule2...) {
+ t.Fatal("rule2 does not exist")
+ }
+}
+
+func TestPrerouting(t *testing.T) {
+ args := []string{
+ "-i", "lo",
+ "-d", "192.168.1.1"}
+
+ err := natChain.Prerouting(Insert, args...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !Exists(natChain.Table, "PREROUTING", args...) {
+ t.Fatal("rule does not exist")
+ }
+
+ delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, args...)
+ if _, err = Raw(delRule...); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestOutput(t *testing.T) {
+ args := []string{
+ "-o", "lo",
+ "-d", "192.168.1.1"}
+
+ err := natChain.Output(Insert, args...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !Exists(natChain.Table, "OUTPUT", args...) {
+ t.Fatal("rule does not exist")
+ }
+
+ delRule := append([]string{"-D", "OUTPUT", "-t",
+ string(natChain.Table)}, args...)
+ if _, err = Raw(delRule...); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestConcurrencyWithWait(t *testing.T) {
+ RunConcurrencyTest(t, true)
+}
+
+func TestConcurrencyNoWait(t *testing.T) {
+ RunConcurrencyTest(t, false)
+}
+
+// Runs 10 concurrent rule additions. This will fail if iptables
+// is actually invoked simultaneously without --wait.
+// Note that if iptables does not support the xtable lock on this
+// system, then allowXlock has no effect -- it will always be off.
+func RunConcurrencyTest(t *testing.T, allowXlock bool) {
+ var wg sync.WaitGroup
+
+ if !allowXlock && supportsXlock {
+ supportsXlock = false
+ defer func() { supportsXlock = true }()
+ }
+
+ ip := net.ParseIP("192.168.1.1")
+ port := 1234
+ dstAddr := "172.17.0.1"
+ dstPort := 4321
+ proto := "tcp"
+
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ err := natChain.Forward(Append, ip, port, proto, dstAddr, dstPort, "lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+ }
+ wg.Wait()
+}
+
+func TestCleanup(t *testing.T) {
+ var err error
+ var rules []byte
+
+ // Cleanup filter/FORWARD first otherwise output of iptables-save is dirty
+ link := []string{"-t", string(filterChain.Table),
+ string(Delete), "FORWARD",
+ "-o", bridgeName,
+ "-j", filterChain.Name}
+ if _, err = Raw(link...); err != nil {
+ t.Fatal(err)
+ }
+ filterChain.Remove()
+
+ err = RemoveExistingChain(chainName, Nat)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rules, err = exec.Command("iptables-save").Output()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.Contains(string(rules), chainName) {
+ t.Fatalf("Removing chain failed. %s found in iptables-save", chainName)
+ }
+}
+
+func TestExistsRaw(t *testing.T) {
+ testChain1 := "ABCD"
+ testChain2 := "EFGH"
+
+ _, err := NewChain(testChain1, Filter, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ RemoveExistingChain(testChain1, Filter)
+ }()
+
+ _, err = NewChain(testChain2, Filter, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ RemoveExistingChain(testChain2, Filter)
+ }()
+
+ // Test detection over full and truncated rule string
+ input := []struct{ rule []string }{
+ {[]string{"-s", "172.8.9.9/32", "-j", "ACCEPT"}},
+ {[]string{"-d", "172.8.9.0/24", "-j", "DROP"}},
+ {[]string{"-s", "172.0.3.0/24", "-d", "172.17.0.0/24", "-p", "tcp", "-m", "tcp", "--dport", "80", "-j", testChain2}},
+ {[]string{"-j", "RETURN"}},
+ }
+
+ for i, r := range input {
+ ruleAdd := append([]string{"-t", string(Filter), "-A", testChain1}, r.rule...)
+ err = RawCombinedOutput(ruleAdd...)
+ if err != nil {
+ t.Fatalf("i=%d, err: %v", i, err)
+ }
+ if !existsRaw(Filter, testChain1, r.rule...) {
+ t.Fatalf("Failed to detect rule. i=%d", i)
+ }
+ // Truncate the rule
+ trg := r.rule[len(r.rule)-1]
+ trg = trg[:len(trg)-2]
+ r.rule[len(r.rule)-1] = trg
+ if existsRaw(Filter, testChain1, r.rule...) {
+ t.Fatalf("Invalid detection. i=%d", i)
+ }
+ }
+}
+
+func TestGetVersion(t *testing.T) {
+ mj, mn, mc := parseVersionNumbers("iptables v1.4.19.1-alpha")
+ if mj != 1 || mn != 4 || mc != 19 {
+ t.Fatal("Failed to parse version numbers")
+ }
+}
+
+func TestSupportsCOption(t *testing.T) {
+ input := []struct {
+ mj int
+ mn int
+ mc int
+ ok bool
+ }{
+ {1, 4, 11, true},
+ {1, 4, 12, true},
+ {1, 5, 0, true},
+ {0, 4, 11, false},
+ {0, 5, 12, false},
+ {1, 3, 12, false},
+ {1, 4, 10, false},
+ }
+ for ind, inp := range input {
+ if inp.ok != supportsCOption(inp.mj, inp.mn, inp.mc) {
+ t.Fatalf("Incorrect check: %d", ind)
+ }
+ }
+}
--- /dev/null
+// +build linux
+
+package ipvs
+
+const (
+ genlCtrlID = 0x10
+)
+
+// GENL control commands
+const (
+ genlCtrlCmdUnspec uint8 = iota
+ genlCtrlCmdNewFamily
+ genlCtrlCmdDelFamily
+ genlCtrlCmdGetFamily
+)
+
+// GENL family attributes
+const (
+ genlCtrlAttrUnspec int = iota
+ genlCtrlAttrFamilyID
+ genlCtrlAttrFamilyName
+)
+
+// IPVS genl commands
+const (
+ ipvsCmdUnspec uint8 = iota
+ ipvsCmdNewService
+ ipvsCmdSetService
+ ipvsCmdDelService
+ ipvsCmdGetService
+ ipvsCmdNewDest
+ ipvsCmdSetDest
+ ipvsCmdDelDest
+ ipvsCmdGetDest
+ ipvsCmdNewDaemon
+ ipvsCmdDelDaemon
+ ipvsCmdGetDaemon
+ ipvsCmdSetConfig
+ ipvsCmdGetConfig
+ ipvsCmdSetInfo
+ ipvsCmdGetInfo
+ ipvsCmdZero
+ ipvsCmdFlush
+)
+
+// Attributes used in the first level of commands
+const (
+ ipvsCmdAttrUnspec int = iota
+ ipvsCmdAttrService
+ ipvsCmdAttrDest
+ ipvsCmdAttrDaemon
+ ipvsCmdAttrTimeoutTCP
+ ipvsCmdAttrTimeoutTCPFin
+ ipvsCmdAttrTimeoutUDP
+)
+
+// Attributes used to describe a service. Used inside nested attribute
+// ipvsCmdAttrService
+const (
+ ipvsSvcAttrUnspec int = iota
+ ipvsSvcAttrAddressFamily
+ ipvsSvcAttrProtocol
+ ipvsSvcAttrAddress
+ ipvsSvcAttrPort
+ ipvsSvcAttrFWMark
+ ipvsSvcAttrSchedName
+ ipvsSvcAttrFlags
+ ipvsSvcAttrTimeout
+ ipvsSvcAttrNetmask
+ ipvsSvcAttrStats
+ ipvsSvcAttrPEName
+)
+
+// Attributes used to describe a destination (real server). Used
+// inside nested attribute ipvsCmdAttrDest.
+const (
+ ipvsDestAttrUnspec int = iota
+ ipvsDestAttrAddress
+ ipvsDestAttrPort
+ ipvsDestAttrForwardingMethod
+ ipvsDestAttrWeight
+ ipvsDestAttrUpperThreshold
+ ipvsDestAttrLowerThreshold
+ ipvsDestAttrActiveConnections
+ ipvsDestAttrInactiveConnections
+ ipvsDestAttrPersistentConnections
+ ipvsDestAttrStats
+ ipvsDestAttrAddressFamily
+)
+
+// IPVS Svc Statistics constancs
+
+const (
+ ipvsSvcStatsUnspec int = iota
+ ipvsSvcStatsConns
+ ipvsSvcStatsPktsIn
+ ipvsSvcStatsPktsOut
+ ipvsSvcStatsBytesIn
+ ipvsSvcStatsBytesOut
+ ipvsSvcStatsCPS
+ ipvsSvcStatsPPSIn
+ ipvsSvcStatsPPSOut
+ ipvsSvcStatsBPSIn
+ ipvsSvcStatsBPSOut
+)
+
+// Destination forwarding methods
+const (
+ // ConnectionFlagFwdmask indicates the mask in the connection
+ // flags which is used by forwarding method bits.
+ ConnectionFlagFwdMask = 0x0007
+
+ // ConnectionFlagMasq is used for masquerade forwarding method.
+ ConnectionFlagMasq = 0x0000
+
+ // ConnectionFlagLocalNode is used for local node forwarding
+ // method.
+ ConnectionFlagLocalNode = 0x0001
+
+ // ConnectionFlagTunnel is used for tunnel mode forwarding
+ // method.
+ ConnectionFlagTunnel = 0x0002
+
+ // ConnectionFlagDirectRoute is used for direct routing
+ // forwarding method.
+ ConnectionFlagDirectRoute = 0x0003
+)
+
+const (
+ // RoundRobin distributes jobs equally amongst the available
+ // real servers.
+ RoundRobin = "rr"
+
+ // LeastConnection assigns more jobs to real servers with
+ // fewer active jobs.
+ LeastConnection = "lc"
+
+ // DestinationHashing assigns jobs to servers through looking
+ // up a statically assigned hash table by their destination IP
+ // addresses.
+ DestinationHashing = "dh"
+
+ // SourceHashing assigns jobs to servers through looking up
+ // a statically assigned hash table by their source IP
+ // addresses.
+ SourceHashing = "sh"
+)
+
+const (
+ // ConnFwdMask is a mask for the fwd methods
+ ConnFwdMask = 0x0007
+
+ // ConnFwdMasq denotes forwarding via masquerading/NAT
+ ConnFwdMasq = 0x0000
+
+ // ConnFwdLocalNode denotes forwarding to a local node
+ ConnFwdLocalNode = 0x0001
+
+ // ConnFwdTunnel denotes forwarding via a tunnel
+ ConnFwdTunnel = 0x0002
+
+ // ConnFwdDirectRoute denotes forwarding via direct routing
+ ConnFwdDirectRoute = 0x0003
+
+ // ConnFwdBypass denotes forwarding while bypassing the cache
+ ConnFwdBypass = 0x0004
+)
--- /dev/null
+// +build linux
+
+package ipvs
+
+import (
+ "net"
+ "syscall"
+ "time"
+
+ "fmt"
+
+ "github.com/vishvananda/netlink/nl"
+ "github.com/vishvananda/netns"
+)
+
+const (
+ netlinkRecvSocketsTimeout = 3 * time.Second
+ netlinkSendSocketTimeout = 30 * time.Second
+)
+
+// Service defines an IPVS service in its entirety.
+type Service struct {
+ // Virtual service address.
+ Address net.IP
+ Protocol uint16
+ Port uint16
+ FWMark uint32 // Firewall mark of the service.
+
+ // Virtual service options.
+ SchedName string
+ Flags uint32
+ Timeout uint32
+ Netmask uint32
+ AddressFamily uint16
+ PEName string
+ Stats SvcStats
+}
+
+// SvcStats defines an IPVS service statistics
+type SvcStats struct {
+ Connections uint32
+ PacketsIn uint32
+ PacketsOut uint32
+ BytesIn uint64
+ BytesOut uint64
+ CPS uint32
+ BPSOut uint32
+ PPSIn uint32
+ PPSOut uint32
+ BPSIn uint32
+}
+
+// Destination defines an IPVS destination (real server) in its
+// entirety.
+type Destination struct {
+ Address net.IP
+ Port uint16
+ Weight int
+ ConnectionFlags uint32
+ AddressFamily uint16
+ UpperThreshold uint32
+ LowerThreshold uint32
+}
+
+// Handle provides a namespace specific ipvs handle to program ipvs
+// rules.
+type Handle struct {
+ seq uint32
+ sock *nl.NetlinkSocket
+}
+
+// New provides a new ipvs handle in the namespace pointed to by the
+// passed path. It will return a valid handle or an error in case an
+// error occurred while creating the handle.
+func New(path string) (*Handle, error) {
+ setup()
+
+ n := netns.None()
+ if path != "" {
+ var err error
+ n, err = netns.GetFromPath(path)
+ if err != nil {
+ return nil, err
+ }
+ }
+ defer n.Close()
+
+ sock, err := nl.GetNetlinkSocketAt(n, netns.None(), syscall.NETLINK_GENERIC)
+ if err != nil {
+ return nil, err
+ }
+ // Add operation timeout to avoid deadlocks
+ tv := syscall.NsecToTimeval(netlinkSendSocketTimeout.Nanoseconds())
+ if err := sock.SetSendTimeout(&tv); err != nil {
+ return nil, err
+ }
+ tv = syscall.NsecToTimeval(netlinkRecvSocketsTimeout.Nanoseconds())
+ if err := sock.SetReceiveTimeout(&tv); err != nil {
+ return nil, err
+ }
+
+ return &Handle{sock: sock}, nil
+}
+
+// Close closes the ipvs handle. The handle is invalid after Close
+// returns.
+func (i *Handle) Close() {
+ if i.sock != nil {
+ i.sock.Close()
+ }
+}
+
+// NewService creates a new ipvs service in the passed handle.
+func (i *Handle) NewService(s *Service) error {
+ return i.doCmd(s, nil, ipvsCmdNewService)
+}
+
+// IsServicePresent queries for the ipvs service in the passed handle.
+func (i *Handle) IsServicePresent(s *Service) bool {
+ return nil == i.doCmd(s, nil, ipvsCmdGetService)
+}
+
+// UpdateService updates an already existing service in the passed
+// handle.
+func (i *Handle) UpdateService(s *Service) error {
+ return i.doCmd(s, nil, ipvsCmdSetService)
+}
+
+// DelService deletes an already existing service in the passed
+// handle.
+func (i *Handle) DelService(s *Service) error {
+ return i.doCmd(s, nil, ipvsCmdDelService)
+}
+
+// Flush deletes all existing services in the passed
+// handle.
+func (i *Handle) Flush() error {
+ _, err := i.doCmdWithoutAttr(ipvsCmdFlush)
+ return err
+}
+
+// NewDestination creates a new real server in the passed ipvs
+// service which should already be existing in the passed handle.
+func (i *Handle) NewDestination(s *Service, d *Destination) error {
+ return i.doCmd(s, d, ipvsCmdNewDest)
+}
+
+// UpdateDestination updates an already existing real server in the
+// passed ipvs service in the passed handle.
+func (i *Handle) UpdateDestination(s *Service, d *Destination) error {
+ return i.doCmd(s, d, ipvsCmdSetDest)
+}
+
+// DelDestination deletes an already existing real server in the
+// passed ipvs service in the passed handle.
+func (i *Handle) DelDestination(s *Service, d *Destination) error {
+ return i.doCmd(s, d, ipvsCmdDelDest)
+}
+
+// GetServices returns an array of services configured on the Node
+func (i *Handle) GetServices() ([]*Service, error) {
+ return i.doGetServicesCmd(nil)
+}
+
+// GetDestinations returns an array of Destinations configured for this Service
+func (i *Handle) GetDestinations(s *Service) ([]*Destination, error) {
+ return i.doGetDestinationsCmd(s, nil)
+}
+
+// GetService gets details of a specific IPVS services, useful in updating statisics etc.,
+func (i *Handle) GetService(s *Service) (*Service, error) {
+
+ res, err := i.doGetServicesCmd(s)
+ if err != nil {
+ return nil, err
+ }
+
+ // We are looking for exactly one service otherwise error out
+ if len(res) != 1 {
+ return nil, fmt.Errorf("Expected only one service obtained=%d", len(res))
+ }
+
+ return res[0], nil
+}
--- /dev/null
+// +build linux
+
+package ipvs
+
+import (
+ "net"
+ "syscall"
+ "testing"
+
+ "github.com/docker/libnetwork/testutils"
+ "github.com/vishvananda/netlink"
+ "github.com/vishvananda/netlink/nl"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+)
+
+var (
+ schedMethods = []string{
+ RoundRobin,
+ LeastConnection,
+ DestinationHashing,
+ SourceHashing,
+ }
+
+ protocols = []string{
+ "TCP",
+ "UDP",
+ "FWM",
+ }
+
+ fwdMethods = []uint32{
+ ConnectionFlagMasq,
+ ConnectionFlagTunnel,
+ ConnectionFlagDirectRoute,
+ }
+
+ fwdMethodStrings = []string{
+ "Masq",
+ "Tunnel",
+ "Route",
+ }
+)
+
+func lookupFwMethod(fwMethod uint32) string {
+
+ switch fwMethod {
+ case ConnectionFlagMasq:
+ return fwdMethodStrings[0]
+ case ConnectionFlagTunnel:
+ return fwdMethodStrings[1]
+ case ConnectionFlagDirectRoute:
+ return fwdMethodStrings[2]
+ }
+ return ""
+}
+
+func checkDestination(t *testing.T, i *Handle, s *Service, d *Destination, checkPresent bool) {
+ var dstFound bool
+
+ dstArray, err := i.GetDestinations(s)
+ assert.NilError(t, err)
+
+ for _, dst := range dstArray {
+ if dst.Address.Equal(d.Address) && dst.Port == d.Port && lookupFwMethod(dst.ConnectionFlags) == lookupFwMethod(d.ConnectionFlags) {
+ dstFound = true
+ break
+ }
+ }
+
+ switch checkPresent {
+ case true: //The test expects the service to be present
+ if !dstFound {
+
+ t.Fatalf("Did not find the service %s in ipvs output", d.Address.String())
+ }
+ case false: //The test expects that the service should not be present
+ if dstFound {
+ t.Fatalf("Did not find the destination %s fwdMethod %s in ipvs output", d.Address.String(), lookupFwMethod(d.ConnectionFlags))
+ }
+ }
+
+}
+
+func checkService(t *testing.T, i *Handle, s *Service, checkPresent bool) {
+
+ svcArray, err := i.GetServices()
+ assert.NilError(t, err)
+
+ var svcFound bool
+
+ for _, svc := range svcArray {
+
+ if svc.Protocol == s.Protocol && svc.Address.String() == s.Address.String() && svc.Port == s.Port {
+ svcFound = true
+ break
+ }
+ }
+
+ switch checkPresent {
+ case true: //The test expects the service to be present
+ if !svcFound {
+
+ t.Fatalf("Did not find the service %s in ipvs output", s.Address.String())
+ }
+ case false: //The test expects that the service should not be present
+ if svcFound {
+ t.Fatalf("Did not expect the service %s in ipvs output", s.Address.String())
+ }
+ }
+
+}
+
+func TestGetFamily(t *testing.T) {
+ if testutils.RunningOnCircleCI() {
+ t.Skip("Skipping as not supported on CIRCLE CI kernel")
+ }
+
+ id, err := getIPVSFamily()
+ assert.NilError(t, err)
+ assert.Check(t, 0 != id)
+}
+
+func TestService(t *testing.T) {
+ if testutils.RunningOnCircleCI() {
+ t.Skip("Skipping as not supported on CIRCLE CI kernel")
+ }
+
+ defer testutils.SetupTestOSContext(t)()
+
+ i, err := New("")
+ assert.NilError(t, err)
+
+ for _, protocol := range protocols {
+ for _, schedMethod := range schedMethods {
+
+ s := Service{
+ AddressFamily: nl.FAMILY_V4,
+ SchedName: schedMethod,
+ }
+
+ switch protocol {
+ case "FWM":
+ s.FWMark = 1234
+ case "TCP":
+ s.Protocol = syscall.IPPROTO_TCP
+ s.Port = 80
+ s.Address = net.ParseIP("1.2.3.4")
+ s.Netmask = 0xFFFFFFFF
+ case "UDP":
+ s.Protocol = syscall.IPPROTO_UDP
+ s.Port = 53
+ s.Address = net.ParseIP("2.3.4.5")
+ }
+
+ err := i.NewService(&s)
+ assert.NilError(t, err)
+ checkService(t, i, &s, true)
+ for _, updateSchedMethod := range schedMethods {
+ if updateSchedMethod == schedMethod {
+ continue
+ }
+
+ s.SchedName = updateSchedMethod
+ err = i.UpdateService(&s)
+ assert.NilError(t, err)
+ checkService(t, i, &s, true)
+
+ scopy, err := i.GetService(&s)
+ assert.NilError(t, err)
+ assert.Check(t, is.Equal((*scopy).Address.String(), s.Address.String()))
+ assert.Check(t, is.Equal((*scopy).Port, s.Port))
+ assert.Check(t, is.Equal((*scopy).Protocol, s.Protocol))
+ }
+
+ err = i.DelService(&s)
+ assert.NilError(t, err)
+ checkService(t, i, &s, false)
+ }
+ }
+
+ svcs := []Service{
+ {
+ AddressFamily: nl.FAMILY_V4,
+ SchedName: RoundRobin,
+ Protocol: syscall.IPPROTO_TCP,
+ Port: 80,
+ Address: net.ParseIP("10.20.30.40"),
+ Netmask: 0xFFFFFFFF,
+ },
+ {
+ AddressFamily: nl.FAMILY_V4,
+ SchedName: LeastConnection,
+ Protocol: syscall.IPPROTO_UDP,
+ Port: 8080,
+ Address: net.ParseIP("10.20.30.41"),
+ Netmask: 0xFFFFFFFF,
+ },
+ }
+ // Create services for testing flush
+ for _, svc := range svcs {
+ if !i.IsServicePresent(&svc) {
+ err = i.NewService(&svc)
+ assert.NilError(t, err)
+ checkService(t, i, &svc, true)
+ } else {
+ t.Errorf("svc: %v exists", svc)
+ }
+ }
+ err = i.Flush()
+ assert.NilError(t, err)
+ got, err := i.GetServices()
+ assert.NilError(t, err)
+ if len(got) != 0 {
+ t.Errorf("Unexpected services after flush")
+ }
+}
+
+func createDummyInterface(t *testing.T) {
+ if testutils.RunningOnCircleCI() {
+ t.Skip("Skipping as not supported on CIRCLE CI kernel")
+ }
+
+ dummy := &netlink.Dummy{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: "dummy",
+ },
+ }
+
+ err := netlink.LinkAdd(dummy)
+ assert.NilError(t, err)
+
+ dummyLink, err := netlink.LinkByName("dummy")
+ assert.NilError(t, err)
+
+ ip, ipNet, err := net.ParseCIDR("10.1.1.1/24")
+ assert.NilError(t, err)
+
+ ipNet.IP = ip
+
+ ipAddr := &netlink.Addr{IPNet: ipNet, Label: ""}
+ err = netlink.AddrAdd(dummyLink, ipAddr)
+ assert.NilError(t, err)
+}
+
+func TestDestination(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ createDummyInterface(t)
+ i, err := New("")
+ assert.NilError(t, err)
+
+ for _, protocol := range protocols {
+
+ s := Service{
+ AddressFamily: nl.FAMILY_V4,
+ SchedName: RoundRobin,
+ }
+
+ switch protocol {
+ case "FWM":
+ s.FWMark = 1234
+ case "TCP":
+ s.Protocol = syscall.IPPROTO_TCP
+ s.Port = 80
+ s.Address = net.ParseIP("1.2.3.4")
+ s.Netmask = 0xFFFFFFFF
+ case "UDP":
+ s.Protocol = syscall.IPPROTO_UDP
+ s.Port = 53
+ s.Address = net.ParseIP("2.3.4.5")
+ }
+
+ err := i.NewService(&s)
+ assert.NilError(t, err)
+ checkService(t, i, &s, true)
+
+ s.SchedName = ""
+ for _, fwdMethod := range fwdMethods {
+ d1 := Destination{
+ AddressFamily: nl.FAMILY_V4,
+ Address: net.ParseIP("10.1.1.2"),
+ Port: 5000,
+ Weight: 1,
+ ConnectionFlags: fwdMethod,
+ }
+
+ err := i.NewDestination(&s, &d1)
+ assert.NilError(t, err)
+ checkDestination(t, i, &s, &d1, true)
+ d2 := Destination{
+ AddressFamily: nl.FAMILY_V4,
+ Address: net.ParseIP("10.1.1.3"),
+ Port: 5000,
+ Weight: 1,
+ ConnectionFlags: fwdMethod,
+ }
+
+ err = i.NewDestination(&s, &d2)
+ assert.NilError(t, err)
+ checkDestination(t, i, &s, &d2, true)
+
+ d3 := Destination{
+ AddressFamily: nl.FAMILY_V4,
+ Address: net.ParseIP("10.1.1.4"),
+ Port: 5000,
+ Weight: 1,
+ ConnectionFlags: fwdMethod,
+ }
+
+ err = i.NewDestination(&s, &d3)
+ assert.NilError(t, err)
+ checkDestination(t, i, &s, &d3, true)
+
+ for _, updateFwdMethod := range fwdMethods {
+ if updateFwdMethod == fwdMethod {
+ continue
+ }
+ d1.ConnectionFlags = updateFwdMethod
+ err = i.UpdateDestination(&s, &d1)
+ assert.NilError(t, err)
+ checkDestination(t, i, &s, &d1, true)
+
+ d2.ConnectionFlags = updateFwdMethod
+ err = i.UpdateDestination(&s, &d2)
+ assert.NilError(t, err)
+ checkDestination(t, i, &s, &d2, true)
+
+ d3.ConnectionFlags = updateFwdMethod
+ err = i.UpdateDestination(&s, &d3)
+ assert.NilError(t, err)
+ checkDestination(t, i, &s, &d3, true)
+ }
+
+ err = i.DelDestination(&s, &d1)
+ assert.NilError(t, err)
+ err = i.DelDestination(&s, &d2)
+ assert.NilError(t, err)
+ err = i.DelDestination(&s, &d3)
+ assert.NilError(t, err)
+ checkDestination(t, i, &s, &d3, false)
+
+ }
+ }
+}
--- /dev/null
+// +build linux
+
+package ipvs
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "net"
+ "os/exec"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "syscall"
+ "unsafe"
+
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink/nl"
+ "github.com/vishvananda/netns"
+)
+
+// For Quick Reference IPVS related netlink message is described at the end of this file.
+var (
+ native = nl.NativeEndian()
+ ipvsFamily int
+ ipvsOnce sync.Once
+)
+
+type genlMsgHdr struct {
+ cmd uint8
+ version uint8
+ reserved uint16
+}
+
+type ipvsFlags struct {
+ flags uint32
+ mask uint32
+}
+
+func deserializeGenlMsg(b []byte) (hdr *genlMsgHdr) {
+ return (*genlMsgHdr)(unsafe.Pointer(&b[0:unsafe.Sizeof(*hdr)][0]))
+}
+
+func (hdr *genlMsgHdr) Serialize() []byte {
+ return (*(*[unsafe.Sizeof(*hdr)]byte)(unsafe.Pointer(hdr)))[:]
+}
+
+func (hdr *genlMsgHdr) Len() int {
+ return int(unsafe.Sizeof(*hdr))
+}
+
+func (f *ipvsFlags) Serialize() []byte {
+ return (*(*[unsafe.Sizeof(*f)]byte)(unsafe.Pointer(f)))[:]
+}
+
+func (f *ipvsFlags) Len() int {
+ return int(unsafe.Sizeof(*f))
+}
+
+func setup() {
+ ipvsOnce.Do(func() {
+ var err error
+ if out, err := exec.Command("modprobe", "-va", "ip_vs").CombinedOutput(); err != nil {
+ logrus.Warnf("Running modprobe ip_vs failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
+ }
+
+ ipvsFamily, err = getIPVSFamily()
+ if err != nil {
+ logrus.Error("Could not get ipvs family information from the kernel. It is possible that ipvs is not enabled in your kernel. Native loadbalancing will not work until this is fixed.")
+ }
+ })
+}
+
+func fillService(s *Service) nl.NetlinkRequestData {
+ cmdAttr := nl.NewRtAttr(ipvsCmdAttrService, nil)
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrAddressFamily, nl.Uint16Attr(s.AddressFamily))
+ if s.FWMark != 0 {
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrFWMark, nl.Uint32Attr(s.FWMark))
+ } else {
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrProtocol, nl.Uint16Attr(s.Protocol))
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrAddress, rawIPData(s.Address))
+
+ // Port needs to be in network byte order.
+ portBuf := new(bytes.Buffer)
+ binary.Write(portBuf, binary.BigEndian, s.Port)
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrPort, portBuf.Bytes())
+ }
+
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrSchedName, nl.ZeroTerminated(s.SchedName))
+ if s.PEName != "" {
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrPEName, nl.ZeroTerminated(s.PEName))
+ }
+ f := &ipvsFlags{
+ flags: s.Flags,
+ mask: 0xFFFFFFFF,
+ }
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrFlags, f.Serialize())
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrTimeout, nl.Uint32Attr(s.Timeout))
+ nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrNetmask, nl.Uint32Attr(s.Netmask))
+ return cmdAttr
+}
+
+func fillDestination(d *Destination) nl.NetlinkRequestData {
+ cmdAttr := nl.NewRtAttr(ipvsCmdAttrDest, nil)
+
+ nl.NewRtAttrChild(cmdAttr, ipvsDestAttrAddress, rawIPData(d.Address))
+ // Port needs to be in network byte order.
+ portBuf := new(bytes.Buffer)
+ binary.Write(portBuf, binary.BigEndian, d.Port)
+ nl.NewRtAttrChild(cmdAttr, ipvsDestAttrPort, portBuf.Bytes())
+
+ nl.NewRtAttrChild(cmdAttr, ipvsDestAttrForwardingMethod, nl.Uint32Attr(d.ConnectionFlags&ConnectionFlagFwdMask))
+ nl.NewRtAttrChild(cmdAttr, ipvsDestAttrWeight, nl.Uint32Attr(uint32(d.Weight)))
+ nl.NewRtAttrChild(cmdAttr, ipvsDestAttrUpperThreshold, nl.Uint32Attr(d.UpperThreshold))
+ nl.NewRtAttrChild(cmdAttr, ipvsDestAttrLowerThreshold, nl.Uint32Attr(d.LowerThreshold))
+
+ return cmdAttr
+}
+
+func (i *Handle) doCmdwithResponse(s *Service, d *Destination, cmd uint8) ([][]byte, error) {
+ req := newIPVSRequest(cmd)
+ req.Seq = atomic.AddUint32(&i.seq, 1)
+
+ if s == nil {
+ req.Flags |= syscall.NLM_F_DUMP //Flag to dump all messages
+ req.AddData(nl.NewRtAttr(ipvsCmdAttrService, nil)) //Add a dummy attribute
+ } else {
+ req.AddData(fillService(s))
+ }
+
+ if d == nil {
+ if cmd == ipvsCmdGetDest {
+ req.Flags |= syscall.NLM_F_DUMP
+ }
+
+ } else {
+ req.AddData(fillDestination(d))
+ }
+
+ res, err := execute(i.sock, req, 0)
+ if err != nil {
+ return [][]byte{}, err
+ }
+
+ return res, nil
+}
+
+func (i *Handle) doCmd(s *Service, d *Destination, cmd uint8) error {
+ _, err := i.doCmdwithResponse(s, d, cmd)
+
+ return err
+}
+
+func getIPVSFamily() (int, error) {
+ sock, err := nl.GetNetlinkSocketAt(netns.None(), netns.None(), syscall.NETLINK_GENERIC)
+ if err != nil {
+ return 0, err
+ }
+ defer sock.Close()
+
+ req := newGenlRequest(genlCtrlID, genlCtrlCmdGetFamily)
+ req.AddData(nl.NewRtAttr(genlCtrlAttrFamilyName, nl.ZeroTerminated("IPVS")))
+
+ msgs, err := execute(sock, req, 0)
+ if err != nil {
+ return 0, err
+ }
+
+ for _, m := range msgs {
+ hdr := deserializeGenlMsg(m)
+ attrs, err := nl.ParseRouteAttr(m[hdr.Len():])
+ if err != nil {
+ return 0, err
+ }
+
+ for _, attr := range attrs {
+ switch int(attr.Attr.Type) {
+ case genlCtrlAttrFamilyID:
+ return int(native.Uint16(attr.Value[0:2])), nil
+ }
+ }
+ }
+
+ return 0, fmt.Errorf("no family id in the netlink response")
+}
+
+func rawIPData(ip net.IP) []byte {
+ family := nl.GetIPFamily(ip)
+ if family == nl.FAMILY_V4 {
+ return ip.To4()
+ }
+ return ip
+}
+
+func newIPVSRequest(cmd uint8) *nl.NetlinkRequest {
+ return newGenlRequest(ipvsFamily, cmd)
+}
+
+func newGenlRequest(familyID int, cmd uint8) *nl.NetlinkRequest {
+ req := nl.NewNetlinkRequest(familyID, syscall.NLM_F_ACK)
+ req.AddData(&genlMsgHdr{cmd: cmd, version: 1})
+ return req
+}
+
+func execute(s *nl.NetlinkSocket, req *nl.NetlinkRequest, resType uint16) ([][]byte, error) {
+ if err := s.Send(req); err != nil {
+ return nil, err
+ }
+
+ pid, err := s.GetPid()
+ if err != nil {
+ return nil, err
+ }
+
+ var res [][]byte
+
+done:
+ for {
+ msgs, err := s.Receive()
+ if err != nil {
+ if s.GetFd() == -1 {
+ return nil, fmt.Errorf("Socket got closed on receive")
+ }
+ if err == syscall.EAGAIN {
+ // timeout fired
+ continue
+ }
+ return nil, err
+ }
+ for _, m := range msgs {
+ if m.Header.Seq != req.Seq {
+ continue
+ }
+ if m.Header.Pid != pid {
+ return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
+ }
+ if m.Header.Type == syscall.NLMSG_DONE {
+ break done
+ }
+ if m.Header.Type == syscall.NLMSG_ERROR {
+ error := int32(native.Uint32(m.Data[0:4]))
+ if error == 0 {
+ break done
+ }
+ return nil, syscall.Errno(-error)
+ }
+ if resType != 0 && m.Header.Type != resType {
+ continue
+ }
+ res = append(res, m.Data)
+ if m.Header.Flags&syscall.NLM_F_MULTI == 0 {
+ break done
+ }
+ }
+ }
+ return res, nil
+}
+
+func parseIP(ip []byte, family uint16) (net.IP, error) {
+
+ var resIP net.IP
+
+ switch family {
+ case syscall.AF_INET:
+ resIP = (net.IP)(ip[:4])
+ case syscall.AF_INET6:
+ resIP = (net.IP)(ip[:16])
+ default:
+ return nil, fmt.Errorf("parseIP Error ip=%v", ip)
+
+ }
+ return resIP, nil
+}
+
+// parseStats
+func assembleStats(msg []byte) (SvcStats, error) {
+
+ var s SvcStats
+
+ attrs, err := nl.ParseRouteAttr(msg)
+ if err != nil {
+ return s, err
+ }
+
+ for _, attr := range attrs {
+ attrType := int(attr.Attr.Type)
+ switch attrType {
+ case ipvsSvcStatsConns:
+ s.Connections = native.Uint32(attr.Value)
+ case ipvsSvcStatsPktsIn:
+ s.PacketsIn = native.Uint32(attr.Value)
+ case ipvsSvcStatsPktsOut:
+ s.PacketsOut = native.Uint32(attr.Value)
+ case ipvsSvcStatsBytesIn:
+ s.BytesIn = native.Uint64(attr.Value)
+ case ipvsSvcStatsBytesOut:
+ s.BytesOut = native.Uint64(attr.Value)
+ case ipvsSvcStatsCPS:
+ s.CPS = native.Uint32(attr.Value)
+ case ipvsSvcStatsPPSIn:
+ s.PPSIn = native.Uint32(attr.Value)
+ case ipvsSvcStatsPPSOut:
+ s.PPSOut = native.Uint32(attr.Value)
+ case ipvsSvcStatsBPSIn:
+ s.BPSIn = native.Uint32(attr.Value)
+ case ipvsSvcStatsBPSOut:
+ s.BPSOut = native.Uint32(attr.Value)
+ }
+ }
+ return s, nil
+}
+
+// assembleService assembles a services back from a hain of netlink attributes
+func assembleService(attrs []syscall.NetlinkRouteAttr) (*Service, error) {
+
+ var s Service
+
+ for _, attr := range attrs {
+
+ attrType := int(attr.Attr.Type)
+
+ switch attrType {
+
+ case ipvsSvcAttrAddressFamily:
+ s.AddressFamily = native.Uint16(attr.Value)
+ case ipvsSvcAttrProtocol:
+ s.Protocol = native.Uint16(attr.Value)
+ case ipvsSvcAttrAddress:
+ ip, err := parseIP(attr.Value, s.AddressFamily)
+ if err != nil {
+ return nil, err
+ }
+ s.Address = ip
+ case ipvsSvcAttrPort:
+ s.Port = binary.BigEndian.Uint16(attr.Value)
+ case ipvsSvcAttrFWMark:
+ s.FWMark = native.Uint32(attr.Value)
+ case ipvsSvcAttrSchedName:
+ s.SchedName = nl.BytesToString(attr.Value)
+ case ipvsSvcAttrFlags:
+ s.Flags = native.Uint32(attr.Value)
+ case ipvsSvcAttrTimeout:
+ s.Timeout = native.Uint32(attr.Value)
+ case ipvsSvcAttrNetmask:
+ s.Netmask = native.Uint32(attr.Value)
+ case ipvsSvcAttrStats:
+ stats, err := assembleStats(attr.Value)
+ if err != nil {
+ return nil, err
+ }
+ s.Stats = stats
+ }
+
+ }
+ return &s, nil
+}
+
+// parseService given a ipvs netlink response this function will respond with a valid service entry, an error otherwise
+func (i *Handle) parseService(msg []byte) (*Service, error) {
+
+ var s *Service
+
+ //Remove General header for this message and parse the NetLink message
+ hdr := deserializeGenlMsg(msg)
+ NetLinkAttrs, err := nl.ParseRouteAttr(msg[hdr.Len():])
+ if err != nil {
+ return nil, err
+ }
+ if len(NetLinkAttrs) == 0 {
+ return nil, fmt.Errorf("error no valid netlink message found while parsing service record")
+ }
+
+ //Now Parse and get IPVS related attributes messages packed in this message.
+ ipvsAttrs, err := nl.ParseRouteAttr(NetLinkAttrs[0].Value)
+ if err != nil {
+ return nil, err
+ }
+
+ //Assemble all the IPVS related attribute messages and create a service record
+ s, err = assembleService(ipvsAttrs)
+ if err != nil {
+ return nil, err
+ }
+
+ return s, nil
+}
+
+// doGetServicesCmd a wrapper which could be used commonly for both GetServices() and GetService(*Service)
+func (i *Handle) doGetServicesCmd(svc *Service) ([]*Service, error) {
+ var res []*Service
+
+ msgs, err := i.doCmdwithResponse(svc, nil, ipvsCmdGetService)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, msg := range msgs {
+ srv, err := i.parseService(msg)
+ if err != nil {
+ return nil, err
+ }
+ res = append(res, srv)
+ }
+
+ return res, nil
+}
+
+// doCmdWithoutAttr a simple wrapper of netlink socket execute command
+func (i *Handle) doCmdWithoutAttr(cmd uint8) ([][]byte, error) {
+ req := newIPVSRequest(cmd)
+ req.Seq = atomic.AddUint32(&i.seq, 1)
+ return execute(i.sock, req, 0)
+}
+
+func assembleDestination(attrs []syscall.NetlinkRouteAttr) (*Destination, error) {
+
+ var d Destination
+
+ for _, attr := range attrs {
+
+ attrType := int(attr.Attr.Type)
+
+ switch attrType {
+ case ipvsDestAttrAddress:
+ ip, err := parseIP(attr.Value, syscall.AF_INET)
+ if err != nil {
+ return nil, err
+ }
+ d.Address = ip
+ case ipvsDestAttrPort:
+ d.Port = binary.BigEndian.Uint16(attr.Value)
+ case ipvsDestAttrForwardingMethod:
+ d.ConnectionFlags = native.Uint32(attr.Value)
+ case ipvsDestAttrWeight:
+ d.Weight = int(native.Uint16(attr.Value))
+ case ipvsDestAttrUpperThreshold:
+ d.UpperThreshold = native.Uint32(attr.Value)
+ case ipvsDestAttrLowerThreshold:
+ d.LowerThreshold = native.Uint32(attr.Value)
+ case ipvsDestAttrAddressFamily:
+ d.AddressFamily = native.Uint16(attr.Value)
+ }
+ }
+ return &d, nil
+}
+
+// parseDestination given a ipvs netlink response this function will respond with a valid destination entry, an error otherwise
+func (i *Handle) parseDestination(msg []byte) (*Destination, error) {
+ var dst *Destination
+
+ //Remove General header for this message
+ hdr := deserializeGenlMsg(msg)
+ NetLinkAttrs, err := nl.ParseRouteAttr(msg[hdr.Len():])
+ if err != nil {
+ return nil, err
+ }
+ if len(NetLinkAttrs) == 0 {
+ return nil, fmt.Errorf("error no valid netlink message found while parsing destination record")
+ }
+
+ //Now Parse and get IPVS related attributes messages packed in this message.
+ ipvsAttrs, err := nl.ParseRouteAttr(NetLinkAttrs[0].Value)
+ if err != nil {
+ return nil, err
+ }
+
+ //Assemble netlink attributes and create a Destination record
+ dst, err = assembleDestination(ipvsAttrs)
+ if err != nil {
+ return nil, err
+ }
+
+ return dst, nil
+}
+
+// doGetDestinationsCmd a wrapper function to be used by GetDestinations and GetDestination(d) apis
+func (i *Handle) doGetDestinationsCmd(s *Service, d *Destination) ([]*Destination, error) {
+
+ var res []*Destination
+
+ msgs, err := i.doCmdwithResponse(s, d, ipvsCmdGetDest)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, msg := range msgs {
+ dest, err := i.parseDestination(msg)
+ if err != nil {
+ return res, err
+ }
+ res = append(res, dest)
+ }
+ return res, nil
+}
+
+// IPVS related netlink message format explained
+
+/* EACH NETLINK MSG is of the below format, this is what we will receive from execute() api.
+ If we have multiple netlink objects to process like GetServices() etc., execute() will
+ supply an array of this below object
+
+ NETLINK MSG
+|-----------------------------------|
+ 0 1 2 3
+|--------|--------|--------|--------| -
+| CMD ID | VER | RESERVED | |==> General Message Header represented by genlMsgHdr
+|-----------------------------------| -
+| ATTR LEN | ATTR TYPE | |
+|-----------------------------------| |
+| | |
+| VALUE | |
+| []byte Array of IPVS MSG | |==> Attribute Message represented by syscall.NetlinkRouteAttr
+| PADDED BY 4 BYTES | |
+| | |
+|-----------------------------------| -
+
+
+ Once We strip genlMsgHdr from above NETLINK MSG, we should parse the VALUE.
+ VALUE will have an array of netlink attributes (syscall.NetlinkRouteAttr) such that each attribute will
+ represent a "Service" or "Destination" object's field. If we assemble these attributes we can construct
+ Service or Destination.
+
+ IPVS MSG
+|-----------------------------------|
+ 0 1 2 3
+|--------|--------|--------|--------|
+| ATTR LEN | ATTR TYPE |
+|-----------------------------------|
+| |
+| |
+| []byte IPVS ATTRIBUTE BY 4 BYTES |
+| |
+| |
+|-----------------------------------|
+ NEXT ATTRIBUTE
+|-----------------------------------|
+| ATTR LEN | ATTR TYPE |
+|-----------------------------------|
+| |
+| |
+| []byte IPVS ATTRIBUTE BY 4 BYTES |
+| |
+| |
+|-----------------------------------|
+ NEXT ATTRIBUTE
+|-----------------------------------|
+| ATTR LEN | ATTR TYPE |
+|-----------------------------------|
+| |
+| |
+| []byte IPVS ATTRIBUTE BY 4 BYTES |
+| |
+| |
+|-----------------------------------|
+
+*/
--- /dev/null
+package libnetwork
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "testing"
+ "time"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/discoverapi"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/internal/setmatrix"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+func TestNetworkMarshalling(t *testing.T) {
+ n := &network{
+ name: "Miao",
+ id: "abccba",
+ ipamType: "default",
+ addrSpace: "viola",
+ networkType: "bridge",
+ enableIPv6: true,
+ persist: true,
+ configOnly: true,
+ configFrom: "configOnlyX",
+ ipamOptions: map[string]string{
+ netlabel.MacAddress: "a:b:c:d:e:f",
+ "primary": "",
+ },
+ ipamV4Config: []*IpamConf{
+ {
+ PreferredPool: "10.2.0.0/16",
+ SubPool: "10.2.0.0/24",
+ Gateway: "",
+ AuxAddresses: nil,
+ },
+ {
+ PreferredPool: "10.2.0.0/16",
+ SubPool: "10.2.1.0/24",
+ Gateway: "10.2.1.254",
+ },
+ },
+ ipamV6Config: []*IpamConf{
+ {
+ PreferredPool: "abcd::/64",
+ SubPool: "abcd:abcd:abcd:abcd:abcd::/80",
+ Gateway: "abcd::29/64",
+ AuxAddresses: nil,
+ },
+ },
+ ipamV4Info: []*IpamInfo{
+ {
+ PoolID: "ipoolverde123",
+ Meta: map[string]string{
+ netlabel.Gateway: "10.2.1.255/16",
+ },
+ IPAMData: driverapi.IPAMData{
+ AddressSpace: "viola",
+ Pool: &net.IPNet{
+ IP: net.IP{10, 2, 0, 0},
+ Mask: net.IPMask{255, 255, 255, 0},
+ },
+ Gateway: nil,
+ AuxAddresses: nil,
+ },
+ },
+ {
+ PoolID: "ipoolblue345",
+ Meta: map[string]string{
+ netlabel.Gateway: "10.2.1.255/16",
+ },
+ IPAMData: driverapi.IPAMData{
+ AddressSpace: "viola",
+ Pool: &net.IPNet{
+ IP: net.IP{10, 2, 1, 0},
+ Mask: net.IPMask{255, 255, 255, 0},
+ },
+ Gateway: &net.IPNet{IP: net.IP{10, 2, 1, 254}, Mask: net.IPMask{255, 255, 255, 0}},
+ AuxAddresses: map[string]*net.IPNet{
+ "ip3": {IP: net.IP{10, 2, 1, 3}, Mask: net.IPMask{255, 255, 255, 0}},
+ "ip5": {IP: net.IP{10, 2, 1, 55}, Mask: net.IPMask{255, 255, 255, 0}},
+ },
+ },
+ },
+ {
+ PoolID: "weirdinfo",
+ IPAMData: driverapi.IPAMData{
+ Gateway: &net.IPNet{
+ IP: net.IP{11, 2, 1, 255},
+ Mask: net.IPMask{255, 0, 0, 0},
+ },
+ },
+ },
+ },
+ ipamV6Info: []*IpamInfo{
+ {
+ PoolID: "ipoolv6",
+ IPAMData: driverapi.IPAMData{
+ AddressSpace: "viola",
+ Pool: &net.IPNet{
+ IP: net.IP{0xab, 0xcd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+ Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0},
+ },
+ Gateway: &net.IPNet{
+ IP: net.IP{0xab, 0xcd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29},
+ Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0},
+ },
+ AuxAddresses: nil,
+ },
+ },
+ },
+ labels: map[string]string{
+ "color": "blue",
+ "superimposed": "",
+ },
+ created: time.Now(),
+ }
+
+ b, err := json.Marshal(n)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ nn := &network{}
+ err = json.Unmarshal(b, nn)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if n.name != nn.name || n.id != nn.id || n.networkType != nn.networkType || n.ipamType != nn.ipamType ||
+ n.addrSpace != nn.addrSpace || n.enableIPv6 != nn.enableIPv6 ||
+ n.persist != nn.persist || !compareIpamConfList(n.ipamV4Config, nn.ipamV4Config) ||
+ !compareIpamInfoList(n.ipamV4Info, nn.ipamV4Info) || !compareIpamConfList(n.ipamV6Config, nn.ipamV6Config) ||
+ !compareIpamInfoList(n.ipamV6Info, nn.ipamV6Info) ||
+ !compareStringMaps(n.ipamOptions, nn.ipamOptions) ||
+ !compareStringMaps(n.labels, nn.labels) ||
+ !n.created.Equal(nn.created) ||
+ n.configOnly != nn.configOnly || n.configFrom != nn.configFrom {
+ t.Fatalf("JSON marsh/unmarsh failed."+
+ "\nOriginal:\n%#v\nDecoded:\n%#v"+
+ "\nOriginal ipamV4Conf: %#v\n\nDecoded ipamV4Conf: %#v"+
+ "\nOriginal ipamV4Info: %s\n\nDecoded ipamV4Info: %s"+
+ "\nOriginal ipamV6Conf: %#v\n\nDecoded ipamV6Conf: %#v"+
+ "\nOriginal ipamV6Info: %s\n\nDecoded ipamV6Info: %s",
+ n, nn, printIpamConf(n.ipamV4Config), printIpamConf(nn.ipamV4Config),
+ printIpamInfo(n.ipamV4Info), printIpamInfo(nn.ipamV4Info),
+ printIpamConf(n.ipamV6Config), printIpamConf(nn.ipamV6Config),
+ printIpamInfo(n.ipamV6Info), printIpamInfo(nn.ipamV6Info))
+ }
+}
+
+func printIpamConf(list []*IpamConf) string {
+ s := fmt.Sprintf("\n[]*IpamConfig{")
+ for _, i := range list {
+ s = fmt.Sprintf("%s %v,", s, i)
+ }
+ s = fmt.Sprintf("%s}", s)
+ return s
+}
+
+func printIpamInfo(list []*IpamInfo) string {
+ s := fmt.Sprintf("\n[]*IpamInfo{")
+ for _, i := range list {
+ s = fmt.Sprintf("%s\n{\n%s\n}", s, i)
+ }
+ s = fmt.Sprintf("%s\n}", s)
+ return s
+}
+
+func TestEndpointMarshalling(t *testing.T) {
+ ip, nw6, err := net.ParseCIDR("2001:db8:4003::122/64")
+ if err != nil {
+ t.Fatal(err)
+ }
+ nw6.IP = ip
+
+ var lla []*net.IPNet
+ for _, nw := range []string{"169.254.0.1/16", "169.254.1.1/16", "169.254.2.2/16"} {
+ ll, _ := types.ParseCIDR(nw)
+ lla = append(lla, ll)
+ }
+
+ e := &endpoint{
+ name: "Bau",
+ id: "efghijklmno",
+ sandboxID: "ambarabaciccicocco",
+ anonymous: true,
+ iface: &endpointInterface{
+ mac: []byte{11, 12, 13, 14, 15, 16},
+ addr: &net.IPNet{
+ IP: net.IP{10, 0, 1, 23},
+ Mask: net.IPMask{255, 255, 255, 0},
+ },
+ addrv6: nw6,
+ srcName: "veth12ab1314",
+ dstPrefix: "eth",
+ v4PoolID: "poolpool",
+ v6PoolID: "poolv6",
+ llAddrs: lla,
+ },
+ }
+
+ b, err := json.Marshal(e)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ee := &endpoint{}
+ err = json.Unmarshal(b, ee)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if e.name != ee.name || e.id != ee.id || e.sandboxID != ee.sandboxID || !compareEndpointInterface(e.iface, ee.iface) || e.anonymous != ee.anonymous {
+ t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%#v\nDecoded:\n%#v\nOriginal iface: %#v\nDecodediface:\n%#v", e, ee, e.iface, ee.iface)
+ }
+}
+
+func compareEndpointInterface(a, b *endpointInterface) bool {
+ if a == b {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ return a.srcName == b.srcName && a.dstPrefix == b.dstPrefix && a.v4PoolID == b.v4PoolID && a.v6PoolID == b.v6PoolID &&
+ types.CompareIPNet(a.addr, b.addr) && types.CompareIPNet(a.addrv6, b.addrv6) && compareNwLists(a.llAddrs, b.llAddrs)
+}
+
+func compareIpamConfList(listA, listB []*IpamConf) bool {
+ var a, b *IpamConf
+ if len(listA) != len(listB) {
+ return false
+ }
+ for i := 0; i < len(listA); i++ {
+ a = listA[i]
+ b = listB[i]
+ if a.PreferredPool != b.PreferredPool ||
+ a.SubPool != b.SubPool ||
+ a.Gateway != b.Gateway || !compareStringMaps(a.AuxAddresses, b.AuxAddresses) {
+ return false
+ }
+ }
+ return true
+}
+
+func compareIpamInfoList(listA, listB []*IpamInfo) bool {
+ var a, b *IpamInfo
+ if len(listA) != len(listB) {
+ return false
+ }
+ for i := 0; i < len(listA); i++ {
+ a = listA[i]
+ b = listB[i]
+ if a.PoolID != b.PoolID || !compareStringMaps(a.Meta, b.Meta) ||
+ !types.CompareIPNet(a.Gateway, b.Gateway) ||
+ a.AddressSpace != b.AddressSpace ||
+ !types.CompareIPNet(a.Pool, b.Pool) ||
+ !compareAddresses(a.AuxAddresses, b.AuxAddresses) {
+ return false
+ }
+ }
+ return true
+}
+
+func compareStringMaps(a, b map[string]string) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ if len(a) > 0 {
+ for k := range a {
+ if a[k] != b[k] {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+func compareAddresses(a, b map[string]*net.IPNet) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ if len(a) > 0 {
+ for k := range a {
+ if !types.CompareIPNet(a[k], b[k]) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+func compareNwLists(a, b []*net.IPNet) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for k := range a {
+ if !types.CompareIPNet(a[k], b[k]) {
+ return false
+ }
+ }
+ return true
+}
+
+func TestAuxAddresses(t *testing.T) {
+ c, err := New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ n := &network{ipamType: ipamapi.DefaultIPAM, networkType: "bridge", ctrlr: c.(*controller)}
+
+ input := []struct {
+ masterPool string
+ subPool string
+ auxAddresses map[string]string
+ good bool
+ }{
+ {"192.168.0.0/16", "", map[string]string{"goodOne": "192.168.2.2"}, true},
+ {"192.168.0.0/16", "", map[string]string{"badOne": "192.169.2.3"}, false},
+ {"192.168.0.0/16", "192.168.1.0/24", map[string]string{"goodOne": "192.168.1.2"}, true},
+ {"192.168.0.0/16", "192.168.1.0/24", map[string]string{"stillGood": "192.168.2.4"}, true},
+ {"192.168.0.0/16", "192.168.1.0/24", map[string]string{"badOne": "192.169.2.4"}, false},
+ }
+
+ for _, i := range input {
+
+ n.ipamV4Config = []*IpamConf{{PreferredPool: i.masterPool, SubPool: i.subPool, AuxAddresses: i.auxAddresses}}
+
+ err = n.ipamAllocate()
+
+ if i.good != (err == nil) {
+ t.Fatalf("Unexpected result for %v: %v", i, err)
+ }
+
+ n.ipamRelease()
+ }
+}
+
+func TestSRVServiceQuery(t *testing.T) {
+ c, err := New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ n, err := c.NewNetwork("bridge", "net1", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := n.CreateEndpoint("testep")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sb, err := c.NewSandbox("c1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sb.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Join(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sr := svcInfo{
+ svcMap: setmatrix.NewSetMatrix(),
+ svcIPv6Map: setmatrix.NewSetMatrix(),
+ ipMap: setmatrix.NewSetMatrix(),
+ service: make(map[string][]servicePorts),
+ }
+ // backing container for the service
+ cTarget := serviceTarget{
+ name: "task1.web.swarm",
+ ip: net.ParseIP("192.168.10.2"),
+ port: 80,
+ }
+ // backing host for the service
+ hTarget := serviceTarget{
+ name: "node1.docker-cluster",
+ ip: net.ParseIP("10.10.10.2"),
+ port: 45321,
+ }
+ httpPort := servicePorts{
+ portName: "_http",
+ proto: "_tcp",
+ target: []serviceTarget{cTarget},
+ }
+
+ extHTTPPort := servicePorts{
+ portName: "_host_http",
+ proto: "_tcp",
+ target: []serviceTarget{hTarget},
+ }
+ sr.service["web.swarm"] = append(sr.service["web.swarm"], httpPort)
+ sr.service["web.swarm"] = append(sr.service["web.swarm"], extHTTPPort)
+
+ c.(*controller).svcRecords[n.ID()] = sr
+
+ _, ip := ep.Info().Sandbox().ResolveService("_http._tcp.web.swarm")
+
+ if len(ip) == 0 {
+ t.Fatal(err)
+ }
+ if ip[0].String() != "192.168.10.2" {
+ t.Fatal(err)
+ }
+
+ _, ip = ep.Info().Sandbox().ResolveService("_host_http._tcp.web.swarm")
+
+ if len(ip) == 0 {
+ t.Fatal(err)
+ }
+ if ip[0].String() != "10.10.10.2" {
+ t.Fatal(err)
+ }
+
+ // Service name with invalid protocol name. Should fail without error
+ _, ip = ep.Info().Sandbox().ResolveService("_http._icmp.web.swarm")
+ if len(ip) != 0 {
+ t.Fatal("Valid response for invalid service name")
+ }
+}
+
+func TestServiceVIPReuse(t *testing.T) {
+ c, err := New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ n, err := c.NewNetwork("bridge", "net1", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := n.CreateEndpoint("testep")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sb, err := c.NewSandbox("c1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sb.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Join(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Add 2 services with same name but different service ID to share the same VIP
+ n.(*network).addSvcRecords("ep1", "service_test", "serviceID1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
+ n.(*network).addSvcRecords("ep2", "service_test", "serviceID2", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
+
+ ipToResolve := netutils.ReverseIP("192.168.0.1")
+
+ ipList, _ := n.(*network).ResolveName("service_test", types.IPv4)
+ if len(ipList) == 0 {
+ t.Fatal("There must be the VIP")
+ }
+ if len(ipList) != 1 {
+ t.Fatal("It must return only 1 VIP")
+ }
+ if ipList[0].String() != "192.168.0.1" {
+ t.Fatal("The service VIP is 192.168.0.1")
+ }
+ name := n.(*network).ResolveIP(ipToResolve)
+ if name == "" {
+ t.Fatal("It must return a name")
+ }
+ if name != "service_test.net1" {
+ t.Fatalf("It must return the service_test.net1 != %s", name)
+ }
+
+ // Delete service record for one of the services, the IP should remain because one service is still associated with it
+ n.(*network).deleteSvcRecords("ep1", "service_test", "serviceID1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
+ ipList, _ = n.(*network).ResolveName("service_test", types.IPv4)
+ if len(ipList) == 0 {
+ t.Fatal("There must be the VIP")
+ }
+ if len(ipList) != 1 {
+ t.Fatal("It must return only 1 VIP")
+ }
+ if ipList[0].String() != "192.168.0.1" {
+ t.Fatal("The service VIP is 192.168.0.1")
+ }
+ name = n.(*network).ResolveIP(ipToResolve)
+ if name == "" {
+ t.Fatal("It must return a name")
+ }
+ if name != "service_test.net1" {
+ t.Fatalf("It must return the service_test.net1 != %s", name)
+ }
+
+ // Delete again the service using the previous service ID, nothing should happen
+ n.(*network).deleteSvcRecords("ep2", "service_test", "serviceID1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
+ ipList, _ = n.(*network).ResolveName("service_test", types.IPv4)
+ if len(ipList) == 0 {
+ t.Fatal("There must be the VIP")
+ }
+ if len(ipList) != 1 {
+ t.Fatal("It must return only 1 VIP")
+ }
+ if ipList[0].String() != "192.168.0.1" {
+ t.Fatal("The service VIP is 192.168.0.1")
+ }
+ name = n.(*network).ResolveIP(ipToResolve)
+ if name == "" {
+ t.Fatal("It must return a name")
+ }
+ if name != "service_test.net1" {
+ t.Fatalf("It must return the service_test.net1 != %s", name)
+ }
+
+ // Delete now using the second service ID, now all the entries should be gone
+ n.(*network).deleteSvcRecords("ep2", "service_test", "serviceID2", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
+ ipList, _ = n.(*network).ResolveName("service_test", types.IPv4)
+ if len(ipList) != 0 {
+ t.Fatal("All the VIPs should be gone now")
+ }
+ name = n.(*network).ResolveIP(ipToResolve)
+ if name != "" {
+ t.Fatalf("It must return empty no more services associated, instead:%s", name)
+ }
+}
+
+func TestIpamReleaseOnNetDriverFailures(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ cfgOptions, err := OptionBoltdbWithRandomDBFile()
+ if err != nil {
+ t.Fatal(err)
+ }
+ c, err := New(cfgOptions...)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ cc := c.(*controller)
+
+ if err := cc.drvRegistry.AddDriver(badDriverName, badDriverInit, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test whether ipam state release is invoked on network create failure from net driver
+ // by checking whether subsequent network creation requesting same gateway IP succeeds
+ ipamOpt := NetworkOptionIpam(ipamapi.DefaultIPAM, "", []*IpamConf{{PreferredPool: "10.34.0.0/16", Gateway: "10.34.255.254"}}, nil, nil)
+ if _, err := c.NewNetwork(badDriverName, "badnet1", "", ipamOpt); err == nil {
+ t.Fatalf("bad network driver should have failed network creation")
+ }
+
+ gnw, err := c.NewNetwork("bridge", "goodnet1", "", ipamOpt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ gnw.Delete()
+
+ // Now check whether ipam release works on endpoint creation failure
+ bd.failNetworkCreation = false
+ bnw, err := c.NewNetwork(badDriverName, "badnet2", "", ipamOpt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer bnw.Delete()
+
+ if _, err := bnw.CreateEndpoint("ep0"); err == nil {
+ t.Fatalf("bad network driver should have failed endpoint creation")
+ }
+
+ // Now create good bridge network with different gateway
+ ipamOpt2 := NetworkOptionIpam(ipamapi.DefaultIPAM, "", []*IpamConf{{PreferredPool: "10.35.0.0/16", Gateway: "10.35.255.253"}}, nil, nil)
+ gnw, err = c.NewNetwork("bridge", "goodnet2", "", ipamOpt2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer gnw.Delete()
+
+ ep, err := gnw.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ep.Delete(false)
+
+ expectedIP, _ := types.ParseCIDR("10.35.0.1/16")
+ if !types.CompareIPNet(ep.Info().Iface().Address(), expectedIP) {
+ t.Fatalf("Ipam release must have failed, endpoint has unexpected address: %v", ep.Info().Iface().Address())
+ }
+}
+
+var badDriverName = "bad network driver"
+
+type badDriver struct {
+ failNetworkCreation bool
+}
+
+var bd = badDriver{failNetworkCreation: true}
+
+func badDriverInit(reg driverapi.DriverCallback, opt map[string]interface{}) error {
+ return reg.RegisterDriver(badDriverName, &bd, driverapi.Capability{DataScope: datastore.LocalScope})
+}
+
+func (b *badDriver) CreateNetwork(nid string, options map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+ if b.failNetworkCreation {
+ return fmt.Errorf("I will not create any network")
+ }
+ return nil
+}
+func (b *badDriver) DeleteNetwork(nid string) error {
+ return nil
+}
+func (b *badDriver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, options map[string]interface{}) error {
+ return fmt.Errorf("I will not create any endpoint")
+}
+func (b *badDriver) DeleteEndpoint(nid, eid string) error {
+ return nil
+}
+func (b *badDriver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+ return nil, nil
+}
+func (b *badDriver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+ return fmt.Errorf("I will not allow any join")
+}
+func (b *badDriver) Leave(nid, eid string) error {
+ return nil
+}
+func (b *badDriver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+func (b *badDriver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+ return nil
+}
+func (b *badDriver) Type() string {
+ return badDriverName
+}
+func (b *badDriver) IsBuiltIn() bool {
+ return false
+}
+func (b *badDriver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+ return nil
+}
+func (b *badDriver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+}
+
+func (b *badDriver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+ return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (b *badDriver) NetworkFree(id string) error {
+ return types.NotImplementedErrorf("not implemented")
+}
+
+func (b *badDriver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (b *badDriver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+ return "", nil
+}
--- /dev/null
+package libnetwork_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/exec"
+ "runtime"
+ "strconv"
+ "strings"
+ "testing"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+ "github.com/vishvananda/netns"
+)
+
+func TestHost(t *testing.T) {
+ sbx1, err := controller.NewSandbox("host_c1",
+ libnetwork.OptionHostname("test1"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"),
+ libnetwork.OptionUseDefaultSandbox())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sbx1.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ sbx2, err := controller.NewSandbox("host_c2",
+ libnetwork.OptionHostname("test2"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"),
+ libnetwork.OptionUseDefaultSandbox())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sbx2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ network, err := createTestNetwork("host", "testhost", options.Generic{}, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ep1, err := network.CreateEndpoint("testep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep1.Join(sbx1); err != nil {
+ t.Fatal(err)
+ }
+
+ ep2, err := network.CreateEndpoint("testep2")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep2.Join(sbx2); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep1.Leave(sbx1); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep2.Leave(sbx2); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep1.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep2.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+
+ // Try to create another host endpoint and join/leave that.
+ cnt3, err := controller.NewSandbox("host_c3",
+ libnetwork.OptionHostname("test3"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"),
+ libnetwork.OptionUseDefaultSandbox())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := cnt3.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep3, err := network.CreateEndpoint("testep3")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep3.Join(sbx2); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep3.Leave(sbx2); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep3.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Testing IPV6 from MAC address
+func TestBridgeIpv6FromMac(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testipv6mac",
+ "EnableICC": true,
+ "EnableIPMasquerade": true,
+ },
+ }
+ ipamV4ConfList := []*libnetwork.IpamConf{{PreferredPool: "192.168.100.0/24", Gateway: "192.168.100.1"}}
+ ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
+
+ network, err := controller.NewNetwork(bridgeNetType, "testipv6mac", "",
+ libnetwork.NetworkOptionGeneric(netOption),
+ libnetwork.NetworkOptionEnableIPv6(true),
+ libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", ipamV4ConfList, ipamV6ConfList, nil),
+ libnetwork.NetworkOptionDeferIPv6Alloc(true))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ mac := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
+ epOption := options.Generic{netlabel.MacAddress: mac}
+
+ ep, err := network.CreateEndpoint("testep", libnetwork.EndpointOptionGeneric(epOption))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ iface := ep.Info().Iface()
+ if !bytes.Equal(iface.MacAddress(), mac) {
+ t.Fatalf("Unexpected mac address: %v", iface.MacAddress())
+ }
+
+ ip, expIP, _ := net.ParseCIDR("fe90::aabb:ccdd:eeff/64")
+ expIP.IP = ip
+ if !types.CompareIPNet(expIP, iface.AddressIPv6()) {
+ t.Fatalf("Expected %v. Got: %v", expIP, iface.AddressIPv6())
+ }
+
+ if err := ep.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := network.Delete(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func checkSandbox(t *testing.T, info libnetwork.EndpointInfo) {
+ key := info.Sandbox().Key()
+ sbNs, err := netns.GetFromPath(key)
+ if err != nil {
+ t.Fatalf("Failed to get network namespace path %q: %v", key, err)
+ }
+ defer sbNs.Close()
+
+ nh, err := netlink.NewHandleAt(sbNs)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = nh.LinkByName("eth0")
+ if err != nil {
+ t.Fatalf("Could not find the interface eth0 inside the sandbox: %v", err)
+ }
+
+ _, err = nh.LinkByName("eth1")
+ if err != nil {
+ t.Fatalf("Could not find the interface eth1 inside the sandbox: %v", err)
+ }
+}
+
+func TestEndpointJoin(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ // Create network 1 and add 2 endpoint: ep11, ep12
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork1",
+ "EnableICC": true,
+ "EnableIPMasquerade": true,
+ },
+ }
+ ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
+ n1, err := controller.NewNetwork(bridgeNetType, "testnetwork1", "",
+ libnetwork.NetworkOptionGeneric(netOption),
+ libnetwork.NetworkOptionEnableIPv6(true),
+ libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", nil, ipamV6ConfList, nil),
+ libnetwork.NetworkOptionDeferIPv6Alloc(true))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n1.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep1, err := n1.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := ep1.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ // Validate if ep.Info() only gives me IP address info and not names and gateway during CreateEndpoint()
+ info := ep1.Info()
+ iface := info.Iface()
+ if iface.Address() != nil && iface.Address().IP.To4() == nil {
+ t.Fatalf("Invalid IP address returned: %v", iface.Address())
+ }
+ if iface.AddressIPv6() != nil && iface.AddressIPv6().IP == nil {
+ t.Fatalf("Invalid IPv6 address returned: %v", iface.Address())
+ }
+
+ if len(info.Gateway()) != 0 {
+ t.Fatalf("Expected empty gateway for an empty endpoint. Instead found a gateway: %v", info.Gateway())
+ }
+ if len(info.GatewayIPv6()) != 0 {
+ t.Fatalf("Expected empty gateway for an empty ipv6 endpoint. Instead found a gateway: %v", info.GatewayIPv6())
+ }
+
+ if info.Sandbox() != nil {
+ t.Fatalf("Expected an empty sandbox key for an empty endpoint. Instead found a non-empty sandbox key: %s", info.Sandbox().Key())
+ }
+
+ // test invalid joins
+ err = ep1.Join(nil)
+ if err == nil {
+ t.Fatalf("Expected to fail join with nil Sandbox")
+ }
+ if _, ok := err.(types.BadRequestError); !ok {
+ t.Fatalf("Unexpected error type returned: %T", err)
+ }
+
+ fsbx := &fakeSandbox{}
+ if err = ep1.Join(fsbx); err == nil {
+ t.Fatalf("Expected to fail join with invalid Sandbox")
+ }
+ if _, ok := err.(types.BadRequestError); !ok {
+ t.Fatalf("Unexpected error type returned: %T", err)
+ }
+
+ sb, err := controller.NewSandbox(containerID,
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer func() {
+ if err := sb.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep1.Join(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep1.Leave(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ // Validate if ep.Info() only gives valid gateway and sandbox key after has container has joined.
+ info = ep1.Info()
+ if len(info.Gateway()) == 0 {
+ t.Fatalf("Expected a valid gateway for a joined endpoint. Instead found an invalid gateway: %v", info.Gateway())
+ }
+ if len(info.GatewayIPv6()) == 0 {
+ t.Fatalf("Expected a valid ipv6 gateway for a joined endpoint. Instead found an invalid gateway: %v", info.GatewayIPv6())
+ }
+
+ if info.Sandbox() == nil {
+ t.Fatalf("Expected an non-empty sandbox key for a joined endpoint. Instead found an empty sandbox key")
+ }
+
+ // Check endpoint provided container information
+ if ep1.Info().Sandbox().Key() != sb.Key() {
+ t.Fatalf("Endpoint Info returned unexpected sandbox key: %s", sb.Key())
+ }
+
+ // Attempt retrieval of endpoint interfaces statistics
+ stats, err := sb.Statistics()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := stats["eth0"]; !ok {
+ t.Fatalf("Did not find eth0 statistics")
+ }
+
+ // Now test the container joining another network
+ n2, err := createTestNetwork(bridgeNetType, "testnetwork2",
+ options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork2",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep2, err := n2.CreateEndpoint("ep2")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := ep2.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep2.Join(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep2.Leave(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ if ep1.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() {
+ t.Fatalf("ep1 and ep2 returned different container sandbox key")
+ }
+
+ checkSandbox(t, info)
+}
+
+func TestExternalKey(t *testing.T) {
+ externalKeyTest(t, false)
+}
+
+func externalKeyTest(t *testing.T, reexec bool) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ n2, err := createTestNetwork(bridgeNetType, "testnetwork2", options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork2",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := n.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep.Delete(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep2, err := n2.CreateEndpoint("ep2")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep2.Delete(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ cnt, err := controller.NewSandbox(containerID,
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionUseExternalKey(),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"))
+ defer func() {
+ if err := cnt.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ osl.GC()
+ }()
+
+ // Join endpoint to sandbox before SetKey
+ err = ep.Join(cnt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep.Leave(cnt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ sbox := ep.Info().Sandbox()
+ if sbox == nil {
+ t.Fatalf("Expected to have a valid Sandbox")
+ }
+
+ if reexec {
+ err := reexecSetKey("this-must-fail", containerID, controller.ID())
+ if err == nil {
+ t.Fatalf("SetExternalKey must fail if the corresponding namespace is not created")
+ }
+ } else {
+ // Setting an non-existing key (namespace) must fail
+ if err := sbox.SetKey("this-must-fail"); err == nil {
+ t.Fatalf("Setkey must fail if the corresponding namespace is not created")
+ }
+ }
+
+ // Create a new OS sandbox using the osl API before using it in SetKey
+ if extOsBox, err := osl.NewSandbox("ValidKey", true, false); err != nil {
+ t.Fatalf("Failed to create new osl sandbox")
+ } else {
+ defer func() {
+ if err := extOsBox.Destroy(); err != nil {
+ logrus.Warnf("Failed to remove os sandbox: %v", err)
+ }
+ }()
+ }
+
+ if reexec {
+ err := reexecSetKey("ValidKey", containerID, controller.ID())
+ if err != nil {
+ t.Fatalf("SetExternalKey failed with %v", err)
+ }
+ } else {
+ if err := sbox.SetKey("ValidKey"); err != nil {
+ t.Fatalf("Setkey failed with %v", err)
+ }
+ }
+
+ // Join endpoint to sandbox after SetKey
+ err = ep2.Join(sbox)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep2.Leave(sbox)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ if ep.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() {
+ t.Fatalf("ep1 and ep2 returned different container sandbox key")
+ }
+
+ checkSandbox(t, ep.Info())
+}
+
+func reexecSetKey(key string, containerID string, controllerID string) error {
+ type libcontainerState struct {
+ NamespacePaths map[string]string
+ }
+ var (
+ state libcontainerState
+ b []byte
+ err error
+ )
+
+ state.NamespacePaths = make(map[string]string)
+ state.NamespacePaths["NEWNET"] = key
+ if b, err = json.Marshal(state); err != nil {
+ return err
+ }
+ cmd := &exec.Cmd{
+ Path: reexec.Self(),
+ Args: append([]string{"libnetwork-setkey"}, containerID, controllerID),
+ Stdin: strings.NewReader(string(b)),
+ Stdout: os.Stdout,
+ Stderr: os.Stderr,
+ }
+ return cmd.Run()
+}
+
+func TestEnableIPv6(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n")
+ expectedResolvConf := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\nnameserver 2001:4860:4860::8888\noptions ndots:0\n")
+ //take a copy of resolv.conf for restoring after test completes
+ resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+ if err != nil {
+ t.Fatal(err)
+ }
+ //cleanup
+ defer func() {
+ if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ netOption := options.Generic{
+ netlabel.EnableIPv6: true,
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }
+ ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe99::/64", Gateway: "fe99::9"}}
+
+ n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, ipamV6ConfList)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep1, err := n.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
+ defer os.Remove(resolvConfPath)
+
+ sb, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sb.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep1.Join(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(resolvConfPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(content, expectedResolvConf) {
+ t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf), string(content))
+ }
+
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestResolvConfHost(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ tmpResolvConf := []byte("search localhost.net\nnameserver 127.0.0.1\nnameserver 2001:4860:4860::8888\n")
+
+ //take a copy of resolv.conf for restoring after test completes
+ resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+ if err != nil {
+ t.Fatal(err)
+ }
+ //cleanup
+ defer func() {
+ if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ n, err := controller.NetworkByName("testhost")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ep1, err := n.CreateEndpoint("ep1", libnetwork.CreateOptionDisableResolution())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
+ defer os.Remove(resolvConfPath)
+
+ sb, err := controller.NewSandbox(containerID,
+ libnetwork.OptionUseDefaultSandbox(),
+ libnetwork.OptionResolvConfPath(resolvConfPath),
+ libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sb.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep1.Join(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep1.Leave(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ finfo, err := os.Stat(resolvConfPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ fmode := (os.FileMode)(0644)
+ if finfo.Mode() != fmode {
+ t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String())
+ }
+
+ content, err := ioutil.ReadFile(resolvConfPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(content, tmpResolvConf) {
+ t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf), string(content))
+ }
+}
+
+func TestResolvConf(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ tmpResolvConf1 := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n")
+ tmpResolvConf2 := []byte("search pommesfrites.fr\nnameserver 112.34.56.78\nnameserver 2001:4860:4860::8888\n")
+ expectedResolvConf1 := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\noptions ndots:0\n")
+ tmpResolvConf3 := []byte("search pommesfrites.fr\nnameserver 113.34.56.78\n")
+
+ //take a copy of resolv.conf for restoring after test completes
+ resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+ if err != nil {
+ t.Fatal(err)
+ }
+ //cleanup
+ defer func() {
+ if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }
+ n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := n.CreateEndpoint("ep")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf1, 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
+ defer os.Remove(resolvConfPath)
+
+ sb1, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sb1.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Join(sb1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ finfo, err := os.Stat(resolvConfPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ fmode := (os.FileMode)(0644)
+ if finfo.Mode() != fmode {
+ t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String())
+ }
+
+ content, err := ioutil.ReadFile(resolvConfPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(content, expectedResolvConf1) {
+ fmt.Printf("\n%v\n%v\n", expectedResolvConf1, content)
+ t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content))
+ }
+
+ err = ep.Leave(sb1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf2, 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ sb2, err := controller.NewSandbox(containerID+"_2", libnetwork.OptionResolvConfPath(resolvConfPath))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sb2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Join(sb2)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err = ioutil.ReadFile(resolvConfPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(content, expectedResolvConf1) {
+ t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content))
+ }
+
+ if err := ioutil.WriteFile(resolvConfPath, tmpResolvConf3, 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ err = ep.Leave(sb2)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = ep.Join(sb2)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err = ioutil.ReadFile(resolvConfPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(content, tmpResolvConf3) {
+ t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf3), string(content))
+ }
+}
+
+func parallelJoin(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) {
+ debugf("J%d.", thrNumber)
+ var err error
+
+ sb := sboxes[thrNumber-1]
+ err = ep.Join(sb)
+
+ runtime.LockOSThread()
+ if err != nil {
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("thread %d: %v", thrNumber, err)
+ }
+ debugf("JE%d(%v).", thrNumber, err)
+ }
+ debugf("JD%d.", thrNumber)
+}
+
+func parallelLeave(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) {
+ debugf("L%d.", thrNumber)
+ var err error
+
+ sb := sboxes[thrNumber-1]
+
+ err = ep.Leave(sb)
+ runtime.LockOSThread()
+ if err != nil {
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("thread %d: %v", thrNumber, err)
+ }
+ debugf("LE%d(%v).", thrNumber, err)
+ }
+ debugf("LD%d.", thrNumber)
+}
+
+func runParallelTests(t *testing.T, thrNumber int) {
+ var (
+ ep libnetwork.Endpoint
+ sb libnetwork.Sandbox
+ err error
+ )
+
+ t.Parallel()
+
+ pTest := flag.Lookup("test.parallel")
+ if pTest == nil {
+ t.Skip("Skipped because test.parallel flag not set;")
+ }
+ numParallel, err := strconv.Atoi(pTest.Value.String())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if numParallel < numThreads {
+ t.Skip("Skipped because t.parallel was less than ", numThreads)
+ }
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ if thrNumber == first {
+ createGlobalInstance(t)
+ }
+
+ if thrNumber != first {
+ <-start
+
+ thrdone := make(chan struct{})
+ done <- thrdone
+ defer close(thrdone)
+
+ if thrNumber == last {
+ defer close(done)
+ }
+
+ err = netns.Set(testns)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ defer netns.Set(origins)
+
+ net1, err := controller.NetworkByName("testhost")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if net1 == nil {
+ t.Fatal("Could not find testhost")
+ }
+
+ net2, err := controller.NetworkByName("network2")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if net2 == nil {
+ t.Fatal("Could not find network2")
+ }
+
+ epName := fmt.Sprintf("pep%d", thrNumber)
+
+ if thrNumber == first {
+ ep, err = net1.EndpointByName(epName)
+ } else {
+ ep, err = net2.EndpointByName(epName)
+ }
+
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ep == nil {
+ t.Fatal("Got nil ep with no error")
+ }
+
+ cid := fmt.Sprintf("%drace", thrNumber)
+ controller.WalkSandboxes(libnetwork.SandboxContainerWalker(&sb, cid))
+ if sb == nil {
+ t.Fatalf("Got nil sandbox for container: %s", cid)
+ }
+
+ for i := 0; i < iterCnt; i++ {
+ parallelJoin(t, sb, ep, thrNumber)
+ parallelLeave(t, sb, ep, thrNumber)
+ }
+
+ debugf("\n")
+
+ err = sb.Delete()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if thrNumber == first {
+ for thrdone := range done {
+ <-thrdone
+ }
+
+ testns.Close()
+ if err := net2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ err = ep.Delete(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+func TestParallel1(t *testing.T) {
+ runParallelTests(t, 1)
+}
+
+func TestParallel2(t *testing.T) {
+ runParallelTests(t, 2)
+}
+
+func TestParallel3(t *testing.T) {
+ runParallelTests(t, 3)
+}
+
+func TestNullIpam(t *testing.T) {
+ _, err := controller.NewNetwork(bridgeNetType, "testnetworkinternal", "", libnetwork.NetworkOptionIpam(ipamapi.NullIPAM, "", nil, nil, nil))
+ if err == nil || err.Error() != "ipv4 pool is empty" {
+ t.Fatal("bridge network should complain empty pool")
+ }
+}
--- /dev/null
+package libnetwork_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "sync"
+ "testing"
+
+ "github.com/docker/docker/errdefs"
+ "github.com/docker/docker/pkg/plugins"
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork"
+ "github.com/docker/libnetwork/config"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netns"
+)
+
+const (
+ bridgeNetType = "bridge"
+)
+
+var controller libnetwork.NetworkController
+
+func TestMain(m *testing.M) {
+ if reexec.Init() {
+ return
+ }
+
+ if err := createController(); err != nil {
+ logrus.Errorf("Error creating controller: %v", err)
+ os.Exit(1)
+ }
+
+ x := m.Run()
+ controller.Stop()
+ os.Exit(x)
+}
+
+func createController() error {
+ var err error
+
+ // Cleanup local datastore file
+ os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+
+ option := options.Generic{
+ "EnableIPForwarding": true,
+ }
+
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = option
+
+ cfgOptions, err := libnetwork.OptionBoltdbWithRandomDBFile()
+ if err != nil {
+ return err
+ }
+ controller, err = libnetwork.New(append(cfgOptions, config.OptionDriverConfig(bridgeNetType, genericOption))...)
+ return err
+}
+
+func createTestNetwork(networkType, networkName string, netOption options.Generic, ipamV4Configs, ipamV6Configs []*libnetwork.IpamConf) (libnetwork.Network, error) {
+ return controller.NewNetwork(networkType, networkName, "",
+ libnetwork.NetworkOptionGeneric(netOption),
+ libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", ipamV4Configs, ipamV6Configs, nil))
+}
+
+func getEmptyGenericOption() map[string]interface{} {
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = options.Generic{}
+ return genericOption
+}
+
+func getPortMapping() []types.PortBinding {
+ return []types.PortBinding{
+ {Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
+ {Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
+ {Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
+ {Proto: types.TCP, Port: uint16(320), HostPort: uint16(32000), HostPortEnd: uint16(32999)},
+ {Proto: types.UDP, Port: uint16(420), HostPort: uint16(42000), HostPortEnd: uint16(42001)},
+ }
+}
+
+func TestNull(t *testing.T) {
+ cnt, err := controller.NewSandbox("null_container",
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ network, err := createTestNetwork("null", "testnull", options.Generic{}, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ep, err := network.CreateEndpoint("testep")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = ep.Join(cnt)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = ep.Leave(cnt)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := cnt.Delete(); err != nil {
+ t.Fatal(err)
+ }
+
+ // host type is special network. Cannot be removed.
+ err = network.Delete()
+ if err == nil {
+ t.Fatal(err)
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("Unexpected error type")
+ }
+}
+
+func TestBridge(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ netOption := options.Generic{
+ netlabel.EnableIPv6: true,
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ "EnableICC": true,
+ "EnableIPMasquerade": true,
+ },
+ }
+ ipamV4ConfList := []*libnetwork.IpamConf{{PreferredPool: "192.168.100.0/24", Gateway: "192.168.100.1"}}
+ ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
+
+ network, err := createTestNetwork(bridgeNetType, "testnetwork", netOption, ipamV4ConfList, ipamV6ConfList)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := network.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := network.CreateEndpoint("testep")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sb, err := controller.NewSandbox(containerID, libnetwork.OptionPortMapping(getPortMapping()))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sb.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Join(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ epInfo, err := ep.DriverInfo()
+ if err != nil {
+ t.Fatal(err)
+ }
+ pmd, ok := epInfo[netlabel.PortMap]
+ if !ok {
+ t.Fatalf("Could not find expected info in endpoint data")
+ }
+ pm, ok := pmd.([]types.PortBinding)
+ if !ok {
+ t.Fatalf("Unexpected format for port mapping in endpoint operational data")
+ }
+ if len(pm) != 5 {
+ t.Fatalf("Incomplete data for port mapping in endpoint operational data: %d", len(pm))
+ }
+}
+
+func TestUnknownDriver(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ _, err := createTestNetwork("unknowndriver", "testnetwork", options.Generic{}, nil, nil)
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+
+ if !errdefs.IsNotFound(err) {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+}
+
+func TestNilRemoteDriver(t *testing.T) {
+ _, err := controller.NewNetwork("framerelay", "dummy", "",
+ libnetwork.NetworkOptionGeneric(getEmptyGenericOption()))
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+
+ if !errdefs.IsNotFound(err) {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+}
+
+func TestNetworkName(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }
+
+ _, err := createTestNetwork(bridgeNetType, "", netOption, nil, nil)
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+
+ if _, ok := err.(libnetwork.ErrInvalidName); !ok {
+ t.Fatalf("Expected to fail with ErrInvalidName error. Got %v", err)
+ }
+
+ networkName := "testnetwork"
+ n, err := createTestNetwork(bridgeNetType, networkName, netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ if n.Name() != networkName {
+ t.Fatalf("Expected network name %s, got %s", networkName, n.Name())
+ }
+}
+
+func TestNetworkType(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }
+
+ n, err := createTestNetwork(bridgeNetType, "testnetwork", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ if n.Type() != bridgeNetType {
+ t.Fatalf("Expected network type %s, got %s", bridgeNetType, n.Type())
+ }
+}
+
+func TestNetworkID(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }
+
+ n, err := createTestNetwork(bridgeNetType, "testnetwork", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ if n.ID() == "" {
+ t.Fatal("Expected non-empty network id")
+ }
+}
+
+func TestDeleteNetworkWithActiveEndpoints(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ netOption := options.Generic{
+ "BridgeName": "testnetwork",
+ }
+ option := options.Generic{
+ netlabel.GenericData: netOption,
+ }
+
+ network, err := createTestNetwork(bridgeNetType, "testnetwork", option, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ep, err := network.CreateEndpoint("testep")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = network.Delete()
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+
+ if _, ok := err.(*libnetwork.ActiveEndpointsError); !ok {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+
+ // Done testing. Now cleanup.
+ if err := ep.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := network.Delete(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestNetworkConfig(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ // Verify config network cannot inherit another config network
+ configNetwork, err := controller.NewNetwork("bridge", "config_network0", "",
+ libnetwork.NetworkOptionConfigOnly(),
+ libnetwork.NetworkOptionConfigFrom("anotherConfigNw"))
+
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+
+ // Create supported config network
+ netOption := options.Generic{
+ "EnableICC": false,
+ }
+ option := options.Generic{
+ netlabel.GenericData: netOption,
+ }
+ ipamV4ConfList := []*libnetwork.IpamConf{{PreferredPool: "192.168.100.0/24", SubPool: "192.168.100.128/25", Gateway: "192.168.100.1"}}
+ ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "2001:db8:abcd::/64", SubPool: "2001:db8:abcd::ef99/80", Gateway: "2001:db8:abcd::22"}}
+
+ netOptions := []libnetwork.NetworkOption{
+ libnetwork.NetworkOptionConfigOnly(),
+ libnetwork.NetworkOptionEnableIPv6(true),
+ libnetwork.NetworkOptionGeneric(option),
+ libnetwork.NetworkOptionIpam("default", "", ipamV4ConfList, ipamV6ConfList, nil),
+ }
+
+ configNetwork, err = controller.NewNetwork(bridgeNetType, "config_network0", "", netOptions...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Verify a config-only network cannot be created with network operator configurations
+ for i, opt := range []libnetwork.NetworkOption{
+ libnetwork.NetworkOptionInternalNetwork(),
+ libnetwork.NetworkOptionAttachable(true),
+ libnetwork.NetworkOptionIngress(true),
+ } {
+ _, err = controller.NewNetwork(bridgeNetType, "testBR", "",
+ libnetwork.NetworkOptionConfigOnly(), opt)
+ if err == nil {
+ t.Fatalf("Expected to fail. But instead succeeded for option: %d", i)
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+ }
+
+ // Verify a network cannot be created with both config-from and network specific configurations
+ for i, opt := range []libnetwork.NetworkOption{
+ libnetwork.NetworkOptionEnableIPv6(true),
+ libnetwork.NetworkOptionIpam("my-ipam", "", nil, nil, nil),
+ libnetwork.NetworkOptionIpam("", "", ipamV4ConfList, nil, nil),
+ libnetwork.NetworkOptionIpam("", "", nil, ipamV6ConfList, nil),
+ libnetwork.NetworkOptionLabels(map[string]string{"number": "two"}),
+ libnetwork.NetworkOptionDriverOpts(map[string]string{"com.docker.network.driver.mtu": "1600"}),
+ } {
+ _, err = controller.NewNetwork(bridgeNetType, "testBR", "",
+ libnetwork.NetworkOptionConfigFrom("config_network0"), opt)
+ if err == nil {
+ t.Fatalf("Expected to fail. But instead succeeded for option: %d", i)
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+ }
+
+ // Create a valid network
+ network, err := controller.NewNetwork(bridgeNetType, "testBR", "",
+ libnetwork.NetworkOptionConfigFrom("config_network0"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Verify the config network cannot be removed
+ err = configNetwork.Delete()
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+
+ // Delete network
+ if err := network.Delete(); err != nil {
+ t.Fatal(err)
+ }
+
+ // Verify the config network can now be removed
+ if err := configNetwork.Delete(); err != nil {
+ t.Fatal(err)
+ }
+
+}
+
+func TestUnknownNetwork(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ netOption := options.Generic{
+ "BridgeName": "testnetwork",
+ }
+ option := options.Generic{
+ netlabel.GenericData: netOption,
+ }
+
+ network, err := createTestNetwork(bridgeNetType, "testnetwork", option, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = network.Delete()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = network.Delete()
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+
+ if _, ok := err.(*libnetwork.UnknownNetworkError); !ok {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+}
+
+func TestUnknownEndpoint(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ netOption := options.Generic{
+ "BridgeName": "testnetwork",
+ }
+ option := options.Generic{
+ netlabel.GenericData: netOption,
+ }
+ ipamV4ConfList := []*libnetwork.IpamConf{{PreferredPool: "192.168.100.0/24"}}
+
+ network, err := createTestNetwork(bridgeNetType, "testnetwork", option, ipamV4ConfList, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = network.CreateEndpoint("")
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+ if _, ok := err.(libnetwork.ErrInvalidName); !ok {
+ t.Fatalf("Expected to fail with ErrInvalidName error. Actual error: %v", err)
+ }
+
+ ep, err := network.CreateEndpoint("testep")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = ep.Delete(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Done testing. Now cleanup
+ if err := network.Delete(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestNetworkEndpointsWalkers(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ // Create network 1 and add 2 endpoint: ep11, ep12
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "network1",
+ },
+ }
+
+ net1, err := createTestNetwork(bridgeNetType, "network1", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := net1.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep11, err := net1.CreateEndpoint("ep11")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := ep11.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep12, err := net1.CreateEndpoint("ep12")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := ep12.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ // Test list methods on net1
+ epList1 := net1.Endpoints()
+ if len(epList1) != 2 {
+ t.Fatalf("Endpoints() returned wrong number of elements: %d instead of 2", len(epList1))
+ }
+ // endpoint order is not guaranteed
+ for _, e := range epList1 {
+ if e != ep11 && e != ep12 {
+ t.Fatal("Endpoints() did not return all the expected elements")
+ }
+ }
+
+ // Test Endpoint Walk method
+ var epName string
+ var epWanted libnetwork.Endpoint
+ wlk := func(ep libnetwork.Endpoint) bool {
+ if ep.Name() == epName {
+ epWanted = ep
+ return true
+ }
+ return false
+ }
+
+ // Look for ep1 on network1
+ epName = "ep11"
+ net1.WalkEndpoints(wlk)
+ if epWanted == nil {
+ t.Fatal(err)
+ }
+ if ep11 != epWanted {
+ t.Fatal(err)
+ }
+
+ current := len(controller.Networks())
+
+ // Create network 2
+ netOption = options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "network2",
+ },
+ }
+
+ net2, err := createTestNetwork(bridgeNetType, "network2", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := net2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ // Test Networks method
+ if len(controller.Networks()) != current+1 {
+ t.Fatalf("Did not find the expected number of networks")
+ }
+
+ // Test Network Walk method
+ var netName string
+ var netWanted libnetwork.Network
+ nwWlk := func(nw libnetwork.Network) bool {
+ if nw.Name() == netName {
+ netWanted = nw
+ return true
+ }
+ return false
+ }
+
+ // Look for network named "network1" and "network2"
+ netName = "network1"
+ controller.WalkNetworks(nwWlk)
+ if netWanted == nil {
+ t.Fatal(err)
+ }
+ if net1.ID() != netWanted.ID() {
+ t.Fatal(err)
+ }
+
+ netName = "network2"
+ controller.WalkNetworks(nwWlk)
+ if netWanted == nil {
+ t.Fatal(err)
+ }
+ if net2.ID() != netWanted.ID() {
+ t.Fatal(err)
+ }
+}
+
+func TestDuplicateEndpoint(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }
+ n, err := createTestNetwork(bridgeNetType, "testnetwork", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := n.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := ep.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep2, err := n.CreateEndpoint("ep1")
+ defer func() {
+ // Cleanup ep2 as well, else network cleanup might fail for failure cases
+ if ep2 != nil {
+ if err := ep2.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }
+ }()
+
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+}
+
+func TestControllerQuery(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ // Create network 1
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "network1",
+ },
+ }
+ net1, err := createTestNetwork(bridgeNetType, "network1", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := net1.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ // Create network 2
+ netOption = options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "network2",
+ },
+ }
+ net2, err := createTestNetwork(bridgeNetType, "network2", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := net2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ _, err = controller.NetworkByName("")
+ if err == nil {
+ t.Fatalf("NetworkByName() succeeded with invalid target name")
+ }
+ if _, ok := err.(libnetwork.ErrInvalidName); !ok {
+ t.Fatalf("Expected NetworkByName() to fail with ErrInvalidName error. Got: %v", err)
+ }
+
+ _, err = controller.NetworkByID("")
+ if err == nil {
+ t.Fatalf("NetworkByID() succeeded with invalid target id")
+ }
+ if _, ok := err.(libnetwork.ErrInvalidID); !ok {
+ t.Fatalf("NetworkByID() failed with unexpected error: %v", err)
+ }
+
+ g, err := controller.NetworkByID("network1")
+ if err == nil {
+ t.Fatalf("Unexpected success for NetworkByID(): %v", g)
+ }
+ if _, ok := err.(libnetwork.ErrNoSuchNetwork); !ok {
+ t.Fatalf("NetworkByID() failed with unexpected error: %v", err)
+ }
+
+ g, err = controller.NetworkByName("network1")
+ if err != nil {
+ t.Fatalf("Unexpected failure for NetworkByName(): %v", err)
+ }
+ if g == nil {
+ t.Fatalf("NetworkByName() did not find the network")
+ }
+
+ if g != net1 {
+ t.Fatalf("NetworkByName() returned the wrong network")
+ }
+
+ g, err = controller.NetworkByID(net1.ID())
+ if err != nil {
+ t.Fatalf("Unexpected failure for NetworkByID(): %v", err)
+ }
+ if net1.ID() != g.ID() {
+ t.Fatalf("NetworkByID() returned unexpected element: %v", g)
+ }
+
+ g, err = controller.NetworkByName("network2")
+ if err != nil {
+ t.Fatalf("Unexpected failure for NetworkByName(): %v", err)
+ }
+ if g == nil {
+ t.Fatalf("NetworkByName() did not find the network")
+ }
+
+ if g != net2 {
+ t.Fatalf("NetworkByName() returned the wrong network")
+ }
+
+ g, err = controller.NetworkByID(net2.ID())
+ if err != nil {
+ t.Fatalf("Unexpected failure for NetworkByID(): %v", err)
+ }
+ if net2.ID() != g.ID() {
+ t.Fatalf("NetworkByID() returned unexpected element: %v", g)
+ }
+}
+
+func TestNetworkQuery(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ // Create network 1 and add 2 endpoint: ep11, ep12
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "network1",
+ },
+ }
+ net1, err := createTestNetwork(bridgeNetType, "network1", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := net1.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep11, err := net1.CreateEndpoint("ep11")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := ep11.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep12, err := net1.CreateEndpoint("ep12")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := ep12.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ e, err := net1.EndpointByName("ep11")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ep11 != e {
+ t.Fatalf("EndpointByName() returned %v instead of %v", e, ep11)
+ }
+
+ e, err = net1.EndpointByName("")
+ if err == nil {
+ t.Fatalf("EndpointByName() succeeded with invalid target name")
+ }
+ if _, ok := err.(libnetwork.ErrInvalidName); !ok {
+ t.Fatalf("Expected EndpointByName() to fail with ErrInvalidName error. Got: %v", err)
+ }
+
+ e, err = net1.EndpointByName("IamNotAnEndpoint")
+ if err == nil {
+ t.Fatalf("EndpointByName() succeeded with unknown target name")
+ }
+ if _, ok := err.(libnetwork.ErrNoSuchEndpoint); !ok {
+ t.Fatal(err)
+ }
+ if e != nil {
+ t.Fatalf("EndpointByName(): expected nil, got %v", e)
+ }
+
+ e, err = net1.EndpointByID(ep12.ID())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ep12.ID() != e.ID() {
+ t.Fatalf("EndpointByID() returned %v instead of %v", e, ep12)
+ }
+
+ e, err = net1.EndpointByID("")
+ if err == nil {
+ t.Fatalf("EndpointByID() succeeded with invalid target id")
+ }
+ if _, ok := err.(libnetwork.ErrInvalidID); !ok {
+ t.Fatalf("EndpointByID() failed with unexpected error: %v", err)
+ }
+}
+
+const containerID = "valid_c"
+
+type fakeSandbox struct{}
+
+func (f *fakeSandbox) ID() string {
+ return "fake sandbox"
+}
+
+func (f *fakeSandbox) ContainerID() string {
+ return ""
+}
+
+func (f *fakeSandbox) Key() string {
+ return "fake key"
+}
+
+func (f *fakeSandbox) Labels() map[string]interface{} {
+ return nil
+}
+
+func (f *fakeSandbox) Statistics() (map[string]*types.InterfaceStatistics, error) {
+ return nil, nil
+}
+
+func (f *fakeSandbox) Refresh(opts ...libnetwork.SandboxOption) error {
+ return nil
+}
+
+func (f *fakeSandbox) Delete() error {
+ return nil
+}
+
+func (f *fakeSandbox) Rename(name string) error {
+ return nil
+}
+
+func (f *fakeSandbox) SetKey(key string) error {
+ return nil
+}
+
+func (f *fakeSandbox) ResolveName(name string, ipType int) ([]net.IP, bool) {
+ return nil, false
+}
+
+func (f *fakeSandbox) ResolveIP(ip string) string {
+ return ""
+}
+
+func (f *fakeSandbox) ResolveService(name string) ([]*net.SRV, []net.IP) {
+ return nil, nil
+}
+
+func (f *fakeSandbox) Endpoints() []libnetwork.Endpoint {
+ return nil
+}
+
+func (f *fakeSandbox) EnableService() error {
+ return nil
+}
+
+func (f *fakeSandbox) DisableService() error {
+ return nil
+}
+
+func TestEndpointDeleteWithActiveContainer(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ n2, err := createTestNetwork(bridgeNetType, "testnetwork2", options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork2",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := n.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep.Delete(false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ cnt, err := controller.NewSandbox(containerID,
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"))
+ defer func() {
+ if err := cnt.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Join(cnt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep.Leave(cnt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Delete(false)
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+
+ if _, ok := err.(*libnetwork.ActiveContainerError); !ok {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+}
+
+func TestEndpointMultipleJoins(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ n, err := createTestNetwork(bridgeNetType, "testmultiple", options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testmultiple",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := n.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := ep.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ sbx1, err := controller.NewSandbox(containerID,
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"))
+ defer func() {
+ if err := sbx1.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ sbx2, err := controller.NewSandbox("c2")
+ defer func() {
+ if err := sbx2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Join(sbx1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = ep.Leave(sbx1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Join(sbx2)
+ if err == nil {
+ t.Fatal("Expected to fail multiple joins for the same endpoint")
+ }
+
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("Failed with unexpected error type: %T. Desc: %s", err, err.Error())
+ }
+
+}
+
+func TestLeaveAll(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ // If this goes through, it means cnt.Delete() effectively detached from all the endpoints
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ n2, err := createTestNetwork(bridgeNetType, "testnetwork2", options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork2",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep1, err := n.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ep2, err := n2.CreateEndpoint("ep2")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cnt, err := controller.NewSandbox("leaveall")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = ep1.Join(cnt)
+ if err != nil {
+ t.Fatalf("Failed to join ep1: %v", err)
+ }
+
+ err = ep2.Join(cnt)
+ if err != nil {
+ t.Fatalf("Failed to join ep2: %v", err)
+ }
+
+ err = cnt.Delete()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestContainerInvalidLeave(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := n.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := ep.Delete(false); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ cnt, err := controller.NewSandbox(containerID,
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := cnt.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep.Leave(cnt)
+ if err == nil {
+ t.Fatal("Expected to fail leave from an endpoint which has no active join")
+ }
+ if _, ok := err.(types.ForbiddenError); !ok {
+ t.Fatalf("Failed with unexpected error type: %T. Desc: %s", err, err.Error())
+ }
+
+ if err = ep.Leave(nil); err == nil {
+ t.Fatalf("Expected to fail leave nil Sandbox")
+ }
+ if _, ok := err.(types.BadRequestError); !ok {
+ t.Fatalf("Unexpected error type returned: %T. Desc: %s", err, err.Error())
+ }
+
+ fsbx := &fakeSandbox{}
+ if err = ep.Leave(fsbx); err == nil {
+ t.Fatalf("Expected to fail leave with invalid Sandbox")
+ }
+ if _, ok := err.(types.BadRequestError); !ok {
+ t.Fatalf("Unexpected error type returned: %T. Desc: %s", err, err.Error())
+ }
+}
+
+func TestEndpointUpdateParent(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ n, err := createTestNetwork("bridge", "testnetwork", options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "testnetwork",
+ },
+ }, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep1, err := n.CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ep2, err := n.CreateEndpoint("ep2")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sbx1, err := controller.NewSandbox(containerID,
+ libnetwork.OptionHostname("test"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionExtraHost("web", "192.168.0.1"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sbx1.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ sbx2, err := controller.NewSandbox("c2",
+ libnetwork.OptionHostname("test2"),
+ libnetwork.OptionDomainname("docker.io"),
+ libnetwork.OptionHostsPath("/var/lib/docker/test_network/container2/hosts"),
+ libnetwork.OptionExtraHost("web", "192.168.0.2"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := sbx2.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = ep1.Join(sbx1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = ep2.Join(sbx2)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestInvalidRemoteDriver(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ t.Skip("Skipping test when not running inside a Container")
+ }
+
+ mux := http.NewServeMux()
+ server := httptest.NewServer(mux)
+ if server == nil {
+ t.Fatal("Failed to start an HTTP Server")
+ }
+ defer server.Close()
+
+ type pluginRequest struct {
+ name string
+ }
+
+ mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, `{"Implements": ["InvalidDriver"]}`)
+ })
+
+ if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := os.RemoveAll("/etc/docker/plugins"); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ if err := ioutil.WriteFile("/etc/docker/plugins/invalid-network-driver.spec", []byte(server.URL), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ ctrlr, err := libnetwork.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ctrlr.Stop()
+
+ _, err = ctrlr.NewNetwork("invalid-network-driver", "dummy", "",
+ libnetwork.NetworkOptionGeneric(getEmptyGenericOption()))
+ if err == nil {
+ t.Fatal("Expected to fail. But instead succeeded")
+ }
+
+ if err != plugins.ErrNotImplements {
+ t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+ }
+}
+
+func TestValidRemoteDriver(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ t.Skip("Skipping test when not running inside a Container")
+ }
+
+ mux := http.NewServeMux()
+ server := httptest.NewServer(mux)
+ if server == nil {
+ t.Fatal("Failed to start an HTTP Server")
+ }
+ defer server.Close()
+
+ type pluginRequest struct {
+ name string
+ }
+
+ mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType)
+ })
+ mux.HandleFunc(fmt.Sprintf("/%s.GetCapabilities", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprintf(w, `{"Scope":"local"}`)
+ })
+ mux.HandleFunc(fmt.Sprintf("/%s.CreateNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprintf(w, "null")
+ })
+ mux.HandleFunc(fmt.Sprintf("/%s.DeleteNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+ fmt.Fprintf(w, "null")
+ })
+
+ if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := os.RemoveAll("/etc/docker/plugins"); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ if err := ioutil.WriteFile("/etc/docker/plugins/valid-network-driver.spec", []byte(server.URL), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ n, err := controller.NewNetwork("valid-network-driver", "dummy", "",
+ libnetwork.NetworkOptionGeneric(getEmptyGenericOption()))
+ if err != nil {
+ // Only fail if we could not find the plugin driver
+ if errdefs.IsNotFound(err) {
+ t.Fatal(err)
+ }
+ return
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+}
+
+var (
+ once sync.Once
+ start = make(chan struct{})
+ done = make(chan chan struct{}, numThreads-1)
+ origins = netns.None()
+ testns = netns.None()
+ sboxes = make([]libnetwork.Sandbox, numThreads)
+)
+
+const (
+ iterCnt = 25
+ numThreads = 3
+ first = 1
+ last = numThreads
+ debug = false
+)
+
+func createGlobalInstance(t *testing.T) {
+ var err error
+ defer close(start)
+
+ origins, err = netns.Get()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if testutils.IsRunningInContainer() {
+ testns = origins
+ } else {
+ testns, err = netns.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": "network",
+ },
+ }
+
+ net1, err := controller.NetworkByName("testhost")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ net2, err := createTestNetwork("bridge", "network2", netOption, nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = net1.CreateEndpoint("pep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = net2.CreateEndpoint("pep2")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = net2.CreateEndpoint("pep3")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if sboxes[first-1], err = controller.NewSandbox(fmt.Sprintf("%drace", first), libnetwork.OptionUseDefaultSandbox()); err != nil {
+ t.Fatal(err)
+ }
+ for thd := first + 1; thd <= last; thd++ {
+ if sboxes[thd-1], err = controller.NewSandbox(fmt.Sprintf("%drace", thd)); err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+func debugf(format string, a ...interface{}) (int, error) {
+ if debug {
+ return fmt.Printf(format, a...)
+ }
+
+ return 0, nil
+}
--- /dev/null
+#/bin/sh
+
+set -e
+
+usage()
+{
+cat << EOF
+NAME:
+ machines - Create Test Environments for Docker Networking
+
+VERSION:
+ 0.1
+
+USAGE:
+ $0 <command> [command_options] [arguments...]
+
+COMMANDS:
+ help
+ Help and usage
+
+ up <kv-store> <scale>
+ Create environment with given KV store
+ zookeeper | etcd | consul (default)
+ Create N nodes, default = 2
+
+ destroy
+ Destroy Environment
+
+EOF
+}
+
+step() {
+ printf "\033[0;36m-----> $@\033[0m\n"
+}
+
+up()
+{
+ step "Creating KV Store Machine"
+ docker-machine create \
+ -d virtualbox \
+ mh-kv
+
+ step "KV Store is $1"
+ step "Starting KV Container"
+ case "$1" in
+ etcd)
+ cluster_store="cluster-store=etcd://$(docker-machine ip mh-kv):2379"
+ docker $(docker-machine config mh-kv) run -d \
+ -p "2379:2379" \
+ -h "etcd" \
+ --name "etcd" \
+ quay.io/coreos/etcd:v2.2.1 \
+ --listen-client-urls="http://0.0.0.0:2379" \
+ --advertise-client-urls="http://$(docker-machine ip mh-kv):2379"
+ ;;
+ zookeeper)
+ cluster_store="cluster-store=zk://$(docker-machine ip mh-kv):2181"
+ docker $(docker-machine config mh-kv) run -d \
+ -p "2181:2181" \
+ -h "zookeeper" \
+ --name "zookeeper" \
+ tianon/zookeeper
+ ;;
+ *)
+ cluster_store="cluster-store=consul://$(docker-machine ip mh-kv):8500"
+ docker $(docker-machine config mh-kv) run -d \
+ -p "8500:8500" \
+ -h "consul" \
+ --name "consul" \
+ progrium/consul -server -bootstrap-expect 1
+ ;;
+ esac
+
+ machines=$2
+ if [ -z machines ]; then
+ machines=2
+ fi
+ step "Creating $machines Machines"
+
+ for i in $(seq $machines); do
+ step "Creating machine $i"
+ docker-machine create \
+ -d virtualbox \
+ --engine-opt="cluster-advertise=eth1:2376" \
+ --engine-opt="$cluster_store" \
+ mh-$i
+ done
+}
+
+destroy()
+{
+ for x in $(docker-machine ls | grep mh- | awk '{ print $1 }'); do
+ docker-machine rm $x
+ done
+}
+
+case "$1" in
+ up)
+ shift
+ up $@
+ ;;
+ destroy)
+ destroy $@
+ ;;
+ help)
+ usage
+ ;;
+ *)
+ usage
+ ;;
+esac
--- /dev/null
+package netlabel
+
+import (
+ "strings"
+)
+
+const (
+ // Prefix constant marks the reserved label space for libnetwork
+ Prefix = "com.docker.network"
+
+ // DriverPrefix constant marks the reserved label space for libnetwork drivers
+ DriverPrefix = Prefix + ".driver"
+
+ // DriverPrivatePrefix constant marks the reserved label space
+ // for internal libnetwork drivers
+ DriverPrivatePrefix = DriverPrefix + ".private"
+
+ // GenericData constant that helps to identify an option as a Generic constant
+ GenericData = Prefix + ".generic"
+
+ // PortMap constant represents Port Mapping
+ PortMap = Prefix + ".portmap"
+
+ // MacAddress constant represents Mac Address config of a Container
+ MacAddress = Prefix + ".endpoint.macaddress"
+
+ // ExposedPorts constant represents the container's Exposed Ports
+ ExposedPorts = Prefix + ".endpoint.exposedports"
+
+ // DNSServers A list of DNS servers associated with the endpoint
+ DNSServers = Prefix + ".endpoint.dnsservers"
+
+ //EnableIPv6 constant represents enabling IPV6 at network level
+ EnableIPv6 = Prefix + ".enable_ipv6"
+
+ // DriverMTU constant represents the MTU size for the network driver
+ DriverMTU = DriverPrefix + ".mtu"
+
+ // OverlayBindInterface constant represents overlay driver bind interface
+ OverlayBindInterface = DriverPrefix + ".overlay.bind_interface"
+
+ // OverlayNeighborIP constant represents overlay driver neighbor IP
+ OverlayNeighborIP = DriverPrefix + ".overlay.neighbor_ip"
+
+ // OverlayVxlanIDList constant represents a list of VXLAN Ids as csv
+ OverlayVxlanIDList = DriverPrefix + ".overlay.vxlanid_list"
+
+ // Gateway represents the gateway for the network
+ Gateway = Prefix + ".gateway"
+
+ // Internal constant represents that the network is internal which disables default gateway service
+ Internal = Prefix + ".internal"
+
+ // ContainerIfacePrefix can be used to override the interface prefix used inside the container
+ ContainerIfacePrefix = Prefix + ".container_iface_prefix"
+)
+
+var (
+ // GlobalKVProvider constant represents the KV provider backend
+ GlobalKVProvider = MakeKVProvider("global")
+
+ // GlobalKVProviderURL constant represents the KV provider URL
+ GlobalKVProviderURL = MakeKVProviderURL("global")
+
+ // GlobalKVProviderConfig constant represents the KV provider Config
+ GlobalKVProviderConfig = MakeKVProviderConfig("global")
+
+ // GlobalKVClient constants represents the global kv store client
+ GlobalKVClient = MakeKVClient("global")
+
+ // LocalKVProvider constant represents the KV provider backend
+ LocalKVProvider = MakeKVProvider("local")
+
+ // LocalKVProviderURL constant represents the KV provider URL
+ LocalKVProviderURL = MakeKVProviderURL("local")
+
+ // LocalKVProviderConfig constant represents the KV provider Config
+ LocalKVProviderConfig = MakeKVProviderConfig("local")
+
+ // LocalKVClient constants represents the local kv store client
+ LocalKVClient = MakeKVClient("local")
+)
+
+// MakeKVProvider returns the kvprovider label for the scope
+func MakeKVProvider(scope string) string {
+ return DriverPrivatePrefix + scope + "kv_provider"
+}
+
+// MakeKVProviderURL returns the kvprovider url label for the scope
+func MakeKVProviderURL(scope string) string {
+ return DriverPrivatePrefix + scope + "kv_provider_url"
+}
+
+// MakeKVProviderConfig returns the kvprovider config label for the scope
+func MakeKVProviderConfig(scope string) string {
+ return DriverPrivatePrefix + scope + "kv_provider_config"
+}
+
+// MakeKVClient returns the kv client label for the scope
+func MakeKVClient(scope string) string {
+ return DriverPrivatePrefix + scope + "kv_client"
+}
+
+// Key extracts the key portion of the label
+func Key(label string) (key string) {
+ if kv := strings.SplitN(label, "=", 2); len(kv) > 0 {
+ key = kv[0]
+ }
+ return
+}
+
+// Value extracts the value portion of the label
+func Value(label string) (value string) {
+ if kv := strings.SplitN(label, "=", 2); len(kv) > 1 {
+ value = kv[1]
+ }
+ return
+}
+
+// KeyValue decomposes the label in the (key,value) pair
+func KeyValue(label string) (key string, value string) {
+ if kv := strings.SplitN(label, "=", 2); len(kv) > 0 {
+ key = kv[0]
+ if len(kv) > 1 {
+ value = kv[1]
+ }
+ }
+ return
+}
--- /dev/null
+package netlabel
+
+import (
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+var input = []struct {
+ label string
+ key string
+ value string
+}{
+ {"com.directory.person.name=joe", "com.directory.person.name", "joe"},
+ {"com.directory.person.age=24", "com.directory.person.age", "24"},
+ {"com.directory.person.address=1234 First st.", "com.directory.person.address", "1234 First st."},
+ {"com.directory.person.friends=", "com.directory.person.friends", ""},
+ {"com.directory.person.nickname=o=u=8", "com.directory.person.nickname", "o=u=8"},
+ {"", "", ""},
+ {"com.directory.person.student", "com.directory.person.student", ""},
+}
+
+func TestKeyValue(t *testing.T) {
+ for _, i := range input {
+ k, v := KeyValue(i.label)
+ if k != i.key || v != i.value {
+ t.Fatalf("unexpected: %s, %s", k, v)
+ }
+ }
+}
--- /dev/null
+// Network utility functions.
+
+package netutils
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "strings"
+
+ "github.com/docker/libnetwork/types"
+)
+
+var (
+ // ErrNetworkOverlapsWithNameservers preformatted error
+ ErrNetworkOverlapsWithNameservers = errors.New("requested network overlaps with nameserver")
+ // ErrNetworkOverlaps preformatted error
+ ErrNetworkOverlaps = errors.New("requested network overlaps with existing network")
+ // ErrNoDefaultRoute preformatted error
+ ErrNoDefaultRoute = errors.New("no default route")
+)
+
+// CheckNameserverOverlaps checks whether the passed network overlaps with any of the nameservers
+func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error {
+ if len(nameservers) > 0 {
+ for _, ns := range nameservers {
+ _, nsNetwork, err := net.ParseCIDR(ns)
+ if err != nil {
+ return err
+ }
+ if NetworkOverlaps(toCheck, nsNetwork) {
+ return ErrNetworkOverlapsWithNameservers
+ }
+ }
+ }
+ 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)
+}
+
+// NetworkRange calculates the first and last IP addresses in an IPNet
+func NetworkRange(network *net.IPNet) (net.IP, net.IP) {
+ if network == nil {
+ return nil, nil
+ }
+
+ firstIP := network.IP.Mask(network.Mask)
+ lastIP := types.GetIPCopy(firstIP)
+ for i := 0; i < len(firstIP); i++ {
+ lastIP[i] = firstIP[i] | ^network.Mask[i]
+ }
+
+ if network.IP.To4() != nil {
+ firstIP = firstIP.To4()
+ lastIP = lastIP.To4()
+ }
+
+ return firstIP, lastIP
+}
+
+// GetIfaceAddr returns the first IPv4 address and slice of IPv6 addresses for the specified network interface
+func GetIfaceAddr(name string) (net.Addr, []net.Addr, error) {
+ iface, err := net.InterfaceByName(name)
+ if err != nil {
+ return nil, nil, err
+ }
+ addrs, err := iface.Addrs()
+ if err != nil {
+ return nil, nil, err
+ }
+ var addrs4 []net.Addr
+ var addrs6 []net.Addr
+ for _, addr := range addrs {
+ ip := (addr.(*net.IPNet)).IP
+ if ip4 := ip.To4(); ip4 != nil {
+ addrs4 = append(addrs4, addr)
+ } else if ip6 := ip.To16(); len(ip6) == net.IPv6len {
+ addrs6 = append(addrs6, addr)
+ }
+ }
+ switch {
+ case len(addrs4) == 0:
+ return nil, nil, fmt.Errorf("Interface %v has no IPv4 addresses", name)
+ case len(addrs4) > 1:
+ fmt.Printf("Interface %v has more than 1 IPv4 address. Defaulting to using %v\n",
+ name, (addrs4[0].(*net.IPNet)).IP)
+ }
+ return addrs4[0], addrs6, nil
+}
+
+func genMAC(ip net.IP) net.HardwareAddr {
+ hw := make(net.HardwareAddr, 6)
+ // The first byte of the MAC address has to comply with these rules:
+ // 1. Unicast: Set the least-significant bit to 0.
+ // 2. Address is locally administered: Set the second-least-significant bit (U/L) to 1.
+ hw[0] = 0x02
+ // The first 24 bits of the MAC represent the Organizationally Unique Identifier (OUI).
+ // Since this address is locally administered, we can do whatever we want as long as
+ // it doesn't conflict with other addresses.
+ hw[1] = 0x42
+ // Fill the remaining 4 bytes based on the input
+ if ip == nil {
+ rand.Read(hw[2:])
+ } else {
+ copy(hw[2:], ip.To4())
+ }
+ return hw
+}
+
+// GenerateRandomMAC returns a new 6-byte(48-bit) hardware address (MAC)
+func GenerateRandomMAC() net.HardwareAddr {
+ return genMAC(nil)
+}
+
+// GenerateMACFromIP returns a locally administered MAC address where the 4 least
+// significant bytes are derived from the IPv4 address.
+func GenerateMACFromIP(ip net.IP) net.HardwareAddr {
+ return genMAC(ip)
+}
+
+// GenerateRandomName returns a new name joined with a prefix. This size
+// specified is used to truncate the randomly generated value
+func GenerateRandomName(prefix string, size int) (string, error) {
+ id := make([]byte, 32)
+ if _, err := io.ReadFull(rand.Reader, id); err != nil {
+ return "", err
+ }
+ return prefix + hex.EncodeToString(id)[:size], nil
+}
+
+// ReverseIP accepts a V4 or V6 IP string in the canonical form and returns a reversed IP in
+// the dotted decimal form . This is used to setup the IP to service name mapping in the optimal
+// way for the DNS PTR queries.
+func ReverseIP(IP string) string {
+ var reverseIP []string
+
+ if net.ParseIP(IP).To4() != nil {
+ reverseIP = strings.Split(IP, ".")
+ l := len(reverseIP)
+ for i, j := 0, l-1; i < l/2; i, j = i+1, j-1 {
+ reverseIP[i], reverseIP[j] = reverseIP[j], reverseIP[i]
+ }
+ } else {
+ reverseIP = strings.Split(IP, ":")
+
+ // Reversed IPv6 is represented in dotted decimal instead of the typical
+ // colon hex notation
+ for key := range reverseIP {
+ if len(reverseIP[key]) == 0 { // expand the compressed 0s
+ reverseIP[key] = strings.Repeat("0000", 8-strings.Count(IP, ":"))
+ } else if len(reverseIP[key]) < 4 { // 0-padding needed
+ reverseIP[key] = strings.Repeat("0", 4-len(reverseIP[key])) + reverseIP[key]
+ }
+ }
+
+ reverseIP = strings.Split(strings.Join(reverseIP, ""), "")
+
+ l := len(reverseIP)
+ for i, j := 0, l-1; i < l/2; i, j = i+1, j-1 {
+ reverseIP[i], reverseIP[j] = reverseIP[j], reverseIP[i]
+ }
+ }
+
+ return strings.Join(reverseIP, ".")
+}
+
+// ParseAlias parses and validates the specified string as an alias format (name:alias)
+func ParseAlias(val string) (string, string, error) {
+ if val == "" {
+ return "", "", errors.New("empty string specified for alias")
+ }
+ arr := strings.Split(val, ":")
+ if len(arr) > 2 {
+ return "", "", fmt.Errorf("bad format for alias: %s", val)
+ }
+ if len(arr) == 1 {
+ return val, val, nil
+ }
+ return arr[0], arr[1], nil
+}
+
+// ValidateAlias validates that the specified string has a valid alias format (containerName:alias).
+func ValidateAlias(val string) (string, error) {
+ if _, _, err := ParseAlias(val); err != nil {
+ return val, err
+ }
+ return val, nil
+}
--- /dev/null
+package netutils
+
+import (
+ "net"
+
+ "github.com/docker/libnetwork/types"
+)
+
+// ElectInterfaceAddresses looks for an interface on the OS with the specified name
+// and returns returns all its IPv4 and IPv6 addresses in CIDR notation.
+// If a failure in retrieving the addresses or no IPv4 address is found, an error is returned.
+// If the interface does not exist, it chooses from a predefined
+// 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")
+}
--- /dev/null
+// +build linux
+// Network utility functions.
+
+package netutils
+
+import (
+ "fmt"
+ "net"
+ "strings"
+
+ "github.com/docker/libnetwork/ipamutils"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/resolvconf"
+ "github.com/docker/libnetwork/types"
+ "github.com/vishvananda/netlink"
+)
+
+var (
+ networkGetRoutesFct func(netlink.Link, int) ([]netlink.Route, error)
+)
+
+// CheckRouteOverlaps checks whether the passed network overlaps with any existing routes
+func CheckRouteOverlaps(toCheck *net.IPNet) error {
+ if networkGetRoutesFct == nil {
+ networkGetRoutesFct = ns.NlHandle().RouteList
+ }
+ 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(nlh *netlink.Handle, prefix string, len int) (string, error) {
+ linkByName := netlink.LinkByName
+ if nlh != nil {
+ linkByName = nlh.LinkByName
+ }
+ for i := 0; i < 3; i++ {
+ name, err := GenerateRandomName(prefix, len)
+ if err != nil {
+ continue
+ }
+ _, err = linkByName(name)
+ if err != nil {
+ if strings.Contains(err.Error(), "not found") {
+ return name, nil
+ }
+ return "", err
+ }
+ }
+ return "", types.InternalErrorf("could not generate interface name")
+}
+
+// ElectInterfaceAddresses looks for an interface on the OS with the
+// specified name and returns returns all its IPv4 and IPv6 addresses in CIDR notation.
+// If a failure in retrieving the addresses or no IPv4 address is found, an error is returned.
+// If the interface does not exist, it chooses from a predefined
+// list the first IPv4 address which does not conflict with other
+// interfaces on the system.
+func ElectInterfaceAddresses(name string) ([]*net.IPNet, []*net.IPNet, error) {
+ var (
+ v4Nets []*net.IPNet
+ v6Nets []*net.IPNet
+ )
+
+ defer osl.InitOSContext()()
+
+ link, _ := ns.NlHandle().LinkByName(name)
+ if link != nil {
+ v4addr, err := ns.NlHandle().AddrList(link, netlink.FAMILY_V4)
+ if err != nil {
+ return nil, nil, err
+ }
+ v6addr, err := ns.NlHandle().AddrList(link, netlink.FAMILY_V6)
+ if err != nil {
+ return nil, nil, err
+ }
+ for _, nlAddr := range v4addr {
+ v4Nets = append(v4Nets, nlAddr.IPNet)
+ }
+ for _, nlAddr := range v6addr {
+ v6Nets = append(v6Nets, nlAddr.IPNet)
+ }
+ }
+
+ if link == nil || len(v4Nets) == 0 {
+ // Choose from predefined local scope networks
+ v4Net, err := FindAvailableNetwork(ipamutils.PredefinedLocalScopeDefaultNetworks)
+ if err != nil {
+ return nil, nil, err
+ }
+ v4Nets = append(v4Nets, v4Net)
+ }
+
+ return v4Nets, v6Nets, nil
+}
+
+// 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) {
+ // We don't check for an error here, because we don't really care if we
+ // can't read /etc/resolv.conf. So instead we skip the append if resolvConf
+ // is nil. It either doesn't exist, or we can't read it for some reason.
+ var nameservers []string
+ if rc, err := resolvconf.Get(); err == nil {
+ nameservers = resolvconf.GetNameserversAsCIDR(rc.Content)
+ }
+ for _, nw := range list {
+ if err := CheckNameserverOverlaps(nameservers, nw); err == nil {
+ if err := CheckRouteOverlaps(nw); err == nil {
+ return nw, nil
+ }
+ }
+ }
+ return nil, fmt.Errorf("no available network")
+}
--- /dev/null
+package netutils
+
+import (
+ "bytes"
+ "net"
+ "sort"
+ "testing"
+
+ "github.com/docker/libnetwork/ipamutils"
+ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+ "github.com/vishvananda/netlink"
+)
+
+func TestNonOverlappingNameservers(t *testing.T) {
+ network := &net.IPNet{
+ IP: []byte{192, 168, 0, 1},
+ Mask: []byte{255, 255, 255, 0},
+ }
+ nameservers := []string{
+ "127.0.0.1/32",
+ }
+
+ if err := CheckNameserverOverlaps(nameservers, network); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestOverlappingNameservers(t *testing.T) {
+ network := &net.IPNet{
+ IP: []byte{192, 168, 0, 1},
+ Mask: []byte{255, 255, 255, 0},
+ }
+ nameservers := []string{
+ "192.168.0.1/32",
+ }
+
+ if err := CheckNameserverOverlaps(nameservers, network); err == nil {
+ t.Fatalf("Expected error %s got %s", ErrNetworkOverlapsWithNameservers, err)
+ }
+}
+
+func TestCheckRouteOverlaps(t *testing.T) {
+ networkGetRoutesFct = func(netlink.Link, int) ([]netlink.Route, error) {
+ routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"}
+ routes := []netlink.Route{}
+ for _, addr := range routesData {
+ _, netX, _ := net.ParseCIDR(addr)
+ routes = append(routes, netlink.Route{Dst: netX})
+ }
+ return routes, nil
+ }
+ defer func() { networkGetRoutesFct = nil }()
+
+ _, netX, _ := net.ParseCIDR("172.16.0.1/24")
+ if err := CheckRouteOverlaps(netX); err != nil {
+ t.Fatal(err)
+ }
+
+ _, netX, _ = net.ParseCIDR("10.0.2.0/24")
+ if err := CheckRouteOverlaps(netX); err == nil {
+ t.Fatal("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't")
+ }
+}
+
+func TestCheckNameserverOverlaps(t *testing.T) {
+ nameservers := []string{"10.0.2.3/32", "192.168.102.1/32"}
+
+ _, netX, _ := net.ParseCIDR("10.0.2.3/32")
+
+ if err := CheckNameserverOverlaps(nameservers, netX); err == nil {
+ t.Fatalf("%s should overlap 10.0.2.3/32 but doesn't", netX)
+ }
+
+ _, netX, _ = net.ParseCIDR("192.168.102.2/32")
+
+ if err := CheckNameserverOverlaps(nameservers, netX); err != nil {
+ t.Fatalf("%s should not overlap %v but it does", netX, nameservers)
+ }
+}
+
+func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) {
+ _, netX, _ := net.ParseCIDR(CIDRx)
+ _, netY, _ := net.ParseCIDR(CIDRy)
+ if !NetworkOverlaps(netX, netY) {
+ t.Errorf("%v and %v should overlap", netX, netY)
+ }
+}
+
+func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) {
+ _, netX, _ := net.ParseCIDR(CIDRx)
+ _, netY, _ := net.ParseCIDR(CIDRy)
+ if NetworkOverlaps(netX, netY) {
+ t.Errorf("%v and %v should not overlap", netX, netY)
+ }
+}
+
+func TestNetworkOverlaps(t *testing.T) {
+ //netY starts at same IP and ends within netX
+ AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t)
+ //netY starts within netX and ends at same IP
+ AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t)
+ //netY starts and ends within netX
+ AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t)
+ //netY starts at same IP and ends outside of netX
+ AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t)
+ //netY starts before and ends at same IP of netX
+ AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t)
+ //netY starts before and ends outside of netX
+ AssertOverlap("172.16.1.1/24", "172.16.0.1/22", t)
+ //netY starts and ends before netX
+ AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t)
+ //netX starts and ends before netY
+ AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t)
+}
+
+func TestNetworkRange(t *testing.T) {
+ // Simple class C test
+ _, network, _ := net.ParseCIDR("192.168.0.1/24")
+ first, last := NetworkRange(network)
+ if !first.Equal(net.ParseIP("192.168.0.0")) {
+ t.Error(first.String())
+ }
+ if !last.Equal(net.ParseIP("192.168.0.255")) {
+ t.Error(last.String())
+ }
+
+ // Class A test
+ _, network, _ = net.ParseCIDR("10.0.0.1/8")
+ first, last = NetworkRange(network)
+ if !first.Equal(net.ParseIP("10.0.0.0")) {
+ t.Error(first.String())
+ }
+ if !last.Equal(net.ParseIP("10.255.255.255")) {
+ t.Error(last.String())
+ }
+
+ // Class A, random IP address
+ _, network, _ = net.ParseCIDR("10.1.2.3/8")
+ first, last = NetworkRange(network)
+ if !first.Equal(net.ParseIP("10.0.0.0")) {
+ t.Error(first.String())
+ }
+ if !last.Equal(net.ParseIP("10.255.255.255")) {
+ t.Error(last.String())
+ }
+
+ // 32bit mask
+ _, network, _ = net.ParseCIDR("10.1.2.3/32")
+ first, last = NetworkRange(network)
+ if !first.Equal(net.ParseIP("10.1.2.3")) {
+ t.Error(first.String())
+ }
+ if !last.Equal(net.ParseIP("10.1.2.3")) {
+ t.Error(last.String())
+ }
+
+ // 31bit mask
+ _, network, _ = net.ParseCIDR("10.1.2.3/31")
+ first, last = NetworkRange(network)
+ if !first.Equal(net.ParseIP("10.1.2.2")) {
+ t.Error(first.String())
+ }
+ if !last.Equal(net.ParseIP("10.1.2.3")) {
+ t.Error(last.String())
+ }
+
+ // 26bit mask
+ _, network, _ = net.ParseCIDR("10.1.2.3/26")
+ first, last = NetworkRange(network)
+ if !first.Equal(net.ParseIP("10.1.2.0")) {
+ t.Error(first.String())
+ }
+ if !last.Equal(net.ParseIP("10.1.2.63")) {
+ t.Error(last.String())
+ }
+}
+
+// Test veth name generation "veth"+rand (e.g.veth0f60e2c)
+func TestGenerateRandomName(t *testing.T) {
+ name1, err := GenerateRandomName("veth", 7)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // veth plus generated append equals a len of 11
+ if len(name1) != 11 {
+ t.Fatalf("Expected 11 characters, instead received %d characters", len(name1))
+ }
+ name2, err := GenerateRandomName("veth", 7)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // Fail if the random generated names equal one another
+ if name1 == name2 {
+ t.Fatalf("Expected differing values but received %s and %s", name1, name2)
+ }
+}
+
+// Test mac generation.
+func TestUtilGenerateRandomMAC(t *testing.T) {
+ mac1 := GenerateRandomMAC()
+ mac2 := GenerateRandomMAC()
+ // ensure bytes are unique
+ if bytes.Equal(mac1, mac2) {
+ t.Fatalf("mac1 %s should not equal mac2 %s", mac1, mac2)
+ }
+ // existing tests check string functionality so keeping the pattern
+ if mac1.String() == mac2.String() {
+ t.Fatalf("mac1 %s should not equal mac2 %s", mac1, mac2)
+ }
+}
+
+func TestNetworkRequest(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nw, err := FindAvailableNetwork(ipamutils.PredefinedLocalScopeDefaultNetworks)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var found bool
+ for _, exp := range ipamutils.PredefinedLocalScopeDefaultNetworks {
+ if types.CompareIPNet(exp, nw) {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatalf("Found unexpected broad network %s", nw)
+ }
+
+ nw, err = FindAvailableNetwork(ipamutils.PredefinedGlobalScopeDefaultNetworks)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ found = false
+ for _, exp := range ipamutils.PredefinedGlobalScopeDefaultNetworks {
+ if types.CompareIPNet(exp, nw) {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatalf("Found unexpected granular network %s", nw)
+ }
+
+ // Add iface and ssert returned address on request
+ createInterface(t, "test", "172.17.42.1/16")
+
+ _, exp, err := net.ParseCIDR("172.18.0.0/16")
+ if err != nil {
+ t.Fatal(err)
+ }
+ nw, err = FindAvailableNetwork(ipamutils.PredefinedLocalScopeDefaultNetworks)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !types.CompareIPNet(exp, nw) {
+ t.Fatalf("expected %s. got %s", exp, nw)
+ }
+}
+
+func TestElectInterfaceAddressMultipleAddresses(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nws := []string{"172.101.202.254/16", "172.102.202.254/16"}
+ createInterface(t, "test", nws...)
+
+ ipv4NwList, ipv6NwList, err := ElectInterfaceAddresses("test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(ipv4NwList) == 0 {
+ t.Fatal("unexpected empty ipv4 network addresses")
+ }
+
+ if len(ipv6NwList) == 0 {
+ t.Fatal("unexpected empty ipv6 network addresses")
+ }
+
+ nwList := []string{}
+ for _, ipv4Nw := range ipv4NwList {
+ nwList = append(nwList, ipv4Nw.String())
+ }
+ sort.Strings(nws)
+ sort.Strings(nwList)
+
+ if len(nws) != len(nwList) {
+ t.Fatalf("expected %v. got %v", nws, nwList)
+ }
+ for i, nw := range nws {
+ if nw != nwList[i] {
+ t.Fatalf("expected %v. got %v", nw, nwList[i])
+ }
+ }
+}
+
+func TestElectInterfaceAddress(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ nws := "172.101.202.254/16"
+ createInterface(t, "test", nws)
+
+ ipv4Nw, ipv6Nw, err := ElectInterfaceAddresses("test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(ipv4Nw) == 0 {
+ t.Fatal("unexpected empty ipv4 network addresses")
+ }
+
+ if len(ipv6Nw) == 0 {
+ t.Fatal("unexpected empty ipv6 network addresses")
+ }
+
+ if nws != ipv4Nw[0].String() {
+ t.Fatalf("expected %s. got %s", nws, ipv4Nw[0])
+ }
+}
+
+func createInterface(t *testing.T, name string, nws ...string) {
+ // Add interface
+ link := &netlink.Bridge{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: "test",
+ },
+ }
+ bips := []*net.IPNet{}
+ for _, nw := range nws {
+ bip, err := types.ParseCIDR(nw)
+ if err != nil {
+ t.Fatal(err)
+ }
+ bips = append(bips, bip)
+ }
+ if err := netlink.LinkAdd(link); err != nil {
+ t.Fatalf("Failed to create interface via netlink: %v", err)
+ }
+ for _, bip := range bips {
+ if err := netlink.AddrAdd(link, &netlink.Addr{IPNet: bip}); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if err := netlink.LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+}
--- /dev/null
+package netutils
+
+import (
+ "net"
+
+ "github.com/docker/libnetwork/types"
+)
+
+// ElectInterfaceAddresses looks for an interface on the OS with the specified name
+// and returns returns all its IPv4 and IPv6 addresses in CIDR notation.
+// If a failure in retrieving the addresses or no IPv4 address is found, an error is returned.
+// If the interface does not exist, it chooses from a predefined
+// 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 windows")
+}
+
+// FindAvailableNetwork returns a network from the passed list which does not
+// overlap with existing interfaces in the system
+
+// TODO : Use appropriate windows APIs to identify non-overlapping subnets
+func FindAvailableNetwork(list []*net.IPNet) (*net.IPNet, error) {
+ return nil, nil
+}
--- /dev/null
+package libnetwork
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/libnetwork/config"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/driverapi"
+ "github.com/docker/libnetwork/etchosts"
+ "github.com/docker/libnetwork/internal/setmatrix"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/netutils"
+ "github.com/docker/libnetwork/networkdb"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// A Network represents a logical connectivity zone that containers may
+// join using the Link method. A Network is managed by a specific driver.
+type Network interface {
+ // A user chosen name for this network.
+ Name() string
+
+ // A system generated id for this network.
+ ID() string
+
+ // The type of network, which corresponds to its managing driver.
+ Type() string
+
+ // Create a new endpoint to this network symbolically identified by the
+ // specified unique name. The options parameter carries driver specific options.
+ CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error)
+
+ // Delete the network.
+ Delete(options ...NetworkDeleteOption) error
+
+ // Endpoints returns the list of Endpoint(s) in this network.
+ Endpoints() []Endpoint
+
+ // WalkEndpoints uses the provided function to walk the Endpoints
+ WalkEndpoints(walker EndpointWalker)
+
+ // EndpointByName returns the Endpoint which has the passed name. If not found, the error ErrNoSuchEndpoint is returned.
+ EndpointByName(name string) (Endpoint, error)
+
+ // EndpointByID returns the Endpoint which has the passed id. If not found, the error ErrNoSuchEndpoint is returned.
+ EndpointByID(id string) (Endpoint, error)
+
+ // Return certain operational data belonging to this network
+ Info() NetworkInfo
+}
+
+// NetworkInfo returns some configuration and operational information about the network
+type NetworkInfo interface {
+ IpamConfig() (string, map[string]string, []*IpamConf, []*IpamConf)
+ IpamInfo() ([]*IpamInfo, []*IpamInfo)
+ DriverOptions() map[string]string
+ Scope() string
+ IPv6Enabled() bool
+ Internal() bool
+ Attachable() bool
+ Ingress() bool
+ ConfigFrom() string
+ ConfigOnly() bool
+ Labels() map[string]string
+ Dynamic() bool
+ Created() time.Time
+ // Peers returns a slice of PeerInfo structures which has the information about the peer
+ // nodes participating in the same overlay network. This is currently the per-network
+ // gossip cluster. For non-dynamic overlay networks and bridge networks it returns an
+ // empty slice
+ Peers() []networkdb.PeerInfo
+ //Services returns a map of services keyed by the service name with the details
+ //of all the tasks that belong to the service. Applicable only in swarm mode.
+ Services() map[string]ServiceInfo
+}
+
+// EndpointWalker is a client provided function which will be used to walk the Endpoints.
+// When the function returns true, the walk will stop.
+type EndpointWalker func(ep Endpoint) bool
+
+// ipInfo is the reverse mapping from IP to service name to serve the PTR query.
+// extResolver is set if an external server resolves a service name to this IP.
+// Its an indication to defer PTR queries also to that external server.
+type ipInfo struct {
+ name string
+ serviceID string
+ extResolver bool
+}
+
+// svcMapEntry is the body of the element into the svcMap
+// The ip is a string because the SetMatrix does not accept non hashable values
+type svcMapEntry struct {
+ ip string
+ serviceID string
+}
+
+type svcInfo struct {
+ svcMap setmatrix.SetMatrix
+ svcIPv6Map setmatrix.SetMatrix
+ ipMap setmatrix.SetMatrix
+ service map[string][]servicePorts
+}
+
+// backing container or host's info
+type serviceTarget struct {
+ name string
+ ip net.IP
+ port uint16
+}
+
+type servicePorts struct {
+ portName string
+ proto string
+ target []serviceTarget
+}
+
+type networkDBTable struct {
+ name string
+ objType driverapi.ObjectType
+}
+
+// IpamConf contains all the ipam related configurations for a network
+type IpamConf struct {
+ // The master address pool for containers and network interfaces
+ PreferredPool string
+ // A subset of the master pool. If specified,
+ // this becomes the container pool
+ SubPool string
+ // Preferred Network Gateway address (optional)
+ Gateway string
+ // Auxiliary addresses for network driver. Must be within the master pool.
+ // libnetwork will reserve them if they fall into the container pool
+ AuxAddresses map[string]string
+}
+
+// Validate checks whether the configuration is valid
+func (c *IpamConf) Validate() error {
+ if c.Gateway != "" && nil == net.ParseIP(c.Gateway) {
+ return types.BadRequestErrorf("invalid gateway address %s in Ipam configuration", c.Gateway)
+ }
+ return nil
+}
+
+// IpamInfo contains all the ipam related operational info for a network
+type IpamInfo struct {
+ PoolID string
+ Meta map[string]string
+ driverapi.IPAMData
+}
+
+// MarshalJSON encodes IpamInfo into json message
+func (i *IpamInfo) MarshalJSON() ([]byte, error) {
+ m := map[string]interface{}{
+ "PoolID": i.PoolID,
+ }
+ v, err := json.Marshal(&i.IPAMData)
+ if err != nil {
+ return nil, err
+ }
+ m["IPAMData"] = string(v)
+
+ if i.Meta != nil {
+ m["Meta"] = i.Meta
+ }
+ return json.Marshal(m)
+}
+
+// UnmarshalJSON decodes json message into PoolData
+func (i *IpamInfo) UnmarshalJSON(data []byte) error {
+ var (
+ m map[string]interface{}
+ err error
+ )
+ if err = json.Unmarshal(data, &m); err != nil {
+ return err
+ }
+ i.PoolID = m["PoolID"].(string)
+ if v, ok := m["Meta"]; ok {
+ b, _ := json.Marshal(v)
+ if err = json.Unmarshal(b, &i.Meta); err != nil {
+ return err
+ }
+ }
+ if v, ok := m["IPAMData"]; ok {
+ if err = json.Unmarshal([]byte(v.(string)), &i.IPAMData); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+type network struct {
+ ctrlr *controller
+ name string
+ networkType string
+ id string
+ created time.Time
+ scope string // network data scope
+ labels map[string]string
+ ipamType string
+ ipamOptions map[string]string
+ addrSpace string
+ ipamV4Config []*IpamConf
+ ipamV6Config []*IpamConf
+ ipamV4Info []*IpamInfo
+ ipamV6Info []*IpamInfo
+ enableIPv6 bool
+ postIPv6 bool
+ epCnt *endpointCnt
+ generic options.Generic
+ dbIndex uint64
+ dbExists bool
+ persist bool
+ stopWatchCh chan struct{}
+ drvOnce *sync.Once
+ resolverOnce sync.Once
+ resolver []Resolver
+ internal bool
+ attachable bool
+ inDelete bool
+ ingress bool
+ driverTables []networkDBTable
+ dynamic bool
+ configOnly bool
+ configFrom string
+ loadBalancerIP net.IP
+ loadBalancerMode string
+ sync.Mutex
+}
+
+const (
+ loadBalancerModeNAT = "NAT"
+ loadBalancerModeDSR = "DSR"
+ loadBalancerModeDefault = loadBalancerModeNAT
+)
+
+func (n *network) Name() string {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.name
+}
+
+func (n *network) ID() string {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.id
+}
+
+func (n *network) Created() time.Time {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.created
+}
+
+func (n *network) Type() string {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.networkType
+}
+
+func (n *network) Key() []string {
+ n.Lock()
+ defer n.Unlock()
+ return []string{datastore.NetworkKeyPrefix, n.id}
+}
+
+func (n *network) KeyPrefix() []string {
+ return []string{datastore.NetworkKeyPrefix}
+}
+
+func (n *network) Value() []byte {
+ n.Lock()
+ defer n.Unlock()
+ b, err := json.Marshal(n)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (n *network) SetValue(value []byte) error {
+ return json.Unmarshal(value, n)
+}
+
+func (n *network) Index() uint64 {
+ n.Lock()
+ defer n.Unlock()
+ return n.dbIndex
+}
+
+func (n *network) SetIndex(index uint64) {
+ n.Lock()
+ n.dbIndex = index
+ n.dbExists = true
+ n.Unlock()
+}
+
+func (n *network) Exists() bool {
+ n.Lock()
+ defer n.Unlock()
+ return n.dbExists
+}
+
+func (n *network) Skip() bool {
+ n.Lock()
+ defer n.Unlock()
+ return !n.persist
+}
+
+func (n *network) New() datastore.KVObject {
+ n.Lock()
+ defer n.Unlock()
+
+ return &network{
+ ctrlr: n.ctrlr,
+ drvOnce: &sync.Once{},
+ scope: n.scope,
+ }
+}
+
+// CopyTo deep copies to the destination IpamConfig
+func (c *IpamConf) CopyTo(dstC *IpamConf) error {
+ dstC.PreferredPool = c.PreferredPool
+ dstC.SubPool = c.SubPool
+ dstC.Gateway = c.Gateway
+ if c.AuxAddresses != nil {
+ dstC.AuxAddresses = make(map[string]string, len(c.AuxAddresses))
+ for k, v := range c.AuxAddresses {
+ dstC.AuxAddresses[k] = v
+ }
+ }
+ return nil
+}
+
+// CopyTo deep copies to the destination IpamInfo
+func (i *IpamInfo) CopyTo(dstI *IpamInfo) error {
+ dstI.PoolID = i.PoolID
+ if i.Meta != nil {
+ dstI.Meta = make(map[string]string)
+ for k, v := range i.Meta {
+ dstI.Meta[k] = v
+ }
+ }
+
+ dstI.AddressSpace = i.AddressSpace
+ dstI.Pool = types.GetIPNetCopy(i.Pool)
+ dstI.Gateway = types.GetIPNetCopy(i.Gateway)
+
+ if i.AuxAddresses != nil {
+ dstI.AuxAddresses = make(map[string]*net.IPNet)
+ for k, v := range i.AuxAddresses {
+ dstI.AuxAddresses[k] = types.GetIPNetCopy(v)
+ }
+ }
+
+ return nil
+}
+
+func (n *network) validateConfiguration() error {
+ if n.configOnly {
+ // Only supports network specific configurations.
+ // Network operator configurations are not supported.
+ if n.ingress || n.internal || n.attachable || n.scope != "" {
+ return types.ForbiddenErrorf("configuration network can only contain network " +
+ "specific fields. Network operator fields like " +
+ "[ ingress | internal | attachable | scope ] are not supported.")
+ }
+ }
+ if n.configFrom != "" {
+ if n.configOnly {
+ return types.ForbiddenErrorf("a configuration network cannot depend on another configuration network")
+ }
+ if n.ipamType != "" &&
+ n.ipamType != defaultIpamForNetworkType(n.networkType) ||
+ n.enableIPv6 ||
+ len(n.labels) > 0 || len(n.ipamOptions) > 0 ||
+ len(n.ipamV4Config) > 0 || len(n.ipamV6Config) > 0 {
+ return types.ForbiddenErrorf("user specified configurations are not supported if the network depends on a configuration network")
+ }
+ if len(n.generic) > 0 {
+ if data, ok := n.generic[netlabel.GenericData]; ok {
+ var (
+ driverOptions map[string]string
+ opts interface{}
+ )
+ switch t := data.(type) {
+ case map[string]interface{}, map[string]string:
+ opts = t
+ }
+ ba, err := json.Marshal(opts)
+ if err != nil {
+ return fmt.Errorf("failed to validate network configuration: %v", err)
+ }
+ if err := json.Unmarshal(ba, &driverOptions); err != nil {
+ return fmt.Errorf("failed to validate network configuration: %v", err)
+ }
+ if len(driverOptions) > 0 {
+ return types.ForbiddenErrorf("network driver options are not supported if the network depends on a configuration network")
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// Applies network specific configurations
+func (n *network) applyConfigurationTo(to *network) error {
+ to.enableIPv6 = n.enableIPv6
+ if len(n.labels) > 0 {
+ to.labels = make(map[string]string, len(n.labels))
+ for k, v := range n.labels {
+ if _, ok := to.labels[k]; !ok {
+ to.labels[k] = v
+ }
+ }
+ }
+ if len(n.ipamType) != 0 {
+ to.ipamType = n.ipamType
+ }
+ if len(n.ipamOptions) > 0 {
+ to.ipamOptions = make(map[string]string, len(n.ipamOptions))
+ for k, v := range n.ipamOptions {
+ if _, ok := to.ipamOptions[k]; !ok {
+ to.ipamOptions[k] = v
+ }
+ }
+ }
+ if len(n.ipamV4Config) > 0 {
+ to.ipamV4Config = make([]*IpamConf, 0, len(n.ipamV4Config))
+ to.ipamV4Config = append(to.ipamV4Config, n.ipamV4Config...)
+ }
+ if len(n.ipamV6Config) > 0 {
+ to.ipamV6Config = make([]*IpamConf, 0, len(n.ipamV6Config))
+ to.ipamV6Config = append(to.ipamV6Config, n.ipamV6Config...)
+ }
+ if len(n.generic) > 0 {
+ to.generic = options.Generic{}
+ for k, v := range n.generic {
+ to.generic[k] = v
+ }
+ }
+ return nil
+}
+
+func (n *network) CopyTo(o datastore.KVObject) error {
+ n.Lock()
+ defer n.Unlock()
+
+ dstN := o.(*network)
+ dstN.name = n.name
+ dstN.id = n.id
+ dstN.created = n.created
+ dstN.networkType = n.networkType
+ dstN.scope = n.scope
+ dstN.dynamic = n.dynamic
+ dstN.ipamType = n.ipamType
+ dstN.enableIPv6 = n.enableIPv6
+ dstN.persist = n.persist
+ dstN.postIPv6 = n.postIPv6
+ dstN.dbIndex = n.dbIndex
+ dstN.dbExists = n.dbExists
+ dstN.drvOnce = n.drvOnce
+ dstN.internal = n.internal
+ dstN.attachable = n.attachable
+ dstN.inDelete = n.inDelete
+ dstN.ingress = n.ingress
+ dstN.configOnly = n.configOnly
+ dstN.configFrom = n.configFrom
+ dstN.loadBalancerIP = n.loadBalancerIP
+ dstN.loadBalancerMode = n.loadBalancerMode
+
+ // copy labels
+ if dstN.labels == nil {
+ dstN.labels = make(map[string]string, len(n.labels))
+ }
+ for k, v := range n.labels {
+ dstN.labels[k] = v
+ }
+
+ if n.ipamOptions != nil {
+ dstN.ipamOptions = make(map[string]string, len(n.ipamOptions))
+ for k, v := range n.ipamOptions {
+ dstN.ipamOptions[k] = v
+ }
+ }
+
+ for _, v4conf := range n.ipamV4Config {
+ dstV4Conf := &IpamConf{}
+ v4conf.CopyTo(dstV4Conf)
+ dstN.ipamV4Config = append(dstN.ipamV4Config, dstV4Conf)
+ }
+
+ for _, v4info := range n.ipamV4Info {
+ dstV4Info := &IpamInfo{}
+ v4info.CopyTo(dstV4Info)
+ dstN.ipamV4Info = append(dstN.ipamV4Info, dstV4Info)
+ }
+
+ for _, v6conf := range n.ipamV6Config {
+ dstV6Conf := &IpamConf{}
+ v6conf.CopyTo(dstV6Conf)
+ dstN.ipamV6Config = append(dstN.ipamV6Config, dstV6Conf)
+ }
+
+ for _, v6info := range n.ipamV6Info {
+ dstV6Info := &IpamInfo{}
+ v6info.CopyTo(dstV6Info)
+ dstN.ipamV6Info = append(dstN.ipamV6Info, dstV6Info)
+ }
+
+ dstN.generic = options.Generic{}
+ for k, v := range n.generic {
+ dstN.generic[k] = v
+ }
+
+ return nil
+}
+
+func (n *network) DataScope() string {
+ s := n.Scope()
+ // All swarm scope networks have local datascope
+ if s == datastore.SwarmScope {
+ s = datastore.LocalScope
+ }
+ return s
+}
+
+func (n *network) getEpCnt() *endpointCnt {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.epCnt
+}
+
+// TODO : Can be made much more generic with the help of reflection (but has some golang limitations)
+func (n *network) MarshalJSON() ([]byte, error) {
+ netMap := make(map[string]interface{})
+ netMap["name"] = n.name
+ netMap["id"] = n.id
+ netMap["created"] = n.created
+ netMap["networkType"] = n.networkType
+ netMap["scope"] = n.scope
+ netMap["labels"] = n.labels
+ netMap["ipamType"] = n.ipamType
+ netMap["ipamOptions"] = n.ipamOptions
+ netMap["addrSpace"] = n.addrSpace
+ netMap["enableIPv6"] = n.enableIPv6
+ if n.generic != nil {
+ netMap["generic"] = n.generic
+ }
+ netMap["persist"] = n.persist
+ netMap["postIPv6"] = n.postIPv6
+ if len(n.ipamV4Config) > 0 {
+ ics, err := json.Marshal(n.ipamV4Config)
+ if err != nil {
+ return nil, err
+ }
+ netMap["ipamV4Config"] = string(ics)
+ }
+ if len(n.ipamV4Info) > 0 {
+ iis, err := json.Marshal(n.ipamV4Info)
+ if err != nil {
+ return nil, err
+ }
+ netMap["ipamV4Info"] = string(iis)
+ }
+ if len(n.ipamV6Config) > 0 {
+ ics, err := json.Marshal(n.ipamV6Config)
+ if err != nil {
+ return nil, err
+ }
+ netMap["ipamV6Config"] = string(ics)
+ }
+ if len(n.ipamV6Info) > 0 {
+ iis, err := json.Marshal(n.ipamV6Info)
+ if err != nil {
+ return nil, err
+ }
+ netMap["ipamV6Info"] = string(iis)
+ }
+ netMap["internal"] = n.internal
+ netMap["attachable"] = n.attachable
+ netMap["inDelete"] = n.inDelete
+ netMap["ingress"] = n.ingress
+ netMap["configOnly"] = n.configOnly
+ netMap["configFrom"] = n.configFrom
+ netMap["loadBalancerIP"] = n.loadBalancerIP
+ netMap["loadBalancerMode"] = n.loadBalancerMode
+ return json.Marshal(netMap)
+}
+
+// TODO : Can be made much more generic with the help of reflection (but has some golang limitations)
+func (n *network) UnmarshalJSON(b []byte) (err error) {
+ var netMap map[string]interface{}
+ if err := json.Unmarshal(b, &netMap); err != nil {
+ return err
+ }
+ n.name = netMap["name"].(string)
+ n.id = netMap["id"].(string)
+ // "created" is not available in older versions
+ if v, ok := netMap["created"]; ok {
+ // n.created is time.Time but marshalled as string
+ if err = n.created.UnmarshalText([]byte(v.(string))); err != nil {
+ logrus.Warnf("failed to unmarshal creation time %v: %v", v, err)
+ n.created = time.Time{}
+ }
+ }
+ n.networkType = netMap["networkType"].(string)
+ n.enableIPv6 = netMap["enableIPv6"].(bool)
+
+ // if we weren't unmarshaling to netMap we could simply set n.labels
+ // unfortunately, we can't because map[string]interface{} != map[string]string
+ if labels, ok := netMap["labels"].(map[string]interface{}); ok {
+ n.labels = make(map[string]string, len(labels))
+ for label, value := range labels {
+ n.labels[label] = value.(string)
+ }
+ }
+
+ if v, ok := netMap["ipamOptions"]; ok {
+ if iOpts, ok := v.(map[string]interface{}); ok {
+ n.ipamOptions = make(map[string]string, len(iOpts))
+ for k, v := range iOpts {
+ n.ipamOptions[k] = v.(string)
+ }
+ }
+ }
+
+ if v, ok := netMap["generic"]; ok {
+ n.generic = v.(map[string]interface{})
+ // Restore opts in their map[string]string form
+ if v, ok := n.generic[netlabel.GenericData]; ok {
+ var lmap map[string]string
+ ba, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+ if err := json.Unmarshal(ba, &lmap); err != nil {
+ return err
+ }
+ n.generic[netlabel.GenericData] = lmap
+ }
+ }
+ if v, ok := netMap["persist"]; ok {
+ n.persist = v.(bool)
+ }
+ if v, ok := netMap["postIPv6"]; ok {
+ n.postIPv6 = v.(bool)
+ }
+ if v, ok := netMap["ipamType"]; ok {
+ n.ipamType = v.(string)
+ } else {
+ n.ipamType = ipamapi.DefaultIPAM
+ }
+ if v, ok := netMap["addrSpace"]; ok {
+ n.addrSpace = v.(string)
+ }
+ if v, ok := netMap["ipamV4Config"]; ok {
+ if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Config); err != nil {
+ return err
+ }
+ }
+ if v, ok := netMap["ipamV4Info"]; ok {
+ if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Info); err != nil {
+ return err
+ }
+ }
+ if v, ok := netMap["ipamV6Config"]; ok {
+ if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Config); err != nil {
+ return err
+ }
+ }
+ if v, ok := netMap["ipamV6Info"]; ok {
+ if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Info); err != nil {
+ return err
+ }
+ }
+ if v, ok := netMap["internal"]; ok {
+ n.internal = v.(bool)
+ }
+ if v, ok := netMap["attachable"]; ok {
+ n.attachable = v.(bool)
+ }
+ if s, ok := netMap["scope"]; ok {
+ n.scope = s.(string)
+ }
+ if v, ok := netMap["inDelete"]; ok {
+ n.inDelete = v.(bool)
+ }
+ if v, ok := netMap["ingress"]; ok {
+ n.ingress = v.(bool)
+ }
+ if v, ok := netMap["configOnly"]; ok {
+ n.configOnly = v.(bool)
+ }
+ if v, ok := netMap["configFrom"]; ok {
+ n.configFrom = v.(string)
+ }
+ if v, ok := netMap["loadBalancerIP"]; ok {
+ n.loadBalancerIP = net.ParseIP(v.(string))
+ }
+ n.loadBalancerMode = loadBalancerModeDefault
+ if v, ok := netMap["loadBalancerMode"]; ok {
+ n.loadBalancerMode = v.(string)
+ }
+ // Reconcile old networks with the recently added `--ipv6` flag
+ if !n.enableIPv6 {
+ n.enableIPv6 = len(n.ipamV6Info) > 0
+ }
+ return nil
+}
+
+// NetworkOption is an option setter function type used to pass various options to
+// NewNetwork method. The various setter functions of type NetworkOption are
+// provided by libnetwork, they look like NetworkOptionXXXX(...)
+type NetworkOption func(n *network)
+
+// NetworkOptionGeneric function returns an option setter for a Generic option defined
+// in a Dictionary of Key-Value pair
+func NetworkOptionGeneric(generic map[string]interface{}) NetworkOption {
+ return func(n *network) {
+ if n.generic == nil {
+ n.generic = make(map[string]interface{})
+ }
+ if val, ok := generic[netlabel.EnableIPv6]; ok {
+ n.enableIPv6 = val.(bool)
+ }
+ if val, ok := generic[netlabel.Internal]; ok {
+ n.internal = val.(bool)
+ }
+ for k, v := range generic {
+ n.generic[k] = v
+ }
+ }
+}
+
+// NetworkOptionIngress returns an option setter to indicate if a network is
+// an ingress network.
+func NetworkOptionIngress(ingress bool) NetworkOption {
+ return func(n *network) {
+ n.ingress = ingress
+ }
+}
+
+// NetworkOptionPersist returns an option setter to set persistence policy for a network
+func NetworkOptionPersist(persist bool) NetworkOption {
+ return func(n *network) {
+ n.persist = persist
+ }
+}
+
+// NetworkOptionEnableIPv6 returns an option setter to explicitly configure IPv6
+func NetworkOptionEnableIPv6(enableIPv6 bool) NetworkOption {
+ return func(n *network) {
+ if n.generic == nil {
+ n.generic = make(map[string]interface{})
+ }
+ n.enableIPv6 = enableIPv6
+ n.generic[netlabel.EnableIPv6] = enableIPv6
+ }
+}
+
+// NetworkOptionInternalNetwork returns an option setter to config the network
+// to be internal which disables default gateway service
+func NetworkOptionInternalNetwork() NetworkOption {
+ return func(n *network) {
+ if n.generic == nil {
+ n.generic = make(map[string]interface{})
+ }
+ n.internal = true
+ n.generic[netlabel.Internal] = true
+ }
+}
+
+// NetworkOptionAttachable returns an option setter to set attachable for a network
+func NetworkOptionAttachable(attachable bool) NetworkOption {
+ return func(n *network) {
+ n.attachable = attachable
+ }
+}
+
+// NetworkOptionScope returns an option setter to overwrite the network's scope.
+// By default the network's scope is set to the network driver's datascope.
+func NetworkOptionScope(scope string) NetworkOption {
+ return func(n *network) {
+ n.scope = scope
+ }
+}
+
+// NetworkOptionIpam function returns an option setter for the ipam configuration for this network
+func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf, opts map[string]string) NetworkOption {
+ return func(n *network) {
+ if ipamDriver != "" {
+ n.ipamType = ipamDriver
+ if ipamDriver == ipamapi.DefaultIPAM {
+ n.ipamType = defaultIpamForNetworkType(n.Type())
+ }
+ }
+ n.ipamOptions = opts
+ n.addrSpace = addrSpace
+ n.ipamV4Config = ipV4
+ n.ipamV6Config = ipV6
+ }
+}
+
+// NetworkOptionLBEndpoint function returns an option setter for the configuration of the load balancer endpoint for this network
+func NetworkOptionLBEndpoint(ip net.IP) NetworkOption {
+ return func(n *network) {
+ n.loadBalancerIP = ip
+ }
+}
+
+// NetworkOptionDriverOpts function returns an option setter for any driver parameter described by a map
+func NetworkOptionDriverOpts(opts map[string]string) NetworkOption {
+ return func(n *network) {
+ if n.generic == nil {
+ n.generic = make(map[string]interface{})
+ }
+ if opts == nil {
+ opts = make(map[string]string)
+ }
+ // Store the options
+ n.generic[netlabel.GenericData] = opts
+ }
+}
+
+// NetworkOptionLabels function returns an option setter for labels specific to a network
+func NetworkOptionLabels(labels map[string]string) NetworkOption {
+ return func(n *network) {
+ n.labels = labels
+ }
+}
+
+// NetworkOptionDynamic function returns an option setter for dynamic option for a network
+func NetworkOptionDynamic() NetworkOption {
+ return func(n *network) {
+ n.dynamic = true
+ }
+}
+
+// NetworkOptionDeferIPv6Alloc instructs the network to defer the IPV6 address allocation until after the endpoint has been created
+// It is being provided to support the specific docker daemon flags where user can deterministically assign an IPv6 address
+// to a container as combination of fixed-cidr-v6 + mac-address
+// TODO: Remove this option setter once we support endpoint ipam options
+func NetworkOptionDeferIPv6Alloc(enable bool) NetworkOption {
+ return func(n *network) {
+ n.postIPv6 = enable
+ }
+}
+
+// NetworkOptionConfigOnly tells controller this network is
+// a configuration only network. It serves as a configuration
+// for other networks.
+func NetworkOptionConfigOnly() NetworkOption {
+ return func(n *network) {
+ n.configOnly = true
+ }
+}
+
+// NetworkOptionConfigFrom tells controller to pick the
+// network configuration from a configuration only network
+func NetworkOptionConfigFrom(name string) NetworkOption {
+ return func(n *network) {
+ n.configFrom = name
+ }
+}
+
+func (n *network) processOptions(options ...NetworkOption) {
+ for _, opt := range options {
+ if opt != nil {
+ opt(n)
+ }
+ }
+}
+
+type networkDeleteParams struct {
+ rmLBEndpoint bool
+}
+
+// NetworkDeleteOption is a type for optional parameters to pass to the
+// network.Delete() function.
+type NetworkDeleteOption func(p *networkDeleteParams)
+
+// NetworkDeleteOptionRemoveLB informs a network.Delete() operation that should
+// remove the load balancer endpoint for this network. Note that the Delete()
+// method will automatically remove a load balancing endpoint for most networks
+// when the network is otherwise empty. However, this does not occur for some
+// networks. In particular, networks marked as ingress (which are supposed to
+// be more permanent than other overlay networks) won't automatically remove
+// the LB endpoint on Delete(). This method allows for explicit removal of
+// such networks provided there are no other endpoints present in the network.
+// If the network still has non-LB endpoints present, Delete() will not
+// remove the LB endpoint and will return an error.
+func NetworkDeleteOptionRemoveLB(p *networkDeleteParams) {
+ p.rmLBEndpoint = true
+}
+
+func (n *network) resolveDriver(name string, load bool) (driverapi.Driver, *driverapi.Capability, error) {
+ c := n.getController()
+
+ // Check if a driver for the specified network type is available
+ d, cap := c.drvRegistry.Driver(name)
+ if d == nil {
+ if load {
+ err := c.loadDriver(name)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ d, cap = c.drvRegistry.Driver(name)
+ if d == nil {
+ return nil, nil, fmt.Errorf("could not resolve driver %s in registry", name)
+ }
+ } else {
+ // don't fail if driver loading is not required
+ return nil, nil, nil
+ }
+ }
+
+ return d, cap, nil
+}
+
+func (n *network) driverScope() string {
+ _, cap, err := n.resolveDriver(n.networkType, true)
+ if err != nil {
+ // If driver could not be resolved simply return an empty string
+ return ""
+ }
+
+ return cap.DataScope
+}
+
+func (n *network) driverIsMultihost() bool {
+ _, cap, err := n.resolveDriver(n.networkType, true)
+ if err != nil {
+ return false
+ }
+ return cap.ConnectivityScope == datastore.GlobalScope
+}
+
+func (n *network) driver(load bool) (driverapi.Driver, error) {
+ d, cap, err := n.resolveDriver(n.networkType, load)
+ if err != nil {
+ return nil, err
+ }
+
+ n.Lock()
+ // If load is not required, driver, cap and err may all be nil
+ if n.scope == "" && cap != nil {
+ n.scope = cap.DataScope
+ }
+ if n.dynamic {
+ // If the network is dynamic, then it is swarm
+ // scoped regardless of the backing driver.
+ n.scope = datastore.SwarmScope
+ }
+ n.Unlock()
+ return d, nil
+}
+
+func (n *network) Delete(options ...NetworkDeleteOption) error {
+ var params networkDeleteParams
+ for _, opt := range options {
+ opt(¶ms)
+ }
+ return n.delete(false, params.rmLBEndpoint)
+}
+
+// This function gets called in 3 ways:
+// * Delete() -- (false, false)
+// remove if endpoint count == 0 or endpoint count == 1 and
+// there is a load balancer IP
+// * Delete(libnetwork.NetworkDeleteOptionRemoveLB) -- (false, true)
+// remove load balancer and network if endpoint count == 1
+// * controller.networkCleanup() -- (true, true)
+// remove the network no matter what
+func (n *network) delete(force bool, rmLBEndpoint bool) error {
+ n.Lock()
+ c := n.ctrlr
+ name := n.name
+ id := n.id
+ n.Unlock()
+
+ c.networkLocker.Lock(id)
+ defer c.networkLocker.Unlock(id)
+
+ n, err := c.getNetworkFromStore(id)
+ if err != nil {
+ return &UnknownNetworkError{name: name, id: id}
+ }
+
+ // Only remove ingress on force removal or explicit LB endpoint removal
+ if n.ingress && !force && !rmLBEndpoint {
+ return &ActiveEndpointsError{name: n.name, id: n.id}
+ }
+
+ // Check that the network is empty
+ var emptyCount uint64
+ if n.hasLoadBalancerEndpoint() {
+ emptyCount = 1
+ }
+ if !force && n.getEpCnt().EndpointCnt() > emptyCount {
+ if n.configOnly {
+ return types.ForbiddenErrorf("configuration network %q is in use", n.Name())
+ }
+ return &ActiveEndpointsError{name: n.name, id: n.id}
+ }
+
+ if n.hasLoadBalancerEndpoint() {
+ // If we got to this point, then the following must hold:
+ // * force is true OR endpoint count == 1
+ if err := n.deleteLoadBalancerSandbox(); err != nil {
+ if !force {
+ return err
+ }
+ // continue deletion when force is true even on error
+ logrus.Warnf("Error deleting load balancer sandbox: %v", err)
+ }
+ //Reload the network from the store to update the epcnt.
+ n, err = c.getNetworkFromStore(id)
+ if err != nil {
+ return &UnknownNetworkError{name: name, id: id}
+ }
+ }
+
+ // Up to this point, errors that we returned were recoverable.
+ // From here on, any errors leave us in an inconsistent state.
+ // This is unfortunate, but there isn't a safe way to
+ // reconstitute a load-balancer endpoint after removing it.
+
+ // Mark the network for deletion
+ n.inDelete = true
+ if err = c.updateToStore(n); err != nil {
+ return fmt.Errorf("error marking network %s (%s) for deletion: %v", n.Name(), n.ID(), err)
+ }
+
+ if n.ConfigFrom() != "" {
+ if t, err := c.getConfigNetwork(n.ConfigFrom()); err == nil {
+ if err := t.getEpCnt().DecEndpointCnt(); err != nil {
+ logrus.Warnf("Failed to update reference count for configuration network %q on removal of network %q: %v",
+ t.Name(), n.Name(), err)
+ }
+ } else {
+ logrus.Warnf("Could not find configuration network %q during removal of network %q", n.configFrom, n.Name())
+ }
+ }
+
+ if n.configOnly {
+ goto removeFromStore
+ }
+
+ if err = n.deleteNetwork(); err != nil {
+ if !force {
+ return err
+ }
+ logrus.Debugf("driver failed to delete stale network %s (%s): %v", n.Name(), n.ID(), err)
+ }
+
+ n.ipamRelease()
+ if err = c.updateToStore(n); err != nil {
+ logrus.Warnf("Failed to update store after ipam release for network %s (%s): %v", n.Name(), n.ID(), err)
+ }
+
+ // We are about to delete the network. Leave the gossip
+ // cluster for the network to stop all incoming network
+ // specific gossip updates before cleaning up all the service
+ // bindings for the network. But cleanup service binding
+ // before deleting the network from the store since service
+ // bindings cleanup requires the network in the store.
+ n.cancelDriverWatches()
+ if err = n.leaveCluster(); err != nil {
+ logrus.Errorf("Failed leaving network %s from the agent cluster: %v", n.Name(), err)
+ }
+
+ // Cleanup the service discovery for this network
+ c.cleanupServiceDiscovery(n.ID())
+
+removeFromStore:
+ // deleteFromStore performs an atomic delete operation and the
+ // network.epCnt will help prevent any possible
+ // race between endpoint join and network delete
+ if err = c.deleteFromStore(n.getEpCnt()); err != nil {
+ if !force {
+ return fmt.Errorf("error deleting network endpoint count from store: %v", err)
+ }
+ logrus.Debugf("Error deleting endpoint count from store for stale network %s (%s) for deletion: %v", n.Name(), n.ID(), err)
+ }
+
+ if err = c.deleteFromStore(n); err != nil {
+ return fmt.Errorf("error deleting network from store: %v", err)
+ }
+
+ return nil
+}
+
+func (n *network) deleteNetwork() error {
+ d, err := n.driver(true)
+ if err != nil {
+ return fmt.Errorf("failed deleting network: %v", err)
+ }
+
+ if err := d.DeleteNetwork(n.ID()); err != nil {
+ // Forbidden Errors should be honored
+ if _, ok := err.(types.ForbiddenError); ok {
+ return err
+ }
+
+ if _, ok := err.(types.MaskableError); !ok {
+ logrus.Warnf("driver error deleting network %s : %v", n.name, err)
+ }
+ }
+
+ for _, resolver := range n.resolver {
+ resolver.Stop()
+ }
+ return nil
+}
+
+func (n *network) addEndpoint(ep *endpoint) error {
+ d, err := n.driver(true)
+ if err != nil {
+ return fmt.Errorf("failed to add endpoint: %v", err)
+ }
+
+ err = d.CreateEndpoint(n.id, ep.id, ep.Interface(), ep.generic)
+ if err != nil {
+ return types.InternalErrorf("failed to create endpoint %s on network %s: %v",
+ ep.Name(), n.Name(), err)
+ }
+
+ return nil
+}
+
+func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error) {
+ var err error
+ if !config.IsValidName(name) {
+ return nil, ErrInvalidName(name)
+ }
+
+ if n.ConfigOnly() {
+ return nil, types.ForbiddenErrorf("cannot create endpoint on configuration-only network")
+ }
+
+ if _, err = n.EndpointByName(name); err == nil {
+ return nil, types.ForbiddenErrorf("endpoint with name %s already exists in network %s", name, n.Name())
+ }
+
+ n.ctrlr.networkLocker.Lock(n.id)
+ defer n.ctrlr.networkLocker.Unlock(n.id)
+
+ return n.createEndpoint(name, options...)
+
+}
+
+func (n *network) createEndpoint(name string, options ...EndpointOption) (Endpoint, error) {
+ var err error
+
+ ep := &endpoint{name: name, generic: make(map[string]interface{}), iface: &endpointInterface{}}
+ ep.id = stringid.GenerateRandomID()
+
+ // Initialize ep.network with a possibly stale copy of n. We need this to get network from
+ // store. But once we get it from store we will have the most uptodate copy possibly.
+ ep.network = n
+ ep.locator = n.getController().clusterHostID()
+ ep.network, err = ep.getNetworkFromStore()
+ if err != nil {
+ return nil, fmt.Errorf("failed to get network during CreateEndpoint: %v", err)
+ }
+ n = ep.network
+
+ ep.processOptions(options...)
+
+ for _, llIPNet := range ep.Iface().LinkLocalAddresses() {
+ if !llIPNet.IP.IsLinkLocalUnicast() {
+ return nil, types.BadRequestErrorf("invalid link local IP address: %v", llIPNet.IP)
+ }
+ }
+
+ if opt, ok := ep.generic[netlabel.MacAddress]; ok {
+ if mac, ok := opt.(net.HardwareAddr); ok {
+ ep.iface.mac = mac
+ }
+ }
+
+ ipam, cap, err := n.getController().getIPAMDriver(n.ipamType)
+ if err != nil {
+ return nil, err
+ }
+
+ if cap.RequiresMACAddress {
+ if ep.iface.mac == nil {
+ ep.iface.mac = netutils.GenerateRandomMAC()
+ }
+ if ep.ipamOptions == nil {
+ ep.ipamOptions = make(map[string]string)
+ }
+ ep.ipamOptions[netlabel.MacAddress] = ep.iface.mac.String()
+ }
+
+ if err = ep.assignAddress(ipam, true, n.enableIPv6 && !n.postIPv6); err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err != nil {
+ ep.releaseAddress()
+ }
+ }()
+
+ if err = n.addEndpoint(ep); err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err != nil {
+ if e := ep.deleteEndpoint(false); e != nil {
+ logrus.Warnf("cleaning up endpoint failed %s : %v", name, e)
+ }
+ }
+ }()
+
+ // We should perform updateToStore call right after addEndpoint
+ // in order to have iface properly configured
+ if err = n.getController().updateToStore(ep); err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err != nil {
+ if e := n.getController().deleteFromStore(ep); e != nil {
+ logrus.Warnf("error rolling back endpoint %s from store: %v", name, e)
+ }
+ }
+ }()
+
+ if err = ep.assignAddress(ipam, false, n.enableIPv6 && n.postIPv6); err != nil {
+ return nil, err
+ }
+
+ // Watch for service records
+ n.getController().watchSvcRecord(ep)
+ defer func() {
+ if err != nil {
+ n.getController().unWatchSvcRecord(ep)
+ }
+ }()
+
+ // Increment endpoint count to indicate completion of endpoint addition
+ if err = n.getEpCnt().IncEndpointCnt(); err != nil {
+ return nil, err
+ }
+
+ return ep, nil
+}
+
+func (n *network) Endpoints() []Endpoint {
+ var list []Endpoint
+
+ endpoints, err := n.getEndpointsFromStore()
+ if err != nil {
+ logrus.Error(err)
+ }
+
+ for _, ep := range endpoints {
+ list = append(list, ep)
+ }
+
+ return list
+}
+
+func (n *network) WalkEndpoints(walker EndpointWalker) {
+ for _, e := range n.Endpoints() {
+ if walker(e) {
+ return
+ }
+ }
+}
+
+func (n *network) EndpointByName(name string) (Endpoint, error) {
+ if name == "" {
+ return nil, ErrInvalidName(name)
+ }
+ var e Endpoint
+
+ s := func(current Endpoint) bool {
+ if current.Name() == name {
+ e = current
+ return true
+ }
+ return false
+ }
+
+ n.WalkEndpoints(s)
+
+ if e == nil {
+ return nil, ErrNoSuchEndpoint(name)
+ }
+
+ return e, nil
+}
+
+func (n *network) EndpointByID(id string) (Endpoint, error) {
+ if id == "" {
+ return nil, ErrInvalidID(id)
+ }
+
+ ep, err := n.getEndpointFromStore(id)
+ if err != nil {
+ return nil, ErrNoSuchEndpoint(id)
+ }
+
+ return ep, nil
+}
+
+func (n *network) updateSvcRecord(ep *endpoint, localEps []*endpoint, isAdd bool) {
+ var ipv6 net.IP
+ epName := ep.Name()
+ if iface := ep.Iface(); iface.Address() != nil {
+ myAliases := ep.MyAliases()
+ if iface.AddressIPv6() != nil {
+ ipv6 = iface.AddressIPv6().IP
+ }
+
+ serviceID := ep.svcID
+ if serviceID == "" {
+ serviceID = ep.ID()
+ }
+ if isAdd {
+ // If anonymous endpoint has an alias use the first alias
+ // for ip->name mapping. Not having the reverse mapping
+ // breaks some apps
+ if ep.isAnonymous() {
+ if len(myAliases) > 0 {
+ n.addSvcRecords(ep.ID(), myAliases[0], serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord")
+ }
+ } else {
+ n.addSvcRecords(ep.ID(), epName, serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord")
+ }
+ for _, alias := range myAliases {
+ n.addSvcRecords(ep.ID(), alias, serviceID, iface.Address().IP, ipv6, false, "updateSvcRecord")
+ }
+ } else {
+ if ep.isAnonymous() {
+ if len(myAliases) > 0 {
+ n.deleteSvcRecords(ep.ID(), myAliases[0], serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord")
+ }
+ } else {
+ n.deleteSvcRecords(ep.ID(), epName, serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord")
+ }
+ for _, alias := range myAliases {
+ n.deleteSvcRecords(ep.ID(), alias, serviceID, iface.Address().IP, ipv6, false, "updateSvcRecord")
+ }
+ }
+ }
+}
+
+func addIPToName(ipMap setmatrix.SetMatrix, name, serviceID string, ip net.IP) {
+ reverseIP := netutils.ReverseIP(ip.String())
+ ipMap.Insert(reverseIP, ipInfo{
+ name: name,
+ serviceID: serviceID,
+ })
+}
+
+func delIPToName(ipMap setmatrix.SetMatrix, name, serviceID string, ip net.IP) {
+ reverseIP := netutils.ReverseIP(ip.String())
+ ipMap.Remove(reverseIP, ipInfo{
+ name: name,
+ serviceID: serviceID,
+ })
+}
+
+func addNameToIP(svcMap setmatrix.SetMatrix, name, serviceID string, epIP net.IP) {
+ svcMap.Insert(name, svcMapEntry{
+ ip: epIP.String(),
+ serviceID: serviceID,
+ })
+}
+
+func delNameToIP(svcMap setmatrix.SetMatrix, name, serviceID string, epIP net.IP) {
+ svcMap.Remove(name, svcMapEntry{
+ ip: epIP.String(),
+ serviceID: serviceID,
+ })
+}
+
+func (n *network) addSvcRecords(eID, name, serviceID string, epIP, epIPv6 net.IP, ipMapUpdate bool, method string) {
+ // Do not add service names for ingress network as this is a
+ // routing only network
+ if n.ingress {
+ return
+ }
+
+ logrus.Debugf("%s (%.7s).addSvcRecords(%s, %s, %s, %t) %s sid:%s", eID, n.ID(), name, epIP, epIPv6, ipMapUpdate, method, serviceID)
+
+ c := n.getController()
+ c.Lock()
+ defer c.Unlock()
+
+ sr, ok := c.svcRecords[n.ID()]
+ if !ok {
+ sr = svcInfo{
+ svcMap: setmatrix.NewSetMatrix(),
+ svcIPv6Map: setmatrix.NewSetMatrix(),
+ ipMap: setmatrix.NewSetMatrix(),
+ }
+ c.svcRecords[n.ID()] = sr
+ }
+
+ if ipMapUpdate {
+ addIPToName(sr.ipMap, name, serviceID, epIP)
+ if epIPv6 != nil {
+ addIPToName(sr.ipMap, name, serviceID, epIPv6)
+ }
+ }
+
+ addNameToIP(sr.svcMap, name, serviceID, epIP)
+ if epIPv6 != nil {
+ addNameToIP(sr.svcIPv6Map, name, serviceID, epIPv6)
+ }
+}
+
+func (n *network) deleteSvcRecords(eID, name, serviceID string, epIP net.IP, epIPv6 net.IP, ipMapUpdate bool, method string) {
+ // Do not delete service names from ingress network as this is a
+ // routing only network
+ if n.ingress {
+ return
+ }
+
+ logrus.Debugf("%s (%.7s).deleteSvcRecords(%s, %s, %s, %t) %s sid:%s ", eID, n.ID(), name, epIP, epIPv6, ipMapUpdate, method, serviceID)
+
+ c := n.getController()
+ c.Lock()
+ defer c.Unlock()
+
+ sr, ok := c.svcRecords[n.ID()]
+ if !ok {
+ return
+ }
+
+ if ipMapUpdate {
+ delIPToName(sr.ipMap, name, serviceID, epIP)
+
+ if epIPv6 != nil {
+ delIPToName(sr.ipMap, name, serviceID, epIPv6)
+ }
+ }
+
+ delNameToIP(sr.svcMap, name, serviceID, epIP)
+
+ if epIPv6 != nil {
+ delNameToIP(sr.svcIPv6Map, name, serviceID, epIPv6)
+ }
+}
+
+func (n *network) getSvcRecords(ep *endpoint) []etchosts.Record {
+ n.Lock()
+ defer n.Unlock()
+
+ if ep == nil {
+ return nil
+ }
+
+ var recs []etchosts.Record
+
+ epName := ep.Name()
+
+ n.ctrlr.Lock()
+ defer n.ctrlr.Unlock()
+ sr, ok := n.ctrlr.svcRecords[n.id]
+ if !ok || sr.svcMap == nil {
+ return nil
+ }
+
+ svcMapKeys := sr.svcMap.Keys()
+ // Loop on service names on this network
+ for _, k := range svcMapKeys {
+ if strings.Split(k, ".")[0] == epName {
+ continue
+ }
+ // Get all the IPs associated to this service
+ mapEntryList, ok := sr.svcMap.Get(k)
+ if !ok {
+ // The key got deleted
+ continue
+ }
+ if len(mapEntryList) == 0 {
+ logrus.Warnf("Found empty list of IP addresses for service %s on network %s (%s)", k, n.name, n.id)
+ continue
+ }
+
+ recs = append(recs, etchosts.Record{
+ Hosts: k,
+ IP: mapEntryList[0].(svcMapEntry).ip,
+ })
+ }
+
+ return recs
+}
+
+func (n *network) getController() *controller {
+ n.Lock()
+ defer n.Unlock()
+ return n.ctrlr
+}
+
+func (n *network) ipamAllocate() error {
+ if n.hasSpecialDriver() {
+ return nil
+ }
+
+ ipam, _, err := n.getController().getIPAMDriver(n.ipamType)
+ if err != nil {
+ return err
+ }
+
+ if n.addrSpace == "" {
+ if n.addrSpace, err = n.deriveAddressSpace(); err != nil {
+ return err
+ }
+ }
+
+ err = n.ipamAllocateVersion(4, ipam)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil {
+ n.ipamReleaseVersion(4, ipam)
+ }
+ }()
+
+ if !n.enableIPv6 {
+ return nil
+ }
+
+ err = n.ipamAllocateVersion(6, ipam)
+ return err
+}
+
+func (n *network) requestPoolHelper(ipam ipamapi.Ipam, addressSpace, preferredPool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
+ for {
+ poolID, pool, meta, err := ipam.RequestPool(addressSpace, preferredPool, subPool, options, v6)
+ if err != nil {
+ return "", nil, nil, err
+ }
+
+ // If the network belongs to global scope or the pool was
+ // explicitly chosen or it is invalid, do not perform the overlap check.
+ if n.Scope() == datastore.GlobalScope || preferredPool != "" || !types.IsIPNetValid(pool) {
+ return poolID, pool, meta, nil
+ }
+
+ // Check for overlap and if none found, we have found the right pool.
+ if _, err := netutils.FindAvailableNetwork([]*net.IPNet{pool}); err == nil {
+ return poolID, pool, meta, nil
+ }
+
+ // Pool obtained in this iteration is
+ // overlapping. Hold onto the pool and don't release
+ // it yet, because we don't want ipam to give us back
+ // the same pool over again. But make sure we still do
+ // a deferred release when we have either obtained a
+ // non-overlapping pool or ran out of pre-defined
+ // pools.
+ defer func() {
+ if err := ipam.ReleasePool(poolID); err != nil {
+ logrus.Warnf("Failed to release overlapping pool %s while returning from pool request helper for network %s", pool, n.Name())
+ }
+ }()
+
+ // If this is a preferred pool request and the network
+ // is local scope and there is an overlap, we fail the
+ // network creation right here. The pool will be
+ // released in the defer.
+ if preferredPool != "" {
+ return "", nil, nil, fmt.Errorf("requested subnet %s overlaps in the host", preferredPool)
+ }
+ }
+}
+
+func (n *network) ipamAllocateVersion(ipVer int, ipam ipamapi.Ipam) error {
+ var (
+ cfgList *[]*IpamConf
+ infoList *[]*IpamInfo
+ err error
+ )
+
+ switch ipVer {
+ case 4:
+ cfgList = &n.ipamV4Config
+ infoList = &n.ipamV4Info
+ case 6:
+ cfgList = &n.ipamV6Config
+ infoList = &n.ipamV6Info
+ default:
+ return types.InternalErrorf("incorrect ip version passed to ipam allocate: %d", ipVer)
+ }
+
+ if len(*cfgList) == 0 {
+ *cfgList = []*IpamConf{{}}
+ }
+
+ *infoList = make([]*IpamInfo, len(*cfgList))
+
+ logrus.Debugf("Allocating IPv%d pools for network %s (%s)", ipVer, n.Name(), n.ID())
+
+ for i, cfg := range *cfgList {
+ if err = cfg.Validate(); err != nil {
+ return err
+ }
+ d := &IpamInfo{}
+ (*infoList)[i] = d
+
+ d.AddressSpace = n.addrSpace
+ d.PoolID, d.Pool, d.Meta, err = n.requestPoolHelper(ipam, n.addrSpace, cfg.PreferredPool, cfg.SubPool, n.ipamOptions, ipVer == 6)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil {
+ if err := ipam.ReleasePool(d.PoolID); err != nil {
+ logrus.Warnf("Failed to release address pool %s after failure to create network %s (%s)", d.PoolID, n.Name(), n.ID())
+ }
+ }
+ }()
+
+ if gws, ok := d.Meta[netlabel.Gateway]; ok {
+ if d.Gateway, err = types.ParseCIDR(gws); err != nil {
+ return types.BadRequestErrorf("failed to parse gateway address (%v) returned by ipam driver: %v", gws, err)
+ }
+ }
+
+ // If user requested a specific gateway, libnetwork will allocate it
+ // irrespective of whether ipam driver returned a gateway already.
+ // If none of the above is true, libnetwork will allocate one.
+ if cfg.Gateway != "" || d.Gateway == nil {
+ var gatewayOpts = map[string]string{
+ ipamapi.RequestAddressType: netlabel.Gateway,
+ }
+ if d.Gateway, _, err = ipam.RequestAddress(d.PoolID, net.ParseIP(cfg.Gateway), gatewayOpts); err != nil {
+ return types.InternalErrorf("failed to allocate gateway (%v): %v", cfg.Gateway, err)
+ }
+ }
+
+ // Auxiliary addresses must be part of the master address pool
+ // If they fall into the container addressable pool, libnetwork will reserve them
+ if cfg.AuxAddresses != nil {
+ var ip net.IP
+ d.IPAMData.AuxAddresses = make(map[string]*net.IPNet, len(cfg.AuxAddresses))
+ for k, v := range cfg.AuxAddresses {
+ if ip = net.ParseIP(v); ip == nil {
+ return types.BadRequestErrorf("non parsable secondary ip address (%s:%s) passed for network %s", k, v, n.Name())
+ }
+ if !d.Pool.Contains(ip) {
+ return types.ForbiddenErrorf("auxiliary address: (%s:%s) must belong to the master pool: %s", k, v, d.Pool)
+ }
+ // Attempt reservation in the container addressable pool, silent the error if address does not belong to that pool
+ if d.IPAMData.AuxAddresses[k], _, err = ipam.RequestAddress(d.PoolID, ip, nil); err != nil && err != ipamapi.ErrIPOutOfRange {
+ return types.InternalErrorf("failed to allocate secondary ip address (%s:%s): %v", k, v, err)
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func (n *network) ipamRelease() {
+ if n.hasSpecialDriver() {
+ return
+ }
+ ipam, _, err := n.getController().getIPAMDriver(n.ipamType)
+ if err != nil {
+ logrus.Warnf("Failed to retrieve ipam driver to release address pool(s) on delete of network %s (%s): %v", n.Name(), n.ID(), err)
+ return
+ }
+ n.ipamReleaseVersion(4, ipam)
+ n.ipamReleaseVersion(6, ipam)
+}
+
+func (n *network) ipamReleaseVersion(ipVer int, ipam ipamapi.Ipam) {
+ var infoList *[]*IpamInfo
+
+ switch ipVer {
+ case 4:
+ infoList = &n.ipamV4Info
+ case 6:
+ infoList = &n.ipamV6Info
+ default:
+ logrus.Warnf("incorrect ip version passed to ipam release: %d", ipVer)
+ return
+ }
+
+ if len(*infoList) == 0 {
+ return
+ }
+
+ logrus.Debugf("releasing IPv%d pools from network %s (%s)", ipVer, n.Name(), n.ID())
+
+ for _, d := range *infoList {
+ if d.Gateway != nil {
+ if err := ipam.ReleaseAddress(d.PoolID, d.Gateway.IP); err != nil {
+ logrus.Warnf("Failed to release gateway ip address %s on delete of network %s (%s): %v", d.Gateway.IP, n.Name(), n.ID(), err)
+ }
+ }
+ if d.IPAMData.AuxAddresses != nil {
+ for k, nw := range d.IPAMData.AuxAddresses {
+ if d.Pool.Contains(nw.IP) {
+ if err := ipam.ReleaseAddress(d.PoolID, nw.IP); err != nil && err != ipamapi.ErrIPOutOfRange {
+ logrus.Warnf("Failed to release secondary ip address %s (%v) on delete of network %s (%s): %v", k, nw.IP, n.Name(), n.ID(), err)
+ }
+ }
+ }
+ }
+ if err := ipam.ReleasePool(d.PoolID); err != nil {
+ logrus.Warnf("Failed to release address pool %s on delete of network %s (%s): %v", d.PoolID, n.Name(), n.ID(), err)
+ }
+ }
+
+ *infoList = nil
+}
+
+func (n *network) getIPInfo(ipVer int) []*IpamInfo {
+ var info []*IpamInfo
+ switch ipVer {
+ case 4:
+ info = n.ipamV4Info
+ case 6:
+ info = n.ipamV6Info
+ default:
+ return nil
+ }
+ l := make([]*IpamInfo, 0, len(info))
+ n.Lock()
+ l = append(l, info...)
+ n.Unlock()
+ return l
+}
+
+func (n *network) getIPData(ipVer int) []driverapi.IPAMData {
+ var info []*IpamInfo
+ switch ipVer {
+ case 4:
+ info = n.ipamV4Info
+ case 6:
+ info = n.ipamV6Info
+ default:
+ return nil
+ }
+ l := make([]driverapi.IPAMData, 0, len(info))
+ n.Lock()
+ for _, d := range info {
+ l = append(l, d.IPAMData)
+ }
+ n.Unlock()
+ return l
+}
+
+func (n *network) deriveAddressSpace() (string, error) {
+ local, global, err := n.getController().drvRegistry.IPAMDefaultAddressSpaces(n.ipamType)
+ if err != nil {
+ return "", types.NotFoundErrorf("failed to get default address space: %v", err)
+ }
+ if n.DataScope() == datastore.GlobalScope {
+ return global, nil
+ }
+ return local, nil
+}
+
+func (n *network) Info() NetworkInfo {
+ return n
+}
+
+func (n *network) Peers() []networkdb.PeerInfo {
+ if !n.Dynamic() {
+ return []networkdb.PeerInfo{}
+ }
+
+ agent := n.getController().getAgent()
+ if agent == nil {
+ return []networkdb.PeerInfo{}
+ }
+
+ return agent.networkDB.Peers(n.ID())
+}
+
+func (n *network) DriverOptions() map[string]string {
+ n.Lock()
+ defer n.Unlock()
+ if n.generic != nil {
+ if m, ok := n.generic[netlabel.GenericData]; ok {
+ return m.(map[string]string)
+ }
+ }
+ return map[string]string{}
+}
+
+func (n *network) Scope() string {
+ n.Lock()
+ defer n.Unlock()
+ return n.scope
+}
+
+func (n *network) IpamConfig() (string, map[string]string, []*IpamConf, []*IpamConf) {
+ n.Lock()
+ defer n.Unlock()
+
+ v4L := make([]*IpamConf, len(n.ipamV4Config))
+ v6L := make([]*IpamConf, len(n.ipamV6Config))
+
+ for i, c := range n.ipamV4Config {
+ cc := &IpamConf{}
+ c.CopyTo(cc)
+ v4L[i] = cc
+ }
+
+ for i, c := range n.ipamV6Config {
+ cc := &IpamConf{}
+ c.CopyTo(cc)
+ v6L[i] = cc
+ }
+
+ return n.ipamType, n.ipamOptions, v4L, v6L
+}
+
+func (n *network) IpamInfo() ([]*IpamInfo, []*IpamInfo) {
+ n.Lock()
+ defer n.Unlock()
+
+ v4Info := make([]*IpamInfo, len(n.ipamV4Info))
+ v6Info := make([]*IpamInfo, len(n.ipamV6Info))
+
+ for i, info := range n.ipamV4Info {
+ ic := &IpamInfo{}
+ info.CopyTo(ic)
+ v4Info[i] = ic
+ }
+
+ for i, info := range n.ipamV6Info {
+ ic := &IpamInfo{}
+ info.CopyTo(ic)
+ v6Info[i] = ic
+ }
+
+ return v4Info, v6Info
+}
+
+func (n *network) Internal() bool {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.internal
+}
+
+func (n *network) Attachable() bool {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.attachable
+}
+
+func (n *network) Ingress() bool {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.ingress
+}
+
+func (n *network) Dynamic() bool {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.dynamic
+}
+
+func (n *network) IPv6Enabled() bool {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.enableIPv6
+}
+
+func (n *network) ConfigFrom() string {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.configFrom
+}
+
+func (n *network) ConfigOnly() bool {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.configOnly
+}
+
+func (n *network) Labels() map[string]string {
+ n.Lock()
+ defer n.Unlock()
+
+ var lbls = make(map[string]string, len(n.labels))
+ for k, v := range n.labels {
+ lbls[k] = v
+ }
+
+ return lbls
+}
+
+func (n *network) TableEventRegister(tableName string, objType driverapi.ObjectType) error {
+ if !driverapi.IsValidType(objType) {
+ return fmt.Errorf("invalid object type %v in registering table, %s", objType, tableName)
+ }
+
+ t := networkDBTable{
+ name: tableName,
+ objType: objType,
+ }
+ n.Lock()
+ defer n.Unlock()
+ n.driverTables = append(n.driverTables, t)
+ return nil
+}
+
+// Special drivers are ones which do not need to perform any network plumbing
+func (n *network) hasSpecialDriver() bool {
+ return n.Type() == "host" || n.Type() == "null"
+}
+
+func (n *network) hasLoadBalancerEndpoint() bool {
+ return len(n.loadBalancerIP) != 0
+}
+
+func (n *network) ResolveName(req string, ipType int) ([]net.IP, bool) {
+ var ipv6Miss bool
+
+ c := n.getController()
+ c.Lock()
+ defer c.Unlock()
+ sr, ok := c.svcRecords[n.ID()]
+
+ if !ok {
+ return nil, false
+ }
+
+ req = strings.TrimSuffix(req, ".")
+ ipSet, ok := sr.svcMap.Get(req)
+
+ if ipType == types.IPv6 {
+ // If the name resolved to v4 address then its a valid name in
+ // the docker network domain. If the network is not v6 enabled
+ // set ipv6Miss to filter the DNS query from going to external
+ // resolvers.
+ if ok && !n.enableIPv6 {
+ ipv6Miss = true
+ }
+ ipSet, ok = sr.svcIPv6Map.Get(req)
+ }
+
+ if ok && len(ipSet) > 0 {
+ // this map is to avoid IP duplicates, this can happen during a transition period where 2 services are using the same IP
+ noDup := make(map[string]bool)
+ var ipLocal []net.IP
+ for _, ip := range ipSet {
+ if _, dup := noDup[ip.(svcMapEntry).ip]; !dup {
+ noDup[ip.(svcMapEntry).ip] = true
+ ipLocal = append(ipLocal, net.ParseIP(ip.(svcMapEntry).ip))
+ }
+ }
+ return ipLocal, ok
+ }
+
+ return nil, ipv6Miss
+}
+
+func (n *network) HandleQueryResp(name string, ip net.IP) {
+ c := n.getController()
+ c.Lock()
+ defer c.Unlock()
+ sr, ok := c.svcRecords[n.ID()]
+
+ if !ok {
+ return
+ }
+
+ ipStr := netutils.ReverseIP(ip.String())
+ // If an object with extResolver == true is already in the set this call will fail
+ // but anyway it means that has already been inserted before
+ if ok, _ := sr.ipMap.Contains(ipStr, ipInfo{name: name}); ok {
+ sr.ipMap.Remove(ipStr, ipInfo{name: name})
+ sr.ipMap.Insert(ipStr, ipInfo{name: name, extResolver: true})
+ }
+}
+
+func (n *network) ResolveIP(ip string) string {
+ c := n.getController()
+ c.Lock()
+ defer c.Unlock()
+ sr, ok := c.svcRecords[n.ID()]
+
+ if !ok {
+ return ""
+ }
+
+ nwName := n.Name()
+
+ elemSet, ok := sr.ipMap.Get(ip)
+ if !ok || len(elemSet) == 0 {
+ return ""
+ }
+ // NOTE it is possible to have more than one element in the Set, this will happen
+ // because of interleave of different events from different sources (local container create vs
+ // network db notifications)
+ // In such cases the resolution will be based on the first element of the set, and can vary
+ // during the system stabilitation
+ elem, ok := elemSet[0].(ipInfo)
+ if !ok {
+ setStr, b := sr.ipMap.String(ip)
+ logrus.Errorf("expected set of ipInfo type for key %s set:%t %s", ip, b, setStr)
+ return ""
+ }
+
+ if elem.extResolver {
+ return ""
+ }
+
+ return elem.name + "." + nwName
+}
+
+func (n *network) ResolveService(name string) ([]*net.SRV, []net.IP) {
+ c := n.getController()
+
+ srv := []*net.SRV{}
+ ip := []net.IP{}
+
+ logrus.Debugf("Service name To resolve: %v", name)
+
+ // There are DNS implementations that allow SRV queries for names not in
+ // the format defined by RFC 2782. Hence specific validations checks are
+ // not done
+ parts := strings.Split(name, ".")
+ if len(parts) < 3 {
+ return nil, nil
+ }
+
+ portName := parts[0]
+ proto := parts[1]
+ svcName := strings.Join(parts[2:], ".")
+
+ c.Lock()
+ defer c.Unlock()
+ sr, ok := c.svcRecords[n.ID()]
+
+ if !ok {
+ return nil, nil
+ }
+
+ svcs, ok := sr.service[svcName]
+ if !ok {
+ return nil, nil
+ }
+
+ for _, svc := range svcs {
+ if svc.portName != portName {
+ continue
+ }
+ if svc.proto != proto {
+ continue
+ }
+ for _, t := range svc.target {
+ srv = append(srv,
+ &net.SRV{
+ Target: t.name,
+ Port: t.port,
+ })
+
+ ip = append(ip, t.ip)
+ }
+ }
+
+ return srv, ip
+}
+
+func (n *network) ExecFunc(f func()) error {
+ return types.NotImplementedErrorf("ExecFunc not supported by network")
+}
+
+func (n *network) NdotsSet() bool {
+ return false
+}
+
+// config-only network is looked up by name
+func (c *controller) getConfigNetwork(name string) (*network, error) {
+ var n Network
+
+ s := func(current Network) bool {
+ if current.Info().ConfigOnly() && current.Name() == name {
+ n = current
+ return true
+ }
+ return false
+ }
+
+ c.WalkNetworks(s)
+
+ if n == nil {
+ return nil, types.NotFoundErrorf("configuration network %q not found", name)
+ }
+
+ return n.(*network), nil
+}
+
+func (n *network) lbSandboxName() string {
+ name := "lb-" + n.name
+ if n.ingress {
+ name = n.name + "-sbox"
+ }
+ return name
+}
+
+func (n *network) lbEndpointName() string {
+ return n.name + "-endpoint"
+}
+
+func (n *network) createLoadBalancerSandbox() (retErr error) {
+ sandboxName := n.lbSandboxName()
+ // Mark the sandbox to be a load balancer
+ sbOptions := []SandboxOption{OptionLoadBalancer(n.id)}
+ if n.ingress {
+ sbOptions = append(sbOptions, OptionIngress())
+ }
+ sb, err := n.ctrlr.NewSandbox(sandboxName, sbOptions...)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if retErr != nil {
+ if e := n.ctrlr.SandboxDestroy(sandboxName); e != nil {
+ logrus.Warnf("could not delete sandbox %s on failure on failure (%v): %v", sandboxName, retErr, e)
+ }
+ }
+ }()
+
+ endpointName := n.lbEndpointName()
+ epOptions := []EndpointOption{
+ CreateOptionIpam(n.loadBalancerIP, nil, nil, nil),
+ CreateOptionLoadBalancer(),
+ }
+ if n.hasLoadBalancerEndpoint() && !n.ingress {
+ // Mark LB endpoints as anonymous so they don't show up in DNS
+ epOptions = append(epOptions, CreateOptionAnonymous())
+ }
+ ep, err := n.createEndpoint(endpointName, epOptions...)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if retErr != nil {
+ if e := ep.Delete(true); e != nil {
+ logrus.Warnf("could not delete endpoint %s on failure on failure (%v): %v", endpointName, retErr, e)
+ }
+ }
+ }()
+
+ if err := ep.Join(sb, nil); err != nil {
+ return err
+ }
+
+ return sb.EnableService()
+}
+
+func (n *network) deleteLoadBalancerSandbox() error {
+ n.Lock()
+ c := n.ctrlr
+ name := n.name
+ n.Unlock()
+
+ sandboxName := n.lbSandboxName()
+ endpointName := n.lbEndpointName()
+
+ endpoint, err := n.EndpointByName(endpointName)
+ if err != nil {
+ logrus.Warnf("Failed to find load balancer endpoint %s on network %s: %v", endpointName, name, err)
+ } else {
+
+ info := endpoint.Info()
+ if info != nil {
+ sb := info.Sandbox()
+ if sb != nil {
+ if err := sb.DisableService(); err != nil {
+ logrus.Warnf("Failed to disable service on sandbox %s: %v", sandboxName, err)
+ //Ignore error and attempt to delete the load balancer endpoint
+ }
+ }
+ }
+
+ if err := endpoint.Delete(true); err != nil {
+ logrus.Warnf("Failed to delete endpoint %s (%s) in %s: %v", endpoint.Name(), endpoint.ID(), sandboxName, err)
+ //Ignore error and attempt to delete the sandbox.
+ }
+ }
+
+ if err := c.SandboxDestroy(sandboxName); err != nil {
+ return fmt.Errorf("Failed to delete %s sandbox: %v", sandboxName, err)
+ }
+ return nil
+}
--- /dev/null
+// +build !windows
+
+package libnetwork
+
+import "github.com/docker/libnetwork/ipamapi"
+
+// Stub implementations for DNS related functions
+
+func (n *network) startResolver() {
+}
+
+func defaultIpamForNetworkType(networkType string) string {
+ return ipamapi.DefaultIPAM
+}
--- /dev/null
+// +build windows
+
+package libnetwork
+
+import (
+ "runtime"
+ "time"
+
+ "github.com/Microsoft/hcsshim"
+ "github.com/docker/libnetwork/drivers/windows"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/ipams/windowsipam"
+ "github.com/sirupsen/logrus"
+)
+
+func executeInCompartment(compartmentID uint32, x func()) {
+ runtime.LockOSThread()
+
+ if err := hcsshim.SetCurrentThreadCompartmentId(compartmentID); err != nil {
+ logrus.Error(err)
+ }
+ defer func() {
+ hcsshim.SetCurrentThreadCompartmentId(0)
+ runtime.UnlockOSThread()
+ }()
+
+ x()
+}
+
+func (n *network) startResolver() {
+ if n.networkType == "ics" {
+ return
+ }
+ n.resolverOnce.Do(func() {
+ logrus.Debugf("Launching DNS server for network %q", n.Name())
+ options := n.Info().DriverOptions()
+ hnsid := options[windows.HNSID]
+
+ if hnsid == "" {
+ return
+ }
+
+ hnsresponse, err := hcsshim.HNSNetworkRequest("GET", hnsid, "")
+ if err != nil {
+ logrus.Errorf("Resolver Setup/Start failed for container %s, %q", n.Name(), err)
+ return
+ }
+
+ for _, subnet := range hnsresponse.Subnets {
+ if subnet.GatewayAddress != "" {
+ for i := 0; i < 3; i++ {
+ resolver := NewResolver(subnet.GatewayAddress, false, "", n)
+ logrus.Debugf("Binding a resolver on network %s gateway %s", n.Name(), subnet.GatewayAddress)
+ executeInCompartment(hnsresponse.DNSServerCompartment, resolver.SetupFunc(53))
+
+ if err = resolver.Start(); err != nil {
+ logrus.Errorf("Resolver Setup/Start failed for container %s, %q", n.Name(), err)
+ time.Sleep(1 * time.Second)
+ } else {
+ logrus.Debugf("Resolver bound successfully for network %s", n.Name())
+ n.resolver = append(n.resolver, resolver)
+ break
+ }
+ }
+ }
+ }
+ })
+}
+
+func defaultIpamForNetworkType(networkType string) string {
+ if windows.IsBuiltinLocalDriver(networkType) {
+ return windowsipam.DefaultIPAM
+ }
+ return ipamapi.DefaultIPAM
+}
--- /dev/null
+package networkdb
+
+import (
+ "errors"
+ "time"
+
+ "github.com/hashicorp/memberlist"
+ "github.com/hashicorp/serf/serf"
+)
+
+const broadcastTimeout = 5 * time.Second
+
+type networkEventMessage struct {
+ id string
+ node string
+ msg []byte
+}
+
+func (m *networkEventMessage) Invalidates(other memberlist.Broadcast) bool {
+ otherm := other.(*networkEventMessage)
+ return m.id == otherm.id && m.node == otherm.node
+}
+
+func (m *networkEventMessage) Message() []byte {
+ return m.msg
+}
+
+func (m *networkEventMessage) Finished() {
+}
+
+func (nDB *NetworkDB) sendNetworkEvent(nid string, event NetworkEvent_Type, ltime serf.LamportTime) error {
+ nEvent := NetworkEvent{
+ Type: event,
+ LTime: ltime,
+ NodeName: nDB.config.NodeID,
+ NetworkID: nid,
+ }
+
+ raw, err := encodeMessage(MessageTypeNetworkEvent, &nEvent)
+ if err != nil {
+ return err
+ }
+
+ nDB.networkBroadcasts.QueueBroadcast(&networkEventMessage{
+ msg: raw,
+ id: nid,
+ node: nDB.config.NodeID,
+ })
+ return nil
+}
+
+type nodeEventMessage struct {
+ msg []byte
+ notify chan<- struct{}
+}
+
+func (m *nodeEventMessage) Invalidates(other memberlist.Broadcast) bool {
+ return false
+}
+
+func (m *nodeEventMessage) Message() []byte {
+ return m.msg
+}
+
+func (m *nodeEventMessage) Finished() {
+ if m.notify != nil {
+ close(m.notify)
+ }
+}
+
+func (nDB *NetworkDB) sendNodeEvent(event NodeEvent_Type) error {
+ nEvent := NodeEvent{
+ Type: event,
+ LTime: nDB.networkClock.Increment(),
+ NodeName: nDB.config.NodeID,
+ }
+
+ raw, err := encodeMessage(MessageTypeNodeEvent, &nEvent)
+ if err != nil {
+ return err
+ }
+
+ notifyCh := make(chan struct{})
+ nDB.nodeBroadcasts.QueueBroadcast(&nodeEventMessage{
+ msg: raw,
+ notify: notifyCh,
+ })
+
+ nDB.RLock()
+ noPeers := len(nDB.nodes) <= 1
+ nDB.RUnlock()
+
+ // Message enqueued, do not wait for a send if no peer is present
+ if noPeers {
+ return nil
+ }
+
+ // Wait for the broadcast
+ select {
+ case <-notifyCh:
+ case <-time.After(broadcastTimeout):
+ return errors.New("timed out broadcasting node event")
+ }
+
+ return nil
+}
+
+type tableEventMessage struct {
+ id string
+ tname string
+ key string
+ msg []byte
+}
+
+func (m *tableEventMessage) Invalidates(other memberlist.Broadcast) bool {
+ otherm := other.(*tableEventMessage)
+ return m.tname == otherm.tname && m.id == otherm.id && m.key == otherm.key
+}
+
+func (m *tableEventMessage) Message() []byte {
+ return m.msg
+}
+
+func (m *tableEventMessage) Finished() {
+}
+
+func (nDB *NetworkDB) sendTableEvent(event TableEvent_Type, nid string, tname string, key string, entry *entry) error {
+ tEvent := TableEvent{
+ Type: event,
+ LTime: entry.ltime,
+ NodeName: nDB.config.NodeID,
+ NetworkID: nid,
+ TableName: tname,
+ Key: key,
+ Value: entry.value,
+ // The duration in second is a float that below would be truncated
+ ResidualReapTime: int32(entry.reapTime.Seconds()),
+ }
+
+ raw, err := encodeMessage(MessageTypeTableEvent, &tEvent)
+ if err != nil {
+ return err
+ }
+
+ var broadcastQ *memberlist.TransmitLimitedQueue
+ nDB.RLock()
+ thisNodeNetworks, ok := nDB.networks[nDB.config.NodeID]
+ if ok {
+ // The network may have been removed
+ network, networkOk := thisNodeNetworks[nid]
+ if !networkOk {
+ nDB.RUnlock()
+ return nil
+ }
+
+ broadcastQ = network.tableBroadcasts
+ }
+ nDB.RUnlock()
+
+ // The network may have been removed
+ if broadcastQ == nil {
+ return nil
+ }
+
+ broadcastQ.QueueBroadcast(&tableEventMessage{
+ msg: raw,
+ id: nid,
+ tname: tname,
+ key: key,
+ })
+ return nil
+}
--- /dev/null
+package networkdb
+
+import (
+ "bytes"
+ "context"
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "log"
+ "math/big"
+ rnd "math/rand"
+ "net"
+ "strings"
+ "time"
+
+ "github.com/hashicorp/memberlist"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ reapPeriod = 5 * time.Second
+ rejoinClusterDuration = 10 * time.Second
+ rejoinInterval = 60 * time.Second
+ retryInterval = 1 * time.Second
+ nodeReapInterval = 24 * time.Hour
+ nodeReapPeriod = 2 * time.Hour
+ // considering a cluster with > 20 nodes and a drain speed of 100 msg/s
+ // the following is roughly 1 minute
+ maxQueueLenBroadcastOnSync = 500
+)
+
+type logWriter struct{}
+
+func (l *logWriter) Write(p []byte) (int, error) {
+ str := string(p)
+ str = strings.TrimSuffix(str, "\n")
+
+ switch {
+ case strings.HasPrefix(str, "[WARN] "):
+ str = strings.TrimPrefix(str, "[WARN] ")
+ logrus.Warn(str)
+ case strings.HasPrefix(str, "[DEBUG] "):
+ str = strings.TrimPrefix(str, "[DEBUG] ")
+ logrus.Debug(str)
+ case strings.HasPrefix(str, "[INFO] "):
+ str = strings.TrimPrefix(str, "[INFO] ")
+ logrus.Info(str)
+ case strings.HasPrefix(str, "[ERR] "):
+ str = strings.TrimPrefix(str, "[ERR] ")
+ logrus.Warn(str)
+ }
+
+ return len(p), nil
+}
+
+// SetKey adds a new key to the key ring
+func (nDB *NetworkDB) SetKey(key []byte) {
+ logrus.Debugf("Adding key %.5s", hex.EncodeToString(key))
+ nDB.Lock()
+ defer nDB.Unlock()
+ for _, dbKey := range nDB.config.Keys {
+ if bytes.Equal(key, dbKey) {
+ return
+ }
+ }
+ nDB.config.Keys = append(nDB.config.Keys, key)
+ if nDB.keyring != nil {
+ nDB.keyring.AddKey(key)
+ }
+}
+
+// SetPrimaryKey sets the given key as the primary key. This should have
+// been added apriori through SetKey
+func (nDB *NetworkDB) SetPrimaryKey(key []byte) {
+ logrus.Debugf("Primary Key %.5s", hex.EncodeToString(key))
+ nDB.RLock()
+ defer nDB.RUnlock()
+ for _, dbKey := range nDB.config.Keys {
+ if bytes.Equal(key, dbKey) {
+ if nDB.keyring != nil {
+ nDB.keyring.UseKey(dbKey)
+ }
+ break
+ }
+ }
+}
+
+// RemoveKey removes a key from the key ring. The key being removed
+// can't be the primary key
+func (nDB *NetworkDB) RemoveKey(key []byte) {
+ logrus.Debugf("Remove Key %.5s", hex.EncodeToString(key))
+ nDB.Lock()
+ defer nDB.Unlock()
+ for i, dbKey := range nDB.config.Keys {
+ if bytes.Equal(key, dbKey) {
+ nDB.config.Keys = append(nDB.config.Keys[:i], nDB.config.Keys[i+1:]...)
+ if nDB.keyring != nil {
+ nDB.keyring.RemoveKey(dbKey)
+ }
+ break
+ }
+ }
+}
+
+func (nDB *NetworkDB) clusterInit() error {
+ nDB.lastStatsTimestamp = time.Now()
+ nDB.lastHealthTimestamp = nDB.lastStatsTimestamp
+
+ config := memberlist.DefaultLANConfig()
+ config.Name = nDB.config.NodeID
+ config.BindAddr = nDB.config.BindAddr
+ config.AdvertiseAddr = nDB.config.AdvertiseAddr
+ config.UDPBufferSize = nDB.config.PacketBufferSize
+
+ if nDB.config.BindPort != 0 {
+ config.BindPort = nDB.config.BindPort
+ }
+
+ config.ProtocolVersion = memberlist.ProtocolVersion2Compatible
+ config.Delegate = &delegate{nDB: nDB}
+ config.Events = &eventDelegate{nDB: nDB}
+ // custom logger that does not add time or date, so they are not
+ // duplicated by logrus
+ config.Logger = log.New(&logWriter{}, "", 0)
+
+ var err error
+ if len(nDB.config.Keys) > 0 {
+ for i, key := range nDB.config.Keys {
+ logrus.Debugf("Encryption key %d: %.5s", i+1, hex.EncodeToString(key))
+ }
+ nDB.keyring, err = memberlist.NewKeyring(nDB.config.Keys, nDB.config.Keys[0])
+ if err != nil {
+ return err
+ }
+ config.Keyring = nDB.keyring
+ }
+
+ nDB.networkBroadcasts = &memberlist.TransmitLimitedQueue{
+ NumNodes: func() int {
+ nDB.RLock()
+ num := len(nDB.nodes)
+ nDB.RUnlock()
+ return num
+ },
+ RetransmitMult: config.RetransmitMult,
+ }
+
+ nDB.nodeBroadcasts = &memberlist.TransmitLimitedQueue{
+ NumNodes: func() int {
+ nDB.RLock()
+ num := len(nDB.nodes)
+ nDB.RUnlock()
+ return num
+ },
+ RetransmitMult: config.RetransmitMult,
+ }
+
+ mlist, err := memberlist.Create(config)
+ if err != nil {
+ return fmt.Errorf("failed to create memberlist: %v", err)
+ }
+
+ nDB.ctx, nDB.cancelCtx = context.WithCancel(context.Background())
+ nDB.memberlist = mlist
+
+ for _, trigger := range []struct {
+ interval time.Duration
+ fn func()
+ }{
+ {reapPeriod, nDB.reapState},
+ {config.GossipInterval, nDB.gossip},
+ {config.PushPullInterval, nDB.bulkSyncTables},
+ {retryInterval, nDB.reconnectNode},
+ {nodeReapPeriod, nDB.reapDeadNode},
+ {rejoinInterval, nDB.rejoinClusterBootStrap},
+ } {
+ t := time.NewTicker(trigger.interval)
+ go nDB.triggerFunc(trigger.interval, t.C, trigger.fn)
+ nDB.tickers = append(nDB.tickers, t)
+ }
+
+ return nil
+}
+
+func (nDB *NetworkDB) retryJoin(ctx context.Context, members []string) {
+ t := time.NewTicker(retryInterval)
+ defer t.Stop()
+
+ for {
+ select {
+ case <-t.C:
+ if _, err := nDB.memberlist.Join(members); err != nil {
+ logrus.Errorf("Failed to join memberlist %s on retry: %v", members, err)
+ continue
+ }
+ if err := nDB.sendNodeEvent(NodeEventTypeJoin); err != nil {
+ logrus.Errorf("failed to send node join on retry: %v", err)
+ continue
+ }
+ return
+ case <-ctx.Done():
+ return
+ }
+ }
+
+}
+
+func (nDB *NetworkDB) clusterJoin(members []string) error {
+ mlist := nDB.memberlist
+
+ if _, err := mlist.Join(members); err != nil {
+ // In case of failure, we no longer need to explicitly call retryJoin.
+ // rejoinClusterBootStrap, which runs every minute, will retryJoin for 10sec
+ return fmt.Errorf("could not join node to memberlist: %v", err)
+ }
+
+ if err := nDB.sendNodeEvent(NodeEventTypeJoin); err != nil {
+ return fmt.Errorf("failed to send node join: %v", err)
+ }
+
+ return nil
+}
+
+func (nDB *NetworkDB) clusterLeave() error {
+ mlist := nDB.memberlist
+
+ if err := nDB.sendNodeEvent(NodeEventTypeLeave); err != nil {
+ logrus.Errorf("failed to send node leave: %v", err)
+ }
+
+ if err := mlist.Leave(time.Second); err != nil {
+ return err
+ }
+
+ // cancel the context
+ nDB.cancelCtx()
+
+ for _, t := range nDB.tickers {
+ t.Stop()
+ }
+
+ return mlist.Shutdown()
+}
+
+func (nDB *NetworkDB) triggerFunc(stagger time.Duration, C <-chan time.Time, f func()) {
+ // Use a random stagger to avoid synchronizing
+ randStagger := time.Duration(uint64(rnd.Int63()) % uint64(stagger))
+ select {
+ case <-time.After(randStagger):
+ case <-nDB.ctx.Done():
+ return
+ }
+ for {
+ select {
+ case <-C:
+ f()
+ case <-nDB.ctx.Done():
+ return
+ }
+ }
+}
+
+func (nDB *NetworkDB) reapDeadNode() {
+ nDB.Lock()
+ defer nDB.Unlock()
+ for _, nodeMap := range []map[string]*node{
+ nDB.failedNodes,
+ nDB.leftNodes,
+ } {
+ for id, n := range nodeMap {
+ if n.reapTime > nodeReapPeriod {
+ n.reapTime -= nodeReapPeriod
+ continue
+ }
+ logrus.Debugf("Garbage collect node %v", n.Name)
+ delete(nodeMap, id)
+ }
+ }
+}
+
+// rejoinClusterBootStrap is called periodically to check if all bootStrap nodes are active in the cluster,
+// if not, call the cluster join to merge 2 separate clusters that are formed when all managers
+// stopped/started at the same time
+func (nDB *NetworkDB) rejoinClusterBootStrap() {
+ nDB.RLock()
+ if len(nDB.bootStrapIP) == 0 {
+ nDB.RUnlock()
+ return
+ }
+
+ myself, ok := nDB.nodes[nDB.config.NodeID]
+ if !ok {
+ nDB.RUnlock()
+ logrus.Warnf("rejoinClusterBootstrap unable to find local node info using ID:%v", nDB.config.NodeID)
+ return
+ }
+ bootStrapIPs := make([]string, 0, len(nDB.bootStrapIP))
+ for _, bootIP := range nDB.bootStrapIP {
+ // botostrap IPs are usually IP:port from the Join
+ var bootstrapIP net.IP
+ ipStr, _, err := net.SplitHostPort(bootIP)
+ if err != nil {
+ // try to parse it as an IP with port
+ // Note this seems to be the case for swarm that do not specify any port
+ ipStr = bootIP
+ }
+ bootstrapIP = net.ParseIP(ipStr)
+ if bootstrapIP != nil {
+ for _, node := range nDB.nodes {
+ if node.Addr.Equal(bootstrapIP) && !node.Addr.Equal(myself.Addr) {
+ // One of the bootstrap nodes (and not myself) is part of the cluster, return
+ nDB.RUnlock()
+ return
+ }
+ }
+ bootStrapIPs = append(bootStrapIPs, bootIP)
+ }
+ }
+ nDB.RUnlock()
+ if len(bootStrapIPs) == 0 {
+ // this will also avoid to call the Join with an empty list erasing the current bootstrap ip list
+ logrus.Debug("rejoinClusterBootStrap did not find any valid IP")
+ return
+ }
+ // None of the bootStrap nodes are in the cluster, call memberlist join
+ logrus.Debugf("rejoinClusterBootStrap, calling cluster join with bootStrap %v", bootStrapIPs)
+ ctx, cancel := context.WithTimeout(nDB.ctx, rejoinClusterDuration)
+ defer cancel()
+ nDB.retryJoin(ctx, bootStrapIPs)
+}
+
+func (nDB *NetworkDB) reconnectNode() {
+ nDB.RLock()
+ if len(nDB.failedNodes) == 0 {
+ nDB.RUnlock()
+ return
+ }
+
+ nodes := make([]*node, 0, len(nDB.failedNodes))
+ for _, n := range nDB.failedNodes {
+ nodes = append(nodes, n)
+ }
+ nDB.RUnlock()
+
+ node := nodes[randomOffset(len(nodes))]
+ addr := net.UDPAddr{IP: node.Addr, Port: int(node.Port)}
+
+ if _, err := nDB.memberlist.Join([]string{addr.String()}); err != nil {
+ return
+ }
+
+ if err := nDB.sendNodeEvent(NodeEventTypeJoin); err != nil {
+ return
+ }
+
+ logrus.Debugf("Initiating bulk sync with node %s after reconnect", node.Name)
+ nDB.bulkSync([]string{node.Name}, true)
+}
+
+// For timing the entry deletion in the repaer APIs that doesn't use monotonic clock
+// source (time.Now, Sub etc.) should be avoided. Hence we use reapTime in every
+// entry which is set initially to reapInterval and decremented by reapPeriod every time
+// the reaper runs. NOTE nDB.reapTableEntries updates the reapTime with a readlock. This
+// is safe as long as no other concurrent path touches the reapTime field.
+func (nDB *NetworkDB) reapState() {
+ // The reapTableEntries leverage the presence of the network so garbage collect entries first
+ nDB.reapTableEntries()
+ nDB.reapNetworks()
+}
+
+func (nDB *NetworkDB) reapNetworks() {
+ nDB.Lock()
+ for _, nn := range nDB.networks {
+ for id, n := range nn {
+ if n.leaving {
+ if n.reapTime <= 0 {
+ delete(nn, id)
+ continue
+ }
+ n.reapTime -= reapPeriod
+ }
+ }
+ }
+ nDB.Unlock()
+}
+
+func (nDB *NetworkDB) reapTableEntries() {
+ var nodeNetworks []string
+ // This is best effort, if the list of network changes will be picked up in the next cycle
+ nDB.RLock()
+ for nid := range nDB.networks[nDB.config.NodeID] {
+ nodeNetworks = append(nodeNetworks, nid)
+ }
+ nDB.RUnlock()
+
+ cycleStart := time.Now()
+ // In order to avoid blocking the database for a long time, apply the garbage collection logic by network
+ // The lock is taken at the beginning of the cycle and the deletion is inline
+ for _, nid := range nodeNetworks {
+ nDB.Lock()
+ nDB.indexes[byNetwork].WalkPrefix(fmt.Sprintf("/%s", nid), func(path string, v interface{}) bool {
+ // timeCompensation compensate in case the lock took some time to be released
+ timeCompensation := time.Since(cycleStart)
+ entry, ok := v.(*entry)
+ if !ok || !entry.deleting {
+ return false
+ }
+
+ // In this check we are adding an extra 1 second to guarantee that when the number is truncated to int32 to fit the packet
+ // for the tableEvent the number is always strictly > 1 and never 0
+ if entry.reapTime > reapPeriod+timeCompensation+time.Second {
+ entry.reapTime -= reapPeriod + timeCompensation
+ return false
+ }
+
+ params := strings.Split(path[1:], "/")
+ nid := params[0]
+ tname := params[1]
+ key := params[2]
+
+ okTable, okNetwork := nDB.deleteEntry(nid, tname, key)
+ if !okTable {
+ logrus.Errorf("Table tree delete failed, entry with key:%s does not exists in the table:%s network:%s", key, tname, nid)
+ }
+ if !okNetwork {
+ logrus.Errorf("Network tree delete failed, entry with key:%s does not exists in the network:%s table:%s", key, nid, tname)
+ }
+
+ return false
+ })
+ nDB.Unlock()
+ }
+}
+
+func (nDB *NetworkDB) gossip() {
+ networkNodes := make(map[string][]string)
+ nDB.RLock()
+ thisNodeNetworks := nDB.networks[nDB.config.NodeID]
+ for nid := range thisNodeNetworks {
+ networkNodes[nid] = nDB.networkNodes[nid]
+ }
+ printStats := time.Since(nDB.lastStatsTimestamp) >= nDB.config.StatsPrintPeriod
+ printHealth := time.Since(nDB.lastHealthTimestamp) >= nDB.config.HealthPrintPeriod
+ nDB.RUnlock()
+
+ if printHealth {
+ healthScore := nDB.memberlist.GetHealthScore()
+ if healthScore != 0 {
+ logrus.Warnf("NetworkDB stats %v(%v) - healthscore:%d (connectivity issues)", nDB.config.Hostname, nDB.config.NodeID, healthScore)
+ }
+ nDB.lastHealthTimestamp = time.Now()
+ }
+
+ for nid, nodes := range networkNodes {
+ mNodes := nDB.mRandomNodes(3, nodes)
+ bytesAvail := nDB.config.PacketBufferSize - compoundHeaderOverhead
+
+ nDB.RLock()
+ network, ok := thisNodeNetworks[nid]
+ nDB.RUnlock()
+ if !ok || network == nil {
+ // It is normal for the network to be removed
+ // between the time we collect the network
+ // attachments of this node and processing
+ // them here.
+ continue
+ }
+
+ broadcastQ := network.tableBroadcasts
+
+ if broadcastQ == nil {
+ logrus.Errorf("Invalid broadcastQ encountered while gossiping for network %s", nid)
+ continue
+ }
+
+ msgs := broadcastQ.GetBroadcasts(compoundOverhead, bytesAvail)
+ // Collect stats and print the queue info, note this code is here also to have a view of the queues empty
+ network.qMessagesSent += len(msgs)
+ if printStats {
+ logrus.Infof("NetworkDB stats %v(%v) - netID:%s leaving:%t netPeers:%d entries:%d Queue qLen:%d netMsg/s:%d",
+ nDB.config.Hostname, nDB.config.NodeID,
+ nid, network.leaving, broadcastQ.NumNodes(), network.entriesNumber, broadcastQ.NumQueued(),
+ network.qMessagesSent/int((nDB.config.StatsPrintPeriod/time.Second)))
+ network.qMessagesSent = 0
+ }
+
+ if len(msgs) == 0 {
+ continue
+ }
+
+ // Create a compound message
+ compound := makeCompoundMessage(msgs)
+
+ for _, node := range mNodes {
+ nDB.RLock()
+ mnode := nDB.nodes[node]
+ nDB.RUnlock()
+
+ if mnode == nil {
+ break
+ }
+
+ // Send the compound message
+ if err := nDB.memberlist.SendBestEffort(&mnode.Node, compound); err != nil {
+ logrus.Errorf("Failed to send gossip to %s: %s", mnode.Addr, err)
+ }
+ }
+ }
+ // Reset the stats
+ if printStats {
+ nDB.lastStatsTimestamp = time.Now()
+ }
+}
+
+func (nDB *NetworkDB) bulkSyncTables() {
+ var networks []string
+ nDB.RLock()
+ for nid, network := range nDB.networks[nDB.config.NodeID] {
+ if network.leaving {
+ continue
+ }
+ networks = append(networks, nid)
+ }
+ nDB.RUnlock()
+
+ for {
+ if len(networks) == 0 {
+ break
+ }
+
+ nid := networks[0]
+ networks = networks[1:]
+
+ nDB.RLock()
+ nodes := nDB.networkNodes[nid]
+ nDB.RUnlock()
+
+ // No peer nodes on this network. Move on.
+ if len(nodes) == 0 {
+ continue
+ }
+
+ completed, err := nDB.bulkSync(nodes, false)
+ if err != nil {
+ logrus.Errorf("periodic bulk sync failure for network %s: %v", nid, err)
+ continue
+ }
+
+ // Remove all the networks for which we have
+ // successfully completed bulk sync in this iteration.
+ updatedNetworks := make([]string, 0, len(networks))
+ for _, nid := range networks {
+ var found bool
+ for _, completedNid := range completed {
+ if nid == completedNid {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ updatedNetworks = append(updatedNetworks, nid)
+ }
+ }
+
+ networks = updatedNetworks
+ }
+}
+
+func (nDB *NetworkDB) bulkSync(nodes []string, all bool) ([]string, error) {
+ if !all {
+ // Get 2 random nodes. 2nd node will be tried if the bulk sync to
+ // 1st node fails.
+ nodes = nDB.mRandomNodes(2, nodes)
+ }
+
+ if len(nodes) == 0 {
+ return nil, nil
+ }
+
+ var err error
+ var networks []string
+ var success bool
+ for _, node := range nodes {
+ if node == nDB.config.NodeID {
+ continue
+ }
+ logrus.Debugf("%v(%v): Initiating bulk sync with node %v", nDB.config.Hostname, nDB.config.NodeID, node)
+ networks = nDB.findCommonNetworks(node)
+ err = nDB.bulkSyncNode(networks, node, true)
+ if err != nil {
+ err = fmt.Errorf("bulk sync to node %s failed: %v", node, err)
+ logrus.Warn(err.Error())
+ } else {
+ // bulk sync succeeded
+ success = true
+ // if its periodic bulksync stop after the first successful sync
+ if !all {
+ break
+ }
+ }
+ }
+
+ if success {
+ // if at least one node sync succeeded
+ return networks, nil
+ }
+
+ return nil, err
+}
+
+// Bulk sync all the table entries belonging to a set of networks to a
+// single peer node. It can be unsolicited or can be in response to an
+// unsolicited bulk sync
+func (nDB *NetworkDB) bulkSyncNode(networks []string, node string, unsolicited bool) error {
+ var msgs [][]byte
+
+ var unsolMsg string
+ if unsolicited {
+ unsolMsg = "unsolicited"
+ }
+
+ logrus.Debugf("%v(%v): Initiating %s bulk sync for networks %v with node %s",
+ nDB.config.Hostname, nDB.config.NodeID, unsolMsg, networks, node)
+
+ nDB.RLock()
+ mnode := nDB.nodes[node]
+ if mnode == nil {
+ nDB.RUnlock()
+ return nil
+ }
+
+ for _, nid := range networks {
+ nDB.indexes[byNetwork].WalkPrefix(fmt.Sprintf("/%s", nid), func(path string, v interface{}) bool {
+ entry, ok := v.(*entry)
+ if !ok {
+ return false
+ }
+
+ eType := TableEventTypeCreate
+ if entry.deleting {
+ eType = TableEventTypeDelete
+ }
+
+ params := strings.Split(path[1:], "/")
+ tEvent := TableEvent{
+ Type: eType,
+ LTime: entry.ltime,
+ NodeName: entry.node,
+ NetworkID: nid,
+ TableName: params[1],
+ Key: params[2],
+ Value: entry.value,
+ // The duration in second is a float that below would be truncated
+ ResidualReapTime: int32(entry.reapTime.Seconds()),
+ }
+
+ msg, err := encodeMessage(MessageTypeTableEvent, &tEvent)
+ if err != nil {
+ logrus.Errorf("Encode failure during bulk sync: %#v", tEvent)
+ return false
+ }
+
+ msgs = append(msgs, msg)
+ return false
+ })
+ }
+ nDB.RUnlock()
+
+ // Create a compound message
+ compound := makeCompoundMessage(msgs)
+
+ bsm := BulkSyncMessage{
+ LTime: nDB.tableClock.Time(),
+ Unsolicited: unsolicited,
+ NodeName: nDB.config.NodeID,
+ Networks: networks,
+ Payload: compound,
+ }
+
+ buf, err := encodeMessage(MessageTypeBulkSync, &bsm)
+ if err != nil {
+ return fmt.Errorf("failed to encode bulk sync message: %v", err)
+ }
+
+ nDB.Lock()
+ ch := make(chan struct{})
+ nDB.bulkSyncAckTbl[node] = ch
+ nDB.Unlock()
+
+ err = nDB.memberlist.SendReliable(&mnode.Node, buf)
+ if err != nil {
+ nDB.Lock()
+ delete(nDB.bulkSyncAckTbl, node)
+ nDB.Unlock()
+
+ return fmt.Errorf("failed to send a TCP message during bulk sync: %v", err)
+ }
+
+ // Wait on a response only if it is unsolicited.
+ if unsolicited {
+ startTime := time.Now()
+ t := time.NewTimer(30 * time.Second)
+ select {
+ case <-t.C:
+ logrus.Errorf("Bulk sync to node %s timed out", node)
+ case <-ch:
+ logrus.Debugf("%v(%v): Bulk sync to node %s took %s", nDB.config.Hostname, nDB.config.NodeID, node, time.Since(startTime))
+ }
+ t.Stop()
+ }
+
+ return nil
+}
+
+// Returns a random offset between 0 and n
+func randomOffset(n int) int {
+ if n == 0 {
+ return 0
+ }
+
+ val, err := rand.Int(rand.Reader, big.NewInt(int64(n)))
+ if err != nil {
+ logrus.Errorf("Failed to get a random offset: %v", err)
+ return 0
+ }
+
+ return int(val.Int64())
+}
+
+// mRandomNodes is used to select up to m random nodes. It is possible
+// that less than m nodes are returned.
+func (nDB *NetworkDB) mRandomNodes(m int, nodes []string) []string {
+ n := len(nodes)
+ mNodes := make([]string, 0, m)
+OUTER:
+ // Probe up to 3*n times, with large n this is not necessary
+ // since k << n, but with small n we want search to be
+ // exhaustive
+ for i := 0; i < 3*n && len(mNodes) < m; i++ {
+ // Get random node
+ idx := randomOffset(n)
+ node := nodes[idx]
+
+ if node == nDB.config.NodeID {
+ continue
+ }
+
+ // Check if we have this node already
+ for j := 0; j < len(mNodes); j++ {
+ if node == mNodes[j] {
+ continue OUTER
+ }
+ }
+
+ // Append the node
+ mNodes = append(mNodes, node)
+ }
+
+ return mNodes
+}
--- /dev/null
+package networkdb
+
+import (
+ "net"
+ "time"
+
+ "github.com/gogo/protobuf/proto"
+ "github.com/sirupsen/logrus"
+)
+
+type delegate struct {
+ nDB *NetworkDB
+}
+
+func (d *delegate) NodeMeta(limit int) []byte {
+ return []byte{}
+}
+
+func (nDB *NetworkDB) handleNodeEvent(nEvent *NodeEvent) bool {
+ // Update our local clock if the received messages has newer
+ // time.
+ nDB.networkClock.Witness(nEvent.LTime)
+
+ nDB.Lock()
+ defer nDB.Unlock()
+
+ // check if the node exists
+ n, _, _ := nDB.findNode(nEvent.NodeName)
+ if n == nil {
+ return false
+ }
+
+ // check if the event is fresh
+ if n.ltime >= nEvent.LTime {
+ return false
+ }
+
+ // If we are here means that the event is fresher and the node is known. Update the laport time
+ n.ltime = nEvent.LTime
+
+ // If the node is not known from memberlist we cannot process save any state of it else if it actually
+ // dies we won't receive any notification and we will remain stuck with it
+ if _, ok := nDB.nodes[nEvent.NodeName]; !ok {
+ logrus.Errorf("node: %s is unknown to memberlist", nEvent.NodeName)
+ return false
+ }
+
+ switch nEvent.Type {
+ case NodeEventTypeJoin:
+ moved, err := nDB.changeNodeState(n.Name, nodeActiveState)
+ if err != nil {
+ logrus.WithError(err).Error("unable to find the node to move")
+ return false
+ }
+ if moved {
+ logrus.Infof("%v(%v): Node join event for %s/%s", nDB.config.Hostname, nDB.config.NodeID, n.Name, n.Addr)
+ }
+ return moved
+ case NodeEventTypeLeave:
+ moved, err := nDB.changeNodeState(n.Name, nodeLeftState)
+ if err != nil {
+ logrus.WithError(err).Error("unable to find the node to move")
+ return false
+ }
+ if moved {
+ logrus.Infof("%v(%v): Node leave event for %s/%s", nDB.config.Hostname, nDB.config.NodeID, n.Name, n.Addr)
+ }
+ return moved
+ }
+
+ return false
+}
+
+func (nDB *NetworkDB) handleNetworkEvent(nEvent *NetworkEvent) bool {
+ // Update our local clock if the received messages has newer
+ // time.
+ nDB.networkClock.Witness(nEvent.LTime)
+
+ nDB.Lock()
+ defer nDB.Unlock()
+
+ if nEvent.NodeName == nDB.config.NodeID {
+ return false
+ }
+
+ nodeNetworks, ok := nDB.networks[nEvent.NodeName]
+ if !ok {
+ // We haven't heard about this node at all. Ignore the leave
+ if nEvent.Type == NetworkEventTypeLeave {
+ return false
+ }
+
+ nodeNetworks = make(map[string]*network)
+ nDB.networks[nEvent.NodeName] = nodeNetworks
+ }
+
+ if n, ok := nodeNetworks[nEvent.NetworkID]; ok {
+ // We have the latest state. Ignore the event
+ // since it is stale.
+ if n.ltime >= nEvent.LTime {
+ return false
+ }
+
+ n.ltime = nEvent.LTime
+ n.leaving = nEvent.Type == NetworkEventTypeLeave
+ if n.leaving {
+ n.reapTime = nDB.config.reapNetworkInterval
+
+ // The remote node is leaving the network, but not the gossip cluster.
+ // Mark all its entries in deleted state, this will guarantee that
+ // if some node bulk sync with us, the deleted state of
+ // these entries will be propagated.
+ nDB.deleteNodeNetworkEntries(nEvent.NetworkID, nEvent.NodeName)
+ }
+
+ if nEvent.Type == NetworkEventTypeLeave {
+ nDB.deleteNetworkNode(nEvent.NetworkID, nEvent.NodeName)
+ } else {
+ nDB.addNetworkNode(nEvent.NetworkID, nEvent.NodeName)
+ }
+
+ return true
+ }
+
+ if nEvent.Type == NetworkEventTypeLeave {
+ return false
+ }
+
+ // If the node is not known from memberlist we cannot process save any state of it else if it actually
+ // dies we won't receive any notification and we will remain stuck with it
+ if _, ok := nDB.nodes[nEvent.NodeName]; !ok {
+ return false
+ }
+
+ // This remote network join is being seen the first time.
+ nodeNetworks[nEvent.NetworkID] = &network{
+ id: nEvent.NetworkID,
+ ltime: nEvent.LTime,
+ }
+
+ nDB.addNetworkNode(nEvent.NetworkID, nEvent.NodeName)
+ return true
+}
+
+func (nDB *NetworkDB) handleTableEvent(tEvent *TableEvent, isBulkSync bool) bool {
+ // Update our local clock if the received messages has newer time.
+ nDB.tableClock.Witness(tEvent.LTime)
+
+ // Ignore the table events for networks that are in the process of going away
+ nDB.RLock()
+ networks := nDB.networks[nDB.config.NodeID]
+ network, ok := networks[tEvent.NetworkID]
+ // Check if the owner of the event is still part of the network
+ nodes := nDB.networkNodes[tEvent.NetworkID]
+ var nodePresent bool
+ for _, node := range nodes {
+ if node == tEvent.NodeName {
+ nodePresent = true
+ break
+ }
+ }
+ nDB.RUnlock()
+
+ if !ok || network.leaving || !nodePresent {
+ // I'm out of the network OR the event owner is not anymore part of the network so do not propagate
+ return false
+ }
+
+ nDB.Lock()
+ e, err := nDB.getEntry(tEvent.TableName, tEvent.NetworkID, tEvent.Key)
+ if err == nil {
+ // We have the latest state. Ignore the event
+ // since it is stale.
+ if e.ltime >= tEvent.LTime {
+ nDB.Unlock()
+ return false
+ }
+ } else if tEvent.Type == TableEventTypeDelete && !isBulkSync {
+ nDB.Unlock()
+ // We don't know the entry, the entry is being deleted and the message is an async message
+ // In this case the safest approach is to ignore it, it is possible that the queue grew so much to
+ // exceed the garbage collection time (the residual reap time that is in the message is not being
+ // updated, to avoid inserting too many messages in the queue).
+ // Instead the messages coming from TCP bulk sync are safe with the latest value for the garbage collection time
+ return false
+ }
+
+ e = &entry{
+ ltime: tEvent.LTime,
+ node: tEvent.NodeName,
+ value: tEvent.Value,
+ deleting: tEvent.Type == TableEventTypeDelete,
+ reapTime: time.Duration(tEvent.ResidualReapTime) * time.Second,
+ }
+
+ // All the entries marked for deletion should have a reapTime set greater than 0
+ // This case can happen if the cluster is running different versions of the engine where the old version does not have the
+ // field. If that is not the case, this can be a BUG
+ if e.deleting && e.reapTime == 0 {
+ logrus.Warnf("%v(%v) handleTableEvent object %+v has a 0 reapTime, is the cluster running the same docker engine version?",
+ nDB.config.Hostname, nDB.config.NodeID, tEvent)
+ e.reapTime = nDB.config.reapEntryInterval
+ }
+ nDB.createOrUpdateEntry(tEvent.NetworkID, tEvent.TableName, tEvent.Key, e)
+ nDB.Unlock()
+
+ if err != nil && tEvent.Type == TableEventTypeDelete {
+ // Again we don't know the entry but this is coming from a TCP sync so the message body is up to date.
+ // We had saved the state so to speed up convergence and be able to avoid accepting create events.
+ // Now we will rebroadcast the message if 2 conditions are met:
+ // 1) we had already synced this network (during the network join)
+ // 2) the residual reapTime is higher than 1/6 of the total reapTime.
+ // If the residual reapTime is lower or equal to 1/6 of the total reapTime don't bother broadcasting it around
+ // most likely the cluster is already aware of it
+ // This also reduce the possibility that deletion of entries close to their garbage collection ends up circuling around
+ // forever
+ //logrus.Infof("exiting on delete not knowing the obj with rebroadcast:%t", network.inSync)
+ return network.inSync && e.reapTime > nDB.config.reapEntryInterval/6
+ }
+
+ var op opType
+ switch tEvent.Type {
+ case TableEventTypeCreate:
+ op = opCreate
+ case TableEventTypeUpdate:
+ op = opUpdate
+ case TableEventTypeDelete:
+ op = opDelete
+ }
+
+ nDB.broadcaster.Write(makeEvent(op, tEvent.TableName, tEvent.NetworkID, tEvent.Key, tEvent.Value))
+ return network.inSync
+}
+
+func (nDB *NetworkDB) handleCompound(buf []byte, isBulkSync bool) {
+ // Decode the parts
+ parts, err := decodeCompoundMessage(buf)
+ if err != nil {
+ logrus.Errorf("Failed to decode compound request: %v", err)
+ return
+ }
+
+ // Handle each message
+ for _, part := range parts {
+ nDB.handleMessage(part, isBulkSync)
+ }
+}
+
+func (nDB *NetworkDB) handleTableMessage(buf []byte, isBulkSync bool) {
+ var tEvent TableEvent
+ if err := proto.Unmarshal(buf, &tEvent); err != nil {
+ logrus.Errorf("Error decoding table event message: %v", err)
+ return
+ }
+
+ // Ignore messages that this node generated.
+ if tEvent.NodeName == nDB.config.NodeID {
+ return
+ }
+
+ if rebroadcast := nDB.handleTableEvent(&tEvent, isBulkSync); rebroadcast {
+ var err error
+ buf, err = encodeRawMessage(MessageTypeTableEvent, buf)
+ if err != nil {
+ logrus.Errorf("Error marshalling gossip message for network event rebroadcast: %v", err)
+ return
+ }
+
+ nDB.RLock()
+ n, ok := nDB.networks[nDB.config.NodeID][tEvent.NetworkID]
+ nDB.RUnlock()
+
+ // if the network is not there anymore, OR we are leaving the network OR the broadcast queue is not present
+ if !ok || n.leaving || n.tableBroadcasts == nil {
+ return
+ }
+
+ // if the queue is over the threshold, avoid distributing information coming from TCP sync
+ if isBulkSync && n.tableBroadcasts.NumQueued() > maxQueueLenBroadcastOnSync {
+ return
+ }
+
+ n.tableBroadcasts.QueueBroadcast(&tableEventMessage{
+ msg: buf,
+ id: tEvent.NetworkID,
+ tname: tEvent.TableName,
+ key: tEvent.Key,
+ })
+ }
+}
+
+func (nDB *NetworkDB) handleNodeMessage(buf []byte) {
+ var nEvent NodeEvent
+ if err := proto.Unmarshal(buf, &nEvent); err != nil {
+ logrus.Errorf("Error decoding node event message: %v", err)
+ return
+ }
+
+ if rebroadcast := nDB.handleNodeEvent(&nEvent); rebroadcast {
+ var err error
+ buf, err = encodeRawMessage(MessageTypeNodeEvent, buf)
+ if err != nil {
+ logrus.Errorf("Error marshalling gossip message for node event rebroadcast: %v", err)
+ return
+ }
+
+ nDB.nodeBroadcasts.QueueBroadcast(&nodeEventMessage{
+ msg: buf,
+ })
+ }
+}
+
+func (nDB *NetworkDB) handleNetworkMessage(buf []byte) {
+ var nEvent NetworkEvent
+ if err := proto.Unmarshal(buf, &nEvent); err != nil {
+ logrus.Errorf("Error decoding network event message: %v", err)
+ return
+ }
+
+ if rebroadcast := nDB.handleNetworkEvent(&nEvent); rebroadcast {
+ var err error
+ buf, err = encodeRawMessage(MessageTypeNetworkEvent, buf)
+ if err != nil {
+ logrus.Errorf("Error marshalling gossip message for network event rebroadcast: %v", err)
+ return
+ }
+
+ nDB.networkBroadcasts.QueueBroadcast(&networkEventMessage{
+ msg: buf,
+ id: nEvent.NetworkID,
+ node: nEvent.NodeName,
+ })
+ }
+}
+
+func (nDB *NetworkDB) handleBulkSync(buf []byte) {
+ var bsm BulkSyncMessage
+ if err := proto.Unmarshal(buf, &bsm); err != nil {
+ logrus.Errorf("Error decoding bulk sync message: %v", err)
+ return
+ }
+
+ if bsm.LTime > 0 {
+ nDB.tableClock.Witness(bsm.LTime)
+ }
+
+ nDB.handleMessage(bsm.Payload, true)
+
+ // Don't respond to a bulk sync which was not unsolicited
+ if !bsm.Unsolicited {
+ nDB.Lock()
+ ch, ok := nDB.bulkSyncAckTbl[bsm.NodeName]
+ if ok {
+ close(ch)
+ delete(nDB.bulkSyncAckTbl, bsm.NodeName)
+ }
+ nDB.Unlock()
+
+ return
+ }
+
+ var nodeAddr net.IP
+ nDB.RLock()
+ if node, ok := nDB.nodes[bsm.NodeName]; ok {
+ nodeAddr = node.Addr
+ }
+ nDB.RUnlock()
+
+ if err := nDB.bulkSyncNode(bsm.Networks, bsm.NodeName, false); err != nil {
+ logrus.Errorf("Error in responding to bulk sync from node %s: %v", nodeAddr, err)
+ }
+}
+
+func (nDB *NetworkDB) handleMessage(buf []byte, isBulkSync bool) {
+ mType, data, err := decodeMessage(buf)
+ if err != nil {
+ logrus.Errorf("Error decoding gossip message to get message type: %v", err)
+ return
+ }
+
+ switch mType {
+ case MessageTypeNodeEvent:
+ nDB.handleNodeMessage(data)
+ case MessageTypeNetworkEvent:
+ nDB.handleNetworkMessage(data)
+ case MessageTypeTableEvent:
+ nDB.handleTableMessage(data, isBulkSync)
+ case MessageTypeBulkSync:
+ nDB.handleBulkSync(data)
+ case MessageTypeCompound:
+ nDB.handleCompound(data, isBulkSync)
+ default:
+ logrus.Errorf("%v(%v): unknown message type %d", nDB.config.Hostname, nDB.config.NodeID, mType)
+ }
+}
+
+func (d *delegate) NotifyMsg(buf []byte) {
+ if len(buf) == 0 {
+ return
+ }
+
+ d.nDB.handleMessage(buf, false)
+}
+
+func (d *delegate) GetBroadcasts(overhead, limit int) [][]byte {
+ msgs := d.nDB.networkBroadcasts.GetBroadcasts(overhead, limit)
+ msgs = append(msgs, d.nDB.nodeBroadcasts.GetBroadcasts(overhead, limit)...)
+ return msgs
+}
+
+func (d *delegate) LocalState(join bool) []byte {
+ if join {
+ // Update all the local node/network state to a new time to
+ // force update on the node we are trying to rejoin, just in
+ // case that node has these in leaving state still. This is
+ // facilitate fast convergence after recovering from a gossip
+ // failure.
+ d.nDB.updateLocalNetworkTime()
+ }
+
+ d.nDB.RLock()
+ defer d.nDB.RUnlock()
+
+ pp := NetworkPushPull{
+ LTime: d.nDB.networkClock.Time(),
+ NodeName: d.nDB.config.NodeID,
+ }
+
+ for name, nn := range d.nDB.networks {
+ for _, n := range nn {
+ pp.Networks = append(pp.Networks, &NetworkEntry{
+ LTime: n.ltime,
+ NetworkID: n.id,
+ NodeName: name,
+ Leaving: n.leaving,
+ })
+ }
+ }
+
+ buf, err := encodeMessage(MessageTypePushPull, &pp)
+ if err != nil {
+ logrus.Errorf("Failed to encode local network state: %v", err)
+ return nil
+ }
+
+ return buf
+}
+
+func (d *delegate) MergeRemoteState(buf []byte, isJoin bool) {
+ if len(buf) == 0 {
+ logrus.Error("zero byte remote network state received")
+ return
+ }
+
+ var gMsg GossipMessage
+ err := proto.Unmarshal(buf, &gMsg)
+ if err != nil {
+ logrus.Errorf("Error unmarshalling push pull message: %v", err)
+ return
+ }
+
+ if gMsg.Type != MessageTypePushPull {
+ logrus.Errorf("Invalid message type %v received from remote", buf[0])
+ }
+
+ pp := NetworkPushPull{}
+ if err := proto.Unmarshal(gMsg.Data, &pp); err != nil {
+ logrus.Errorf("Failed to decode remote network state: %v", err)
+ return
+ }
+
+ nodeEvent := &NodeEvent{
+ LTime: pp.LTime,
+ NodeName: pp.NodeName,
+ Type: NodeEventTypeJoin,
+ }
+ d.nDB.handleNodeEvent(nodeEvent)
+
+ for _, n := range pp.Networks {
+ nEvent := &NetworkEvent{
+ LTime: n.LTime,
+ NodeName: n.NodeName,
+ NetworkID: n.NetworkID,
+ Type: NetworkEventTypeJoin,
+ }
+
+ if n.Leaving {
+ nEvent.Type = NetworkEventTypeLeave
+ }
+
+ d.nDB.handleNetworkEvent(nEvent)
+ }
+
+}
--- /dev/null
+package networkdb
+
+import (
+ "encoding/json"
+ "net"
+
+ "github.com/hashicorp/memberlist"
+ "github.com/sirupsen/logrus"
+)
+
+type eventDelegate struct {
+ nDB *NetworkDB
+}
+
+func (e *eventDelegate) broadcastNodeEvent(addr net.IP, op opType) {
+ value, err := json.Marshal(&NodeAddr{addr})
+ if err == nil {
+ e.nDB.broadcaster.Write(makeEvent(op, NodeTable, "", "", value))
+ } else {
+ logrus.Errorf("Error marshalling node broadcast event %s", addr.String())
+ }
+}
+
+func (e *eventDelegate) NotifyJoin(mn *memberlist.Node) {
+ logrus.Infof("Node %s/%s, joined gossip cluster", mn.Name, mn.Addr)
+ e.broadcastNodeEvent(mn.Addr, opCreate)
+ e.nDB.Lock()
+ defer e.nDB.Unlock()
+
+ // In case the node is rejoining after a failure or leave,
+ // just add the node back to active
+ if moved, _ := e.nDB.changeNodeState(mn.Name, nodeActiveState); moved {
+ return
+ }
+
+ // Every node has a unique ID
+ // Check on the base of the IP address if the new node that joined is actually a new incarnation of a previous
+ // failed or shutdown one
+ e.nDB.purgeReincarnation(mn)
+
+ e.nDB.nodes[mn.Name] = &node{Node: *mn}
+ logrus.Infof("Node %s/%s, added to nodes list", mn.Name, mn.Addr)
+}
+
+func (e *eventDelegate) NotifyLeave(mn *memberlist.Node) {
+ logrus.Infof("Node %s/%s, left gossip cluster", mn.Name, mn.Addr)
+ e.broadcastNodeEvent(mn.Addr, opDelete)
+
+ e.nDB.Lock()
+ defer e.nDB.Unlock()
+
+ n, currState, _ := e.nDB.findNode(mn.Name)
+ if n == nil {
+ logrus.Errorf("Node %s/%s not found in the node lists", mn.Name, mn.Addr)
+ return
+ }
+ // if the node was active means that did not send the leave cluster message, so it's probable that
+ // failed. Else would be already in the left list so nothing else has to be done
+ if currState == nodeActiveState {
+ moved, err := e.nDB.changeNodeState(mn.Name, nodeFailedState)
+ if err != nil {
+ logrus.WithError(err).Errorf("impossible condition, node %s/%s not present in the list", mn.Name, mn.Addr)
+ return
+ }
+ if moved {
+ logrus.Infof("Node %s/%s, added to failed nodes list", mn.Name, mn.Addr)
+ }
+ }
+}
+
+func (e *eventDelegate) NotifyUpdate(n *memberlist.Node) {
+}
--- /dev/null
+package networkdb
+
+import "github.com/gogo/protobuf/proto"
+
+const (
+ // Compound message header overhead 1 byte(message type) + 4
+ // bytes (num messages)
+ compoundHeaderOverhead = 5
+
+ // Overhead for each embedded message in a compound message 4
+ // bytes (len of embedded message)
+ compoundOverhead = 4
+)
+
+func encodeRawMessage(t MessageType, raw []byte) ([]byte, error) {
+ gMsg := GossipMessage{
+ Type: t,
+ Data: raw,
+ }
+
+ buf, err := proto.Marshal(&gMsg)
+ if err != nil {
+ return nil, err
+ }
+
+ return buf, nil
+}
+
+func encodeMessage(t MessageType, msg interface{}) ([]byte, error) {
+ buf, err := proto.Marshal(msg.(proto.Message))
+ if err != nil {
+ return nil, err
+ }
+
+ buf, err = encodeRawMessage(t, buf)
+ if err != nil {
+ return nil, err
+ }
+
+ return buf, nil
+}
+
+func decodeMessage(buf []byte) (MessageType, []byte, error) {
+ var gMsg GossipMessage
+
+ err := proto.Unmarshal(buf, &gMsg)
+ if err != nil {
+ return MessageTypeInvalid, nil, err
+ }
+
+ return gMsg.Type, gMsg.Data, nil
+}
+
+// makeCompoundMessage takes a list of messages and generates
+// a single compound message containing all of them
+func makeCompoundMessage(msgs [][]byte) []byte {
+ cMsg := CompoundMessage{}
+
+ cMsg.Messages = make([]*CompoundMessage_SimpleMessage, 0, len(msgs))
+ for _, m := range msgs {
+ cMsg.Messages = append(cMsg.Messages, &CompoundMessage_SimpleMessage{
+ Payload: m,
+ })
+ }
+
+ buf, err := proto.Marshal(&cMsg)
+ if err != nil {
+ return nil
+ }
+
+ gMsg := GossipMessage{
+ Type: MessageTypeCompound,
+ Data: buf,
+ }
+
+ buf, err = proto.Marshal(&gMsg)
+ if err != nil {
+ return nil
+ }
+
+ return buf
+}
+
+// decodeCompoundMessage splits a compound message and returns
+// the slices of individual messages. Returns any potential error.
+func decodeCompoundMessage(buf []byte) ([][]byte, error) {
+ var cMsg CompoundMessage
+ if err := proto.Unmarshal(buf, &cMsg); err != nil {
+ return nil, err
+ }
+
+ parts := make([][]byte, 0, len(cMsg.Messages))
+ for _, m := range cMsg.Messages {
+ parts = append(parts, m.Payload)
+ }
+
+ return parts, nil
+}
--- /dev/null
+package networkdb
+
+//go:generate protoc -I.:../vendor/github.com/gogo/protobuf --gogo_out=import_path=github.com/docker/libnetwork/networkdb,Mgogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto:. networkdb.proto
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/armon/go-radix"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/go-events"
+ "github.com/docker/libnetwork/types"
+ "github.com/hashicorp/memberlist"
+ "github.com/hashicorp/serf/serf"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ byTable int = 1 + iota
+ byNetwork
+)
+
+// NetworkDB instance drives the networkdb cluster and acts the broker
+// for cluster-scoped and network-scoped gossip and watches.
+type NetworkDB struct {
+ // The clocks MUST be the first things
+ // in this struct due to Golang issue #599.
+
+ // Global lamport clock for node network attach events.
+ networkClock serf.LamportClock
+
+ // Global lamport clock for table events.
+ tableClock serf.LamportClock
+
+ sync.RWMutex
+
+ // NetworkDB configuration.
+ config *Config
+
+ // All the tree index (byTable, byNetwork) that we maintain
+ // the db.
+ indexes map[int]*radix.Tree
+
+ // Memberlist we use to drive the cluster.
+ memberlist *memberlist.Memberlist
+
+ // List of all peer nodes in the cluster not-limited to any
+ // network.
+ nodes map[string]*node
+
+ // List of all peer nodes which have failed
+ failedNodes map[string]*node
+
+ // List of all peer nodes which have left
+ leftNodes map[string]*node
+
+ // A multi-dimensional map of network/node attachments. The
+ // first key is a node name and the second key is a network ID
+ // for the network that node is participating in.
+ networks map[string]map[string]*network
+
+ // A map of nodes which are participating in a given
+ // network. The key is a network ID.
+ networkNodes map[string][]string
+
+ // A table of ack channels for every node from which we are
+ // waiting for an ack.
+ bulkSyncAckTbl map[string]chan struct{}
+
+ // Broadcast queue for network event gossip.
+ networkBroadcasts *memberlist.TransmitLimitedQueue
+
+ // Broadcast queue for node event gossip.
+ nodeBroadcasts *memberlist.TransmitLimitedQueue
+
+ // A central context to stop all go routines running on
+ // behalf of the NetworkDB instance.
+ ctx context.Context
+ cancelCtx context.CancelFunc
+
+ // A central broadcaster for all local watchers watching table
+ // events.
+ broadcaster *events.Broadcaster
+
+ // List of all tickers which needed to be stopped when
+ // cleaning up.
+ tickers []*time.Ticker
+
+ // Reference to the memberlist's keyring to add & remove keys
+ keyring *memberlist.Keyring
+
+ // bootStrapIP is the list of IPs that can be used to bootstrap
+ // the gossip.
+ bootStrapIP []string
+
+ // lastStatsTimestamp is the last timestamp when the stats got printed
+ lastStatsTimestamp time.Time
+
+ // lastHealthTimestamp is the last timestamp when the health score got printed
+ lastHealthTimestamp time.Time
+}
+
+// PeerInfo represents the peer (gossip cluster) nodes of a network
+type PeerInfo struct {
+ Name string
+ IP string
+}
+
+// PeerClusterInfo represents the peer (gossip cluster) nodes
+type PeerClusterInfo struct {
+ PeerInfo
+}
+
+type node struct {
+ memberlist.Node
+ ltime serf.LamportTime
+ // Number of hours left before the reaper removes the node
+ reapTime time.Duration
+}
+
+// network describes the node/network attachment.
+type network struct {
+ // Network ID
+ id string
+
+ // Lamport time for the latest state of the entry.
+ ltime serf.LamportTime
+
+ // Gets set to true after the first bulk sync happens
+ inSync bool
+
+ // Node leave is in progress.
+ leaving bool
+
+ // Number of seconds still left before a deleted network entry gets
+ // removed from networkDB
+ reapTime time.Duration
+
+ // The broadcast queue for table event gossip. This is only
+ // initialized for this node's network attachment entries.
+ tableBroadcasts *memberlist.TransmitLimitedQueue
+
+ // Number of gossip messages sent related to this network during the last stats collection period
+ qMessagesSent int
+
+ // Number of entries on the network. This value is the sum of all the entries of all the tables of a specific network.
+ // Its use is for statistics purposes. It keep tracks of database size and is printed per network every StatsPrintPeriod
+ // interval
+ entriesNumber int
+}
+
+// Config represents the configuration of the networkdb instance and
+// can be passed by the caller.
+type Config struct {
+ // NodeID is the node unique identifier of the node when is part of the cluster
+ NodeID string
+
+ // Hostname is the node hostname.
+ Hostname string
+
+ // BindAddr is the IP on which networkdb listens. It can be
+ // 0.0.0.0 to listen on all addresses on the host.
+ BindAddr string
+
+ // AdvertiseAddr is the node's IP address that we advertise for
+ // cluster communication.
+ AdvertiseAddr string
+
+ // BindPort is the local node's port to which we bind to for
+ // cluster communication.
+ BindPort int
+
+ // Keys to be added to the Keyring of the memberlist. Key at index
+ // 0 is the primary key
+ Keys [][]byte
+
+ // PacketBufferSize is the maximum number of bytes that memberlist will
+ // put in a packet (this will be for UDP packets by default with a NetTransport).
+ // A safe value for this is typically 1400 bytes (which is the default). However,
+ // depending on your network's MTU (Maximum Transmission Unit) you may
+ // be able to increase this to get more content into each gossip packet.
+ PacketBufferSize int
+
+ // reapEntryInterval duration of a deleted entry before being garbage collected
+ reapEntryInterval time.Duration
+
+ // reapNetworkInterval duration of a delted network before being garbage collected
+ // NOTE this MUST always be higher than reapEntryInterval
+ reapNetworkInterval time.Duration
+
+ // StatsPrintPeriod the period to use to print queue stats
+ // Default is 5min
+ StatsPrintPeriod time.Duration
+
+ // HealthPrintPeriod the period to use to print the health score
+ // Default is 1min
+ HealthPrintPeriod time.Duration
+}
+
+// entry defines a table entry
+type entry struct {
+ // node from which this entry was learned.
+ node string
+
+ // Lamport time for the most recent update to the entry
+ ltime serf.LamportTime
+
+ // Opaque value store in the entry
+ value []byte
+
+ // Deleting the entry is in progress. All entries linger in
+ // the cluster for certain amount of time after deletion.
+ deleting bool
+
+ // Number of seconds still left before a deleted table entry gets
+ // removed from networkDB
+ reapTime time.Duration
+}
+
+// DefaultConfig returns a NetworkDB config with default values
+func DefaultConfig() *Config {
+ hostname, _ := os.Hostname()
+ return &Config{
+ NodeID: stringid.TruncateID(stringid.GenerateRandomID()),
+ Hostname: hostname,
+ BindAddr: "0.0.0.0",
+ PacketBufferSize: 1400,
+ StatsPrintPeriod: 5 * time.Minute,
+ HealthPrintPeriod: 1 * time.Minute,
+ reapEntryInterval: 30 * time.Minute,
+ }
+}
+
+// New creates a new instance of NetworkDB using the Config passed by
+// the caller.
+func New(c *Config) (*NetworkDB, error) {
+ // The garbage collection logic for entries leverage the presence of the network.
+ // For this reason the expiration time of the network is put slightly higher than the entry expiration so that
+ // there is at least 5 extra cycle to make sure that all the entries are properly deleted before deleting the network.
+ c.reapNetworkInterval = c.reapEntryInterval + 5*reapPeriod
+
+ nDB := &NetworkDB{
+ config: c,
+ indexes: make(map[int]*radix.Tree),
+ networks: make(map[string]map[string]*network),
+ nodes: make(map[string]*node),
+ failedNodes: make(map[string]*node),
+ leftNodes: make(map[string]*node),
+ networkNodes: make(map[string][]string),
+ bulkSyncAckTbl: make(map[string]chan struct{}),
+ broadcaster: events.NewBroadcaster(),
+ }
+
+ nDB.indexes[byTable] = radix.New()
+ nDB.indexes[byNetwork] = radix.New()
+
+ logrus.Infof("New memberlist node - Node:%v will use memberlist nodeID:%v with config:%+v", c.Hostname, c.NodeID, c)
+ if err := nDB.clusterInit(); err != nil {
+ return nil, err
+ }
+
+ return nDB, nil
+}
+
+// Join joins this NetworkDB instance with a list of peer NetworkDB
+// instances passed by the caller in the form of addr:port
+func (nDB *NetworkDB) Join(members []string) error {
+ nDB.Lock()
+ nDB.bootStrapIP = append([]string(nil), members...)
+ logrus.Infof("The new bootstrap node list is:%v", nDB.bootStrapIP)
+ nDB.Unlock()
+ return nDB.clusterJoin(members)
+}
+
+// Close destroys this NetworkDB instance by leave the cluster,
+// stopping timers, canceling goroutines etc.
+func (nDB *NetworkDB) Close() {
+ if err := nDB.clusterLeave(); err != nil {
+ logrus.Errorf("%v(%v) Could not close DB: %v", nDB.config.Hostname, nDB.config.NodeID, err)
+ }
+
+ //Avoid (*Broadcaster).run goroutine leak
+ nDB.broadcaster.Close()
+}
+
+// ClusterPeers returns all the gossip cluster peers.
+func (nDB *NetworkDB) ClusterPeers() []PeerInfo {
+ nDB.RLock()
+ defer nDB.RUnlock()
+ peers := make([]PeerInfo, 0, len(nDB.nodes))
+ for _, node := range nDB.nodes {
+ peers = append(peers, PeerInfo{
+ Name: node.Name,
+ IP: node.Node.Addr.String(),
+ })
+ }
+ return peers
+}
+
+// Peers returns the gossip peers for a given network.
+func (nDB *NetworkDB) Peers(nid string) []PeerInfo {
+ nDB.RLock()
+ defer nDB.RUnlock()
+ peers := make([]PeerInfo, 0, len(nDB.networkNodes[nid]))
+ for _, nodeName := range nDB.networkNodes[nid] {
+ if node, ok := nDB.nodes[nodeName]; ok {
+ peers = append(peers, PeerInfo{
+ Name: node.Name,
+ IP: node.Addr.String(),
+ })
+ } else {
+ // Added for testing purposes, this condition should never happen else mean that the network list
+ // is out of sync with the node list
+ peers = append(peers, PeerInfo{Name: nodeName, IP: "unknown"})
+ }
+ }
+ return peers
+}
+
+// GetEntry retrieves the value of a table entry in a given (network,
+// table, key) tuple
+func (nDB *NetworkDB) GetEntry(tname, nid, key string) ([]byte, error) {
+ nDB.RLock()
+ defer nDB.RUnlock()
+ entry, err := nDB.getEntry(tname, nid, key)
+ if err != nil {
+ return nil, err
+ }
+ if entry != nil && entry.deleting {
+ return nil, types.NotFoundErrorf("entry in table %s network id %s and key %s deleted and pending garbage collection", tname, nid, key)
+ }
+
+ return entry.value, nil
+}
+
+func (nDB *NetworkDB) getEntry(tname, nid, key string) (*entry, error) {
+ e, ok := nDB.indexes[byTable].Get(fmt.Sprintf("/%s/%s/%s", tname, nid, key))
+ if !ok {
+ return nil, types.NotFoundErrorf("could not get entry in table %s with network id %s and key %s", tname, nid, key)
+ }
+
+ return e.(*entry), nil
+}
+
+// CreateEntry creates a table entry in NetworkDB for given (network,
+// table, key) tuple and if the NetworkDB is part of the cluster
+// propagates this event to the cluster. It is an error to create an
+// entry for the same tuple for which there is already an existing
+// entry unless the current entry is deleting state.
+func (nDB *NetworkDB) CreateEntry(tname, nid, key string, value []byte) error {
+ nDB.Lock()
+ oldEntry, err := nDB.getEntry(tname, nid, key)
+ if err == nil || (oldEntry != nil && !oldEntry.deleting) {
+ nDB.Unlock()
+ return fmt.Errorf("cannot create entry in table %s with network id %s and key %s, already exists", tname, nid, key)
+ }
+
+ entry := &entry{
+ ltime: nDB.tableClock.Increment(),
+ node: nDB.config.NodeID,
+ value: value,
+ }
+
+ nDB.createOrUpdateEntry(nid, tname, key, entry)
+ nDB.Unlock()
+
+ if err := nDB.sendTableEvent(TableEventTypeCreate, nid, tname, key, entry); err != nil {
+ return fmt.Errorf("cannot send create event for table %s, %v", tname, err)
+ }
+
+ return nil
+}
+
+// UpdateEntry updates a table entry in NetworkDB for given (network,
+// table, key) tuple and if the NetworkDB is part of the cluster
+// propagates this event to the cluster. It is an error to update a
+// non-existent entry.
+func (nDB *NetworkDB) UpdateEntry(tname, nid, key string, value []byte) error {
+ nDB.Lock()
+ if _, err := nDB.getEntry(tname, nid, key); err != nil {
+ nDB.Unlock()
+ return fmt.Errorf("cannot update entry as the entry in table %s with network id %s and key %s does not exist", tname, nid, key)
+ }
+
+ entry := &entry{
+ ltime: nDB.tableClock.Increment(),
+ node: nDB.config.NodeID,
+ value: value,
+ }
+
+ nDB.createOrUpdateEntry(nid, tname, key, entry)
+ nDB.Unlock()
+
+ if err := nDB.sendTableEvent(TableEventTypeUpdate, nid, tname, key, entry); err != nil {
+ return fmt.Errorf("cannot send table update event: %v", err)
+ }
+
+ return nil
+}
+
+// TableElem elem
+type TableElem struct {
+ Value []byte
+ owner string
+}
+
+// GetTableByNetwork walks the networkdb by the give table and network id and
+// returns a map of keys and values
+func (nDB *NetworkDB) GetTableByNetwork(tname, nid string) map[string]*TableElem {
+ entries := make(map[string]*TableElem)
+ nDB.indexes[byTable].WalkPrefix(fmt.Sprintf("/%s/%s", tname, nid), func(k string, v interface{}) bool {
+ entry := v.(*entry)
+ if entry.deleting {
+ return false
+ }
+ key := k[strings.LastIndex(k, "/")+1:]
+ entries[key] = &TableElem{Value: entry.value, owner: entry.node}
+ return false
+ })
+ return entries
+}
+
+// DeleteEntry deletes a table entry in NetworkDB for given (network,
+// table, key) tuple and if the NetworkDB is part of the cluster
+// propagates this event to the cluster.
+func (nDB *NetworkDB) DeleteEntry(tname, nid, key string) error {
+ nDB.Lock()
+ oldEntry, err := nDB.getEntry(tname, nid, key)
+ if err != nil || oldEntry == nil || oldEntry.deleting {
+ nDB.Unlock()
+ return fmt.Errorf("cannot delete entry %s with network id %s and key %s "+
+ "does not exist or is already being deleted", tname, nid, key)
+ }
+
+ entry := &entry{
+ ltime: nDB.tableClock.Increment(),
+ node: nDB.config.NodeID,
+ value: oldEntry.value,
+ deleting: true,
+ reapTime: nDB.config.reapEntryInterval,
+ }
+
+ nDB.createOrUpdateEntry(nid, tname, key, entry)
+ nDB.Unlock()
+
+ if err := nDB.sendTableEvent(TableEventTypeDelete, nid, tname, key, entry); err != nil {
+ return fmt.Errorf("cannot send table delete event: %v", err)
+ }
+
+ return nil
+}
+
+func (nDB *NetworkDB) deleteNodeFromNetworks(deletedNode string) {
+ for nid, nodes := range nDB.networkNodes {
+ updatedNodes := make([]string, 0, len(nodes))
+ for _, node := range nodes {
+ if node == deletedNode {
+ continue
+ }
+
+ updatedNodes = append(updatedNodes, node)
+ }
+
+ nDB.networkNodes[nid] = updatedNodes
+ }
+
+ delete(nDB.networks, deletedNode)
+}
+
+// deleteNodeNetworkEntries is called in 2 conditions with 2 different outcomes:
+// 1) when a notification is coming of a node leaving the network
+// - Walk all the network entries and mark the leaving node's entries for deletion
+// These will be garbage collected when the reap timer will expire
+// 2) when the local node is leaving the network
+// - Walk all the network entries:
+// A) if the entry is owned by the local node
+// then we will mark it for deletion. This will ensure that if a node did not
+// yet received the notification that the local node is leaving, will be aware
+// of the entries to be deleted.
+// B) if the entry is owned by a remote node, then we can safely delete it. This
+// ensures that if we join back this network as we receive the CREATE event for
+// entries owned by remote nodes, we will accept them and we notify the application
+func (nDB *NetworkDB) deleteNodeNetworkEntries(nid, node string) {
+ // Indicates if the delete is triggered for the local node
+ isNodeLocal := node == nDB.config.NodeID
+
+ nDB.indexes[byNetwork].WalkPrefix(fmt.Sprintf("/%s", nid),
+ func(path string, v interface{}) bool {
+ oldEntry := v.(*entry)
+ params := strings.Split(path[1:], "/")
+ nid := params[0]
+ tname := params[1]
+ key := params[2]
+
+ // If the entry is owned by a remote node and this node is not leaving the network
+ if oldEntry.node != node && !isNodeLocal {
+ // Don't do anything because the event is triggered for a node that does not own this entry
+ return false
+ }
+
+ // If this entry is already marked for deletion and this node is not leaving the network
+ if oldEntry.deleting && !isNodeLocal {
+ // Don't do anything this entry will be already garbage collected using the old reapTime
+ return false
+ }
+
+ entry := &entry{
+ ltime: oldEntry.ltime,
+ node: oldEntry.node,
+ value: oldEntry.value,
+ deleting: true,
+ reapTime: nDB.config.reapEntryInterval,
+ }
+
+ // we arrived at this point in 2 cases:
+ // 1) this entry is owned by the node that is leaving the network
+ // 2) the local node is leaving the network
+ if oldEntry.node == node {
+ if isNodeLocal {
+ // TODO fcrisciani: this can be removed if there is no way to leave the network
+ // without doing a delete of all the objects
+ entry.ltime++
+ }
+
+ if !oldEntry.deleting {
+ nDB.createOrUpdateEntry(nid, tname, key, entry)
+ }
+ } else {
+ // the local node is leaving the network, all the entries of remote nodes can be safely removed
+ nDB.deleteEntry(nid, tname, key)
+ }
+
+ // Notify to the upper layer only entries not already marked for deletion
+ if !oldEntry.deleting {
+ nDB.broadcaster.Write(makeEvent(opDelete, tname, nid, key, entry.value))
+ }
+ return false
+ })
+}
+
+func (nDB *NetworkDB) deleteNodeTableEntries(node string) {
+ nDB.indexes[byTable].Walk(func(path string, v interface{}) bool {
+ oldEntry := v.(*entry)
+ if oldEntry.node != node {
+ return false
+ }
+
+ params := strings.Split(path[1:], "/")
+ tname := params[0]
+ nid := params[1]
+ key := params[2]
+
+ nDB.deleteEntry(nid, tname, key)
+
+ if !oldEntry.deleting {
+ nDB.broadcaster.Write(makeEvent(opDelete, tname, nid, key, oldEntry.value))
+ }
+ return false
+ })
+}
+
+// WalkTable walks a single table in NetworkDB and invokes the passed
+// function for each entry in the table passing the network, key,
+// value. The walk stops if the passed function returns a true.
+func (nDB *NetworkDB) WalkTable(tname string, fn func(string, string, []byte, bool) bool) error {
+ nDB.RLock()
+ values := make(map[string]interface{})
+ nDB.indexes[byTable].WalkPrefix(fmt.Sprintf("/%s", tname), func(path string, v interface{}) bool {
+ values[path] = v
+ return false
+ })
+ nDB.RUnlock()
+
+ for k, v := range values {
+ params := strings.Split(k[1:], "/")
+ nid := params[1]
+ key := params[2]
+ if fn(nid, key, v.(*entry).value, v.(*entry).deleting) {
+ return nil
+ }
+ }
+
+ return nil
+}
+
+// JoinNetwork joins this node to a given network and propagates this
+// event across the cluster. This triggers this node joining the
+// sub-cluster of this network and participates in the network-scoped
+// gossip and bulk sync for this network.
+func (nDB *NetworkDB) JoinNetwork(nid string) error {
+ ltime := nDB.networkClock.Increment()
+
+ nDB.Lock()
+ nodeNetworks, ok := nDB.networks[nDB.config.NodeID]
+ if !ok {
+ nodeNetworks = make(map[string]*network)
+ nDB.networks[nDB.config.NodeID] = nodeNetworks
+ }
+ n, ok := nodeNetworks[nid]
+ var entries int
+ if ok {
+ entries = n.entriesNumber
+ }
+ nodeNetworks[nid] = &network{id: nid, ltime: ltime, entriesNumber: entries}
+ nodeNetworks[nid].tableBroadcasts = &memberlist.TransmitLimitedQueue{
+ NumNodes: func() int {
+ //TODO fcrisciani this can be optimized maybe avoiding the lock?
+ // this call is done each GetBroadcasts call to evaluate the number of
+ // replicas for the message
+ nDB.RLock()
+ defer nDB.RUnlock()
+ return len(nDB.networkNodes[nid])
+ },
+ RetransmitMult: 4,
+ }
+ nDB.addNetworkNode(nid, nDB.config.NodeID)
+ networkNodes := nDB.networkNodes[nid]
+ n = nodeNetworks[nid]
+ nDB.Unlock()
+
+ if err := nDB.sendNetworkEvent(nid, NetworkEventTypeJoin, ltime); err != nil {
+ return fmt.Errorf("failed to send leave network event for %s: %v", nid, err)
+ }
+
+ logrus.Debugf("%v(%v): joined network %s", nDB.config.Hostname, nDB.config.NodeID, nid)
+ if _, err := nDB.bulkSync(networkNodes, true); err != nil {
+ logrus.Errorf("Error bulk syncing while joining network %s: %v", nid, err)
+ }
+
+ // Mark the network as being synced
+ // note this is a best effort, we are not checking the result of the bulk sync
+ nDB.Lock()
+ n.inSync = true
+ nDB.Unlock()
+
+ return nil
+}
+
+// LeaveNetwork leaves this node from a given network and propagates
+// this event across the cluster. This triggers this node leaving the
+// sub-cluster of this network and as a result will no longer
+// participate in the network-scoped gossip and bulk sync for this
+// network. Also remove all the table entries for this network from
+// networkdb
+func (nDB *NetworkDB) LeaveNetwork(nid string) error {
+ ltime := nDB.networkClock.Increment()
+ if err := nDB.sendNetworkEvent(nid, NetworkEventTypeLeave, ltime); err != nil {
+ return fmt.Errorf("failed to send leave network event for %s: %v", nid, err)
+ }
+
+ nDB.Lock()
+ defer nDB.Unlock()
+
+ // Remove myself from the list of the nodes participating to the network
+ nDB.deleteNetworkNode(nid, nDB.config.NodeID)
+
+ // Update all the local entries marking them for deletion and delete all the remote entries
+ nDB.deleteNodeNetworkEntries(nid, nDB.config.NodeID)
+
+ nodeNetworks, ok := nDB.networks[nDB.config.NodeID]
+ if !ok {
+ return fmt.Errorf("could not find self node for network %s while trying to leave", nid)
+ }
+
+ n, ok := nodeNetworks[nid]
+ if !ok {
+ return fmt.Errorf("could not find network %s while trying to leave", nid)
+ }
+
+ logrus.Debugf("%v(%v): leaving network %s", nDB.config.Hostname, nDB.config.NodeID, nid)
+ n.ltime = ltime
+ n.reapTime = nDB.config.reapNetworkInterval
+ n.leaving = true
+ return nil
+}
+
+// addNetworkNode adds the node to the list of nodes which participate
+// in the passed network only if it is not already present. Caller
+// should hold the NetworkDB lock while calling this
+func (nDB *NetworkDB) addNetworkNode(nid string, nodeName string) {
+ nodes := nDB.networkNodes[nid]
+ for _, node := range nodes {
+ if node == nodeName {
+ return
+ }
+ }
+
+ nDB.networkNodes[nid] = append(nDB.networkNodes[nid], nodeName)
+}
+
+// Deletes the node from the list of nodes which participate in the
+// passed network. Caller should hold the NetworkDB lock while calling
+// this
+func (nDB *NetworkDB) deleteNetworkNode(nid string, nodeName string) {
+ nodes, ok := nDB.networkNodes[nid]
+ if !ok || len(nodes) == 0 {
+ return
+ }
+ newNodes := make([]string, 0, len(nodes)-1)
+ for _, name := range nodes {
+ if name == nodeName {
+ continue
+ }
+ newNodes = append(newNodes, name)
+ }
+ nDB.networkNodes[nid] = newNodes
+}
+
+// findCommonnetworks find the networks that both this node and the
+// passed node have joined.
+func (nDB *NetworkDB) findCommonNetworks(nodeName string) []string {
+ nDB.RLock()
+ defer nDB.RUnlock()
+
+ var networks []string
+ for nid := range nDB.networks[nDB.config.NodeID] {
+ if n, ok := nDB.networks[nodeName][nid]; ok {
+ if !n.leaving {
+ networks = append(networks, nid)
+ }
+ }
+ }
+
+ return networks
+}
+
+func (nDB *NetworkDB) updateLocalNetworkTime() {
+ nDB.Lock()
+ defer nDB.Unlock()
+
+ ltime := nDB.networkClock.Increment()
+ for _, n := range nDB.networks[nDB.config.NodeID] {
+ n.ltime = ltime
+ }
+}
+
+// createOrUpdateEntry this function handles the creation or update of entries into the local
+// tree store. It is also used to keep in sync the entries number of the network (all tables are aggregated)
+func (nDB *NetworkDB) createOrUpdateEntry(nid, tname, key string, entry interface{}) (bool, bool) {
+ _, okTable := nDB.indexes[byTable].Insert(fmt.Sprintf("/%s/%s/%s", tname, nid, key), entry)
+ _, okNetwork := nDB.indexes[byNetwork].Insert(fmt.Sprintf("/%s/%s/%s", nid, tname, key), entry)
+ if !okNetwork {
+ // Add only if it is an insert not an update
+ n, ok := nDB.networks[nDB.config.NodeID][nid]
+ if ok {
+ n.entriesNumber++
+ }
+ }
+ return okTable, okNetwork
+}
+
+// deleteEntry this function handles the deletion of entries into the local tree store.
+// It is also used to keep in sync the entries number of the network (all tables are aggregated)
+func (nDB *NetworkDB) deleteEntry(nid, tname, key string) (bool, bool) {
+ _, okTable := nDB.indexes[byTable].Delete(fmt.Sprintf("/%s/%s/%s", tname, nid, key))
+ _, okNetwork := nDB.indexes[byNetwork].Delete(fmt.Sprintf("/%s/%s/%s", nid, tname, key))
+ if okNetwork {
+ // Remove only if the delete is successful
+ n, ok := nDB.networks[nDB.config.NodeID][nid]
+ if ok {
+ n.entriesNumber--
+ }
+ }
+ return okTable, okNetwork
+}
--- /dev/null
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: networkdb/networkdb.proto
+
+/*
+ Package networkdb is a generated protocol buffer package.
+
+ It is generated from these files:
+ networkdb/networkdb.proto
+
+ It has these top-level messages:
+ GossipMessage
+ NodeEvent
+ NetworkEvent
+ NetworkEntry
+ NetworkPushPull
+ TableEvent
+ BulkSyncMessage
+ CompoundMessage
+*/
+package networkdb
+
+import proto "github.com/gogo/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import _ "github.com/gogo/protobuf/gogoproto"
+
+import github_com_hashicorp_serf_serf "github.com/hashicorp/serf/serf"
+
+import strings "strings"
+import reflect "reflect"
+
+import io "io"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
+
+// MessageType enum defines all the core message types that networkdb
+// uses to communicate to peers.
+type MessageType int32
+
+const (
+ MessageTypeInvalid MessageType = 0
+ // NetworkEvent message type is used to communicate network
+ // attachments on the node.
+ MessageTypeNetworkEvent MessageType = 1
+ // TableEvent message type is used to communicate any table
+ // CRUD event that happened on the node.
+ MessageTypeTableEvent MessageType = 2
+ // PushPull message type is used to syncup all network
+ // attachments on a peer node either during startup of this
+ // node or with a random peer node periodically thereafter.
+ MessageTypePushPull MessageType = 3
+ // BulkSync message is used to bulksync the whole networkdb
+ // state with a peer node during startup of this node or with
+ // a random peer node periodically thereafter.
+ MessageTypeBulkSync MessageType = 4
+ // Compound message type is used to form a compound message
+ // which is a pack of many message of above types, packed into
+ // a single compound message.
+ MessageTypeCompound MessageType = 5
+ // NodeEvent message type is used to communicate node
+ // join/leave events in the cluster
+ MessageTypeNodeEvent MessageType = 6
+)
+
+var MessageType_name = map[int32]string{
+ 0: "INVALID",
+ 1: "NETWORK_EVENT",
+ 2: "TABLE_EVENT",
+ 3: "PUSH_PULL",
+ 4: "BULK_SYNC",
+ 5: "COMPOUND",
+ 6: "NODE_EVENT",
+}
+var MessageType_value = map[string]int32{
+ "INVALID": 0,
+ "NETWORK_EVENT": 1,
+ "TABLE_EVENT": 2,
+ "PUSH_PULL": 3,
+ "BULK_SYNC": 4,
+ "COMPOUND": 5,
+ "NODE_EVENT": 6,
+}
+
+func (x MessageType) String() string {
+ return proto.EnumName(MessageType_name, int32(x))
+}
+func (MessageType) EnumDescriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{0} }
+
+type NodeEvent_Type int32
+
+const (
+ NodeEventTypeInvalid NodeEvent_Type = 0
+ // Join event is generated when this node joins the cluster.
+ NodeEventTypeJoin NodeEvent_Type = 1
+ // Leave event is generated when this node leaves the cluster.
+ NodeEventTypeLeave NodeEvent_Type = 2
+)
+
+var NodeEvent_Type_name = map[int32]string{
+ 0: "INVALID",
+ 1: "JOIN",
+ 2: "LEAVE",
+}
+var NodeEvent_Type_value = map[string]int32{
+ "INVALID": 0,
+ "JOIN": 1,
+ "LEAVE": 2,
+}
+
+func (x NodeEvent_Type) String() string {
+ return proto.EnumName(NodeEvent_Type_name, int32(x))
+}
+func (NodeEvent_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{1, 0} }
+
+type NetworkEvent_Type int32
+
+const (
+ NetworkEventTypeInvalid NetworkEvent_Type = 0
+ // Join event is generated when this node joins a network.
+ NetworkEventTypeJoin NetworkEvent_Type = 1
+ // Leave event is generated when this node leaves a network.
+ NetworkEventTypeLeave NetworkEvent_Type = 2
+)
+
+var NetworkEvent_Type_name = map[int32]string{
+ 0: "INVALID",
+ 1: "JOIN",
+ 2: "LEAVE",
+}
+var NetworkEvent_Type_value = map[string]int32{
+ "INVALID": 0,
+ "JOIN": 1,
+ "LEAVE": 2,
+}
+
+func (x NetworkEvent_Type) String() string {
+ return proto.EnumName(NetworkEvent_Type_name, int32(x))
+}
+func (NetworkEvent_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{2, 0} }
+
+type TableEvent_Type int32
+
+const (
+ TableEventTypeInvalid TableEvent_Type = 0
+ // Create signifies that this table entry was just
+ // created.
+ TableEventTypeCreate TableEvent_Type = 1
+ // Update signifies that this table entry was just
+ // updated.
+ TableEventTypeUpdate TableEvent_Type = 2
+ // Delete signifies that this table entry was just
+ // updated.
+ TableEventTypeDelete TableEvent_Type = 3
+)
+
+var TableEvent_Type_name = map[int32]string{
+ 0: "INVALID",
+ 1: "CREATE",
+ 2: "UPDATE",
+ 3: "DELETE",
+}
+var TableEvent_Type_value = map[string]int32{
+ "INVALID": 0,
+ "CREATE": 1,
+ "UPDATE": 2,
+ "DELETE": 3,
+}
+
+func (x TableEvent_Type) String() string {
+ return proto.EnumName(TableEvent_Type_name, int32(x))
+}
+func (TableEvent_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{5, 0} }
+
+// GossipMessage is a basic message header used by all messages types.
+type GossipMessage struct {
+ Type MessageType `protobuf:"varint,1,opt,name=type,proto3,enum=networkdb.MessageType" json:"type,omitempty"`
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (m *GossipMessage) Reset() { *m = GossipMessage{} }
+func (*GossipMessage) ProtoMessage() {}
+func (*GossipMessage) Descriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{0} }
+
+func (m *GossipMessage) GetType() MessageType {
+ if m != nil {
+ return m.Type
+ }
+ return MessageTypeInvalid
+}
+
+func (m *GossipMessage) GetData() []byte {
+ if m != nil {
+ return m.Data
+ }
+ return nil
+}
+
+// NodeEvent message payload definition.
+type NodeEvent struct {
+ Type NodeEvent_Type `protobuf:"varint,1,opt,name=type,proto3,enum=networkdb.NodeEvent_Type" json:"type,omitempty"`
+ // Lamport time using a network lamport clock indicating the
+ // time this event was generated on the node where it was
+ // generated.
+ LTime github_com_hashicorp_serf_serf.LamportTime `protobuf:"varint,2,opt,name=l_time,json=lTime,proto3,customtype=github.com/hashicorp/serf/serf.LamportTime" json:"l_time"`
+ // Source node name.
+ NodeName string `protobuf:"bytes,3,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"`
+}
+
+func (m *NodeEvent) Reset() { *m = NodeEvent{} }
+func (*NodeEvent) ProtoMessage() {}
+func (*NodeEvent) Descriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{1} }
+
+func (m *NodeEvent) GetType() NodeEvent_Type {
+ if m != nil {
+ return m.Type
+ }
+ return NodeEventTypeInvalid
+}
+
+func (m *NodeEvent) GetNodeName() string {
+ if m != nil {
+ return m.NodeName
+ }
+ return ""
+}
+
+// NetworkEvent message payload definition.
+type NetworkEvent struct {
+ Type NetworkEvent_Type `protobuf:"varint,1,opt,name=type,proto3,enum=networkdb.NetworkEvent_Type" json:"type,omitempty"`
+ // Lamport time using a network lamport clock indicating the
+ // time this event was generated on the node where it was
+ // generated.
+ LTime github_com_hashicorp_serf_serf.LamportTime `protobuf:"varint,2,opt,name=l_time,json=lTime,proto3,customtype=github.com/hashicorp/serf/serf.LamportTime" json:"l_time"`
+ // Source node name.
+ NodeName string `protobuf:"bytes,3,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"`
+ // ID of the network for which the event is generated.
+ NetworkID string `protobuf:"bytes,4,opt,name=network_id,json=networkId,proto3" json:"network_id,omitempty"`
+}
+
+func (m *NetworkEvent) Reset() { *m = NetworkEvent{} }
+func (*NetworkEvent) ProtoMessage() {}
+func (*NetworkEvent) Descriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{2} }
+
+func (m *NetworkEvent) GetType() NetworkEvent_Type {
+ if m != nil {
+ return m.Type
+ }
+ return NetworkEventTypeInvalid
+}
+
+func (m *NetworkEvent) GetNodeName() string {
+ if m != nil {
+ return m.NodeName
+ }
+ return ""
+}
+
+func (m *NetworkEvent) GetNetworkID() string {
+ if m != nil {
+ return m.NetworkID
+ }
+ return ""
+}
+
+// NetworkEntry for push pull of networks.
+type NetworkEntry struct {
+ // ID of the network
+ NetworkID string `protobuf:"bytes,1,opt,name=network_id,json=networkId,proto3" json:"network_id,omitempty"`
+ // Latest lamport time of the network attachment when this
+ // network event was recorded.
+ LTime github_com_hashicorp_serf_serf.LamportTime `protobuf:"varint,2,opt,name=l_time,json=lTime,proto3,customtype=github.com/hashicorp/serf/serf.LamportTime" json:"l_time"`
+ // Source node name where this network attachment happened.
+ NodeName string `protobuf:"bytes,3,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"`
+ // Indicates if a leave from this network is in progress.
+ Leaving bool `protobuf:"varint,4,opt,name=leaving,proto3" json:"leaving,omitempty"`
+}
+
+func (m *NetworkEntry) Reset() { *m = NetworkEntry{} }
+func (*NetworkEntry) ProtoMessage() {}
+func (*NetworkEntry) Descriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{3} }
+
+func (m *NetworkEntry) GetNetworkID() string {
+ if m != nil {
+ return m.NetworkID
+ }
+ return ""
+}
+
+func (m *NetworkEntry) GetNodeName() string {
+ if m != nil {
+ return m.NodeName
+ }
+ return ""
+}
+
+func (m *NetworkEntry) GetLeaving() bool {
+ if m != nil {
+ return m.Leaving
+ }
+ return false
+}
+
+// NetworkPushpull message payload definition.
+type NetworkPushPull struct {
+ // Lamport time when this push pull was initiated.
+ LTime github_com_hashicorp_serf_serf.LamportTime `protobuf:"varint,1,opt,name=l_time,json=lTime,proto3,customtype=github.com/hashicorp/serf/serf.LamportTime" json:"l_time"`
+ Networks []*NetworkEntry `protobuf:"bytes,2,rep,name=networks" json:"networks,omitempty"`
+ // Name of the node sending this push pull payload.
+ NodeName string `protobuf:"bytes,3,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"`
+}
+
+func (m *NetworkPushPull) Reset() { *m = NetworkPushPull{} }
+func (*NetworkPushPull) ProtoMessage() {}
+func (*NetworkPushPull) Descriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{4} }
+
+func (m *NetworkPushPull) GetNetworks() []*NetworkEntry {
+ if m != nil {
+ return m.Networks
+ }
+ return nil
+}
+
+func (m *NetworkPushPull) GetNodeName() string {
+ if m != nil {
+ return m.NodeName
+ }
+ return ""
+}
+
+// TableEvent message payload definition.
+type TableEvent struct {
+ Type TableEvent_Type `protobuf:"varint,1,opt,name=type,proto3,enum=networkdb.TableEvent_Type" json:"type,omitempty"`
+ // Lamport time when this event was generated.
+ LTime github_com_hashicorp_serf_serf.LamportTime `protobuf:"varint,2,opt,name=l_time,json=lTime,proto3,customtype=github.com/hashicorp/serf/serf.LamportTime" json:"l_time"`
+ // Node name where this event originated.
+ NodeName string `protobuf:"bytes,3,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"`
+ // ID of the network to which this table entry belongs.
+ NetworkID string `protobuf:"bytes,4,opt,name=network_id,json=networkId,proto3" json:"network_id,omitempty"`
+ // Name of the table to which this table entry belongs.
+ TableName string `protobuf:"bytes,5,opt,name=table_name,json=tableName,proto3" json:"table_name,omitempty"`
+ // Entry key.
+ Key string `protobuf:"bytes,6,opt,name=key,proto3" json:"key,omitempty"`
+ // Entry value.
+ Value []byte `protobuf:"bytes,7,opt,name=value,proto3" json:"value,omitempty"`
+ // Residual reap time for the entry before getting deleted in seconds
+ ResidualReapTime int32 `protobuf:"varint,8,opt,name=residual_reap_time,json=residualReapTime,proto3" json:"residual_reap_time,omitempty"`
+}
+
+func (m *TableEvent) Reset() { *m = TableEvent{} }
+func (*TableEvent) ProtoMessage() {}
+func (*TableEvent) Descriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{5} }
+
+func (m *TableEvent) GetType() TableEvent_Type {
+ if m != nil {
+ return m.Type
+ }
+ return TableEventTypeInvalid
+}
+
+func (m *TableEvent) GetNodeName() string {
+ if m != nil {
+ return m.NodeName
+ }
+ return ""
+}
+
+func (m *TableEvent) GetNetworkID() string {
+ if m != nil {
+ return m.NetworkID
+ }
+ return ""
+}
+
+func (m *TableEvent) GetTableName() string {
+ if m != nil {
+ return m.TableName
+ }
+ return ""
+}
+
+func (m *TableEvent) GetKey() string {
+ if m != nil {
+ return m.Key
+ }
+ return ""
+}
+
+func (m *TableEvent) GetValue() []byte {
+ if m != nil {
+ return m.Value
+ }
+ return nil
+}
+
+func (m *TableEvent) GetResidualReapTime() int32 {
+ if m != nil {
+ return m.ResidualReapTime
+ }
+ return 0
+}
+
+// BulkSync message payload definition.
+type BulkSyncMessage struct {
+ // Lamport time when this bulk sync was initiated.
+ LTime github_com_hashicorp_serf_serf.LamportTime `protobuf:"varint,1,opt,name=l_time,json=lTime,proto3,customtype=github.com/hashicorp/serf/serf.LamportTime" json:"l_time"`
+ // Indicates if this bulksync is a response to a bulk sync
+ // request from a peer node.
+ Unsolicited bool `protobuf:"varint,2,opt,name=unsolicited,proto3" json:"unsolicited,omitempty"`
+ // Name of the node which is producing this bulk sync message.
+ NodeName string `protobuf:"bytes,3,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"`
+ // List of network names whose table entries are getting
+ // bulksynced as part of the bulksync.
+ Networks []string `protobuf:"bytes,4,rep,name=networks" json:"networks,omitempty"`
+ // Bulksync payload
+ Payload []byte `protobuf:"bytes,5,opt,name=payload,proto3" json:"payload,omitempty"`
+}
+
+func (m *BulkSyncMessage) Reset() { *m = BulkSyncMessage{} }
+func (*BulkSyncMessage) ProtoMessage() {}
+func (*BulkSyncMessage) Descriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{6} }
+
+func (m *BulkSyncMessage) GetUnsolicited() bool {
+ if m != nil {
+ return m.Unsolicited
+ }
+ return false
+}
+
+func (m *BulkSyncMessage) GetNodeName() string {
+ if m != nil {
+ return m.NodeName
+ }
+ return ""
+}
+
+func (m *BulkSyncMessage) GetNetworks() []string {
+ if m != nil {
+ return m.Networks
+ }
+ return nil
+}
+
+func (m *BulkSyncMessage) GetPayload() []byte {
+ if m != nil {
+ return m.Payload
+ }
+ return nil
+}
+
+// Compound message payload definition.
+type CompoundMessage struct {
+ // A list of simple messages.
+ Messages []*CompoundMessage_SimpleMessage `protobuf:"bytes,1,rep,name=messages" json:"messages,omitempty"`
+}
+
+func (m *CompoundMessage) Reset() { *m = CompoundMessage{} }
+func (*CompoundMessage) ProtoMessage() {}
+func (*CompoundMessage) Descriptor() ([]byte, []int) { return fileDescriptorNetworkdb, []int{7} }
+
+func (m *CompoundMessage) GetMessages() []*CompoundMessage_SimpleMessage {
+ if m != nil {
+ return m.Messages
+ }
+ return nil
+}
+
+type CompoundMessage_SimpleMessage struct {
+ // Bytestring payload of a message constructed using
+ // other message type definitions.
+ Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"`
+}
+
+func (m *CompoundMessage_SimpleMessage) Reset() { *m = CompoundMessage_SimpleMessage{} }
+func (*CompoundMessage_SimpleMessage) ProtoMessage() {}
+func (*CompoundMessage_SimpleMessage) Descriptor() ([]byte, []int) {
+ return fileDescriptorNetworkdb, []int{7, 0}
+}
+
+func (m *CompoundMessage_SimpleMessage) GetPayload() []byte {
+ if m != nil {
+ return m.Payload
+ }
+ return nil
+}
+
+func init() {
+ proto.RegisterType((*GossipMessage)(nil), "networkdb.GossipMessage")
+ proto.RegisterType((*NodeEvent)(nil), "networkdb.NodeEvent")
+ proto.RegisterType((*NetworkEvent)(nil), "networkdb.NetworkEvent")
+ proto.RegisterType((*NetworkEntry)(nil), "networkdb.NetworkEntry")
+ proto.RegisterType((*NetworkPushPull)(nil), "networkdb.NetworkPushPull")
+ proto.RegisterType((*TableEvent)(nil), "networkdb.TableEvent")
+ proto.RegisterType((*BulkSyncMessage)(nil), "networkdb.BulkSyncMessage")
+ proto.RegisterType((*CompoundMessage)(nil), "networkdb.CompoundMessage")
+ proto.RegisterType((*CompoundMessage_SimpleMessage)(nil), "networkdb.CompoundMessage.SimpleMessage")
+ proto.RegisterEnum("networkdb.MessageType", MessageType_name, MessageType_value)
+ proto.RegisterEnum("networkdb.NodeEvent_Type", NodeEvent_Type_name, NodeEvent_Type_value)
+ proto.RegisterEnum("networkdb.NetworkEvent_Type", NetworkEvent_Type_name, NetworkEvent_Type_value)
+ proto.RegisterEnum("networkdb.TableEvent_Type", TableEvent_Type_name, TableEvent_Type_value)
+}
+func (this *GossipMessage) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 6)
+ s = append(s, "&networkdb.GossipMessage{")
+ s = append(s, "Type: "+fmt.Sprintf("%#v", this.Type)+",\n")
+ s = append(s, "Data: "+fmt.Sprintf("%#v", this.Data)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func (this *NodeEvent) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 7)
+ s = append(s, "&networkdb.NodeEvent{")
+ s = append(s, "Type: "+fmt.Sprintf("%#v", this.Type)+",\n")
+ s = append(s, "LTime: "+fmt.Sprintf("%#v", this.LTime)+",\n")
+ s = append(s, "NodeName: "+fmt.Sprintf("%#v", this.NodeName)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func (this *NetworkEvent) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 8)
+ s = append(s, "&networkdb.NetworkEvent{")
+ s = append(s, "Type: "+fmt.Sprintf("%#v", this.Type)+",\n")
+ s = append(s, "LTime: "+fmt.Sprintf("%#v", this.LTime)+",\n")
+ s = append(s, "NodeName: "+fmt.Sprintf("%#v", this.NodeName)+",\n")
+ s = append(s, "NetworkID: "+fmt.Sprintf("%#v", this.NetworkID)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func (this *NetworkEntry) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 8)
+ s = append(s, "&networkdb.NetworkEntry{")
+ s = append(s, "NetworkID: "+fmt.Sprintf("%#v", this.NetworkID)+",\n")
+ s = append(s, "LTime: "+fmt.Sprintf("%#v", this.LTime)+",\n")
+ s = append(s, "NodeName: "+fmt.Sprintf("%#v", this.NodeName)+",\n")
+ s = append(s, "Leaving: "+fmt.Sprintf("%#v", this.Leaving)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func (this *NetworkPushPull) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 7)
+ s = append(s, "&networkdb.NetworkPushPull{")
+ s = append(s, "LTime: "+fmt.Sprintf("%#v", this.LTime)+",\n")
+ if this.Networks != nil {
+ s = append(s, "Networks: "+fmt.Sprintf("%#v", this.Networks)+",\n")
+ }
+ s = append(s, "NodeName: "+fmt.Sprintf("%#v", this.NodeName)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func (this *TableEvent) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 12)
+ s = append(s, "&networkdb.TableEvent{")
+ s = append(s, "Type: "+fmt.Sprintf("%#v", this.Type)+",\n")
+ s = append(s, "LTime: "+fmt.Sprintf("%#v", this.LTime)+",\n")
+ s = append(s, "NodeName: "+fmt.Sprintf("%#v", this.NodeName)+",\n")
+ s = append(s, "NetworkID: "+fmt.Sprintf("%#v", this.NetworkID)+",\n")
+ s = append(s, "TableName: "+fmt.Sprintf("%#v", this.TableName)+",\n")
+ s = append(s, "Key: "+fmt.Sprintf("%#v", this.Key)+",\n")
+ s = append(s, "Value: "+fmt.Sprintf("%#v", this.Value)+",\n")
+ s = append(s, "ResidualReapTime: "+fmt.Sprintf("%#v", this.ResidualReapTime)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func (this *BulkSyncMessage) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 9)
+ s = append(s, "&networkdb.BulkSyncMessage{")
+ s = append(s, "LTime: "+fmt.Sprintf("%#v", this.LTime)+",\n")
+ s = append(s, "Unsolicited: "+fmt.Sprintf("%#v", this.Unsolicited)+",\n")
+ s = append(s, "NodeName: "+fmt.Sprintf("%#v", this.NodeName)+",\n")
+ s = append(s, "Networks: "+fmt.Sprintf("%#v", this.Networks)+",\n")
+ s = append(s, "Payload: "+fmt.Sprintf("%#v", this.Payload)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func (this *CompoundMessage) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 5)
+ s = append(s, "&networkdb.CompoundMessage{")
+ if this.Messages != nil {
+ s = append(s, "Messages: "+fmt.Sprintf("%#v", this.Messages)+",\n")
+ }
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func (this *CompoundMessage_SimpleMessage) GoString() string {
+ if this == nil {
+ return "nil"
+ }
+ s := make([]string, 0, 5)
+ s = append(s, "&networkdb.CompoundMessage_SimpleMessage{")
+ s = append(s, "Payload: "+fmt.Sprintf("%#v", this.Payload)+",\n")
+ s = append(s, "}")
+ return strings.Join(s, "")
+}
+func valueToGoStringNetworkdb(v interface{}, typ string) string {
+ rv := reflect.ValueOf(v)
+ if rv.IsNil() {
+ return "nil"
+ }
+ pv := reflect.Indirect(rv).Interface()
+ return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)
+}
+func (m *GossipMessage) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *GossipMessage) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if m.Type != 0 {
+ dAtA[i] = 0x8
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.Type))
+ }
+ if len(m.Data) > 0 {
+ dAtA[i] = 0x12
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.Data)))
+ i += copy(dAtA[i:], m.Data)
+ }
+ return i, nil
+}
+
+func (m *NodeEvent) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *NodeEvent) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if m.Type != 0 {
+ dAtA[i] = 0x8
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.Type))
+ }
+ if m.LTime != 0 {
+ dAtA[i] = 0x10
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.LTime))
+ }
+ if len(m.NodeName) > 0 {
+ dAtA[i] = 0x1a
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.NodeName)))
+ i += copy(dAtA[i:], m.NodeName)
+ }
+ return i, nil
+}
+
+func (m *NetworkEvent) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *NetworkEvent) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if m.Type != 0 {
+ dAtA[i] = 0x8
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.Type))
+ }
+ if m.LTime != 0 {
+ dAtA[i] = 0x10
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.LTime))
+ }
+ if len(m.NodeName) > 0 {
+ dAtA[i] = 0x1a
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.NodeName)))
+ i += copy(dAtA[i:], m.NodeName)
+ }
+ if len(m.NetworkID) > 0 {
+ dAtA[i] = 0x22
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.NetworkID)))
+ i += copy(dAtA[i:], m.NetworkID)
+ }
+ return i, nil
+}
+
+func (m *NetworkEntry) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *NetworkEntry) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if len(m.NetworkID) > 0 {
+ dAtA[i] = 0xa
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.NetworkID)))
+ i += copy(dAtA[i:], m.NetworkID)
+ }
+ if m.LTime != 0 {
+ dAtA[i] = 0x10
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.LTime))
+ }
+ if len(m.NodeName) > 0 {
+ dAtA[i] = 0x1a
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.NodeName)))
+ i += copy(dAtA[i:], m.NodeName)
+ }
+ if m.Leaving {
+ dAtA[i] = 0x20
+ i++
+ if m.Leaving {
+ dAtA[i] = 1
+ } else {
+ dAtA[i] = 0
+ }
+ i++
+ }
+ return i, nil
+}
+
+func (m *NetworkPushPull) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *NetworkPushPull) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if m.LTime != 0 {
+ dAtA[i] = 0x8
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.LTime))
+ }
+ if len(m.Networks) > 0 {
+ for _, msg := range m.Networks {
+ dAtA[i] = 0x12
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(msg.Size()))
+ n, err := msg.MarshalTo(dAtA[i:])
+ if err != nil {
+ return 0, err
+ }
+ i += n
+ }
+ }
+ if len(m.NodeName) > 0 {
+ dAtA[i] = 0x1a
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.NodeName)))
+ i += copy(dAtA[i:], m.NodeName)
+ }
+ return i, nil
+}
+
+func (m *TableEvent) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *TableEvent) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if m.Type != 0 {
+ dAtA[i] = 0x8
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.Type))
+ }
+ if m.LTime != 0 {
+ dAtA[i] = 0x10
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.LTime))
+ }
+ if len(m.NodeName) > 0 {
+ dAtA[i] = 0x1a
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.NodeName)))
+ i += copy(dAtA[i:], m.NodeName)
+ }
+ if len(m.NetworkID) > 0 {
+ dAtA[i] = 0x22
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.NetworkID)))
+ i += copy(dAtA[i:], m.NetworkID)
+ }
+ if len(m.TableName) > 0 {
+ dAtA[i] = 0x2a
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.TableName)))
+ i += copy(dAtA[i:], m.TableName)
+ }
+ if len(m.Key) > 0 {
+ dAtA[i] = 0x32
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.Key)))
+ i += copy(dAtA[i:], m.Key)
+ }
+ if len(m.Value) > 0 {
+ dAtA[i] = 0x3a
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.Value)))
+ i += copy(dAtA[i:], m.Value)
+ }
+ if m.ResidualReapTime != 0 {
+ dAtA[i] = 0x40
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.ResidualReapTime))
+ }
+ return i, nil
+}
+
+func (m *BulkSyncMessage) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *BulkSyncMessage) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if m.LTime != 0 {
+ dAtA[i] = 0x8
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(m.LTime))
+ }
+ if m.Unsolicited {
+ dAtA[i] = 0x10
+ i++
+ if m.Unsolicited {
+ dAtA[i] = 1
+ } else {
+ dAtA[i] = 0
+ }
+ i++
+ }
+ if len(m.NodeName) > 0 {
+ dAtA[i] = 0x1a
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.NodeName)))
+ i += copy(dAtA[i:], m.NodeName)
+ }
+ if len(m.Networks) > 0 {
+ for _, s := range m.Networks {
+ dAtA[i] = 0x22
+ i++
+ l = len(s)
+ for l >= 1<<7 {
+ dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
+ l >>= 7
+ i++
+ }
+ dAtA[i] = uint8(l)
+ i++
+ i += copy(dAtA[i:], s)
+ }
+ }
+ if len(m.Payload) > 0 {
+ dAtA[i] = 0x2a
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.Payload)))
+ i += copy(dAtA[i:], m.Payload)
+ }
+ return i, nil
+}
+
+func (m *CompoundMessage) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *CompoundMessage) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if len(m.Messages) > 0 {
+ for _, msg := range m.Messages {
+ dAtA[i] = 0xa
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(msg.Size()))
+ n, err := msg.MarshalTo(dAtA[i:])
+ if err != nil {
+ return 0, err
+ }
+ i += n
+ }
+ }
+ return i, nil
+}
+
+func (m *CompoundMessage_SimpleMessage) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalTo(dAtA)
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *CompoundMessage_SimpleMessage) MarshalTo(dAtA []byte) (int, error) {
+ var i int
+ _ = i
+ var l int
+ _ = l
+ if len(m.Payload) > 0 {
+ dAtA[i] = 0xa
+ i++
+ i = encodeVarintNetworkdb(dAtA, i, uint64(len(m.Payload)))
+ i += copy(dAtA[i:], m.Payload)
+ }
+ return i, nil
+}
+
+func encodeVarintNetworkdb(dAtA []byte, offset int, v uint64) int {
+ for v >= 1<<7 {
+ dAtA[offset] = uint8(v&0x7f | 0x80)
+ v >>= 7
+ offset++
+ }
+ dAtA[offset] = uint8(v)
+ return offset + 1
+}
+func (m *GossipMessage) Size() (n int) {
+ var l int
+ _ = l
+ if m.Type != 0 {
+ n += 1 + sovNetworkdb(uint64(m.Type))
+ }
+ l = len(m.Data)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ return n
+}
+
+func (m *NodeEvent) Size() (n int) {
+ var l int
+ _ = l
+ if m.Type != 0 {
+ n += 1 + sovNetworkdb(uint64(m.Type))
+ }
+ if m.LTime != 0 {
+ n += 1 + sovNetworkdb(uint64(m.LTime))
+ }
+ l = len(m.NodeName)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ return n
+}
+
+func (m *NetworkEvent) Size() (n int) {
+ var l int
+ _ = l
+ if m.Type != 0 {
+ n += 1 + sovNetworkdb(uint64(m.Type))
+ }
+ if m.LTime != 0 {
+ n += 1 + sovNetworkdb(uint64(m.LTime))
+ }
+ l = len(m.NodeName)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ l = len(m.NetworkID)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ return n
+}
+
+func (m *NetworkEntry) Size() (n int) {
+ var l int
+ _ = l
+ l = len(m.NetworkID)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ if m.LTime != 0 {
+ n += 1 + sovNetworkdb(uint64(m.LTime))
+ }
+ l = len(m.NodeName)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ if m.Leaving {
+ n += 2
+ }
+ return n
+}
+
+func (m *NetworkPushPull) Size() (n int) {
+ var l int
+ _ = l
+ if m.LTime != 0 {
+ n += 1 + sovNetworkdb(uint64(m.LTime))
+ }
+ if len(m.Networks) > 0 {
+ for _, e := range m.Networks {
+ l = e.Size()
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ }
+ l = len(m.NodeName)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ return n
+}
+
+func (m *TableEvent) Size() (n int) {
+ var l int
+ _ = l
+ if m.Type != 0 {
+ n += 1 + sovNetworkdb(uint64(m.Type))
+ }
+ if m.LTime != 0 {
+ n += 1 + sovNetworkdb(uint64(m.LTime))
+ }
+ l = len(m.NodeName)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ l = len(m.NetworkID)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ l = len(m.TableName)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ l = len(m.Key)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ l = len(m.Value)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ if m.ResidualReapTime != 0 {
+ n += 1 + sovNetworkdb(uint64(m.ResidualReapTime))
+ }
+ return n
+}
+
+func (m *BulkSyncMessage) Size() (n int) {
+ var l int
+ _ = l
+ if m.LTime != 0 {
+ n += 1 + sovNetworkdb(uint64(m.LTime))
+ }
+ if m.Unsolicited {
+ n += 2
+ }
+ l = len(m.NodeName)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ if len(m.Networks) > 0 {
+ for _, s := range m.Networks {
+ l = len(s)
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ }
+ l = len(m.Payload)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ return n
+}
+
+func (m *CompoundMessage) Size() (n int) {
+ var l int
+ _ = l
+ if len(m.Messages) > 0 {
+ for _, e := range m.Messages {
+ l = e.Size()
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ }
+ return n
+}
+
+func (m *CompoundMessage_SimpleMessage) Size() (n int) {
+ var l int
+ _ = l
+ l = len(m.Payload)
+ if l > 0 {
+ n += 1 + l + sovNetworkdb(uint64(l))
+ }
+ return n
+}
+
+func sovNetworkdb(x uint64) (n int) {
+ for {
+ n++
+ x >>= 7
+ if x == 0 {
+ break
+ }
+ }
+ return n
+}
+func sozNetworkdb(x uint64) (n int) {
+ return sovNetworkdb(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (this *GossipMessage) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&GossipMessage{`,
+ `Type:` + fmt.Sprintf("%v", this.Type) + `,`,
+ `Data:` + fmt.Sprintf("%v", this.Data) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func (this *NodeEvent) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&NodeEvent{`,
+ `Type:` + fmt.Sprintf("%v", this.Type) + `,`,
+ `LTime:` + fmt.Sprintf("%v", this.LTime) + `,`,
+ `NodeName:` + fmt.Sprintf("%v", this.NodeName) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func (this *NetworkEvent) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&NetworkEvent{`,
+ `Type:` + fmt.Sprintf("%v", this.Type) + `,`,
+ `LTime:` + fmt.Sprintf("%v", this.LTime) + `,`,
+ `NodeName:` + fmt.Sprintf("%v", this.NodeName) + `,`,
+ `NetworkID:` + fmt.Sprintf("%v", this.NetworkID) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func (this *NetworkEntry) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&NetworkEntry{`,
+ `NetworkID:` + fmt.Sprintf("%v", this.NetworkID) + `,`,
+ `LTime:` + fmt.Sprintf("%v", this.LTime) + `,`,
+ `NodeName:` + fmt.Sprintf("%v", this.NodeName) + `,`,
+ `Leaving:` + fmt.Sprintf("%v", this.Leaving) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func (this *NetworkPushPull) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&NetworkPushPull{`,
+ `LTime:` + fmt.Sprintf("%v", this.LTime) + `,`,
+ `Networks:` + strings.Replace(fmt.Sprintf("%v", this.Networks), "NetworkEntry", "NetworkEntry", 1) + `,`,
+ `NodeName:` + fmt.Sprintf("%v", this.NodeName) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func (this *TableEvent) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&TableEvent{`,
+ `Type:` + fmt.Sprintf("%v", this.Type) + `,`,
+ `LTime:` + fmt.Sprintf("%v", this.LTime) + `,`,
+ `NodeName:` + fmt.Sprintf("%v", this.NodeName) + `,`,
+ `NetworkID:` + fmt.Sprintf("%v", this.NetworkID) + `,`,
+ `TableName:` + fmt.Sprintf("%v", this.TableName) + `,`,
+ `Key:` + fmt.Sprintf("%v", this.Key) + `,`,
+ `Value:` + fmt.Sprintf("%v", this.Value) + `,`,
+ `ResidualReapTime:` + fmt.Sprintf("%v", this.ResidualReapTime) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func (this *BulkSyncMessage) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&BulkSyncMessage{`,
+ `LTime:` + fmt.Sprintf("%v", this.LTime) + `,`,
+ `Unsolicited:` + fmt.Sprintf("%v", this.Unsolicited) + `,`,
+ `NodeName:` + fmt.Sprintf("%v", this.NodeName) + `,`,
+ `Networks:` + fmt.Sprintf("%v", this.Networks) + `,`,
+ `Payload:` + fmt.Sprintf("%v", this.Payload) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func (this *CompoundMessage) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&CompoundMessage{`,
+ `Messages:` + strings.Replace(fmt.Sprintf("%v", this.Messages), "CompoundMessage_SimpleMessage", "CompoundMessage_SimpleMessage", 1) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func (this *CompoundMessage_SimpleMessage) String() string {
+ if this == nil {
+ return "nil"
+ }
+ s := strings.Join([]string{`&CompoundMessage_SimpleMessage{`,
+ `Payload:` + fmt.Sprintf("%v", this.Payload) + `,`,
+ `}`,
+ }, "")
+ return s
+}
+func valueToStringNetworkdb(v interface{}) string {
+ rv := reflect.ValueOf(v)
+ if rv.IsNil() {
+ return "nil"
+ }
+ pv := reflect.Indirect(rv).Interface()
+ return fmt.Sprintf("*%v", pv)
+}
+func (m *GossipMessage) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: GossipMessage: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: GossipMessage: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
+ }
+ m.Type = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.Type |= (MessageType(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
+ }
+ var byteLen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ byteLen |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if byteLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + byteLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
+ if m.Data == nil {
+ m.Data = []byte{}
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipNetworkdb(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *NodeEvent) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: NodeEvent: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: NodeEvent: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
+ }
+ m.Type = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.Type |= (NodeEvent_Type(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 2:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field LTime", wireType)
+ }
+ m.LTime = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.LTime |= (github_com_hashicorp_serf_serf.LamportTime(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 3:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NodeName", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.NodeName = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipNetworkdb(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *NetworkEvent) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: NetworkEvent: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: NetworkEvent: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
+ }
+ m.Type = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.Type |= (NetworkEvent_Type(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 2:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field LTime", wireType)
+ }
+ m.LTime = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.LTime |= (github_com_hashicorp_serf_serf.LamportTime(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 3:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NodeName", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.NodeName = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 4:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NetworkID", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.NetworkID = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipNetworkdb(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *NetworkEntry) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: NetworkEntry: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: NetworkEntry: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NetworkID", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.NetworkID = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 2:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field LTime", wireType)
+ }
+ m.LTime = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.LTime |= (github_com_hashicorp_serf_serf.LamportTime(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 3:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NodeName", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.NodeName = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 4:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Leaving", wireType)
+ }
+ var v int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ v |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ m.Leaving = bool(v != 0)
+ default:
+ iNdEx = preIndex
+ skippy, err := skipNetworkdb(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *NetworkPushPull) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: NetworkPushPull: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: NetworkPushPull: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field LTime", wireType)
+ }
+ m.LTime = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.LTime |= (github_com_hashicorp_serf_serf.LamportTime(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Networks", wireType)
+ }
+ var msglen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ msglen |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if msglen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + msglen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Networks = append(m.Networks, &NetworkEntry{})
+ if err := m.Networks[len(m.Networks)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ case 3:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NodeName", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.NodeName = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipNetworkdb(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *TableEvent) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: TableEvent: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: TableEvent: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
+ }
+ m.Type = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.Type |= (TableEvent_Type(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 2:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field LTime", wireType)
+ }
+ m.LTime = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.LTime |= (github_com_hashicorp_serf_serf.LamportTime(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 3:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NodeName", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.NodeName = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 4:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NetworkID", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.NetworkID = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 5:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field TableName", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.TableName = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 6:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Key = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 7:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
+ }
+ var byteLen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ byteLen |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if byteLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + byteLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Value = append(m.Value[:0], dAtA[iNdEx:postIndex]...)
+ if m.Value == nil {
+ m.Value = []byte{}
+ }
+ iNdEx = postIndex
+ case 8:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field ResidualReapTime", wireType)
+ }
+ m.ResidualReapTime = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.ResidualReapTime |= (int32(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ default:
+ iNdEx = preIndex
+ skippy, err := skipNetworkdb(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *BulkSyncMessage) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: BulkSyncMessage: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: BulkSyncMessage: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field LTime", wireType)
+ }
+ m.LTime = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.LTime |= (github_com_hashicorp_serf_serf.LamportTime(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 2:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Unsolicited", wireType)
+ }
+ var v int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ v |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ m.Unsolicited = bool(v != 0)
+ case 3:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NodeName", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.NodeName = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 4:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Networks", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Networks = append(m.Networks, string(dAtA[iNdEx:postIndex]))
+ iNdEx = postIndex
+ case 5:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType)
+ }
+ var byteLen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ byteLen |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if byteLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + byteLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...)
+ if m.Payload == nil {
+ m.Payload = []byte{}
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipNetworkdb(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *CompoundMessage) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: CompoundMessage: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: CompoundMessage: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Messages", wireType)
+ }
+ var msglen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ msglen |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if msglen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + msglen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Messages = append(m.Messages, &CompoundMessage_SimpleMessage{})
+ if err := m.Messages[len(m.Messages)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipNetworkdb(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *CompoundMessage_SimpleMessage) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: SimpleMessage: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: SimpleMessage: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType)
+ }
+ var byteLen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ byteLen |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if byteLen < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ postIndex := iNdEx + byteLen
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...)
+ if m.Payload == nil {
+ m.Payload = []byte{}
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipNetworkdb(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if skippy < 0 {
+ return ErrInvalidLengthNetworkdb
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func skipNetworkdb(dAtA []byte) (n int, err error) {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ wireType := int(wire & 0x7)
+ switch wireType {
+ case 0:
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ iNdEx++
+ if dAtA[iNdEx-1] < 0x80 {
+ break
+ }
+ }
+ return iNdEx, nil
+ case 1:
+ iNdEx += 8
+ return iNdEx, nil
+ case 2:
+ var length int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ length |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ iNdEx += length
+ if length < 0 {
+ return 0, ErrInvalidLengthNetworkdb
+ }
+ return iNdEx, nil
+ case 3:
+ for {
+ var innerWire uint64
+ var start int = iNdEx
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowNetworkdb
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ innerWire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ innerWireType := int(innerWire & 0x7)
+ if innerWireType == 4 {
+ break
+ }
+ next, err := skipNetworkdb(dAtA[start:])
+ if err != nil {
+ return 0, err
+ }
+ iNdEx = start + next
+ }
+ return iNdEx, nil
+ case 4:
+ return iNdEx, nil
+ case 5:
+ iNdEx += 4
+ return iNdEx, nil
+ default:
+ return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+ }
+ }
+ panic("unreachable")
+}
+
+var (
+ ErrInvalidLengthNetworkdb = fmt.Errorf("proto: negative length found during unmarshaling")
+ ErrIntOverflowNetworkdb = fmt.Errorf("proto: integer overflow")
+)
+
+func init() { proto.RegisterFile("networkdb/networkdb.proto", fileDescriptorNetworkdb) }
+
+var fileDescriptorNetworkdb = []byte{
+ // 955 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x96, 0xcd, 0x6e, 0xe3, 0x54,
+ 0x14, 0xc7, 0x7b, 0xf3, 0xd1, 0x26, 0xa7, 0x29, 0x35, 0x77, 0x3a, 0x53, 0xd7, 0x03, 0x89, 0x31,
+ 0x33, 0x55, 0xa6, 0x82, 0x14, 0x75, 0x9e, 0xa0, 0x49, 0x2c, 0xc8, 0x4c, 0xc6, 0x89, 0xdc, 0xa4,
+ 0x88, 0x55, 0x74, 0x5b, 0x5f, 0x52, 0xab, 0x8e, 0x6d, 0xd9, 0x4e, 0x50, 0x56, 0x20, 0x56, 0xa3,
+ 0x2c, 0x78, 0x83, 0xac, 0x86, 0x35, 0x0f, 0x80, 0x58, 0xb2, 0x98, 0x05, 0x0b, 0xd8, 0x21, 0x16,
+ 0x11, 0xcd, 0x13, 0xf0, 0x08, 0xc8, 0xd7, 0x76, 0x72, 0x93, 0x56, 0x23, 0x21, 0x46, 0x82, 0x4d,
+ 0x72, 0x3f, 0x7e, 0x39, 0x3e, 0xe7, 0xef, 0xff, 0xb9, 0x37, 0x70, 0x60, 0xd3, 0xe0, 0x2b, 0xc7,
+ 0xbb, 0x36, 0x2e, 0x8e, 0x17, 0xa3, 0x8a, 0xeb, 0x39, 0x81, 0x83, 0xf3, 0x8b, 0x05, 0x69, 0xaf,
+ 0xef, 0xf4, 0x1d, 0xb6, 0x7a, 0x1c, 0x8e, 0x22, 0x40, 0x69, 0xc1, 0xce, 0xa7, 0x8e, 0xef, 0x9b,
+ 0xee, 0x0b, 0xea, 0xfb, 0xa4, 0x4f, 0xf1, 0x11, 0x64, 0x82, 0xb1, 0x4b, 0x45, 0x24, 0xa3, 0xf2,
+ 0x3b, 0x27, 0x0f, 0x2a, 0xcb, 0x88, 0x31, 0xd1, 0x19, 0xbb, 0x54, 0x67, 0x0c, 0xc6, 0x90, 0x31,
+ 0x48, 0x40, 0xc4, 0x94, 0x8c, 0xca, 0x05, 0x9d, 0x8d, 0x95, 0x57, 0x29, 0xc8, 0x6b, 0x8e, 0x41,
+ 0xd5, 0x11, 0xb5, 0x03, 0xfc, 0xf1, 0x4a, 0xb4, 0x03, 0x2e, 0xda, 0x82, 0xa9, 0x70, 0x01, 0x1b,
+ 0xb0, 0x69, 0xf5, 0x02, 0x73, 0x40, 0x59, 0xc8, 0x4c, 0xf5, 0xe4, 0xf5, 0xac, 0xb4, 0xf1, 0xc7,
+ 0xac, 0x74, 0xd4, 0x37, 0x83, 0xab, 0xe1, 0x45, 0xe5, 0xd2, 0x19, 0x1c, 0x5f, 0x11, 0xff, 0xca,
+ 0xbc, 0x74, 0x3c, 0xf7, 0xd8, 0xa7, 0xde, 0x97, 0xec, 0xa3, 0xd2, 0x24, 0x03, 0xd7, 0xf1, 0x82,
+ 0x8e, 0x39, 0xa0, 0x7a, 0xd6, 0x0a, 0xbf, 0xf0, 0x43, 0xc8, 0xdb, 0x8e, 0x41, 0x7b, 0x36, 0x19,
+ 0x50, 0x31, 0x2d, 0xa3, 0x72, 0x5e, 0xcf, 0x85, 0x0b, 0x1a, 0x19, 0x50, 0xe5, 0x6b, 0xc8, 0x84,
+ 0x4f, 0xc5, 0x8f, 0x61, 0xab, 0xa1, 0x9d, 0x9f, 0x36, 0x1b, 0x75, 0x61, 0x43, 0x12, 0x27, 0x53,
+ 0x79, 0x6f, 0x91, 0x56, 0xb8, 0xdf, 0xb0, 0x47, 0xc4, 0x32, 0x0d, 0x5c, 0x82, 0xcc, 0xb3, 0x56,
+ 0x43, 0x13, 0x90, 0x74, 0x7f, 0x32, 0x95, 0xdf, 0x5d, 0x61, 0x9e, 0x39, 0xa6, 0x8d, 0x3f, 0x80,
+ 0x6c, 0x53, 0x3d, 0x3d, 0x57, 0x85, 0x94, 0xf4, 0x60, 0x32, 0x95, 0xf1, 0x0a, 0xd1, 0xa4, 0x64,
+ 0x44, 0xa5, 0xc2, 0xcb, 0x57, 0xc5, 0x8d, 0x1f, 0xbf, 0x2f, 0xb2, 0x07, 0x2b, 0x37, 0x29, 0x28,
+ 0x68, 0x91, 0x16, 0x91, 0x50, 0x9f, 0xac, 0x08, 0xf5, 0x1e, 0x2f, 0x14, 0x87, 0xfd, 0x07, 0x5a,
+ 0xe1, 0x8f, 0x00, 0xe2, 0x64, 0x7a, 0xa6, 0x21, 0x66, 0xc2, 0xdd, 0xea, 0xce, 0x7c, 0x56, 0xca,
+ 0xc7, 0x89, 0x35, 0xea, 0x7a, 0xe2, 0xb2, 0x86, 0xa1, 0xbc, 0x44, 0xb1, 0xb4, 0x65, 0x5e, 0xda,
+ 0x87, 0x93, 0xa9, 0xbc, 0xcf, 0x17, 0xc2, 0xab, 0xab, 0x2c, 0xd4, 0x8d, 0xde, 0xc0, 0x1a, 0xc6,
+ 0x04, 0x7e, 0xb4, 0x14, 0xf8, 0x60, 0x32, 0x95, 0xef, 0xaf, 0x43, 0x77, 0x69, 0xfc, 0x0b, 0x5a,
+ 0x6a, 0x6c, 0x07, 0xde, 0x78, 0xad, 0x12, 0xf4, 0xe6, 0x4a, 0xde, 0xa6, 0xbe, 0x4f, 0x6e, 0xe9,
+ 0x5b, 0x2d, 0xcc, 0x67, 0xa5, 0x9c, 0x16, 0x6b, 0xcc, 0xa9, 0x2d, 0xc2, 0x96, 0x45, 0xc9, 0xc8,
+ 0xb4, 0xfb, 0x4c, 0xea, 0x9c, 0x9e, 0x4c, 0x95, 0x9f, 0x10, 0xec, 0xc6, 0x89, 0xb6, 0x87, 0xfe,
+ 0x55, 0x7b, 0x68, 0x59, 0x5c, 0x8e, 0xe8, 0xdf, 0xe6, 0xf8, 0x14, 0x72, 0x71, 0xed, 0xbe, 0x98,
+ 0x92, 0xd3, 0xe5, 0xed, 0x93, 0xfd, 0x3b, 0x4c, 0x18, 0xea, 0xa8, 0x2f, 0xc0, 0x7f, 0x50, 0x98,
+ 0xf2, 0x5d, 0x06, 0xa0, 0x43, 0x2e, 0xac, 0xf8, 0x60, 0xa8, 0xac, 0xf8, 0x5d, 0xe2, 0x1e, 0xb5,
+ 0x84, 0xfe, 0xf7, 0x6e, 0xc7, 0xef, 0x03, 0x04, 0x61, 0xba, 0x51, 0xac, 0x2c, 0x8b, 0x95, 0x67,
+ 0x2b, 0x2c, 0x98, 0x00, 0xe9, 0x6b, 0x3a, 0x16, 0x37, 0xd9, 0x7a, 0x38, 0xc4, 0x7b, 0x90, 0x1d,
+ 0x11, 0x6b, 0x48, 0xc5, 0x2d, 0x76, 0x64, 0x46, 0x13, 0x5c, 0x05, 0xec, 0x51, 0xdf, 0x34, 0x86,
+ 0xc4, 0xea, 0x79, 0x94, 0xb8, 0x51, 0xa1, 0x39, 0x19, 0x95, 0xb3, 0xd5, 0xbd, 0xf9, 0xac, 0x24,
+ 0xe8, 0xf1, 0xae, 0x4e, 0x89, 0xcb, 0x4a, 0x11, 0xbc, 0xb5, 0x15, 0xe5, 0x87, 0xa4, 0xf1, 0x0e,
+ 0xf9, 0xc6, 0x63, 0xcd, 0xb2, 0x54, 0x94, 0x6f, 0xbb, 0x47, 0xb0, 0x59, 0xd3, 0xd5, 0xd3, 0x8e,
+ 0x9a, 0x34, 0xde, 0x2a, 0x56, 0xf3, 0x28, 0x09, 0x68, 0x48, 0x75, 0xdb, 0xf5, 0x90, 0x4a, 0xdd,
+ 0x45, 0x75, 0x5d, 0x23, 0xa6, 0xea, 0x6a, 0x53, 0xed, 0xa8, 0x42, 0xfa, 0x2e, 0xaa, 0x4e, 0x2d,
+ 0x1a, 0xac, 0xb7, 0xe7, 0x6f, 0x08, 0x76, 0xab, 0x43, 0xeb, 0xfa, 0x6c, 0x6c, 0x5f, 0x26, 0x97,
+ 0xcf, 0x5b, 0xf4, 0xb3, 0x0c, 0xdb, 0x43, 0xdb, 0x77, 0x2c, 0xf3, 0xd2, 0x0c, 0xa8, 0xc1, 0x5c,
+ 0x93, 0xd3, 0xf9, 0xa5, 0x37, 0xfb, 0x40, 0xe2, 0xda, 0x21, 0x23, 0xa7, 0xd9, 0x5e, 0xe2, 0x7a,
+ 0x11, 0xb6, 0x5c, 0x32, 0xb6, 0x1c, 0x62, 0xb0, 0x57, 0x5e, 0xd0, 0x93, 0xa9, 0xf2, 0x2d, 0x82,
+ 0xdd, 0x9a, 0x33, 0x70, 0x9d, 0xa1, 0x6d, 0x24, 0x35, 0xd5, 0x21, 0x37, 0x88, 0x86, 0xbe, 0x88,
+ 0x58, 0x63, 0x95, 0x39, 0xb7, 0xaf, 0xd1, 0x95, 0x33, 0x73, 0xe0, 0x5a, 0x34, 0x9e, 0xe9, 0x8b,
+ 0x5f, 0x4a, 0x4f, 0x60, 0x67, 0x65, 0x2b, 0x4c, 0xa2, 0x1d, 0x27, 0x81, 0x56, 0x92, 0x38, 0xfa,
+ 0x39, 0x05, 0xdb, 0xdc, 0x5d, 0x8d, 0x3f, 0xe4, 0x0d, 0xc1, 0xae, 0x27, 0x6e, 0x37, 0x71, 0x43,
+ 0x05, 0x76, 0x34, 0xb5, 0xf3, 0x79, 0x4b, 0x7f, 0xde, 0x53, 0xcf, 0x55, 0xad, 0x23, 0xa0, 0xe8,
+ 0xd0, 0xe6, 0xd0, 0x95, 0xfb, 0xea, 0x08, 0xb6, 0x3b, 0xa7, 0xd5, 0xa6, 0x1a, 0xd3, 0xf1, 0xb1,
+ 0xcc, 0xd1, 0x5c, 0xaf, 0x1f, 0x42, 0xbe, 0xdd, 0x3d, 0xfb, 0xac, 0xd7, 0xee, 0x36, 0x9b, 0x42,
+ 0x5a, 0xda, 0x9f, 0x4c, 0xe5, 0x7b, 0x1c, 0xb9, 0x38, 0xcd, 0x0e, 0x21, 0x5f, 0xed, 0x36, 0x9f,
+ 0xf7, 0xce, 0xbe, 0xd0, 0x6a, 0x42, 0xe6, 0x16, 0x97, 0x98, 0x05, 0x3f, 0x86, 0x5c, 0xad, 0xf5,
+ 0xa2, 0xdd, 0xea, 0x6a, 0x75, 0x21, 0x7b, 0x0b, 0x4b, 0x14, 0xc5, 0x65, 0x00, 0xad, 0x55, 0x4f,
+ 0x32, 0xdc, 0x8c, 0x8c, 0xc9, 0xd7, 0x93, 0x5c, 0xd2, 0xd2, 0xbd, 0xd8, 0x98, 0xbc, 0x6c, 0x55,
+ 0xf1, 0xf7, 0x9b, 0xe2, 0xc6, 0x5f, 0x37, 0x45, 0xf4, 0xcd, 0xbc, 0x88, 0x5e, 0xcf, 0x8b, 0xe8,
+ 0xd7, 0x79, 0x11, 0xfd, 0x39, 0x2f, 0xa2, 0x8b, 0x4d, 0xf6, 0xd7, 0xe9, 0xe9, 0xdf, 0x01, 0x00,
+ 0x00, 0xff, 0xff, 0x02, 0x9d, 0x53, 0x72, 0x78, 0x09, 0x00, 0x00,
+}
--- /dev/null
+syntax = "proto3";
+
+import "gogoproto/gogo.proto";
+
+package networkdb;
+
+option (gogoproto.marshaler_all) = true;
+option (gogoproto.unmarshaler_all) = true;
+option (gogoproto.stringer_all) = true;
+option (gogoproto.gostring_all) = true;
+option (gogoproto.sizer_all) = true;
+option (gogoproto.goproto_stringer_all) = false;
+
+// MessageType enum defines all the core message types that networkdb
+// uses to communicate to peers.
+enum MessageType {
+ option (gogoproto.goproto_enum_prefix) = false;
+ option (gogoproto.enum_customname) = "MessageType";
+
+ INVALID = 0 [(gogoproto.enumvalue_customname) = "MessageTypeInvalid"];
+
+ // NetworkEvent message type is used to communicate network
+ // attachments on the node.
+ NETWORK_EVENT = 1 [(gogoproto.enumvalue_customname) = "MessageTypeNetworkEvent"];
+
+ // TableEvent message type is used to communicate any table
+ // CRUD event that happened on the node.
+ TABLE_EVENT = 2 [(gogoproto.enumvalue_customname) = "MessageTypeTableEvent"];
+
+ // PushPull message type is used to syncup all network
+ // attachments on a peer node either during startup of this
+ // node or with a random peer node periodically thereafter.
+ PUSH_PULL = 3 [(gogoproto.enumvalue_customname) = "MessageTypePushPull"];
+
+ // BulkSync message is used to bulksync the whole networkdb
+ // state with a peer node during startup of this node or with
+ // a random peer node periodically thereafter.
+ BULK_SYNC = 4 [(gogoproto.enumvalue_customname) = "MessageTypeBulkSync"];
+
+ // Compound message type is used to form a compound message
+ // which is a pack of many message of above types, packed into
+ // a single compound message.
+ COMPOUND = 5 [(gogoproto.enumvalue_customname) = "MessageTypeCompound"];
+
+ // NodeEvent message type is used to communicate node
+ // join/leave events in the cluster
+ NODE_EVENT = 6 [(gogoproto.enumvalue_customname) = "MessageTypeNodeEvent"];
+}
+
+// GossipMessage is a basic message header used by all messages types.
+message GossipMessage {
+ MessageType type = 1; // type defines one of the message types defined above.
+ bytes data = 2; // Payload of the message of any type defined here.
+}
+
+// NodeEvent message payload definition.
+message NodeEvent {
+ enum Type {
+ option (gogoproto.goproto_enum_prefix) = false;
+ option (gogoproto.enum_customname) = "Type";
+
+ INVALID = 0 [(gogoproto.enumvalue_customname) = "NodeEventTypeInvalid"];
+ // Join event is generated when this node joins the cluster.
+ JOIN = 1 [(gogoproto.enumvalue_customname) = "NodeEventTypeJoin"];;
+ // Leave event is generated when this node leaves the cluster.
+ LEAVE = 2 [(gogoproto.enumvalue_customname) = "NodeEventTypeLeave"];;
+ }
+
+ Type type = 1;
+
+ // Lamport time using a network lamport clock indicating the
+ // time this event was generated on the node where it was
+ // generated.
+ uint64 l_time = 2 [(gogoproto.customtype) = "github.com/hashicorp/serf/serf.LamportTime", (gogoproto.nullable) = false];
+ // Source node name.
+ string node_name = 3;
+}
+
+// NetworkEvent message payload definition.
+message NetworkEvent {
+ enum Type {
+ option (gogoproto.goproto_enum_prefix) = false;
+ option (gogoproto.enum_customname) = "Type";
+
+ INVALID = 0 [(gogoproto.enumvalue_customname) = "NetworkEventTypeInvalid"];
+ // Join event is generated when this node joins a network.
+ JOIN = 1 [(gogoproto.enumvalue_customname) = "NetworkEventTypeJoin"];;
+ // Leave event is generated when this node leaves a network.
+ LEAVE = 2 [(gogoproto.enumvalue_customname) = "NetworkEventTypeLeave"];;
+ }
+
+ Type type = 1;
+
+ // Lamport time using a network lamport clock indicating the
+ // time this event was generated on the node where it was
+ // generated.
+ uint64 l_time = 2 [(gogoproto.customtype) = "github.com/hashicorp/serf/serf.LamportTime", (gogoproto.nullable) = false];
+ // Source node name.
+ string node_name = 3;
+ // ID of the network for which the event is generated.
+ string network_id = 4 [(gogoproto.customname) = "NetworkID"];
+}
+
+// NetworkEntry for push pull of networks.
+message NetworkEntry {
+ // ID of the network
+ string network_id = 1 [(gogoproto.customname) = "NetworkID"];
+ // Latest lamport time of the network attachment when this
+ // network event was recorded.
+ uint64 l_time = 2 [(gogoproto.customtype) = "github.com/hashicorp/serf/serf.LamportTime", (gogoproto.nullable) = false];
+ // Source node name where this network attachment happened.
+ string node_name = 3 [(gogoproto.customname) = "NodeName"];
+ // Indicates if a leave from this network is in progress.
+ bool leaving = 4;
+}
+
+// NetworkPushpull message payload definition.
+message NetworkPushPull {
+ // Lamport time when this push pull was initiated.
+ uint64 l_time = 1 [(gogoproto.customtype) = "github.com/hashicorp/serf/serf.LamportTime", (gogoproto.nullable) = false];
+ repeated NetworkEntry networks = 2;
+ // Name of the node sending this push pull payload.
+ string node_name = 3 [(gogoproto.customname) = "NodeName"];
+}
+
+// TableEvent message payload definition.
+message TableEvent {
+ enum Type {
+ option (gogoproto.goproto_enum_prefix) = false;
+ option (gogoproto.enum_customname) = "Type";
+
+ INVALID = 0 [(gogoproto.enumvalue_customname) = "TableEventTypeInvalid"];
+ // Create signifies that this table entry was just
+ // created.
+ CREATE = 1 [(gogoproto.enumvalue_customname) = "TableEventTypeCreate"];
+ // Update signifies that this table entry was just
+ // updated.
+ UPDATE = 2 [(gogoproto.enumvalue_customname) = "TableEventTypeUpdate"];
+ // Delete signifies that this table entry was just
+ // updated.
+ DELETE = 3 [(gogoproto.enumvalue_customname) = "TableEventTypeDelete"];
+ }
+
+ Type type = 1;
+ // Lamport time when this event was generated.
+ uint64 l_time = 2 [(gogoproto.customtype) = "github.com/hashicorp/serf/serf.LamportTime", (gogoproto.nullable) = false];
+ // Node name where this event originated.
+ string node_name = 3;
+ // ID of the network to which this table entry belongs.
+ string network_id = 4 [(gogoproto.customname) = "NetworkID"];
+ // Name of the table to which this table entry belongs.
+ string table_name = 5;
+ // Entry key.
+ string key = 6;
+ // Entry value.
+ bytes value = 7;
+ // Residual reap time for the entry before getting deleted in seconds
+ int32 residual_reap_time = 8 [(gogoproto.customname) = "ResidualReapTime"];;
+}
+
+// BulkSync message payload definition.
+message BulkSyncMessage {
+ // Lamport time when this bulk sync was initiated.
+ uint64 l_time = 1 [(gogoproto.customtype) = "github.com/hashicorp/serf/serf.LamportTime", (gogoproto.nullable) = false];
+ // Indicates if this bulksync is a response to a bulk sync
+ // request from a peer node.
+ bool unsolicited = 2;
+ // Name of the node which is producing this bulk sync message.
+ string node_name = 3;
+ // List of network names whose table entries are getting
+ // bulksynced as part of the bulksync.
+ repeated string networks = 4;
+ // Bulksync payload
+ bytes payload = 5;
+}
+
+// Compound message payload definition.
+message CompoundMessage {
+ message SimpleMessage {
+ // Bytestring payload of a message constructed using
+ // other message type definitions.
+ bytes Payload = 1;
+ }
+
+ // A list of simple messages.
+ repeated SimpleMessage messages = 1;
+}
--- /dev/null
+package networkdb
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net"
+ "os"
+ "strings"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/go-events"
+ "github.com/hashicorp/memberlist"
+ "github.com/sirupsen/logrus"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+
+ // this takes care of the incontainer flag
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+var dbPort int32 = 10000
+
+func TestMain(m *testing.M) {
+ ioutil.WriteFile("/proc/sys/net/ipv6/conf/lo/disable_ipv6", []byte{'0', '\n'}, 0644)
+ logrus.SetLevel(logrus.ErrorLevel)
+ os.Exit(m.Run())
+}
+
+func launchNode(t *testing.T, conf Config) *NetworkDB {
+ db, err := New(&conf)
+ assert.NilError(t, err)
+ return db
+}
+
+func createNetworkDBInstances(t *testing.T, num int, namePrefix string, conf *Config) []*NetworkDB {
+ var dbs []*NetworkDB
+ for i := 0; i < num; i++ {
+ localConfig := *conf
+ localConfig.Hostname = fmt.Sprintf("%s%d", namePrefix, i+1)
+ localConfig.NodeID = stringid.TruncateID(stringid.GenerateRandomID())
+ localConfig.BindPort = int(atomic.AddInt32(&dbPort, 1))
+ db := launchNode(t, localConfig)
+ if i != 0 {
+ assert.Check(t, db.Join([]string{fmt.Sprintf("localhost:%d", db.config.BindPort-1)}))
+ }
+
+ dbs = append(dbs, db)
+ }
+
+ // Check that the cluster is properly created
+ for i := 0; i < num; i++ {
+ if num != len(dbs[i].ClusterPeers()) {
+ t.Fatalf("Number of nodes for %s into the cluster does not match %d != %d",
+ dbs[i].config.Hostname, num, len(dbs[i].ClusterPeers()))
+ }
+ }
+
+ return dbs
+}
+
+func closeNetworkDBInstances(dbs []*NetworkDB) {
+ log.Print("Closing DB instances...")
+ for _, db := range dbs {
+ db.Close()
+ }
+}
+
+func (db *NetworkDB) verifyNodeExistence(t *testing.T, node string, present bool) {
+ for i := 0; i < 80; i++ {
+ db.RLock()
+ _, ok := db.nodes[node]
+ db.RUnlock()
+ if present && ok {
+ return
+ }
+
+ if !present && !ok {
+ return
+ }
+
+ time.Sleep(50 * time.Millisecond)
+ }
+
+ t.Error(fmt.Sprintf("%v(%v): Node existence verification for node %s failed", db.config.Hostname, db.config.NodeID, node))
+}
+
+func (db *NetworkDB) verifyNetworkExistence(t *testing.T, node string, id string, present bool) {
+ for i := 0; i < 80; i++ {
+ db.RLock()
+ nn, nnok := db.networks[node]
+ db.RUnlock()
+ if nnok {
+ n, ok := nn[id]
+ if present && ok {
+ return
+ }
+
+ if !present &&
+ ((ok && n.leaving) ||
+ !ok) {
+ return
+ }
+ }
+
+ time.Sleep(50 * time.Millisecond)
+ }
+
+ t.Error("Network existence verification failed")
+}
+
+func (db *NetworkDB) verifyEntryExistence(t *testing.T, tname, nid, key, value string, present bool) {
+ n := 80
+ for i := 0; i < n; i++ {
+ entry, err := db.getEntry(tname, nid, key)
+ if present && err == nil && string(entry.value) == value {
+ return
+ }
+
+ if !present &&
+ ((err == nil && entry.deleting) ||
+ (err != nil)) {
+ return
+ }
+
+ if i == n-1 && !present && err != nil {
+ return
+ }
+
+ time.Sleep(50 * time.Millisecond)
+ }
+
+ t.Error(fmt.Sprintf("Entry existence verification test failed for %v(%v)", db.config.Hostname, db.config.NodeID))
+}
+
+func testWatch(t *testing.T, ch chan events.Event, ev interface{}, tname, nid, key, value string) {
+ select {
+ case rcvdEv := <-ch:
+ assert.Check(t, is.Equal(fmt.Sprintf("%T", rcvdEv), fmt.Sprintf("%T", ev)))
+ switch rcvdEv.(type) {
+ case CreateEvent:
+ assert.Check(t, is.Equal(tname, rcvdEv.(CreateEvent).Table))
+ assert.Check(t, is.Equal(nid, rcvdEv.(CreateEvent).NetworkID))
+ assert.Check(t, is.Equal(key, rcvdEv.(CreateEvent).Key))
+ assert.Check(t, is.Equal(value, string(rcvdEv.(CreateEvent).Value)))
+ case UpdateEvent:
+ assert.Check(t, is.Equal(tname, rcvdEv.(UpdateEvent).Table))
+ assert.Check(t, is.Equal(nid, rcvdEv.(UpdateEvent).NetworkID))
+ assert.Check(t, is.Equal(key, rcvdEv.(UpdateEvent).Key))
+ assert.Check(t, is.Equal(value, string(rcvdEv.(UpdateEvent).Value)))
+ case DeleteEvent:
+ assert.Check(t, is.Equal(tname, rcvdEv.(DeleteEvent).Table))
+ assert.Check(t, is.Equal(nid, rcvdEv.(DeleteEvent).NetworkID))
+ assert.Check(t, is.Equal(key, rcvdEv.(DeleteEvent).Key))
+ }
+ case <-time.After(time.Second):
+ t.Fail()
+ return
+ }
+}
+
+func TestNetworkDBSimple(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 2, "node", DefaultConfig())
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBJoinLeaveNetwork(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 2, "node", DefaultConfig())
+
+ err := dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ dbs[1].verifyNetworkExistence(t, dbs[0].config.NodeID, "network1", true)
+
+ err = dbs[0].LeaveNetwork("network1")
+ assert.NilError(t, err)
+
+ dbs[1].verifyNetworkExistence(t, dbs[0].config.NodeID, "network1", false)
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBJoinLeaveNetworks(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 2, "node", DefaultConfig())
+
+ n := 10
+ for i := 1; i <= n; i++ {
+ err := dbs[0].JoinNetwork(fmt.Sprintf("network0%d", i))
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ err := dbs[1].JoinNetwork(fmt.Sprintf("network1%d", i))
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ dbs[1].verifyNetworkExistence(t, dbs[0].config.NodeID, fmt.Sprintf("network0%d", i), true)
+ }
+
+ for i := 1; i <= n; i++ {
+ dbs[0].verifyNetworkExistence(t, dbs[1].config.NodeID, fmt.Sprintf("network1%d", i), true)
+ }
+
+ for i := 1; i <= n; i++ {
+ err := dbs[0].LeaveNetwork(fmt.Sprintf("network0%d", i))
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ err := dbs[1].LeaveNetwork(fmt.Sprintf("network1%d", i))
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ dbs[1].verifyNetworkExistence(t, dbs[0].config.NodeID, fmt.Sprintf("network0%d", i), false)
+ }
+
+ for i := 1; i <= n; i++ {
+ dbs[0].verifyNetworkExistence(t, dbs[1].config.NodeID, fmt.Sprintf("network1%d", i), false)
+ }
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBCRUDTableEntry(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 3, "node", DefaultConfig())
+
+ err := dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ dbs[1].verifyNetworkExistence(t, dbs[0].config.NodeID, "network1", true)
+
+ err = dbs[1].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ err = dbs[0].CreateEntry("test_table", "network1", "test_key", []byte("test_value"))
+ assert.NilError(t, err)
+
+ dbs[1].verifyEntryExistence(t, "test_table", "network1", "test_key", "test_value", true)
+ dbs[2].verifyEntryExistence(t, "test_table", "network1", "test_key", "test_value", false)
+
+ err = dbs[0].UpdateEntry("test_table", "network1", "test_key", []byte("test_updated_value"))
+ assert.NilError(t, err)
+
+ dbs[1].verifyEntryExistence(t, "test_table", "network1", "test_key", "test_updated_value", true)
+
+ err = dbs[0].DeleteEntry("test_table", "network1", "test_key")
+ assert.NilError(t, err)
+
+ dbs[1].verifyEntryExistence(t, "test_table", "network1", "test_key", "", false)
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBCRUDTableEntries(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 2, "node", DefaultConfig())
+
+ err := dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ dbs[1].verifyNetworkExistence(t, dbs[0].config.NodeID, "network1", true)
+
+ err = dbs[1].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ n := 10
+ for i := 1; i <= n; i++ {
+ err = dbs[0].CreateEntry("test_table", "network1",
+ fmt.Sprintf("test_key0%d", i),
+ []byte(fmt.Sprintf("test_value0%d", i)))
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ err = dbs[1].CreateEntry("test_table", "network1",
+ fmt.Sprintf("test_key1%d", i),
+ []byte(fmt.Sprintf("test_value1%d", i)))
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ dbs[0].verifyEntryExistence(t, "test_table", "network1",
+ fmt.Sprintf("test_key1%d", i),
+ fmt.Sprintf("test_value1%d", i), true)
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ dbs[1].verifyEntryExistence(t, "test_table", "network1",
+ fmt.Sprintf("test_key0%d", i),
+ fmt.Sprintf("test_value0%d", i), true)
+ assert.NilError(t, err)
+ }
+
+ // Verify deletes
+ for i := 1; i <= n; i++ {
+ err = dbs[0].DeleteEntry("test_table", "network1",
+ fmt.Sprintf("test_key0%d", i))
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ err = dbs[1].DeleteEntry("test_table", "network1",
+ fmt.Sprintf("test_key1%d", i))
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ dbs[0].verifyEntryExistence(t, "test_table", "network1",
+ fmt.Sprintf("test_key1%d", i), "", false)
+ assert.NilError(t, err)
+ }
+
+ for i := 1; i <= n; i++ {
+ dbs[1].verifyEntryExistence(t, "test_table", "network1",
+ fmt.Sprintf("test_key0%d", i), "", false)
+ assert.NilError(t, err)
+ }
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBNodeLeave(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 2, "node", DefaultConfig())
+
+ err := dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ err = dbs[1].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ err = dbs[0].CreateEntry("test_table", "network1", "test_key", []byte("test_value"))
+ assert.NilError(t, err)
+
+ dbs[1].verifyEntryExistence(t, "test_table", "network1", "test_key", "test_value", true)
+
+ dbs[0].Close()
+ dbs[1].verifyEntryExistence(t, "test_table", "network1", "test_key", "test_value", false)
+ dbs[1].Close()
+}
+
+func TestNetworkDBWatch(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 2, "node", DefaultConfig())
+ err := dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ err = dbs[1].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ ch, cancel := dbs[1].Watch("", "", "")
+
+ err = dbs[0].CreateEntry("test_table", "network1", "test_key", []byte("test_value"))
+ assert.NilError(t, err)
+
+ testWatch(t, ch.C, CreateEvent{}, "test_table", "network1", "test_key", "test_value")
+
+ err = dbs[0].UpdateEntry("test_table", "network1", "test_key", []byte("test_updated_value"))
+ assert.NilError(t, err)
+
+ testWatch(t, ch.C, UpdateEvent{}, "test_table", "network1", "test_key", "test_updated_value")
+
+ err = dbs[0].DeleteEntry("test_table", "network1", "test_key")
+ assert.NilError(t, err)
+
+ testWatch(t, ch.C, DeleteEvent{}, "test_table", "network1", "test_key", "")
+
+ cancel()
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBBulkSync(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 2, "node", DefaultConfig())
+
+ err := dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ dbs[1].verifyNetworkExistence(t, dbs[0].config.NodeID, "network1", true)
+
+ n := 1000
+ for i := 1; i <= n; i++ {
+ err = dbs[0].CreateEntry("test_table", "network1",
+ fmt.Sprintf("test_key0%d", i),
+ []byte(fmt.Sprintf("test_value0%d", i)))
+ assert.NilError(t, err)
+ }
+
+ err = dbs[1].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ dbs[0].verifyNetworkExistence(t, dbs[1].config.NodeID, "network1", true)
+
+ for i := 1; i <= n; i++ {
+ dbs[1].verifyEntryExistence(t, "test_table", "network1",
+ fmt.Sprintf("test_key0%d", i),
+ fmt.Sprintf("test_value0%d", i), true)
+ assert.NilError(t, err)
+ }
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBCRUDMediumCluster(t *testing.T) {
+ n := 5
+
+ dbs := createNetworkDBInstances(t, n, "node", DefaultConfig())
+
+ for i := 0; i < n; i++ {
+ for j := 0; j < n; j++ {
+ if i == j {
+ continue
+ }
+
+ dbs[i].verifyNodeExistence(t, dbs[j].config.NodeID, true)
+ }
+ }
+
+ for i := 0; i < n; i++ {
+ err := dbs[i].JoinNetwork("network1")
+ assert.NilError(t, err)
+ }
+
+ for i := 0; i < n; i++ {
+ for j := 0; j < n; j++ {
+ dbs[i].verifyNetworkExistence(t, dbs[j].config.NodeID, "network1", true)
+ }
+ }
+
+ err := dbs[0].CreateEntry("test_table", "network1", "test_key", []byte("test_value"))
+ assert.NilError(t, err)
+
+ for i := 1; i < n; i++ {
+ dbs[i].verifyEntryExistence(t, "test_table", "network1", "test_key", "test_value", true)
+ }
+
+ err = dbs[0].UpdateEntry("test_table", "network1", "test_key", []byte("test_updated_value"))
+ assert.NilError(t, err)
+
+ for i := 1; i < n; i++ {
+ dbs[i].verifyEntryExistence(t, "test_table", "network1", "test_key", "test_updated_value", true)
+ }
+
+ err = dbs[0].DeleteEntry("test_table", "network1", "test_key")
+ assert.NilError(t, err)
+
+ for i := 1; i < n; i++ {
+ dbs[i].verifyEntryExistence(t, "test_table", "network1", "test_key", "", false)
+ }
+
+ for i := 1; i < n; i++ {
+ _, err = dbs[i].GetEntry("test_table", "network1", "test_key")
+ assert.Check(t, is.ErrorContains(err, ""))
+ assert.Check(t, strings.Contains(err.Error(), "deleted and pending garbage collection"))
+ }
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBNodeJoinLeaveIteration(t *testing.T) {
+ maxRetry := 5
+ dbs := createNetworkDBInstances(t, 2, "node", DefaultConfig())
+
+ // Single node Join/Leave
+ err := dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ if len(dbs[0].networkNodes["network1"]) != 1 {
+ t.Fatalf("The networkNodes list has to have be 1 instead of %d", len(dbs[0].networkNodes["network1"]))
+ }
+
+ err = dbs[0].LeaveNetwork("network1")
+ assert.NilError(t, err)
+
+ if len(dbs[0].networkNodes["network1"]) != 0 {
+ t.Fatalf("The networkNodes list has to have be 0 instead of %d", len(dbs[0].networkNodes["network1"]))
+ }
+
+ // Multiple nodes Join/Leave
+ err = dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ err = dbs[1].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ // Wait for the propagation on db[0]
+ for i := 0; i < maxRetry; i++ {
+ if len(dbs[0].networkNodes["network1"]) == 2 {
+ break
+ }
+ time.Sleep(1 * time.Second)
+ }
+ if len(dbs[0].networkNodes["network1"]) != 2 {
+ t.Fatalf("The networkNodes list has to have be 2 instead of %d - %v", len(dbs[0].networkNodes["network1"]), dbs[0].networkNodes["network1"])
+ }
+ if n, ok := dbs[0].networks[dbs[0].config.NodeID]["network1"]; !ok || n.leaving {
+ t.Fatalf("The network should not be marked as leaving:%t", n.leaving)
+ }
+
+ // Wait for the propagation on db[1]
+ for i := 0; i < maxRetry; i++ {
+ if len(dbs[1].networkNodes["network1"]) == 2 {
+ break
+ }
+ time.Sleep(1 * time.Second)
+ }
+ if len(dbs[1].networkNodes["network1"]) != 2 {
+ t.Fatalf("The networkNodes list has to have be 2 instead of %d - %v", len(dbs[1].networkNodes["network1"]), dbs[1].networkNodes["network1"])
+ }
+ if n, ok := dbs[1].networks[dbs[1].config.NodeID]["network1"]; !ok || n.leaving {
+ t.Fatalf("The network should not be marked as leaving:%t", n.leaving)
+ }
+
+ // Try a quick leave/join
+ err = dbs[0].LeaveNetwork("network1")
+ assert.NilError(t, err)
+ err = dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ for i := 0; i < maxRetry; i++ {
+ if len(dbs[0].networkNodes["network1"]) == 2 {
+ break
+ }
+ time.Sleep(1 * time.Second)
+ }
+ if len(dbs[0].networkNodes["network1"]) != 2 {
+ t.Fatalf("The networkNodes list has to have be 2 instead of %d - %v", len(dbs[0].networkNodes["network1"]), dbs[0].networkNodes["network1"])
+ }
+
+ for i := 0; i < maxRetry; i++ {
+ if len(dbs[1].networkNodes["network1"]) == 2 {
+ break
+ }
+ time.Sleep(1 * time.Second)
+ }
+ if len(dbs[1].networkNodes["network1"]) != 2 {
+ t.Fatalf("The networkNodes list has to have be 2 instead of %d - %v", len(dbs[1].networkNodes["network1"]), dbs[1].networkNodes["network1"])
+ }
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBGarbageCollection(t *testing.T) {
+ keysWriteDelete := 5
+ config := DefaultConfig()
+ config.reapEntryInterval = 30 * time.Second
+ config.StatsPrintPeriod = 15 * time.Second
+
+ dbs := createNetworkDBInstances(t, 3, "node", config)
+
+ // 2 Nodes join network
+ err := dbs[0].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ err = dbs[1].JoinNetwork("network1")
+ assert.NilError(t, err)
+
+ for i := 0; i < keysWriteDelete; i++ {
+ err = dbs[i%2].CreateEntry("testTable", "network1", "key-"+string(i), []byte("value"))
+ assert.NilError(t, err)
+ }
+ time.Sleep(time.Second)
+ for i := 0; i < keysWriteDelete; i++ {
+ err = dbs[i%2].DeleteEntry("testTable", "network1", "key-"+string(i))
+ assert.NilError(t, err)
+ }
+ for i := 0; i < 2; i++ {
+ assert.Check(t, is.Equal(keysWriteDelete, dbs[i].networks[dbs[i].config.NodeID]["network1"].entriesNumber), "entries number should match")
+ }
+
+ // from this point the timer for the garbage collection started, wait 5 seconds and then join a new node
+ time.Sleep(5 * time.Second)
+
+ err = dbs[2].JoinNetwork("network1")
+ assert.NilError(t, err)
+ for i := 0; i < 3; i++ {
+ assert.Check(t, is.Equal(keysWriteDelete, dbs[i].networks[dbs[i].config.NodeID]["network1"].entriesNumber), "entries number should match")
+ }
+ // at this point the entries should had been all deleted
+ time.Sleep(30 * time.Second)
+ for i := 0; i < 3; i++ {
+ assert.Check(t, is.Equal(0, dbs[i].networks[dbs[i].config.NodeID]["network1"].entriesNumber), "entries should had been garbage collected")
+ }
+
+ // make sure that entries are not coming back
+ time.Sleep(15 * time.Second)
+ for i := 0; i < 3; i++ {
+ assert.Check(t, is.Equal(0, dbs[i].networks[dbs[i].config.NodeID]["network1"].entriesNumber), "entries should had been garbage collected")
+ }
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestFindNode(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 1, "node", DefaultConfig())
+
+ dbs[0].nodes["active"] = &node{Node: memberlist.Node{Name: "active"}}
+ dbs[0].failedNodes["failed"] = &node{Node: memberlist.Node{Name: "failed"}}
+ dbs[0].leftNodes["left"] = &node{Node: memberlist.Node{Name: "left"}}
+
+ // active nodes is 2 because the testing node is in the list
+ assert.Check(t, is.Len(dbs[0].nodes, 2))
+ assert.Check(t, is.Len(dbs[0].failedNodes, 1))
+ assert.Check(t, is.Len(dbs[0].leftNodes, 1))
+
+ n, currState, m := dbs[0].findNode("active")
+ assert.Check(t, n != nil)
+ assert.Check(t, is.Equal("active", n.Name))
+ assert.Check(t, is.Equal(nodeActiveState, currState))
+ assert.Check(t, m != nil)
+ // delete the entry manually
+ delete(m, "active")
+
+ // test if can be still find
+ n, currState, m = dbs[0].findNode("active")
+ assert.Check(t, is.Nil(n))
+ assert.Check(t, is.Equal(nodeNotFound, currState))
+ assert.Check(t, is.Nil(m))
+
+ n, currState, m = dbs[0].findNode("failed")
+ assert.Check(t, n != nil)
+ assert.Check(t, is.Equal("failed", n.Name))
+ assert.Check(t, is.Equal(nodeFailedState, currState))
+ assert.Check(t, m != nil)
+
+ // find and remove
+ n, currState, m = dbs[0].findNode("left")
+ assert.Check(t, n != nil)
+ assert.Check(t, is.Equal("left", n.Name))
+ assert.Check(t, is.Equal(nodeLeftState, currState))
+ assert.Check(t, m != nil)
+ delete(m, "left")
+
+ n, currState, m = dbs[0].findNode("left")
+ assert.Check(t, is.Nil(n))
+ assert.Check(t, is.Equal(nodeNotFound, currState))
+ assert.Check(t, is.Nil(m))
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestChangeNodeState(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 1, "node", DefaultConfig())
+
+ dbs[0].nodes["node1"] = &node{Node: memberlist.Node{Name: "node1"}}
+ dbs[0].nodes["node2"] = &node{Node: memberlist.Node{Name: "node2"}}
+ dbs[0].nodes["node3"] = &node{Node: memberlist.Node{Name: "node3"}}
+
+ // active nodes is 4 because the testing node is in the list
+ assert.Check(t, is.Len(dbs[0].nodes, 4))
+
+ n, currState, m := dbs[0].findNode("node1")
+ assert.Check(t, n != nil)
+ assert.Check(t, is.Equal(nodeActiveState, currState))
+ assert.Check(t, is.Equal("node1", n.Name))
+ assert.Check(t, m != nil)
+
+ // node1 to failed
+ dbs[0].changeNodeState("node1", nodeFailedState)
+
+ n, currState, m = dbs[0].findNode("node1")
+ assert.Check(t, n != nil)
+ assert.Check(t, is.Equal(nodeFailedState, currState))
+ assert.Check(t, is.Equal("node1", n.Name))
+ assert.Check(t, m != nil)
+ assert.Check(t, time.Duration(0) != n.reapTime)
+
+ // node1 back to active
+ dbs[0].changeNodeState("node1", nodeActiveState)
+
+ n, currState, m = dbs[0].findNode("node1")
+ assert.Check(t, n != nil)
+ assert.Check(t, is.Equal(nodeActiveState, currState))
+ assert.Check(t, is.Equal("node1", n.Name))
+ assert.Check(t, m != nil)
+ assert.Check(t, is.Equal(time.Duration(0), n.reapTime))
+
+ // node1 to left
+ dbs[0].changeNodeState("node1", nodeLeftState)
+ dbs[0].changeNodeState("node2", nodeLeftState)
+ dbs[0].changeNodeState("node3", nodeLeftState)
+
+ n, currState, m = dbs[0].findNode("node1")
+ assert.Check(t, n != nil)
+ assert.Check(t, is.Equal(nodeLeftState, currState))
+ assert.Check(t, is.Equal("node1", n.Name))
+ assert.Check(t, m != nil)
+ assert.Check(t, time.Duration(0) != n.reapTime)
+
+ n, currState, m = dbs[0].findNode("node2")
+ assert.Check(t, n != nil)
+ assert.Check(t, is.Equal(nodeLeftState, currState))
+ assert.Check(t, is.Equal("node2", n.Name))
+ assert.Check(t, m != nil)
+ assert.Check(t, time.Duration(0) != n.reapTime)
+
+ n, currState, m = dbs[0].findNode("node3")
+ assert.Check(t, n != nil)
+ assert.Check(t, is.Equal(nodeLeftState, currState))
+ assert.Check(t, is.Equal("node3", n.Name))
+ assert.Check(t, m != nil)
+ assert.Check(t, time.Duration(0) != n.reapTime)
+
+ // active nodes is 1 because the testing node is in the list
+ assert.Check(t, is.Len(dbs[0].nodes, 1))
+ assert.Check(t, is.Len(dbs[0].failedNodes, 0))
+ assert.Check(t, is.Len(dbs[0].leftNodes, 3))
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNodeReincarnation(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 1, "node", DefaultConfig())
+
+ dbs[0].nodes["node1"] = &node{Node: memberlist.Node{Name: "node1", Addr: net.ParseIP("192.168.1.1")}}
+ dbs[0].leftNodes["node2"] = &node{Node: memberlist.Node{Name: "node2", Addr: net.ParseIP("192.168.1.2")}}
+ dbs[0].failedNodes["node3"] = &node{Node: memberlist.Node{Name: "node3", Addr: net.ParseIP("192.168.1.3")}}
+
+ // active nodes is 2 because the testing node is in the list
+ assert.Check(t, is.Len(dbs[0].nodes, 2))
+ assert.Check(t, is.Len(dbs[0].failedNodes, 1))
+ assert.Check(t, is.Len(dbs[0].leftNodes, 1))
+
+ b := dbs[0].purgeReincarnation(&memberlist.Node{Name: "node4", Addr: net.ParseIP("192.168.1.1")})
+ assert.Check(t, b)
+ dbs[0].nodes["node4"] = &node{Node: memberlist.Node{Name: "node4", Addr: net.ParseIP("192.168.1.1")}}
+
+ b = dbs[0].purgeReincarnation(&memberlist.Node{Name: "node5", Addr: net.ParseIP("192.168.1.2")})
+ assert.Check(t, b)
+ dbs[0].nodes["node5"] = &node{Node: memberlist.Node{Name: "node5", Addr: net.ParseIP("192.168.1.1")}}
+
+ b = dbs[0].purgeReincarnation(&memberlist.Node{Name: "node6", Addr: net.ParseIP("192.168.1.3")})
+ assert.Check(t, b)
+ dbs[0].nodes["node6"] = &node{Node: memberlist.Node{Name: "node6", Addr: net.ParseIP("192.168.1.1")}}
+
+ b = dbs[0].purgeReincarnation(&memberlist.Node{Name: "node6", Addr: net.ParseIP("192.168.1.10")})
+ assert.Check(t, !b)
+
+ // active nodes is 1 because the testing node is in the list
+ assert.Check(t, is.Len(dbs[0].nodes, 4))
+ assert.Check(t, is.Len(dbs[0].failedNodes, 0))
+ assert.Check(t, is.Len(dbs[0].leftNodes, 3))
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestParallelCreate(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 1, "node", DefaultConfig())
+
+ startCh := make(chan int)
+ doneCh := make(chan error)
+ var success int32
+ for i := 0; i < 20; i++ {
+ go func() {
+ <-startCh
+ err := dbs[0].CreateEntry("testTable", "testNetwork", "key", []byte("value"))
+ if err == nil {
+ atomic.AddInt32(&success, 1)
+ }
+ doneCh <- err
+ }()
+ }
+
+ close(startCh)
+
+ for i := 0; i < 20; i++ {
+ <-doneCh
+ }
+ close(doneCh)
+ // Only 1 write should have succeeded
+ assert.Check(t, is.Equal(int32(1), success))
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestParallelDelete(t *testing.T) {
+ dbs := createNetworkDBInstances(t, 1, "node", DefaultConfig())
+
+ err := dbs[0].CreateEntry("testTable", "testNetwork", "key", []byte("value"))
+ assert.NilError(t, err)
+
+ startCh := make(chan int)
+ doneCh := make(chan error)
+ var success int32
+ for i := 0; i < 20; i++ {
+ go func() {
+ <-startCh
+ err := dbs[0].DeleteEntry("testTable", "testNetwork", "key")
+ if err == nil {
+ atomic.AddInt32(&success, 1)
+ }
+ doneCh <- err
+ }()
+ }
+
+ close(startCh)
+
+ for i := 0; i < 20; i++ {
+ <-doneCh
+ }
+ close(doneCh)
+ // Only 1 write should have succeeded
+ assert.Check(t, is.Equal(int32(1), success))
+
+ closeNetworkDBInstances(dbs)
+}
+
+func TestNetworkDBIslands(t *testing.T) {
+ logrus.SetLevel(logrus.DebugLevel)
+ dbs := createNetworkDBInstances(t, 5, "node", DefaultConfig())
+
+ // Get the node IP used currently
+ node, _ := dbs[0].nodes[dbs[0].config.NodeID]
+ baseIPStr := node.Addr.String()
+ // Node 0,1,2 are going to be the 3 bootstrap nodes
+ members := []string{fmt.Sprintf("%s:%d", baseIPStr, dbs[0].config.BindPort),
+ fmt.Sprintf("%s:%d", baseIPStr, dbs[1].config.BindPort),
+ fmt.Sprintf("%s:%d", baseIPStr, dbs[2].config.BindPort)}
+ // Rejoining will update the list of the bootstrap members
+ for i := 3; i < 5; i++ {
+ assert.Check(t, dbs[i].Join(members))
+ }
+
+ // Now the 3 bootstrap nodes will cleanly leave, and will be properly removed from the other 2 nodes
+ for i := 0; i < 3; i++ {
+ logrus.Infof("node %d leaving", i)
+ dbs[i].Close()
+ time.Sleep(2 * time.Second)
+ }
+
+ // Give some time to let the system propagate the messages and free up the ports
+ time.Sleep(10 * time.Second)
+
+ // Verify that the nodes are actually all gone and marked appropiately
+ for i := 3; i < 5; i++ {
+ assert.Check(t, is.Len(dbs[i].leftNodes, 3))
+ assert.Check(t, is.Len(dbs[i].failedNodes, 0))
+ }
+
+ // Spawn again the first 3 nodes with different names but same IP:port
+ for i := 0; i < 3; i++ {
+ logrus.Infof("node %d coming back", i)
+ dbs[i].config.NodeID = stringid.TruncateID(stringid.GenerateRandomID())
+ dbs[i] = launchNode(t, *dbs[i].config)
+ time.Sleep(2 * time.Second)
+ }
+
+ // Give some time for the reconnect routine to run, it runs every 60s
+ time.Sleep(50 * time.Second)
+
+ // Verify that the cluster is again all connected. Note that the 3 previous node did not do any join
+ for i := 0; i < 5; i++ {
+ assert.Check(t, is.Len(dbs[i].nodes, 5))
+ assert.Check(t, is.Len(dbs[i].failedNodes, 0))
+ if i < 3 {
+ // nodes from 0 to 3 has no left nodes
+ assert.Check(t, is.Len(dbs[i].leftNodes, 0))
+ } else {
+ // nodes from 4 to 5 has the 3 previous left nodes
+ assert.Check(t, is.Len(dbs[i].leftNodes, 3))
+ }
+ }
+}
--- /dev/null
+package networkdb
+
+import (
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/docker/libnetwork/diagnostic"
+ "github.com/docker/libnetwork/internal/caller"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ missingParameter = "missing parameter"
+ dbNotAvailable = "database not available"
+)
+
+// NetDbPaths2Func TODO
+var NetDbPaths2Func = map[string]diagnostic.HTTPHandlerFunc{
+ "/join": dbJoin,
+ "/networkpeers": dbPeers,
+ "/clusterpeers": dbClusterPeers,
+ "/joinnetwork": dbJoinNetwork,
+ "/leavenetwork": dbLeaveNetwork,
+ "/createentry": dbCreateEntry,
+ "/updateentry": dbUpdateEntry,
+ "/deleteentry": dbDeleteEntry,
+ "/getentry": dbGetEntry,
+ "/gettable": dbGetTable,
+ "/networkstats": dbNetworkStats,
+}
+
+func dbJoin(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ _, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("join cluster")
+
+ if len(r.Form["members"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?members=ip1,ip2,...", r.URL.Path))
+ log.Error("join cluster failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ err := nDB.Join(strings.Split(r.Form["members"][0], ","))
+ if err != nil {
+ rsp := diagnostic.FailCommand(fmt.Errorf("%s error in the DB join %s", r.URL.Path, err))
+ log.WithError(err).Error("join cluster failed")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ log.Info("join cluster done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(nil), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbPeers(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ _, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("network peers")
+
+ if len(r.Form["nid"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?nid=test", r.URL.Path))
+ log.Error("network peers failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ peers := nDB.Peers(r.Form["nid"][0])
+ rsp := &diagnostic.TableObj{Length: len(peers)}
+ for i, peerInfo := range peers {
+ if peerInfo.IP == "unknown" {
+ rsp.Elements = append(rsp.Elements, &diagnostic.PeerEntryObj{Index: i, Name: "orphan-" + peerInfo.Name, IP: peerInfo.IP})
+ } else {
+ rsp.Elements = append(rsp.Elements, &diagnostic.PeerEntryObj{Index: i, Name: peerInfo.Name, IP: peerInfo.IP})
+ }
+ }
+ log.WithField("response", fmt.Sprintf("%+v", rsp)).Info("network peers done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(rsp), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbClusterPeers(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ _, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("cluster peers")
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ peers := nDB.ClusterPeers()
+ rsp := &diagnostic.TableObj{Length: len(peers)}
+ for i, peerInfo := range peers {
+ rsp.Elements = append(rsp.Elements, &diagnostic.PeerEntryObj{Index: i, Name: peerInfo.Name, IP: peerInfo.IP})
+ }
+ log.WithField("response", fmt.Sprintf("%+v", rsp)).Info("cluster peers done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(rsp), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbCreateEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ unsafe, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("create entry")
+
+ if len(r.Form["tname"]) < 1 ||
+ len(r.Form["nid"]) < 1 ||
+ len(r.Form["key"]) < 1 ||
+ len(r.Form["value"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k&value=v", r.URL.Path))
+ log.Error("create entry failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ tname := r.Form["tname"][0]
+ nid := r.Form["nid"][0]
+ key := r.Form["key"][0]
+ value := r.Form["value"][0]
+ decodedValue := []byte(value)
+ if !unsafe {
+ var err error
+ decodedValue, err = base64.StdEncoding.DecodeString(value)
+ if err != nil {
+ log.WithError(err).Error("create entry failed")
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(err), json)
+ return
+ }
+ }
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ if err := nDB.CreateEntry(tname, nid, key, decodedValue); err != nil {
+ rsp := diagnostic.FailCommand(err)
+ diagnostic.HTTPReply(w, rsp, json)
+ log.WithError(err).Error("create entry failed")
+ return
+ }
+ log.Info("create entry done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(nil), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbUpdateEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ unsafe, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("update entry")
+
+ if len(r.Form["tname"]) < 1 ||
+ len(r.Form["nid"]) < 1 ||
+ len(r.Form["key"]) < 1 ||
+ len(r.Form["value"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k&value=v", r.URL.Path))
+ log.Error("update entry failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ tname := r.Form["tname"][0]
+ nid := r.Form["nid"][0]
+ key := r.Form["key"][0]
+ value := r.Form["value"][0]
+ decodedValue := []byte(value)
+ if !unsafe {
+ var err error
+ decodedValue, err = base64.StdEncoding.DecodeString(value)
+ if err != nil {
+ log.WithError(err).Error("update entry failed")
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(err), json)
+ return
+ }
+ }
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ if err := nDB.UpdateEntry(tname, nid, key, decodedValue); err != nil {
+ log.WithError(err).Error("update entry failed")
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(err), json)
+ return
+ }
+ log.Info("update entry done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(nil), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbDeleteEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ _, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("delete entry")
+
+ if len(r.Form["tname"]) < 1 ||
+ len(r.Form["nid"]) < 1 ||
+ len(r.Form["key"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k", r.URL.Path))
+ log.Error("delete entry failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ tname := r.Form["tname"][0]
+ nid := r.Form["nid"][0]
+ key := r.Form["key"][0]
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ err := nDB.DeleteEntry(tname, nid, key)
+ if err != nil {
+ log.WithError(err).Error("delete entry failed")
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(err), json)
+ return
+ }
+ log.Info("delete entry done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(nil), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbGetEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ unsafe, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("get entry")
+
+ if len(r.Form["tname"]) < 1 ||
+ len(r.Form["nid"]) < 1 ||
+ len(r.Form["key"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k", r.URL.Path))
+ log.Error("get entry failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ tname := r.Form["tname"][0]
+ nid := r.Form["nid"][0]
+ key := r.Form["key"][0]
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ value, err := nDB.GetEntry(tname, nid, key)
+ if err != nil {
+ log.WithError(err).Error("get entry failed")
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(err), json)
+ return
+ }
+
+ var encodedValue string
+ if unsafe {
+ encodedValue = string(value)
+ } else {
+ encodedValue = base64.StdEncoding.EncodeToString(value)
+ }
+
+ rsp := &diagnostic.TableEntryObj{Key: key, Value: encodedValue}
+ log.WithField("response", fmt.Sprintf("%+v", rsp)).Info("get entry done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(rsp), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbJoinNetwork(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ _, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("join network")
+
+ if len(r.Form["nid"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?nid=network_id", r.URL.Path))
+ log.Error("join network failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ nid := r.Form["nid"][0]
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ if err := nDB.JoinNetwork(nid); err != nil {
+ log.WithError(err).Error("join network failed")
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(err), json)
+ return
+ }
+ log.Info("join network done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(nil), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbLeaveNetwork(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ _, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("leave network")
+
+ if len(r.Form["nid"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?nid=network_id", r.URL.Path))
+ log.Error("leave network failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ nid := r.Form["nid"][0]
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ if err := nDB.LeaveNetwork(nid); err != nil {
+ log.WithError(err).Error("leave network failed")
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(err), json)
+ return
+ }
+ log.Info("leave network done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(nil), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbGetTable(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ unsafe, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("get table")
+
+ if len(r.Form["tname"]) < 1 ||
+ len(r.Form["nid"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id", r.URL.Path))
+ log.Error("get table failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ tname := r.Form["tname"][0]
+ nid := r.Form["nid"][0]
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ table := nDB.GetTableByNetwork(tname, nid)
+ rsp := &diagnostic.TableObj{Length: len(table)}
+ var i = 0
+ for k, v := range table {
+ var encodedValue string
+ if unsafe {
+ encodedValue = string(v.Value)
+ } else {
+ encodedValue = base64.StdEncoding.EncodeToString(v.Value)
+ }
+ rsp.Elements = append(rsp.Elements,
+ &diagnostic.TableEntryObj{
+ Index: i,
+ Key: k,
+ Value: encodedValue,
+ Owner: v.owner,
+ })
+ i++
+ }
+ log.WithField("response", fmt.Sprintf("%+v", rsp)).Info("get table done")
+ diagnostic.HTTPReply(w, diagnostic.CommandSucceed(rsp), json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
+
+func dbNetworkStats(ctx interface{}, w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ diagnostic.DebugHTTPForm(r)
+ _, json := diagnostic.ParseHTTPFormOptions(r)
+
+ // audit logs
+ log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
+ log.Info("network stats")
+
+ if len(r.Form["nid"]) < 1 {
+ rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?nid=test", r.URL.Path))
+ log.Error("network stats failed, wrong input")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+
+ nDB, ok := ctx.(*NetworkDB)
+ if ok {
+ nDB.RLock()
+ networks := nDB.networks[nDB.config.NodeID]
+ network, ok := networks[r.Form["nid"][0]]
+
+ entries := -1
+ qLen := -1
+ if ok {
+ entries = network.entriesNumber
+ qLen = network.tableBroadcasts.NumQueued()
+ }
+ nDB.RUnlock()
+
+ rsp := diagnostic.CommandSucceed(&diagnostic.NetworkStatsResult{Entries: entries, QueueLen: qLen})
+ log.WithField("response", fmt.Sprintf("%+v", rsp)).Info("network stats done")
+ diagnostic.HTTPReply(w, rsp, json)
+ return
+ }
+ diagnostic.HTTPReply(w, diagnostic.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
+}
--- /dev/null
+package networkdb
+
+import (
+ "fmt"
+
+ "github.com/hashicorp/memberlist"
+ "github.com/sirupsen/logrus"
+)
+
+type nodeState int
+
+const (
+ nodeNotFound nodeState = -1
+ nodeActiveState nodeState = 0
+ nodeLeftState nodeState = 1
+ nodeFailedState nodeState = 2
+)
+
+var nodeStateName = map[nodeState]string{
+ -1: "NodeNotFound",
+ 0: "NodeActive",
+ 1: "NodeLeft",
+ 2: "NodeFailed",
+}
+
+// findNode search the node into the 3 node lists and returns the node pointer and the list
+// where it got found
+func (nDB *NetworkDB) findNode(nodeName string) (*node, nodeState, map[string]*node) {
+ for i, nodes := range []map[string]*node{
+ nDB.nodes,
+ nDB.leftNodes,
+ nDB.failedNodes,
+ } {
+ if n, ok := nodes[nodeName]; ok {
+ return n, nodeState(i), nodes
+ }
+ }
+ return nil, nodeNotFound, nil
+}
+
+// changeNodeState changes the state of the node specified, returns true if the node was moved,
+// false if there was no need to change the node state. Error will be returned if the node does not
+// exists
+func (nDB *NetworkDB) changeNodeState(nodeName string, newState nodeState) (bool, error) {
+ n, currState, m := nDB.findNode(nodeName)
+ if n == nil {
+ return false, fmt.Errorf("node %s not found", nodeName)
+ }
+
+ switch newState {
+ case nodeActiveState:
+ if currState == nodeActiveState {
+ return false, nil
+ }
+
+ delete(m, nodeName)
+ // reset the node reap time
+ n.reapTime = 0
+ nDB.nodes[nodeName] = n
+ case nodeLeftState:
+ if currState == nodeLeftState {
+ return false, nil
+ }
+
+ delete(m, nodeName)
+ nDB.leftNodes[nodeName] = n
+ case nodeFailedState:
+ if currState == nodeFailedState {
+ return false, nil
+ }
+
+ delete(m, nodeName)
+ nDB.failedNodes[nodeName] = n
+ }
+
+ logrus.Infof("Node %s change state %s --> %s", nodeName, nodeStateName[currState], nodeStateName[newState])
+
+ if newState == nodeLeftState || newState == nodeFailedState {
+ // set the node reap time, if not already set
+ // It is possible that a node passes from failed to left and the reaptime was already set so keep that value
+ if n.reapTime == 0 {
+ n.reapTime = nodeReapInterval
+ }
+ // The node leave or fails, delete all the entries created by it.
+ // If the node was temporary down, deleting the entries will guarantee that the CREATE events will be accepted
+ // If the node instead left because was going down, then it makes sense to just delete all its state
+ nDB.deleteNodeFromNetworks(n.Name)
+ nDB.deleteNodeTableEntries(n.Name)
+ }
+
+ return true, nil
+}
+
+func (nDB *NetworkDB) purgeReincarnation(mn *memberlist.Node) bool {
+ for name, node := range nDB.nodes {
+ if node.Addr.Equal(mn.Addr) && node.Port == mn.Port && mn.Name != name {
+ logrus.Infof("Node %s/%s, is the new incarnation of the active node %s/%s", mn.Name, mn.Addr, name, node.Addr)
+ nDB.changeNodeState(name, nodeLeftState)
+ return true
+ }
+ }
+
+ for name, node := range nDB.failedNodes {
+ if node.Addr.Equal(mn.Addr) && node.Port == mn.Port && mn.Name != name {
+ logrus.Infof("Node %s/%s, is the new incarnation of the failed node %s/%s", mn.Name, mn.Addr, name, node.Addr)
+ nDB.changeNodeState(name, nodeLeftState)
+ return true
+ }
+ }
+
+ for name, node := range nDB.leftNodes {
+ if node.Addr.Equal(mn.Addr) && node.Port == mn.Port && mn.Name != name {
+ logrus.Infof("Node %s/%s, is the new incarnation of the shutdown node %s/%s", mn.Name, mn.Addr, name, node.Addr)
+ nDB.changeNodeState(name, nodeLeftState)
+ return true
+ }
+ }
+
+ return false
+}
--- /dev/null
+package networkdb
+
+import (
+ "net"
+
+ "github.com/docker/go-events"
+)
+
+type opType uint8
+
+const (
+ opCreate opType = 1 + iota
+ opUpdate
+ opDelete
+)
+
+type event struct {
+ Table string
+ NetworkID string
+ Key string
+ Value []byte
+}
+
+// NodeTable represents table event for node join and leave
+const NodeTable = "NodeTable"
+
+// NodeAddr represents the value carried for node event in NodeTable
+type NodeAddr struct {
+ Addr net.IP
+}
+
+// CreateEvent generates a table entry create event to the watchers
+type CreateEvent event
+
+// UpdateEvent generates a table entry update event to the watchers
+type UpdateEvent event
+
+// DeleteEvent generates a table entry delete event to the watchers
+type DeleteEvent event
+
+// Watch creates a watcher with filters for a particular table or
+// network or key or any combination of the tuple. If any of the
+// filter is an empty string it acts as a wildcard for that
+// field. Watch returns a channel of events, where the events will be
+// sent.
+func (nDB *NetworkDB) Watch(tname, nid, key string) (*events.Channel, func()) {
+ var matcher events.Matcher
+
+ if tname != "" || nid != "" || key != "" {
+ matcher = events.MatcherFunc(func(ev events.Event) bool {
+ var evt event
+ switch ev := ev.(type) {
+ case CreateEvent:
+ evt = event(ev)
+ case UpdateEvent:
+ evt = event(ev)
+ case DeleteEvent:
+ evt = event(ev)
+ }
+
+ if tname != "" && evt.Table != tname {
+ return false
+ }
+
+ if nid != "" && evt.NetworkID != nid {
+ return false
+ }
+
+ if key != "" && evt.Key != key {
+ return false
+ }
+
+ return true
+ })
+ }
+
+ ch := events.NewChannel(0)
+ sink := events.Sink(events.NewQueue(ch))
+
+ if matcher != nil {
+ sink = events.NewFilter(sink, matcher)
+ }
+
+ nDB.broadcaster.Add(sink)
+ return ch, func() {
+ nDB.broadcaster.Remove(sink)
+ ch.Close()
+ sink.Close()
+ }
+}
+
+func makeEvent(op opType, tname, nid, key string, value []byte) events.Event {
+ ev := event{
+ Table: tname,
+ NetworkID: nid,
+ Key: key,
+ Value: value,
+ }
+
+ switch op {
+ case opCreate:
+ return CreateEvent(ev)
+ case opUpdate:
+ return UpdateEvent(ev)
+ case opDelete:
+ return DeleteEvent(ev)
+ }
+
+ return nil
+}
--- /dev/null
+package ns
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+ "github.com/vishvananda/netns"
+)
+
+var (
+ initNs netns.NsHandle
+ initNl *netlink.Handle
+ initOnce sync.Once
+ // NetlinkSocketsTimeout represents the default timeout duration for the sockets
+ NetlinkSocketsTimeout = 3 * time.Second
+)
+
+// Init initializes a new network namespace
+func Init() {
+ var err error
+ initNs, err = netns.Get()
+ if err != nil {
+ logrus.Errorf("could not get initial namespace: %v", err)
+ }
+ initNl, err = netlink.NewHandle(getSupportedNlFamilies()...)
+ if err != nil {
+ logrus.Errorf("could not create netlink handle on initial namespace: %v", err)
+ }
+ err = initNl.SetSocketTimeout(NetlinkSocketsTimeout)
+ if err != nil {
+ logrus.Warnf("Failed to set the timeout on the default netlink handle sockets: %v", err)
+ }
+}
+
+// SetNamespace sets the initial namespace handler
+func SetNamespace() error {
+ initOnce.Do(Init)
+ if err := netns.Set(initNs); err != nil {
+ linkInfo, linkErr := getLink()
+ if linkErr != nil {
+ linkInfo = linkErr.Error()
+ }
+ return fmt.Errorf("failed to set to initial namespace, %v, initns fd %d: %v", linkInfo, initNs, err)
+ }
+ return nil
+}
+
+// ParseHandlerInt transforms the namespace handler into an integer
+func ParseHandlerInt() int {
+ return int(getHandler())
+}
+
+// GetHandler returns the namespace handler
+func getHandler() netns.NsHandle {
+ initOnce.Do(Init)
+ return initNs
+}
+
+func getLink() (string, error) {
+ return os.Readlink(fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), syscall.Gettid()))
+}
+
+// NlHandle returns the netlink handler
+func NlHandle() *netlink.Handle {
+ initOnce.Do(Init)
+ return initNl
+}
+
+func getSupportedNlFamilies() []int {
+ fams := []int{syscall.NETLINK_ROUTE}
+ // NETLINK_XFRM test
+ if err := loadXfrmModules(); err != nil {
+ if checkXfrmSocket() != nil {
+ logrus.Warnf("Could not load necessary modules for IPSEC rules: %v", err)
+ } else {
+ fams = append(fams, syscall.NETLINK_XFRM)
+ }
+ } else {
+ fams = append(fams, syscall.NETLINK_XFRM)
+ }
+ // NETLINK_NETFILTER test
+ if err := loadNfConntrackModules(); err != nil {
+ if checkNfSocket() != nil {
+ logrus.Warnf("Could not load necessary modules for Conntrack: %v", err)
+ } else {
+ fams = append(fams, syscall.NETLINK_NETFILTER)
+ }
+ } else {
+ fams = append(fams, syscall.NETLINK_NETFILTER)
+ }
+
+ return fams
+}
+
+func loadXfrmModules() error {
+ if out, err := exec.Command("modprobe", "-va", "xfrm_user").CombinedOutput(); err != nil {
+ return fmt.Errorf("Running modprobe xfrm_user failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
+ }
+ if out, err := exec.Command("modprobe", "-va", "xfrm_algo").CombinedOutput(); err != nil {
+ return fmt.Errorf("Running modprobe xfrm_algo failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
+ }
+ return nil
+}
+
+// API check on required xfrm modules (xfrm_user, xfrm_algo)
+func checkXfrmSocket() error {
+ fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_XFRM)
+ if err != nil {
+ return err
+ }
+ syscall.Close(fd)
+ return nil
+}
+
+func loadNfConntrackModules() error {
+ if out, err := exec.Command("modprobe", "-va", "nf_conntrack").CombinedOutput(); err != nil {
+ return fmt.Errorf("Running modprobe nf_conntrack failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
+ }
+ if out, err := exec.Command("modprobe", "-va", "nf_conntrack_netlink").CombinedOutput(); err != nil {
+ return fmt.Errorf("Running modprobe nf_conntrack_netlink failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
+ }
+ return nil
+}
+
+// API check on required nf_conntrack* modules (nf_conntrack, nf_conntrack_netlink)
+func checkNfSocket() error {
+ fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_NETFILTER)
+ if err != nil {
+ return err
+ }
+ syscall.Close(fd)
+ return nil
+}
--- /dev/null
+// Package options provides a way to pass unstructured sets of options to a
+// component expecting a strongly-typed configuration structure.
+package options
+
+import (
+ "fmt"
+ "reflect"
+)
+
+// NoSuchFieldError is the error returned when the generic parameters hold a
+// value for a field absent from the destination structure.
+type NoSuchFieldError struct {
+ Field string
+ Type string
+}
+
+func (e NoSuchFieldError) Error() string {
+ return fmt.Sprintf("no field %q in type %q", e.Field, e.Type)
+}
+
+// CannotSetFieldError is the error returned when the generic parameters hold a
+// value for a field that cannot be set in the destination structure.
+type CannotSetFieldError struct {
+ Field string
+ Type string
+}
+
+func (e CannotSetFieldError) Error() string {
+ return fmt.Sprintf("cannot set field %q of type %q", e.Field, e.Type)
+}
+
+// TypeMismatchError is the error returned when the type of the generic value
+// for a field mismatches the type of the destination structure.
+type TypeMismatchError struct {
+ Field string
+ ExpectType string
+ ActualType string
+}
+
+func (e TypeMismatchError) Error() string {
+ return fmt.Sprintf("type mismatch, field %s require type %v, actual type %v", e.Field, e.ExpectType, e.ActualType)
+}
+
+// Generic is a basic type to store arbitrary settings.
+type Generic map[string]interface{}
+
+// NewGeneric returns a new Generic instance.
+func NewGeneric() Generic {
+ return make(Generic)
+}
+
+// GenerateFromModel takes the generic options, and tries to build a new
+// instance of the model's type by matching keys from the generic options to
+// fields in the model.
+//
+// The return value is of the same type than the model (including a potential
+// pointer qualifier).
+func GenerateFromModel(options Generic, model interface{}) (interface{}, error) {
+ modType := reflect.TypeOf(model)
+
+ // If the model is of pointer type, we need to dereference for New.
+ resType := reflect.TypeOf(model)
+ if modType.Kind() == reflect.Ptr {
+ resType = resType.Elem()
+ }
+
+ // Populate the result structure with the generic layout content.
+ res := reflect.New(resType)
+ for name, value := range options {
+ field := res.Elem().FieldByName(name)
+ if !field.IsValid() {
+ return nil, NoSuchFieldError{name, resType.String()}
+ }
+ if !field.CanSet() {
+ return nil, CannotSetFieldError{name, resType.String()}
+ }
+ if reflect.TypeOf(value) != field.Type() {
+ return nil, TypeMismatchError{name, field.Type().String(), reflect.TypeOf(value).String()}
+ }
+ field.Set(reflect.ValueOf(value))
+ }
+
+ // If the model is not of pointer type, return content of the result.
+ if modType.Kind() == reflect.Ptr {
+ return res.Interface(), nil
+ }
+ return res.Elem().Interface(), nil
+}
--- /dev/null
+package options
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func TestGenerate(t *testing.T) {
+ gen := NewGeneric()
+ gen["Int"] = 1
+ gen["Rune"] = 'b'
+ gen["Float64"] = 2.0
+
+ type Model struct {
+ Int int
+ Rune rune
+ Float64 float64
+ }
+
+ result, err := GenerateFromModel(gen, Model{})
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cast, ok := result.(Model)
+ if !ok {
+ t.Fatalf("result has unexpected type %s", reflect.TypeOf(result))
+ }
+ if expected := 1; cast.Int != expected {
+ t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Int)
+ }
+ if expected := 'b'; cast.Rune != expected {
+ t.Fatalf("wrong value for field Rune: expected %v, got %v", expected, cast.Rune)
+ }
+ if expected := 2.0; cast.Float64 != expected {
+ t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Float64)
+ }
+}
+
+func TestGeneratePtr(t *testing.T) {
+ gen := NewGeneric()
+ gen["Int"] = 1
+ gen["Rune"] = 'b'
+ gen["Float64"] = 2.0
+
+ type Model struct {
+ Int int
+ Rune rune
+ Float64 float64
+ }
+
+ result, err := GenerateFromModel(gen, &Model{})
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cast, ok := result.(*Model)
+ if !ok {
+ t.Fatalf("result has unexpected type %s", reflect.TypeOf(result))
+ }
+ if expected := 1; cast.Int != expected {
+ t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Int)
+ }
+ if expected := 'b'; cast.Rune != expected {
+ t.Fatalf("wrong value for field Rune: expected %v, got %v", expected, cast.Rune)
+ }
+ if expected := 2.0; cast.Float64 != expected {
+ t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Float64)
+ }
+}
+
+func TestGenerateMissingField(t *testing.T) {
+ type Model struct{}
+ _, err := GenerateFromModel(Generic{"foo": "bar"}, Model{})
+
+ if _, ok := err.(NoSuchFieldError); !ok {
+ t.Fatalf("expected NoSuchFieldError, got %#v", err)
+ } else if expected := "no field"; !strings.Contains(err.Error(), expected) {
+ t.Fatalf("expected %q in error message, got %s", expected, err.Error())
+ }
+}
+
+func TestFieldCannotBeSet(t *testing.T) {
+ type Model struct{ foo int }
+ _, err := GenerateFromModel(Generic{"foo": "bar"}, Model{})
+
+ if _, ok := err.(CannotSetFieldError); !ok {
+ t.Fatalf("expected CannotSetFieldError, got %#v", err)
+ } else if expected := "cannot set field"; !strings.Contains(err.Error(), expected) {
+ t.Fatalf("expected %q in error message, got %s", expected, err.Error())
+ }
+}
+
+func TestTypeMismatchError(t *testing.T) {
+ type Model struct{ Foo int }
+ _, err := GenerateFromModel(Generic{"Foo": "bar"}, Model{})
+
+ if _, ok := err.(TypeMismatchError); !ok {
+ t.Fatalf("expected TypeMismatchError, got %#v", err)
+ } else if expected := "type mismatch"; !strings.Contains(err.Error(), expected) {
+ t.Fatalf("expected %q in error message, got %s", expected, err.Error())
+ }
+}
--- /dev/null
+package osl
+
+// IfaceOption is a function option type to set interface options
+type IfaceOption func()
--- /dev/null
+package osl
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+ "github.com/vishvananda/netns"
+)
+
+// IfaceOption is a function option type to set interface options
+type IfaceOption func(i *nwIface)
+
+type nwIface struct {
+ srcName string
+ dstName string
+ master string
+ dstMaster string
+ mac net.HardwareAddr
+ address *net.IPNet
+ addressIPv6 *net.IPNet
+ llAddrs []*net.IPNet
+ routes []*net.IPNet
+ bridge bool
+ ns *networkNamespace
+ sync.Mutex
+}
+
+func (i *nwIface) SrcName() string {
+ i.Lock()
+ defer i.Unlock()
+
+ return i.srcName
+}
+
+func (i *nwIface) DstName() string {
+ i.Lock()
+ defer i.Unlock()
+
+ return i.dstName
+}
+
+func (i *nwIface) DstMaster() string {
+ i.Lock()
+ defer i.Unlock()
+
+ return i.dstMaster
+}
+
+func (i *nwIface) Bridge() bool {
+ i.Lock()
+ defer i.Unlock()
+
+ return i.bridge
+}
+
+func (i *nwIface) Master() string {
+ i.Lock()
+ defer i.Unlock()
+
+ return i.master
+}
+
+func (i *nwIface) MacAddress() net.HardwareAddr {
+ i.Lock()
+ defer i.Unlock()
+
+ return types.GetMacCopy(i.mac)
+}
+
+func (i *nwIface) Address() *net.IPNet {
+ i.Lock()
+ defer i.Unlock()
+
+ return types.GetIPNetCopy(i.address)
+}
+
+func (i *nwIface) AddressIPv6() *net.IPNet {
+ i.Lock()
+ defer i.Unlock()
+
+ return types.GetIPNetCopy(i.addressIPv6)
+}
+
+func (i *nwIface) LinkLocalAddresses() []*net.IPNet {
+ i.Lock()
+ defer i.Unlock()
+
+ return i.llAddrs
+}
+
+func (i *nwIface) Routes() []*net.IPNet {
+ i.Lock()
+ defer i.Unlock()
+
+ routes := make([]*net.IPNet, len(i.routes))
+ for index, route := range i.routes {
+ r := types.GetIPNetCopy(route)
+ routes[index] = r
+ }
+
+ return routes
+}
+
+func (n *networkNamespace) Interfaces() []Interface {
+ n.Lock()
+ defer n.Unlock()
+
+ ifaces := make([]Interface, len(n.iFaces))
+
+ for i, iface := range n.iFaces {
+ ifaces[i] = iface
+ }
+
+ return ifaces
+}
+
+func (i *nwIface) Remove() error {
+ i.Lock()
+ n := i.ns
+ i.Unlock()
+
+ n.Lock()
+ isDefault := n.isDefault
+ nlh := n.nlHandle
+ n.Unlock()
+
+ // Find the network interface identified by the DstName attribute.
+ iface, err := nlh.LinkByName(i.DstName())
+ if err != nil {
+ return err
+ }
+
+ // Down the interface before configuring
+ if err := nlh.LinkSetDown(iface); err != nil {
+ return err
+ }
+
+ err = nlh.LinkSetName(iface, i.SrcName())
+ if err != nil {
+ logrus.Debugf("LinkSetName failed for interface %s: %v", i.SrcName(), err)
+ return err
+ }
+
+ // if it is a bridge just delete it.
+ if i.Bridge() {
+ if err := nlh.LinkDel(iface); err != nil {
+ return fmt.Errorf("failed deleting bridge %q: %v", i.SrcName(), err)
+ }
+ } else if !isDefault {
+ // Move the network interface to caller namespace.
+ if err := nlh.LinkSetNsFd(iface, ns.ParseHandlerInt()); err != nil {
+ logrus.Debugf("LinkSetNsPid failed for interface %s: %v", i.SrcName(), err)
+ return err
+ }
+ }
+
+ n.Lock()
+ for index, intf := range n.iFaces {
+ if intf == i {
+ n.iFaces = append(n.iFaces[:index], n.iFaces[index+1:]...)
+ break
+ }
+ }
+ n.Unlock()
+
+ n.checkLoV6()
+
+ return nil
+}
+
+// Returns the sandbox's side veth interface statistics
+func (i *nwIface) Statistics() (*types.InterfaceStatistics, error) {
+ i.Lock()
+ n := i.ns
+ i.Unlock()
+
+ l, err := n.nlHandle.LinkByName(i.DstName())
+ if err != nil {
+ return nil, fmt.Errorf("failed to retrieve the statistics for %s in netns %s: %v", i.DstName(), n.path, err)
+ }
+
+ stats := l.Attrs().Statistics
+ if stats == nil {
+ return nil, fmt.Errorf("no statistics were returned")
+ }
+
+ return &types.InterfaceStatistics{
+ RxBytes: uint64(stats.RxBytes),
+ TxBytes: uint64(stats.TxBytes),
+ RxPackets: uint64(stats.RxPackets),
+ TxPackets: uint64(stats.TxPackets),
+ RxDropped: uint64(stats.RxDropped),
+ TxDropped: uint64(stats.TxDropped),
+ }, nil
+}
+
+func (n *networkNamespace) findDst(srcName string, isBridge bool) string {
+ n.Lock()
+ defer n.Unlock()
+
+ for _, i := range n.iFaces {
+ // The master should match the srcname of the interface and the
+ // master interface should be of type bridge, if searching for a bridge type
+ if i.SrcName() == srcName && (!isBridge || i.Bridge()) {
+ return i.DstName()
+ }
+ }
+
+ return ""
+}
+
+func (n *networkNamespace) AddInterface(srcName, dstPrefix string, options ...IfaceOption) error {
+ i := &nwIface{srcName: srcName, dstName: dstPrefix, ns: n}
+ i.processInterfaceOptions(options...)
+
+ if i.master != "" {
+ i.dstMaster = n.findDst(i.master, true)
+ if i.dstMaster == "" {
+ return fmt.Errorf("could not find an appropriate master %q for %q",
+ i.master, i.srcName)
+ }
+ }
+
+ n.Lock()
+ if n.isDefault {
+ i.dstName = i.srcName
+ } else {
+ i.dstName = fmt.Sprintf("%s%d", dstPrefix, n.nextIfIndex[dstPrefix])
+ n.nextIfIndex[dstPrefix]++
+ }
+
+ path := n.path
+ isDefault := n.isDefault
+ nlh := n.nlHandle
+ nlhHost := ns.NlHandle()
+ n.Unlock()
+
+ // If it is a bridge interface we have to create the bridge inside
+ // the namespace so don't try to lookup the interface using srcName
+ if i.bridge {
+ link := &netlink.Bridge{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: i.srcName,
+ },
+ }
+ if err := nlh.LinkAdd(link); err != nil {
+ return fmt.Errorf("failed to create bridge %q: %v", i.srcName, err)
+ }
+ } else {
+ // Find the network interface identified by the SrcName attribute.
+ iface, err := nlhHost.LinkByName(i.srcName)
+ if err != nil {
+ return fmt.Errorf("failed to get link by name %q: %v", i.srcName, err)
+ }
+
+ // Move the network interface to the destination
+ // namespace only if the namespace is not a default
+ // type
+ if !isDefault {
+ newNs, err := netns.GetFromPath(path)
+ if err != nil {
+ return fmt.Errorf("failed get network namespace %q: %v", path, err)
+ }
+ defer newNs.Close()
+ if err := nlhHost.LinkSetNsFd(iface, int(newNs)); err != nil {
+ return fmt.Errorf("failed to set namespace on link %q: %v", i.srcName, err)
+ }
+ }
+ }
+
+ // Find the network interface identified by the SrcName attribute.
+ iface, err := nlh.LinkByName(i.srcName)
+ if err != nil {
+ return fmt.Errorf("failed to get link by name %q: %v", i.srcName, err)
+ }
+
+ // Down the interface before configuring
+ if err := nlh.LinkSetDown(iface); err != nil {
+ return fmt.Errorf("failed to set link down: %v", err)
+ }
+
+ // Configure the interface now this is moved in the proper namespace.
+ if err := configureInterface(nlh, iface, i); err != nil {
+ // If configuring the device fails move it back to the host namespace
+ // and change the name back to the source name. This allows the caller
+ // to properly cleanup the interface. Its important especially for
+ // interfaces with global attributes, ex: vni id for vxlan interfaces.
+ if nerr := nlh.LinkSetName(iface, i.SrcName()); nerr != nil {
+ logrus.Errorf("renaming interface (%s->%s) failed, %v after config error %v", i.DstName(), i.SrcName(), nerr, err)
+ }
+ if nerr := nlh.LinkSetNsFd(iface, ns.ParseHandlerInt()); nerr != nil {
+ logrus.Errorf("moving inteface %s to host ns failed, %v, after config error %v", i.SrcName(), nerr, err)
+ }
+ return err
+ }
+
+ // Up the interface.
+ cnt := 0
+ for err = nlh.LinkSetUp(iface); err != nil && cnt < 3; cnt++ {
+ logrus.Debugf("retrying link setup because of: %v", err)
+ time.Sleep(10 * time.Millisecond)
+ err = nlh.LinkSetUp(iface)
+ }
+ if err != nil {
+ return fmt.Errorf("failed to set link up: %v", err)
+ }
+
+ // Set the routes on the interface. This can only be done when the interface is up.
+ if err := setInterfaceRoutes(nlh, iface, i); err != nil {
+ return fmt.Errorf("error setting interface %q routes to %q: %v", iface.Attrs().Name, i.Routes(), err)
+ }
+
+ n.Lock()
+ n.iFaces = append(n.iFaces, i)
+ n.Unlock()
+
+ n.checkLoV6()
+
+ return nil
+}
+
+func configureInterface(nlh *netlink.Handle, iface netlink.Link, i *nwIface) error {
+ ifaceName := iface.Attrs().Name
+ ifaceConfigurators := []struct {
+ Fn func(*netlink.Handle, netlink.Link, *nwIface) error
+ ErrMessage string
+ }{
+ {setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, i.DstName())},
+ {setInterfaceMAC, fmt.Sprintf("error setting interface %q MAC to %q", ifaceName, i.MacAddress())},
+ {setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %v", ifaceName, i.Address())},
+ {setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %v", ifaceName, i.AddressIPv6())},
+ {setInterfaceMaster, fmt.Sprintf("error setting interface %q master to %q", ifaceName, i.DstMaster())},
+ {setInterfaceLinkLocalIPs, fmt.Sprintf("error setting interface %q link local IPs to %v", ifaceName, i.LinkLocalAddresses())},
+ }
+
+ for _, config := range ifaceConfigurators {
+ if err := config.Fn(nlh, iface, i); err != nil {
+ return fmt.Errorf("%s: %v", config.ErrMessage, err)
+ }
+ }
+ return nil
+}
+
+func setInterfaceMaster(nlh *netlink.Handle, iface netlink.Link, i *nwIface) error {
+ if i.DstMaster() == "" {
+ return nil
+ }
+
+ return nlh.LinkSetMaster(iface, &netlink.Bridge{
+ LinkAttrs: netlink.LinkAttrs{Name: i.DstMaster()}})
+}
+
+func setInterfaceMAC(nlh *netlink.Handle, iface netlink.Link, i *nwIface) error {
+ if i.MacAddress() == nil {
+ return nil
+ }
+ return nlh.LinkSetHardwareAddr(iface, i.MacAddress())
+}
+
+func setInterfaceIP(nlh *netlink.Handle, iface netlink.Link, i *nwIface) error {
+ if i.Address() == nil {
+ return nil
+ }
+ if err := checkRouteConflict(nlh, i.Address(), netlink.FAMILY_V4); err != nil {
+ return err
+ }
+ ipAddr := &netlink.Addr{IPNet: i.Address(), Label: ""}
+ return nlh.AddrAdd(iface, ipAddr)
+}
+
+func setInterfaceIPv6(nlh *netlink.Handle, iface netlink.Link, i *nwIface) error {
+ if i.AddressIPv6() == nil {
+ return nil
+ }
+ if err := checkRouteConflict(nlh, i.AddressIPv6(), netlink.FAMILY_V6); err != nil {
+ return err
+ }
+ if err := setIPv6(i.ns.path, i.DstName(), true); err != nil {
+ return fmt.Errorf("failed to enable ipv6: %v", err)
+ }
+ ipAddr := &netlink.Addr{IPNet: i.AddressIPv6(), Label: "", Flags: syscall.IFA_F_NODAD}
+ return nlh.AddrAdd(iface, ipAddr)
+}
+
+func setInterfaceLinkLocalIPs(nlh *netlink.Handle, iface netlink.Link, i *nwIface) error {
+ for _, llIP := range i.LinkLocalAddresses() {
+ ipAddr := &netlink.Addr{IPNet: llIP}
+ if err := nlh.AddrAdd(iface, ipAddr); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func setInterfaceName(nlh *netlink.Handle, iface netlink.Link, i *nwIface) error {
+ return nlh.LinkSetName(iface, i.DstName())
+}
+
+func setInterfaceRoutes(nlh *netlink.Handle, iface netlink.Link, i *nwIface) error {
+ for _, route := range i.Routes() {
+ err := nlh.RouteAdd(&netlink.Route{
+ Scope: netlink.SCOPE_LINK,
+ LinkIndex: iface.Attrs().Index,
+ Dst: route,
+ })
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// In older kernels (like the one in Centos 6.6 distro) sysctl does not have netns support. Therefore
+// we cannot gather the statistics from /sys/class/net/<dev>/statistics/<counter> files. Per-netns stats
+// are naturally found in /proc/net/dev in kernels which support netns (ifconfig relies on that).
+const (
+ netStatsFile = "/proc/net/dev"
+ base = "[ ]*%s:([ ]+[0-9]+){16}"
+)
+
+func scanInterfaceStats(data, ifName string, i *types.InterfaceStatistics) error {
+ var (
+ bktStr string
+ bkt uint64
+ )
+
+ regex := fmt.Sprintf(base, ifName)
+ re := regexp.MustCompile(regex)
+ line := re.FindString(data)
+
+ _, err := fmt.Sscanf(line, "%s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
+ &bktStr, &i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped, &bkt, &bkt, &bkt,
+ &bkt, &i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped, &bkt, &bkt, &bkt, &bkt)
+
+ return err
+}
+
+func checkRouteConflict(nlh *netlink.Handle, address *net.IPNet, family int) error {
+ routes, err := nlh.RouteList(nil, family)
+ if err != nil {
+ return err
+ }
+ for _, route := range routes {
+ if route.Dst != nil {
+ if route.Dst.Contains(address.IP) || address.Contains(route.Dst.IP) {
+ return fmt.Errorf("cannot program address %v in sandbox interface because it conflicts with existing route %s",
+ address, route)
+ }
+ }
+ }
+ return nil
+}
--- /dev/null
+package osl
+
+// IfaceOption is a function option type to set interface options
+type IfaceOption func()
--- /dev/null
+package kernel
+
+type conditionalCheck func(val1, val2 string) bool
+
+// OSValue represents a tuple, value defined, check function when to apply the value
+type OSValue struct {
+ Value string
+ CheckFn conditionalCheck
+}
+
+func propertyIsValid(val1, val2 string, check conditionalCheck) bool {
+ if check == nil || check(val1, val2) {
+ return true
+ }
+ return false
+}
--- /dev/null
+package kernel
+
+import (
+ "io/ioutil"
+ "path"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+)
+
+// writeSystemProperty writes the value to a path under /proc/sys as determined from the key.
+// For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward.
+func writeSystemProperty(key, value string) error {
+ keyPath := strings.Replace(key, ".", "/", -1)
+ return ioutil.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0644)
+}
+
+// readSystemProperty reads the value from the path under /proc/sys and returns it
+func readSystemProperty(key string) (string, error) {
+ keyPath := strings.Replace(key, ".", "/", -1)
+ value, err := ioutil.ReadFile(path.Join("/proc/sys", keyPath))
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(string(value)), nil
+}
+
+// ApplyOSTweaks applies the configuration values passed as arguments
+func ApplyOSTweaks(osConfig map[string]*OSValue) {
+ for k, v := range osConfig {
+ // read the existing property from disk
+ oldv, err := readSystemProperty(k)
+ if err != nil {
+ logrus.WithError(err).Errorf("error reading the kernel parameter %s", k)
+ continue
+ }
+
+ if propertyIsValid(oldv, v.Value, v.CheckFn) {
+ // write new prop value to disk
+ if err := writeSystemProperty(k, v.Value); err != nil {
+ logrus.WithError(err).Errorf("error setting the kernel parameter %s = %s, (leaving as %s)", k, v.Value, oldv)
+ continue
+ }
+ logrus.Debugf("updated kernel parameter %s = %s (was %s)", k, v.Value, oldv)
+ }
+ }
+}
--- /dev/null
+package kernel
+
+import (
+ "testing"
+
+ "github.com/sirupsen/logrus"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func TestReadWriteKnobs(t *testing.T) {
+ for _, k := range []string{
+ "net.ipv4.neigh.default.gc_thresh1",
+ "net.ipv4.neigh.default.gc_thresh2",
+ "net.ipv4.neigh.default.gc_thresh3",
+ } {
+ // Check if the test is able to read the value
+ v, err := readSystemProperty(k)
+ if err != nil {
+ logrus.WithError(err).Warnf("Path %v not readable", k)
+ // the path is not there, skip this key
+ continue
+ }
+ // Test the write
+ assert.Check(t, writeSystemProperty(k, "10000"))
+ newV, err := readSystemProperty(k)
+ assert.NilError(t, err)
+ assert.Check(t, is.Equal(newV, "10000"))
+ // Restore value
+ assert.Check(t, writeSystemProperty(k, v))
+ }
+}
--- /dev/null
+// +build !linux
+
+package kernel
+
+// ApplyOSTweaks applies the configuration values passed as arguments
+func ApplyOSTweaks(osConfig map[string]*OSValue) {
+}
--- /dev/null
+package osl
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/osl/kernel"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+ "github.com/vishvananda/netns"
+)
+
+const defaultPrefix = "/var/run/docker"
+
+func init() {
+ reexec.Register("set-ipv6", reexecSetIPv6)
+}
+
+var (
+ once sync.Once
+ garbagePathMap = make(map[string]bool)
+ gpmLock sync.Mutex
+ gpmWg sync.WaitGroup
+ gpmCleanupPeriod = 60 * time.Second
+ gpmChan = make(chan chan struct{})
+ prefix = defaultPrefix
+ loadBalancerConfig = map[string]*kernel.OSValue{
+ // expires connection from the IPVS connection table when the backend is not available
+ // more info: https://github.com/torvalds/linux/blob/master/Documentation/networking/ipvs-sysctl.txt#L126:1
+ "net.ipv4.vs.expire_nodest_conn": {Value: "1", CheckFn: nil},
+ }
+)
+
+// The networkNamespace type is the linux implementation of the Sandbox
+// interface. It represents a linux network namespace, and moves an interface
+// into it when called on method AddInterface or sets the gateway etc.
+type networkNamespace struct {
+ path string
+ iFaces []*nwIface
+ gw net.IP
+ gwv6 net.IP
+ staticRoutes []*types.StaticRoute
+ neighbors []*neigh
+ nextIfIndex map[string]int
+ isDefault bool
+ nlHandle *netlink.Handle
+ loV6Enabled bool
+ sync.Mutex
+}
+
+// SetBasePath sets the base url prefix for the ns path
+func SetBasePath(path string) {
+ prefix = path
+}
+
+func init() {
+ reexec.Register("netns-create", reexecCreateNamespace)
+}
+
+func basePath() string {
+ return filepath.Join(prefix, "netns")
+}
+
+func createBasePath() {
+ err := os.MkdirAll(basePath(), 0755)
+ if err != nil {
+ panic("Could not create net namespace path directory")
+ }
+
+ // Start the garbage collection go routine
+ go removeUnusedPaths()
+}
+
+func removeUnusedPaths() {
+ gpmLock.Lock()
+ period := gpmCleanupPeriod
+ gpmLock.Unlock()
+
+ ticker := time.NewTicker(period)
+ for {
+ var (
+ gc chan struct{}
+ gcOk bool
+ )
+
+ select {
+ case <-ticker.C:
+ case gc, gcOk = <-gpmChan:
+ }
+
+ gpmLock.Lock()
+ pathList := make([]string, 0, len(garbagePathMap))
+ for path := range garbagePathMap {
+ pathList = append(pathList, path)
+ }
+ garbagePathMap = make(map[string]bool)
+ gpmWg.Add(1)
+ gpmLock.Unlock()
+
+ for _, path := range pathList {
+ os.Remove(path)
+ }
+
+ gpmWg.Done()
+ if gcOk {
+ close(gc)
+ }
+ }
+}
+
+func addToGarbagePaths(path string) {
+ gpmLock.Lock()
+ garbagePathMap[path] = true
+ gpmLock.Unlock()
+}
+
+func removeFromGarbagePaths(path string) {
+ gpmLock.Lock()
+ delete(garbagePathMap, path)
+ gpmLock.Unlock()
+}
+
+// GC triggers garbage collection of namespace path right away
+// and waits for it.
+func GC() {
+ gpmLock.Lock()
+ if len(garbagePathMap) == 0 {
+ // No need for GC if map is empty
+ gpmLock.Unlock()
+ return
+ }
+ gpmLock.Unlock()
+
+ // if content exists in the garbage paths
+ // we can trigger GC to run, providing a
+ // channel to be notified on completion
+ waitGC := make(chan struct{})
+ gpmChan <- waitGC
+ // wait for GC completion
+ <-waitGC
+}
+
+// GenerateKey generates a sandbox key based on the passed
+// container id.
+func GenerateKey(containerID string) string {
+ maxLen := 12
+ // Read sandbox key from host for overlay
+ if strings.HasPrefix(containerID, "-") {
+ var (
+ index int
+ indexStr string
+ tmpkey string
+ )
+ dir, err := ioutil.ReadDir(basePath())
+ if err != nil {
+ return ""
+ }
+
+ for _, v := range dir {
+ id := v.Name()
+ if strings.HasSuffix(id, containerID[:maxLen-1]) {
+ indexStr = strings.TrimSuffix(id, containerID[:maxLen-1])
+ tmpindex, err := strconv.Atoi(indexStr)
+ if err != nil {
+ return ""
+ }
+ if tmpindex > index {
+ index = tmpindex
+ tmpkey = id
+ }
+
+ }
+ }
+ containerID = tmpkey
+ if containerID == "" {
+ return ""
+ }
+ }
+
+ if len(containerID) < maxLen {
+ maxLen = len(containerID)
+ }
+
+ return basePath() + "/" + containerID[:maxLen]
+}
+
+// NewSandbox provides a new sandbox instance created in an os specific way
+// provided a key which uniquely identifies the sandbox
+func NewSandbox(key string, osCreate, isRestore bool) (Sandbox, error) {
+ if !isRestore {
+ err := createNetworkNamespace(key, osCreate)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ once.Do(createBasePath)
+ }
+
+ n := &networkNamespace{path: key, isDefault: !osCreate, nextIfIndex: make(map[string]int)}
+
+ sboxNs, err := netns.GetFromPath(n.path)
+ if err != nil {
+ return nil, fmt.Errorf("failed get network namespace %q: %v", n.path, err)
+ }
+ defer sboxNs.Close()
+
+ n.nlHandle, err = netlink.NewHandleAt(sboxNs, syscall.NETLINK_ROUTE)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create a netlink handle: %v", err)
+ }
+
+ err = n.nlHandle.SetSocketTimeout(ns.NetlinkSocketsTimeout)
+ if err != nil {
+ logrus.Warnf("Failed to set the timeout on the sandbox netlink handle sockets: %v", err)
+ }
+ // In live-restore mode, IPV6 entries are getting cleaned up due to below code
+ // We should retain IPV6 configrations in live-restore mode when Docker Daemon
+ // comes back. It should work as it is on other cases
+ // As starting point, disable IPv6 on all interfaces
+ if !isRestore && !n.isDefault {
+ err = setIPv6(n.path, "all", false)
+ if err != nil {
+ logrus.Warnf("Failed to disable IPv6 on all interfaces on network namespace %q: %v", n.path, err)
+ }
+ }
+
+ if err = n.loopbackUp(); err != nil {
+ n.nlHandle.Delete()
+ return nil, err
+ }
+
+ return n, nil
+}
+
+func (n *networkNamespace) InterfaceOptions() IfaceOptionSetter {
+ return n
+}
+
+func (n *networkNamespace) NeighborOptions() NeighborOptionSetter {
+ return n
+}
+
+func mountNetworkNamespace(basePath string, lnPath string) error {
+ return syscall.Mount(basePath, lnPath, "bind", syscall.MS_BIND, "")
+}
+
+// GetSandboxForExternalKey returns sandbox object for the supplied path
+func GetSandboxForExternalKey(basePath string, key string) (Sandbox, error) {
+ if err := createNamespaceFile(key); err != nil {
+ return nil, err
+ }
+
+ if err := mountNetworkNamespace(basePath, key); err != nil {
+ return nil, err
+ }
+ n := &networkNamespace{path: key, nextIfIndex: make(map[string]int)}
+
+ sboxNs, err := netns.GetFromPath(n.path)
+ if err != nil {
+ return nil, fmt.Errorf("failed get network namespace %q: %v", n.path, err)
+ }
+ defer sboxNs.Close()
+
+ n.nlHandle, err = netlink.NewHandleAt(sboxNs, syscall.NETLINK_ROUTE)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create a netlink handle: %v", err)
+ }
+
+ err = n.nlHandle.SetSocketTimeout(ns.NetlinkSocketsTimeout)
+ if err != nil {
+ logrus.Warnf("Failed to set the timeout on the sandbox netlink handle sockets: %v", err)
+ }
+
+ // As starting point, disable IPv6 on all interfaces
+ err = setIPv6(n.path, "all", false)
+ if err != nil {
+ logrus.Warnf("Failed to disable IPv6 on all interfaces on network namespace %q: %v", n.path, err)
+ }
+
+ if err = n.loopbackUp(); err != nil {
+ n.nlHandle.Delete()
+ return nil, err
+ }
+
+ return n, nil
+}
+
+func reexecCreateNamespace() {
+ if len(os.Args) < 2 {
+ logrus.Fatal("no namespace path provided")
+ }
+ if err := mountNetworkNamespace("/proc/self/ns/net", os.Args[1]); err != nil {
+ logrus.Fatal(err)
+ }
+}
+
+func createNetworkNamespace(path string, osCreate bool) error {
+ if err := createNamespaceFile(path); err != nil {
+ return err
+ }
+
+ cmd := &exec.Cmd{
+ Path: reexec.Self(),
+ Args: append([]string{"netns-create"}, path),
+ Stdout: os.Stdout,
+ Stderr: os.Stderr,
+ }
+ if osCreate {
+ cmd.SysProcAttr = &syscall.SysProcAttr{}
+ cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNET
+ }
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("namespace creation reexec command failed: %v", err)
+ }
+
+ return nil
+}
+
+func unmountNamespaceFile(path string) {
+ if _, err := os.Stat(path); err == nil {
+ syscall.Unmount(path, syscall.MNT_DETACH)
+ }
+}
+
+func createNamespaceFile(path string) (err error) {
+ var f *os.File
+
+ once.Do(createBasePath)
+ // Remove it from garbage collection list if present
+ removeFromGarbagePaths(path)
+
+ // If the path is there unmount it first
+ unmountNamespaceFile(path)
+
+ // wait for garbage collection to complete if it is in progress
+ // before trying to create the file.
+ gpmWg.Wait()
+
+ if f, err = os.Create(path); err == nil {
+ f.Close()
+ }
+
+ return err
+}
+
+func (n *networkNamespace) loopbackUp() error {
+ iface, err := n.nlHandle.LinkByName("lo")
+ if err != nil {
+ return err
+ }
+ return n.nlHandle.LinkSetUp(iface)
+}
+
+func (n *networkNamespace) GetLoopbackIfaceName() string {
+ return "lo"
+}
+
+func (n *networkNamespace) AddAliasIP(ifName string, ip *net.IPNet) error {
+ iface, err := n.nlHandle.LinkByName(ifName)
+ if err != nil {
+ return err
+ }
+ return n.nlHandle.AddrAdd(iface, &netlink.Addr{IPNet: ip})
+}
+
+func (n *networkNamespace) RemoveAliasIP(ifName string, ip *net.IPNet) error {
+ iface, err := n.nlHandle.LinkByName(ifName)
+ if err != nil {
+ return err
+ }
+ return n.nlHandle.AddrDel(iface, &netlink.Addr{IPNet: ip})
+}
+
+func (n *networkNamespace) DisableARPForVIP(srcName string) (Err error) {
+ dstName := ""
+ for _, i := range n.Interfaces() {
+ if i.SrcName() == srcName {
+ dstName = i.DstName()
+ break
+ }
+ }
+ if dstName == "" {
+ return fmt.Errorf("failed to find interface %s in sandbox", srcName)
+ }
+
+ err := n.InvokeFunc(func() {
+ path := filepath.Join("/proc/sys/net/ipv4/conf", dstName, "arp_ignore")
+ if err := ioutil.WriteFile(path, []byte{'1', '\n'}, 0644); err != nil {
+ Err = fmt.Errorf("Failed to set %s to 1: %v", path, err)
+ return
+ }
+ path = filepath.Join("/proc/sys/net/ipv4/conf", dstName, "arp_announce")
+ if err := ioutil.WriteFile(path, []byte{'2', '\n'}, 0644); err != nil {
+ Err = fmt.Errorf("Failed to set %s to 2: %v", path, err)
+ return
+ }
+ })
+ if err != nil {
+ return err
+ }
+ return
+}
+
+func (n *networkNamespace) InvokeFunc(f func()) error {
+ return nsInvoke(n.nsPath(), func(nsFD int) error { return nil }, func(callerFD int) error {
+ f()
+ return nil
+ })
+}
+
+// InitOSContext initializes OS context while configuring network resources
+func InitOSContext() func() {
+ runtime.LockOSThread()
+ if err := ns.SetNamespace(); err != nil {
+ logrus.Error(err)
+ }
+ return runtime.UnlockOSThread
+}
+
+func nsInvoke(path string, prefunc func(nsFD int) error, postfunc func(callerFD int) error) error {
+ defer InitOSContext()()
+
+ newNs, err := netns.GetFromPath(path)
+ if err != nil {
+ return fmt.Errorf("failed get network namespace %q: %v", path, err)
+ }
+ defer newNs.Close()
+
+ // Invoked before the namespace switch happens but after the namespace file
+ // handle is obtained.
+ if err := prefunc(int(newNs)); err != nil {
+ return fmt.Errorf("failed in prefunc: %v", err)
+ }
+
+ if err = netns.Set(newNs); err != nil {
+ return err
+ }
+ defer ns.SetNamespace()
+
+ // Invoked after the namespace switch.
+ return postfunc(ns.ParseHandlerInt())
+}
+
+func (n *networkNamespace) nsPath() string {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.path
+}
+
+func (n *networkNamespace) Info() Info {
+ return n
+}
+
+func (n *networkNamespace) Key() string {
+ return n.path
+}
+
+func (n *networkNamespace) Destroy() error {
+ if n.nlHandle != nil {
+ n.nlHandle.Delete()
+ }
+ // Assuming no running process is executing in this network namespace,
+ // unmounting is sufficient to destroy it.
+ if err := syscall.Unmount(n.path, syscall.MNT_DETACH); err != nil {
+ return err
+ }
+
+ // Stash it into the garbage collection list
+ addToGarbagePaths(n.path)
+ return nil
+}
+
+// Restore restore the network namespace
+func (n *networkNamespace) Restore(ifsopt map[string][]IfaceOption, routes []*types.StaticRoute, gw net.IP, gw6 net.IP) error {
+ // restore interfaces
+ for name, opts := range ifsopt {
+ if !strings.Contains(name, "+") {
+ return fmt.Errorf("wrong iface name in restore osl sandbox interface: %s", name)
+ }
+ seps := strings.Split(name, "+")
+ srcName := seps[0]
+ dstPrefix := seps[1]
+ i := &nwIface{srcName: srcName, dstName: dstPrefix, ns: n}
+ i.processInterfaceOptions(opts...)
+ if i.master != "" {
+ i.dstMaster = n.findDst(i.master, true)
+ if i.dstMaster == "" {
+ return fmt.Errorf("could not find an appropriate master %q for %q",
+ i.master, i.srcName)
+ }
+ }
+ if n.isDefault {
+ i.dstName = i.srcName
+ } else {
+ links, err := n.nlHandle.LinkList()
+ if err != nil {
+ return fmt.Errorf("failed to retrieve list of links in network namespace %q during restore", n.path)
+ }
+ // due to the docker network connect/disconnect, so the dstName should
+ // restore from the namespace
+ for _, link := range links {
+ addrs, err := n.nlHandle.AddrList(link, netlink.FAMILY_V4)
+ if err != nil {
+ return err
+ }
+ ifaceName := link.Attrs().Name
+ if strings.HasPrefix(ifaceName, "vxlan") {
+ if i.dstName == "vxlan" {
+ i.dstName = ifaceName
+ break
+ }
+ }
+ // find the interface name by ip
+ if i.address != nil {
+ for _, addr := range addrs {
+ if addr.IPNet.String() == i.address.String() {
+ i.dstName = ifaceName
+ break
+ }
+ continue
+ }
+ if i.dstName == ifaceName {
+ break
+ }
+ }
+ // This is to find the interface name of the pair in overlay sandbox
+ if strings.HasPrefix(ifaceName, "veth") {
+ if i.master != "" && i.dstName == "veth" {
+ i.dstName = ifaceName
+ }
+ }
+ }
+
+ var index int
+ indexStr := strings.TrimPrefix(i.dstName, dstPrefix)
+ if indexStr != "" {
+ index, err = strconv.Atoi(indexStr)
+ if err != nil {
+ return err
+ }
+ }
+ index++
+ n.Lock()
+ if index > n.nextIfIndex[dstPrefix] {
+ n.nextIfIndex[dstPrefix] = index
+ }
+ n.iFaces = append(n.iFaces, i)
+ n.Unlock()
+ }
+ }
+
+ // restore routes
+ for _, r := range routes {
+ n.Lock()
+ n.staticRoutes = append(n.staticRoutes, r)
+ n.Unlock()
+ }
+
+ // restore gateway
+ if len(gw) > 0 {
+ n.Lock()
+ n.gw = gw
+ n.Unlock()
+ }
+
+ if len(gw6) > 0 {
+ n.Lock()
+ n.gwv6 = gw6
+ n.Unlock()
+ }
+
+ return nil
+}
+
+// Checks whether IPv6 needs to be enabled/disabled on the loopback interface
+func (n *networkNamespace) checkLoV6() {
+ var (
+ enable = false
+ action = "disable"
+ )
+
+ n.Lock()
+ for _, iface := range n.iFaces {
+ if iface.AddressIPv6() != nil {
+ enable = true
+ action = "enable"
+ break
+ }
+ }
+ n.Unlock()
+
+ if n.loV6Enabled == enable {
+ return
+ }
+
+ if err := setIPv6(n.path, "lo", enable); err != nil {
+ logrus.Warnf("Failed to %s IPv6 on loopback interface on network namespace %q: %v", action, n.path, err)
+ }
+
+ n.loV6Enabled = enable
+}
+
+func reexecSetIPv6() {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ if len(os.Args) < 3 {
+ logrus.Errorf("invalid number of arguments for %s", os.Args[0])
+ os.Exit(1)
+ }
+
+ ns, err := netns.GetFromPath(os.Args[1])
+ if err != nil {
+ logrus.Errorf("failed get network namespace %q: %v", os.Args[1], err)
+ os.Exit(2)
+ }
+ defer ns.Close()
+
+ if err = netns.Set(ns); err != nil {
+ logrus.Errorf("setting into container netns %q failed: %v", os.Args[1], err)
+ os.Exit(3)
+ }
+
+ var (
+ action = "disable"
+ value = byte('1')
+ path = fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", os.Args[2])
+ )
+
+ if os.Args[3] == "true" {
+ action = "enable"
+ value = byte('0')
+ }
+
+ if _, err := os.Stat(path); err != nil {
+ if os.IsNotExist(err) {
+ logrus.Warnf("file does not exist: %s : %v Has IPv6 been disabled in this node's kernel?", path, err)
+ os.Exit(0)
+ }
+ logrus.Errorf("failed to stat %s : %v", path, err)
+ os.Exit(5)
+ }
+
+ if err = ioutil.WriteFile(path, []byte{value, '\n'}, 0644); err != nil {
+ logrus.Errorf("failed to %s IPv6 forwarding for container's interface %s: %v", action, os.Args[2], err)
+ os.Exit(4)
+ }
+
+ os.Exit(0)
+}
+
+func setIPv6(path, iface string, enable bool) error {
+ cmd := &exec.Cmd{
+ Path: reexec.Self(),
+ Args: append([]string{"set-ipv6"}, path, iface, strconv.FormatBool(enable)),
+ Stdout: os.Stdout,
+ Stderr: os.Stderr,
+ }
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("reexec to set IPv6 failed: %v", err)
+ }
+ return nil
+}
+
+// ApplyOSTweaks applies linux configs on the sandbox
+func (n *networkNamespace) ApplyOSTweaks(types []SandboxType) {
+ for _, t := range types {
+ switch t {
+ case SandboxTypeLoadBalancer:
+ kernel.ApplyOSTweaks(loadBalancerConfig)
+ }
+ }
+}
--- /dev/null
+// +build !linux,!windows,!freebsd
+
+package osl
+
+// GC triggers garbage collection of namespace path right away
+// and waits for it.
+func GC() {
+}
+
+// GetSandboxForExternalKey returns sandbox object for the supplied path
+func GetSandboxForExternalKey(path string, key string) (Sandbox, error) {
+ return nil, nil
+}
+
+// SetBasePath sets the base url prefix for the ns path
+func SetBasePath(path string) {
+}
--- /dev/null
+package osl
+
+import "testing"
+
+// GenerateKey generates a sandbox key based on the passed
+// container id.
+func GenerateKey(containerID string) string {
+ return containerID
+}
+
+// NewSandbox provides a new sandbox instance created in an os specific way
+// provided a key which uniquely identifies the sandbox
+func NewSandbox(key string, osCreate, isRestore bool) (Sandbox, error) {
+ return nil, nil
+}
+
+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() {
+}
+
+// InitOSContext initializes OS context while configuring network resources
+func InitOSContext() func() {
+ return func() {}
+}
+
+// SetupTestOSContext sets up a separate test OS context in which tests will be executed.
+func SetupTestOSContext(t *testing.T) func() {
+ return func() {}
+}
+
+// SetBasePath sets the base url prefix for the ns path
+func SetBasePath(path string) {
+}
--- /dev/null
+package osl
+
+// NeighOption is a function option type to set neighbor options
+type NeighOption func()
--- /dev/null
+package osl
+
+import (
+ "bytes"
+ "fmt"
+ "net"
+
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+// NeighborSearchError indicates that the neighbor is already present
+type NeighborSearchError struct {
+ ip net.IP
+ mac net.HardwareAddr
+ present bool
+}
+
+func (n NeighborSearchError) Error() string {
+ return fmt.Sprintf("Search neighbor failed for IP %v, mac %v, present in db:%t", n.ip, n.mac, n.present)
+}
+
+// NeighOption is a function option type to set interface options
+type NeighOption func(nh *neigh)
+
+type neigh struct {
+ dstIP net.IP
+ dstMac net.HardwareAddr
+ linkName string
+ linkDst string
+ family int
+}
+
+func (n *networkNamespace) findNeighbor(dstIP net.IP, dstMac net.HardwareAddr) *neigh {
+ n.Lock()
+ defer n.Unlock()
+
+ for _, nh := range n.neighbors {
+ if nh.dstIP.Equal(dstIP) && bytes.Equal(nh.dstMac, dstMac) {
+ return nh
+ }
+ }
+
+ return nil
+}
+
+func (n *networkNamespace) DeleteNeighbor(dstIP net.IP, dstMac net.HardwareAddr, osDelete bool) error {
+ var (
+ iface netlink.Link
+ err error
+ )
+
+ nh := n.findNeighbor(dstIP, dstMac)
+ if nh == nil {
+ return NeighborSearchError{dstIP, dstMac, false}
+ }
+
+ if osDelete {
+ n.Lock()
+ nlh := n.nlHandle
+ n.Unlock()
+
+ if nh.linkDst != "" {
+ iface, err = nlh.LinkByName(nh.linkDst)
+ if err != nil {
+ return fmt.Errorf("could not find interface with destination name %s: %v",
+ nh.linkDst, err)
+ }
+ }
+
+ nlnh := &netlink.Neigh{
+ IP: dstIP,
+ State: netlink.NUD_PERMANENT,
+ Family: nh.family,
+ }
+
+ if nlnh.Family > 0 {
+ nlnh.HardwareAddr = dstMac
+ nlnh.Flags = netlink.NTF_SELF
+ }
+
+ if nh.linkDst != "" {
+ nlnh.LinkIndex = iface.Attrs().Index
+ }
+
+ // If the kernel deletion fails for the neighbor entry still remote it
+ // from the namespace cache. Otherwise if the neighbor moves back to the
+ // same host again, kernel update can fail.
+ if err := nlh.NeighDel(nlnh); err != nil {
+ logrus.Warnf("Deleting neighbor IP %s, mac %s failed, %v", dstIP, dstMac, err)
+ }
+
+ // Delete the dynamic entry in the bridge
+ if nlnh.Family > 0 {
+ nlnh := &netlink.Neigh{
+ IP: dstIP,
+ Family: nh.family,
+ }
+
+ nlnh.HardwareAddr = dstMac
+ nlnh.Flags = netlink.NTF_MASTER
+ if nh.linkDst != "" {
+ nlnh.LinkIndex = iface.Attrs().Index
+ }
+ nlh.NeighDel(nlnh)
+ }
+ }
+
+ n.Lock()
+ for i, nh := range n.neighbors {
+ if nh.dstIP.Equal(dstIP) && bytes.Equal(nh.dstMac, dstMac) {
+ n.neighbors = append(n.neighbors[:i], n.neighbors[i+1:]...)
+ break
+ }
+ }
+ n.Unlock()
+ logrus.Debugf("Neighbor entry deleted for IP %v, mac %v osDelete:%t", dstIP, dstMac, osDelete)
+
+ return nil
+}
+
+func (n *networkNamespace) AddNeighbor(dstIP net.IP, dstMac net.HardwareAddr, force bool, options ...NeighOption) error {
+ var (
+ iface netlink.Link
+ err error
+ neighborAlreadyPresent bool
+ )
+
+ // If the namespace already has the neighbor entry but the AddNeighbor is called
+ // because of a miss notification (force flag) program the kernel anyway.
+ nh := n.findNeighbor(dstIP, dstMac)
+ if nh != nil {
+ neighborAlreadyPresent = true
+ logrus.Warnf("Neighbor entry already present for IP %v, mac %v neighbor:%+v forceUpdate:%t", dstIP, dstMac, nh, force)
+ if !force {
+ return NeighborSearchError{dstIP, dstMac, true}
+ }
+ }
+
+ nh = &neigh{
+ dstIP: dstIP,
+ dstMac: dstMac,
+ }
+
+ nh.processNeighOptions(options...)
+
+ if nh.linkName != "" {
+ nh.linkDst = n.findDst(nh.linkName, false)
+ if nh.linkDst == "" {
+ return fmt.Errorf("could not find the interface with name %s", nh.linkName)
+ }
+ }
+
+ n.Lock()
+ nlh := n.nlHandle
+ n.Unlock()
+
+ if nh.linkDst != "" {
+ iface, err = nlh.LinkByName(nh.linkDst)
+ if err != nil {
+ return fmt.Errorf("could not find interface with destination name %s: %v", nh.linkDst, err)
+ }
+ }
+
+ nlnh := &netlink.Neigh{
+ IP: dstIP,
+ HardwareAddr: dstMac,
+ State: netlink.NUD_PERMANENT,
+ Family: nh.family,
+ }
+
+ if nlnh.Family > 0 {
+ nlnh.Flags = netlink.NTF_SELF
+ }
+
+ if nh.linkDst != "" {
+ nlnh.LinkIndex = iface.Attrs().Index
+ }
+
+ if err := nlh.NeighSet(nlnh); err != nil {
+ return fmt.Errorf("could not add neighbor entry:%+v error:%v", nlnh, err)
+ }
+
+ if neighborAlreadyPresent {
+ return nil
+ }
+
+ n.Lock()
+ n.neighbors = append(n.neighbors, nh)
+ n.Unlock()
+ logrus.Debugf("Neighbor entry added for IP:%v, mac:%v on ifc:%s", dstIP, dstMac, nh.linkName)
+
+ return nil
+}
--- /dev/null
+package osl
+
+// NeighOption is a function option type to set neighbor options
+type NeighOption func()
--- /dev/null
+package osl
+
+import "net"
+
+func (nh *neigh) processNeighOptions(options ...NeighOption) {
+ for _, opt := range options {
+ if opt != nil {
+ opt(nh)
+ }
+ }
+}
+
+func (n *networkNamespace) LinkName(name string) NeighOption {
+ return func(nh *neigh) {
+ nh.linkName = name
+ }
+}
+
+func (n *networkNamespace) Family(family int) NeighOption {
+ return func(nh *neigh) {
+ nh.family = family
+ }
+}
+
+func (i *nwIface) processInterfaceOptions(options ...IfaceOption) {
+ for _, opt := range options {
+ if opt != nil {
+ opt(i)
+ }
+ }
+}
+
+func (n *networkNamespace) Bridge(isBridge bool) IfaceOption {
+ return func(i *nwIface) {
+ i.bridge = isBridge
+ }
+}
+
+func (n *networkNamespace) Master(name string) IfaceOption {
+ return func(i *nwIface) {
+ i.master = name
+ }
+}
+
+func (n *networkNamespace) MacAddress(mac net.HardwareAddr) IfaceOption {
+ return func(i *nwIface) {
+ i.mac = mac
+ }
+}
+
+func (n *networkNamespace) Address(addr *net.IPNet) IfaceOption {
+ return func(i *nwIface) {
+ i.address = addr
+ }
+}
+
+func (n *networkNamespace) AddressIPv6(addr *net.IPNet) IfaceOption {
+ return func(i *nwIface) {
+ i.addressIPv6 = addr
+ }
+}
+
+func (n *networkNamespace) LinkLocalAddresses(list []*net.IPNet) IfaceOption {
+ return func(i *nwIface) {
+ i.llAddrs = list
+ }
+}
+
+func (n *networkNamespace) Routes(routes []*net.IPNet) IfaceOption {
+ return func(i *nwIface) {
+ i.routes = routes
+ }
+}
--- /dev/null
+package osl
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/types"
+ "github.com/vishvananda/netlink"
+)
+
+func (n *networkNamespace) Gateway() net.IP {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.gw
+}
+
+func (n *networkNamespace) GatewayIPv6() net.IP {
+ n.Lock()
+ defer n.Unlock()
+
+ return n.gwv6
+}
+
+func (n *networkNamespace) StaticRoutes() []*types.StaticRoute {
+ n.Lock()
+ defer n.Unlock()
+
+ routes := make([]*types.StaticRoute, len(n.staticRoutes))
+ for i, route := range n.staticRoutes {
+ r := route.GetCopy()
+ routes[i] = r
+ }
+
+ return routes
+}
+
+func (n *networkNamespace) setGateway(gw net.IP) {
+ n.Lock()
+ n.gw = gw
+ n.Unlock()
+}
+
+func (n *networkNamespace) setGatewayIPv6(gwv6 net.IP) {
+ n.Lock()
+ n.gwv6 = gwv6
+ n.Unlock()
+}
+
+func (n *networkNamespace) SetGateway(gw net.IP) error {
+ // Silently return if the gateway is empty
+ if len(gw) == 0 {
+ return nil
+ }
+
+ err := n.programGateway(gw, true)
+ if err == nil {
+ n.setGateway(gw)
+ }
+
+ return err
+}
+
+func (n *networkNamespace) UnsetGateway() error {
+ gw := n.Gateway()
+
+ // Silently return if the gateway is empty
+ if len(gw) == 0 {
+ return nil
+ }
+
+ err := n.programGateway(gw, false)
+ if err == nil {
+ n.setGateway(net.IP{})
+ }
+
+ return err
+}
+
+func (n *networkNamespace) programGateway(gw net.IP, isAdd bool) error {
+ gwRoutes, err := n.nlHandle.RouteGet(gw)
+ if err != nil {
+ return fmt.Errorf("route for the gateway %s could not be found: %v", gw, err)
+ }
+
+ var linkIndex int
+ for _, gwRoute := range gwRoutes {
+ if gwRoute.Gw == nil {
+ linkIndex = gwRoute.LinkIndex
+ break
+ }
+ }
+
+ if linkIndex == 0 {
+ return fmt.Errorf("Direct route for the gateway %s could not be found", gw)
+ }
+
+ if isAdd {
+ return n.nlHandle.RouteAdd(&netlink.Route{
+ Scope: netlink.SCOPE_UNIVERSE,
+ LinkIndex: linkIndex,
+ Gw: gw,
+ })
+ }
+
+ return n.nlHandle.RouteDel(&netlink.Route{
+ Scope: netlink.SCOPE_UNIVERSE,
+ LinkIndex: linkIndex,
+ Gw: gw,
+ })
+}
+
+// Program a route in to the namespace routing table.
+func (n *networkNamespace) programRoute(path string, dest *net.IPNet, nh net.IP) error {
+ gwRoutes, err := n.nlHandle.RouteGet(nh)
+ if err != nil {
+ return fmt.Errorf("route for the next hop %s could not be found: %v", nh, err)
+ }
+
+ return n.nlHandle.RouteAdd(&netlink.Route{
+ Scope: netlink.SCOPE_UNIVERSE,
+ LinkIndex: gwRoutes[0].LinkIndex,
+ Gw: nh,
+ Dst: dest,
+ })
+}
+
+// Delete a route from the namespace routing table.
+func (n *networkNamespace) removeRoute(path string, dest *net.IPNet, nh net.IP) error {
+ gwRoutes, err := n.nlHandle.RouteGet(nh)
+ if err != nil {
+ return fmt.Errorf("route for the next hop could not be found: %v", err)
+ }
+
+ return n.nlHandle.RouteDel(&netlink.Route{
+ Scope: netlink.SCOPE_UNIVERSE,
+ LinkIndex: gwRoutes[0].LinkIndex,
+ Gw: nh,
+ Dst: dest,
+ })
+}
+
+func (n *networkNamespace) SetGatewayIPv6(gwv6 net.IP) error {
+ // Silently return if the gateway is empty
+ if len(gwv6) == 0 {
+ return nil
+ }
+
+ err := n.programGateway(gwv6, true)
+ if err == nil {
+ n.setGatewayIPv6(gwv6)
+ }
+
+ return err
+}
+
+func (n *networkNamespace) UnsetGatewayIPv6() error {
+ gwv6 := n.GatewayIPv6()
+
+ // Silently return if the gateway is empty
+ if len(gwv6) == 0 {
+ return nil
+ }
+
+ err := n.programGateway(gwv6, false)
+ if err == nil {
+ n.Lock()
+ n.gwv6 = net.IP{}
+ n.Unlock()
+ }
+
+ return err
+}
+
+func (n *networkNamespace) AddStaticRoute(r *types.StaticRoute) error {
+ err := n.programRoute(n.nsPath(), r.Destination, r.NextHop)
+ if err == nil {
+ n.Lock()
+ n.staticRoutes = append(n.staticRoutes, r)
+ n.Unlock()
+ }
+ return err
+}
+
+func (n *networkNamespace) RemoveStaticRoute(r *types.StaticRoute) error {
+
+ err := n.removeRoute(n.nsPath(), r.Destination, r.NextHop)
+ if err == nil {
+ n.Lock()
+ lastIndex := len(n.staticRoutes) - 1
+ for i, v := range n.staticRoutes {
+ if v == r {
+ // Overwrite the route we're removing with the last element
+ n.staticRoutes[i] = n.staticRoutes[lastIndex]
+ // Shorten the slice to trim the extra element
+ n.staticRoutes = n.staticRoutes[:lastIndex]
+ break
+ }
+ }
+ n.Unlock()
+ }
+ return err
+}
--- /dev/null
+// Package osl describes structures and interfaces which abstract os entities
+package osl
+
+import (
+ "net"
+
+ "github.com/docker/libnetwork/types"
+)
+
+// SandboxType specify the time of the sandbox, this can be used to apply special configs
+type SandboxType int
+
+const (
+ // SandboxTypeIngress indicates that the sandbox is for the ingress
+ SandboxTypeIngress = iota
+ // SandboxTypeLoadBalancer indicates that the sandbox is a load balancer
+ SandboxTypeLoadBalancer = iota
+)
+
+// Sandbox represents a network sandbox, identified by a specific key. It
+// holds a list of Interfaces, routes etc, and more can be added dynamically.
+type Sandbox interface {
+ // The path where the network namespace is mounted.
+ Key() string
+
+ // Add an existing Interface to this sandbox. The operation will rename
+ // from the Interface SrcName to DstName as it moves, and reconfigure the
+ // interface according to the specified settings. The caller is expected
+ // to only provide a prefix for DstName. The AddInterface api will auto-generate
+ // an appropriate suffix for the DstName to disambiguate.
+ AddInterface(SrcName string, DstPrefix string, options ...IfaceOption) error
+
+ // Set default IPv4 gateway for the sandbox
+ SetGateway(gw net.IP) error
+
+ // Set default IPv6 gateway for the sandbox
+ SetGatewayIPv6(gw net.IP) error
+
+ // Unset the previously set default IPv4 gateway in the sandbox
+ UnsetGateway() error
+
+ // Unset the previously set default IPv6 gateway in the sandbox
+ UnsetGatewayIPv6() error
+
+ // GetLoopbackIfaceName returns the name of the loopback interface
+ GetLoopbackIfaceName() string
+
+ // AddAliasIP adds the passed IP address to the named interface
+ AddAliasIP(ifName string, ip *net.IPNet) error
+
+ // RemoveAliasIP removes the passed IP address from the named interface
+ RemoveAliasIP(ifName string, ip *net.IPNet) error
+
+ // DisableARPForVIP disables ARP replies and requests for VIP addresses
+ // on a particular interface
+ DisableARPForVIP(ifName string) error
+
+ // Add a static route to the sandbox.
+ AddStaticRoute(*types.StaticRoute) error
+
+ // Remove a static route from the sandbox.
+ RemoveStaticRoute(*types.StaticRoute) error
+
+ // AddNeighbor adds a neighbor entry into the sandbox.
+ AddNeighbor(dstIP net.IP, dstMac net.HardwareAddr, force bool, option ...NeighOption) error
+
+ // DeleteNeighbor deletes neighbor entry from the sandbox.
+ DeleteNeighbor(dstIP net.IP, dstMac net.HardwareAddr, osDelete bool) error
+
+ // Returns an interface with methods to set neighbor options.
+ NeighborOptions() NeighborOptionSetter
+
+ // Returns an interface with methods to set interface options.
+ InterfaceOptions() IfaceOptionSetter
+
+ //Invoke
+ InvokeFunc(func()) error
+
+ // Returns an interface with methods to get sandbox state.
+ Info() Info
+
+ // Destroy the sandbox
+ Destroy() error
+
+ // restore sandbox
+ Restore(ifsopt map[string][]IfaceOption, routes []*types.StaticRoute, gw net.IP, gw6 net.IP) error
+
+ // ApplyOSTweaks applies operating system specific knobs on the sandbox
+ ApplyOSTweaks([]SandboxType)
+}
+
+// NeighborOptionSetter interface defines the option setter methods for interface options
+type NeighborOptionSetter interface {
+ // LinkName returns an option setter to set the srcName of the link that should
+ // be used in the neighbor entry
+ LinkName(string) NeighOption
+
+ // Family returns an option setter to set the address family for the neighbor
+ // entry. eg. AF_BRIDGE
+ Family(int) NeighOption
+}
+
+// IfaceOptionSetter interface defines the option setter methods for interface options.
+type IfaceOptionSetter interface {
+ // Bridge returns an option setter to set if the interface is a bridge.
+ Bridge(bool) IfaceOption
+
+ // MacAddress returns an option setter to set the MAC address.
+ MacAddress(net.HardwareAddr) IfaceOption
+
+ // Address returns an option setter to set IPv4 address.
+ Address(*net.IPNet) IfaceOption
+
+ // Address returns an option setter to set IPv6 address.
+ AddressIPv6(*net.IPNet) IfaceOption
+
+ // LinkLocalAddresses returns an option setter to set the link-local IP addresses.
+ LinkLocalAddresses([]*net.IPNet) IfaceOption
+
+ // Master returns an option setter to set the master interface if any for this
+ // interface. The master interface name should refer to the srcname of a
+ // previously added interface of type bridge.
+ Master(string) IfaceOption
+
+ // Address returns an option setter to set interface routes.
+ Routes([]*net.IPNet) IfaceOption
+}
+
+// Info represents all possible information that
+// the driver wants to place in the sandbox which includes
+// interfaces, routes and gateway
+type Info interface {
+ // The collection of Interface previously added with the AddInterface
+ // method. Note that this doesn't include network interfaces added in any
+ // other way (such as the default loopback interface which is automatically
+ // created on creation of a sandbox).
+ Interfaces() []Interface
+
+ // IPv4 gateway for the sandbox.
+ Gateway() net.IP
+
+ // IPv6 gateway for the sandbox.
+ GatewayIPv6() net.IP
+
+ // Additional static routes for the sandbox. (Note that directly
+ // connected routes are stored on the particular interface they refer to.)
+ StaticRoutes() []*types.StaticRoute
+
+ // TODO: Add ip tables etc.
+}
+
+// Interface represents the settings and identity of a network device. It is
+// used as a return type for Network.Link, and it is common practice for the
+// caller to use this information when moving interface SrcName from host
+// namespace to DstName in a different net namespace with the appropriate
+// network settings.
+type Interface interface {
+ // The name of the interface in the origin network namespace.
+ SrcName() string
+
+ // The name that will be assigned to the interface once moves inside a
+ // network namespace. When the caller passes in a DstName, it is only
+ // expected to pass a prefix. The name will modified with an appropriately
+ // auto-generated suffix.
+ DstName() string
+
+ // IPv4 address for the interface.
+ Address() *net.IPNet
+
+ // IPv6 address for the interface.
+ AddressIPv6() *net.IPNet
+
+ // LinkLocalAddresses returns the link-local IP addresses assigned to the interface.
+ LinkLocalAddresses() []*net.IPNet
+
+ // IP routes for the interface.
+ Routes() []*net.IPNet
+
+ // Bridge returns true if the interface is a bridge
+ Bridge() bool
+
+ // Master returns the srcname of the master interface for this interface.
+ Master() string
+
+ // Remove an interface from the sandbox by renaming to original name
+ // and moving it out of the sandbox.
+ Remove() error
+
+ // Statistics returns the statistics for this interface
+ Statistics() (*types.InterfaceStatistics, error)
+}
--- /dev/null
+package osl
+
+import "testing"
+
+// GenerateKey generates a sandbox key based on the passed
+// container id.
+func GenerateKey(containerID string) string {
+ maxLen := 12
+ if len(containerID) < maxLen {
+ maxLen = len(containerID)
+ }
+
+ return containerID[:maxLen]
+}
+
+// NewSandbox provides a new sandbox instance created in an os specific way
+// provided a key which uniquely identifies the sandbox
+func NewSandbox(key string, osCreate, isRestore 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() {
+}
+
+// InitOSContext initializes OS context while configuring network resources
+func InitOSContext() func() {
+ return func() {}
+}
+
+// SetupTestOSContext sets up a separate test OS context in which tests will be executed.
+func SetupTestOSContext(t *testing.T) func() {
+ return func() {}
+}
+
+// SetBasePath sets the base url prefix for the ns path
+func SetBasePath(path string) {
+}
--- /dev/null
+package osl
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "io"
+ "net"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+ "github.com/vishvananda/netlink"
+ "github.com/vishvananda/netlink/nl"
+ "github.com/vishvananda/netns"
+)
+
+const (
+ vethName1 = "wierdlongname1"
+ vethName2 = "wierdlongname2"
+ vethName3 = "wierdlongname3"
+ vethName4 = "wierdlongname4"
+ sboxIfaceName = "containername"
+)
+
+func generateRandomName(prefix string, size int) (string, error) {
+ id := make([]byte, 32)
+ if _, err := io.ReadFull(rand.Reader, id); err != nil {
+ return "", err
+ }
+ return prefix + hex.EncodeToString(id)[:size], nil
+}
+
+func newKey(t *testing.T) (string, error) {
+ name, err := generateRandomName("netns", 12)
+ if err != nil {
+ return "", err
+ }
+
+ name = filepath.Join("/tmp", name)
+ if _, err := os.Create(name); err != nil {
+ return "", err
+ }
+
+ // Set the rpmCleanupPeriod to be low to make the test run quicker
+ gpmLock.Lock()
+ gpmCleanupPeriod = 2 * time.Second
+ gpmLock.Unlock()
+
+ return name, nil
+}
+
+func newInfo(hnd *netlink.Handle, t *testing.T) (Sandbox, error) {
+ veth := &netlink.Veth{
+ LinkAttrs: netlink.LinkAttrs{Name: vethName1, TxQLen: 0},
+ PeerName: vethName2}
+ if err := hnd.LinkAdd(veth); err != nil {
+ return nil, err
+ }
+
+ // Store the sandbox side pipe interface
+ // This is needed for cleanup on DeleteEndpoint()
+ intf1 := &nwIface{}
+ intf1.srcName = vethName2
+ intf1.dstName = sboxIfaceName
+
+ ip4, addr, err := net.ParseCIDR("192.168.1.100/24")
+ if err != nil {
+ return nil, err
+ }
+ intf1.address = addr
+ intf1.address.IP = ip4
+
+ ip6, addrv6, err := net.ParseCIDR("fe80::2/64")
+ if err != nil {
+ return nil, err
+ }
+ intf1.addressIPv6 = addrv6
+ intf1.addressIPv6.IP = ip6
+
+ _, route, err := net.ParseCIDR("192.168.2.1/32")
+ if err != nil {
+ return nil, err
+ }
+
+ intf1.routes = []*net.IPNet{route}
+
+ intf2 := &nwIface{}
+ intf2.srcName = "testbridge"
+ intf2.dstName = sboxIfaceName
+ intf2.bridge = true
+
+ veth = &netlink.Veth{
+ LinkAttrs: netlink.LinkAttrs{Name: vethName3, TxQLen: 0},
+ PeerName: vethName4}
+
+ if err := hnd.LinkAdd(veth); err != nil {
+ return nil, err
+ }
+
+ intf3 := &nwIface{}
+ intf3.srcName = vethName4
+ intf3.dstName = sboxIfaceName
+ intf3.master = "testbridge"
+
+ info := &networkNamespace{iFaces: []*nwIface{intf1, intf2, intf3}}
+
+ info.gw = net.ParseIP("192.168.1.1")
+ info.gwv6 = net.ParseIP("fe80::1")
+
+ return info, nil
+}
+
+func verifySandbox(t *testing.T, s Sandbox, ifaceSuffixes []string) {
+ _, ok := s.(*networkNamespace)
+ if !ok {
+ t.Fatalf("The sandbox interface returned is not of type networkNamespace")
+ }
+
+ sbNs, err := netns.GetFromPath(s.Key())
+ if err != nil {
+ t.Fatalf("Failed top open network namespace path %q: %v", s.Key(), err)
+ }
+ defer sbNs.Close()
+
+ nh, err := netlink.NewHandleAt(sbNs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ for _, suffix := range ifaceSuffixes {
+ _, err = nh.LinkByName(sboxIfaceName + suffix)
+ if err != nil {
+ t.Fatalf("Could not find the interface %s inside the sandbox: %v",
+ sboxIfaceName+suffix, err)
+ }
+ }
+}
+
+func verifyCleanup(t *testing.T, s Sandbox, wait bool) {
+ if wait {
+ time.Sleep(time.Duration(gpmCleanupPeriod * 2))
+ }
+
+ if _, err := os.Stat(s.Key()); err == nil {
+ if wait {
+ t.Fatalf("The sandbox path %s is not getting cleaned up even after twice the cleanup period", s.Key())
+ } else {
+ t.Fatalf("The sandbox path %s is not cleaned up after running gc", s.Key())
+ }
+ }
+}
+
+func TestScanStatistics(t *testing.T) {
+ data :=
+ "Inter-| Receive | Transmit\n" +
+ " face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n" +
+ " eth0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" +
+ " wlan0: 7787685 11141 0 0 0 0 0 0 1681390 7220 0 0 0 0 0 0\n" +
+ " lo: 783782 1853 0 0 0 0 0 0 783782 1853 0 0 0 0 0 0\n" +
+ "lxcbr0: 0 0 0 0 0 0 0 0 9006 61 0 0 0 0 0 0\n"
+
+ i := &types.InterfaceStatistics{}
+
+ if err := scanInterfaceStats(data, "wlan0", i); err != nil {
+ t.Fatal(err)
+ }
+ if i.TxBytes != 1681390 || i.TxPackets != 7220 || i.RxBytes != 7787685 || i.RxPackets != 11141 {
+ t.Fatalf("Error scanning the statistics")
+ }
+
+ if err := scanInterfaceStats(data, "lxcbr0", i); err != nil {
+ t.Fatal(err)
+ }
+ if i.TxBytes != 9006 || i.TxPackets != 61 || i.RxBytes != 0 || i.RxPackets != 0 {
+ t.Fatalf("Error scanning the statistics")
+ }
+}
+
+func TestDisableIPv6DAD(t *testing.T) {
+ if testutils.RunningOnCircleCI() {
+ t.Skipf("Skipping as not supported on CIRCLE CI kernel")
+ }
+
+ defer testutils.SetupTestOSContext(t)()
+
+ key, err := newKey(t)
+ if err != nil {
+ t.Fatalf("Failed to obtain a key: %v", err)
+ }
+
+ s, err := NewSandbox(key, true, false)
+ if err != nil {
+ t.Fatalf("Failed to create a new sandbox: %v", err)
+ }
+ runtime.LockOSThread()
+ defer s.Destroy()
+
+ n, ok := s.(*networkNamespace)
+ if !ok {
+ t.Fatal(ok)
+ }
+ nlh := n.nlHandle
+
+ ipv6, _ := types.ParseCIDR("2001:db8::44/64")
+ iface := &nwIface{addressIPv6: ipv6, ns: n, dstName: "sideA"}
+
+ veth := &netlink.Veth{
+ LinkAttrs: netlink.LinkAttrs{Name: "sideA"},
+ PeerName: "sideB",
+ }
+
+ err = nlh.LinkAdd(veth)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ link, err := nlh.LinkByName("sideA")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = setInterfaceIPv6(nlh, link, iface)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ addrList, err := nlh.AddrList(link, nl.FAMILY_V6)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if addrList[0].Flags&syscall.IFA_F_NODAD == 0 {
+ t.Fatalf("Unexpected interface flags: 0x%x. Expected to contain 0x%x", addrList[0].Flags, syscall.IFA_F_NODAD)
+ }
+}
+
+func TestSetInterfaceIP(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ key, err := newKey(t)
+ if err != nil {
+ t.Fatalf("Failed to obtain a key: %v", err)
+ }
+
+ s, err := NewSandbox(key, true, false)
+ if err != nil {
+ t.Fatalf("Failed to create a new sandbox: %v", err)
+ }
+ runtime.LockOSThread()
+ defer s.Destroy()
+
+ n, ok := s.(*networkNamespace)
+ if !ok {
+ t.Fatal(ok)
+ }
+ nlh := n.nlHandle
+
+ ipv4, _ := types.ParseCIDR("172.30.0.33/24")
+ ipv6, _ := types.ParseCIDR("2001:db8::44/64")
+ iface := &nwIface{address: ipv4, addressIPv6: ipv6, ns: n, dstName: "sideA"}
+
+ if err := nlh.LinkAdd(&netlink.Veth{
+ LinkAttrs: netlink.LinkAttrs{Name: "sideA"},
+ PeerName: "sideB",
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ linkA, err := nlh.LinkByName("sideA")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ linkB, err := nlh.LinkByName("sideB")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := nlh.LinkSetUp(linkA); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := nlh.LinkSetUp(linkB); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := setInterfaceIP(nlh, linkA, iface); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := setInterfaceIPv6(nlh, linkA, iface); err != nil {
+ t.Fatal(err)
+ }
+
+ err = setInterfaceIP(nlh, linkB, iface)
+ if err == nil {
+ t.Fatalf("Expected route conflict error, but succeeded")
+ }
+ if !strings.Contains(err.Error(), "conflicts with existing route") {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ err = setInterfaceIPv6(nlh, linkB, iface)
+ if err == nil {
+ t.Fatalf("Expected route conflict error, but succeeded")
+ }
+ if !strings.Contains(err.Error(), "conflicts with existing route") {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+}
+
+func TestLiveRestore(t *testing.T) {
+
+ defer testutils.SetupTestOSContext(t)()
+
+ key, err := newKey(t)
+ if err != nil {
+ t.Fatalf("Failed to obtain a key: %v", err)
+ }
+
+ s, err := NewSandbox(key, true, false)
+ if err != nil {
+ t.Fatalf("Failed to create a new sandbox: %v", err)
+ }
+ runtime.LockOSThread()
+ defer s.Destroy()
+
+ n, ok := s.(*networkNamespace)
+ if !ok {
+ t.Fatal(ok)
+ }
+ nlh := n.nlHandle
+
+ ipv4, _ := types.ParseCIDR("172.30.0.33/24")
+ ipv6, _ := types.ParseCIDR("2001:db8::44/64")
+ iface := &nwIface{address: ipv4, addressIPv6: ipv6, ns: n, dstName: "sideA"}
+
+ if err := nlh.LinkAdd(&netlink.Veth{
+ LinkAttrs: netlink.LinkAttrs{Name: "sideA"},
+ PeerName: "sideB",
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ linkA, err := nlh.LinkByName("sideA")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ linkB, err := nlh.LinkByName("sideB")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := nlh.LinkSetUp(linkA); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := nlh.LinkSetUp(linkB); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := setInterfaceIP(nlh, linkA, iface); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := setInterfaceIPv6(nlh, linkA, iface); err != nil {
+ t.Fatal(err)
+ }
+
+ err = setInterfaceIP(nlh, linkB, iface)
+ if err == nil {
+ t.Fatalf("Expected route conflict error, but succeeded")
+ }
+ if !strings.Contains(err.Error(), "conflicts with existing route") {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ err = setInterfaceIPv6(nlh, linkB, iface)
+ if err == nil {
+ t.Fatalf("Expected route conflict error, but succeeded")
+ }
+ if !strings.Contains(err.Error(), "conflicts with existing route") {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ // Create newsandbox with Restore - TRUE
+ s, err = NewSandbox(key, true, true)
+ if err != nil {
+ t.Fatalf("Failed to create a new sandbox: %v", err)
+ }
+
+ // Check if the IPV4 & IPV6 entry present
+ // If present , we should get error in below call
+ // It shows us , we don't delete any config in live-restore case
+ if err := setInterfaceIPv6(nlh, linkA, iface); err == nil {
+ t.Fatalf("Expected route conflict error, but succeeded for IPV6 ")
+ }
+ if err := setInterfaceIP(nlh, linkA, iface); err == nil {
+ t.Fatalf("Expected route conflict error, but succeeded for IPV4 ")
+ }
+}
--- /dev/null
+package osl
+
+import (
+ "os"
+ "runtime"
+ "testing"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork/ns"
+ "github.com/docker/libnetwork/testutils"
+)
+
+func TestMain(m *testing.M) {
+ if reexec.Init() {
+ return
+ }
+ os.Exit(m.Run())
+}
+
+func TestSandboxCreate(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ key, err := newKey(t)
+ if err != nil {
+ t.Fatalf("Failed to obtain a key: %v", err)
+ }
+
+ s, err := NewSandbox(key, true, false)
+ if err != nil {
+ t.Fatalf("Failed to create a new sandbox: %v", err)
+ }
+
+ if s.Key() != key {
+ t.Fatalf("s.Key() returned %s. Expected %s", s.Key(), key)
+ }
+
+ tbox, err := newInfo(ns.NlHandle(), t)
+ if err != nil {
+ t.Fatalf("Failed to generate new sandbox info: %v", err)
+ }
+
+ for _, i := range tbox.Info().Interfaces() {
+ err = s.AddInterface(i.SrcName(), i.DstName(),
+ tbox.InterfaceOptions().Bridge(i.Bridge()),
+ tbox.InterfaceOptions().Address(i.Address()),
+ tbox.InterfaceOptions().AddressIPv6(i.AddressIPv6()))
+ if err != nil {
+ t.Fatalf("Failed to add interfaces to sandbox: %v", err)
+ }
+ }
+
+ err = s.SetGateway(tbox.Info().Gateway())
+ if err != nil {
+ t.Fatalf("Failed to set gateway to sandbox: %v", err)
+ }
+
+ err = s.SetGatewayIPv6(tbox.Info().GatewayIPv6())
+ if err != nil {
+ t.Fatalf("Failed to set ipv6 gateway to sandbox: %v", err)
+ }
+
+ verifySandbox(t, s, []string{"0", "1", "2"})
+
+ err = s.Destroy()
+ if err != nil {
+ t.Fatal(err)
+ }
+ verifyCleanup(t, s, true)
+}
+
+func TestSandboxCreateTwice(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ key, err := newKey(t)
+ if err != nil {
+ t.Fatalf("Failed to obtain a key: %v", err)
+ }
+
+ _, err = NewSandbox(key, true, false)
+ if err != nil {
+ t.Fatalf("Failed to create a new sandbox: %v", err)
+ }
+ runtime.LockOSThread()
+
+ // Create another sandbox with the same key to see if we handle it
+ // gracefully.
+ s, err := NewSandbox(key, true, false)
+ if err != nil {
+ t.Fatalf("Failed to create a new sandbox: %v", err)
+ }
+ runtime.LockOSThread()
+
+ err = s.Destroy()
+ if err != nil {
+ t.Fatal(err)
+ }
+ GC()
+ verifyCleanup(t, s, false)
+}
+
+func TestSandboxGC(t *testing.T) {
+ key, err := newKey(t)
+ if err != nil {
+ t.Fatalf("Failed to obtain a key: %v", err)
+ }
+
+ s, err := NewSandbox(key, true, false)
+ if err != nil {
+ t.Fatalf("Failed to create a new sandbox: %v", err)
+ }
+
+ err = s.Destroy()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ GC()
+ verifyCleanup(t, s, false)
+}
+
+func TestAddRemoveInterface(t *testing.T) {
+ defer testutils.SetupTestOSContext(t)()
+
+ key, err := newKey(t)
+ if err != nil {
+ t.Fatalf("Failed to obtain a key: %v", err)
+ }
+
+ s, err := NewSandbox(key, true, false)
+ if err != nil {
+ t.Fatalf("Failed to create a new sandbox: %v", err)
+ }
+ runtime.LockOSThread()
+
+ if s.Key() != key {
+ t.Fatalf("s.Key() returned %s. Expected %s", s.Key(), key)
+ }
+
+ tbox, err := newInfo(ns.NlHandle(), t)
+ if err != nil {
+ t.Fatalf("Failed to generate new sandbox info: %v", err)
+ }
+
+ for _, i := range tbox.Info().Interfaces() {
+ err = s.AddInterface(i.SrcName(), i.DstName(),
+ tbox.InterfaceOptions().Bridge(i.Bridge()),
+ tbox.InterfaceOptions().Address(i.Address()),
+ tbox.InterfaceOptions().AddressIPv6(i.AddressIPv6()))
+ if err != nil {
+ t.Fatalf("Failed to add interfaces to sandbox: %v", err)
+ }
+ }
+
+ verifySandbox(t, s, []string{"0", "1", "2"})
+
+ interfaces := s.Info().Interfaces()
+ if err := interfaces[0].Remove(); err != nil {
+ t.Fatalf("Failed to remove interfaces from sandbox: %v", err)
+ }
+
+ verifySandbox(t, s, []string{"1", "2"})
+
+ i := tbox.Info().Interfaces()[0]
+ if err := s.AddInterface(i.SrcName(), i.DstName(),
+ tbox.InterfaceOptions().Bridge(i.Bridge()),
+ tbox.InterfaceOptions().Address(i.Address()),
+ tbox.InterfaceOptions().AddressIPv6(i.AddressIPv6())); err != nil {
+ t.Fatalf("Failed to add interfaces to sandbox: %v", err)
+ }
+
+ verifySandbox(t, s, []string{"1", "2", "3"})
+
+ err = s.Destroy()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ GC()
+ verifyCleanup(t, s, false)
+}
--- /dev/null
+// +build !linux,!windows,!freebsd
+
+package osl
+
+import "errors"
+
+var (
+ // ErrNotImplemented is for platforms which don't implement sandbox
+ ErrNotImplemented = errors.New("not implemented")
+)
+
+// NewSandbox provides a new sandbox instance created in an os specific way
+// provided a key which uniquely identifies the sandbox
+func NewSandbox(key string, osCreate, isRestore bool) (Sandbox, error) {
+ return nil, ErrNotImplemented
+}
+
+// GenerateKey generates a sandbox key based on the passed
+// container id.
+func GenerateKey(containerID string) string {
+ return ""
+}
--- /dev/null
+// +build !linux
+
+package osl
+
+import (
+ "errors"
+ "testing"
+)
+
+var ErrNotImplemented = errors.New("not implemented")
+
+func newKey(t *testing.T) (string, error) {
+ return nil, ErrNotImplemented
+}
+
+func verifySandbox(t *testing.T, s Sandbox) {
+ return
+}
--- /dev/null
+package portallocator
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "sync"
+)
+
+const (
+ // DefaultPortRangeStart indicates the first port in port range
+ DefaultPortRangeStart = 49153
+ // DefaultPortRangeEnd indicates the last port in port range
+ DefaultPortRangeEnd = 65535
+)
+
+type ipMapping map[string]protoMap
+
+var (
+ // ErrAllPortsAllocated is returned when no more ports are available
+ ErrAllPortsAllocated = errors.New("all ports are allocated")
+ // ErrUnknownProtocol is returned when an unknown protocol was specified
+ ErrUnknownProtocol = errors.New("unknown protocol")
+ defaultIP = net.ParseIP("0.0.0.0")
+ once sync.Once
+ instance *PortAllocator
+ createInstance = func() { instance = newInstance() }
+)
+
+// ErrPortAlreadyAllocated is the returned error information when a requested port is already being used
+type ErrPortAlreadyAllocated struct {
+ ip string
+ port int
+}
+
+func newErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
+ return ErrPortAlreadyAllocated{
+ ip: ip,
+ port: port,
+ }
+}
+
+// IP returns the address to which the used port is associated
+func (e ErrPortAlreadyAllocated) IP() string {
+ return e.ip
+}
+
+// Port returns the value of the already used port
+func (e ErrPortAlreadyAllocated) Port() int {
+ return e.port
+}
+
+// IPPort returns the address and the port in the form ip:port
+func (e ErrPortAlreadyAllocated) IPPort() string {
+ return fmt.Sprintf("%s:%d", e.ip, e.port)
+}
+
+// Error is the implementation of error.Error interface
+func (e ErrPortAlreadyAllocated) Error() string {
+ return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
+}
+
+type (
+ // PortAllocator manages the transport ports database
+ PortAllocator struct {
+ mutex sync.Mutex
+ ipMap ipMapping
+ Begin int
+ End int
+ }
+ portRange struct {
+ begin int
+ end int
+ last int
+ }
+ portMap struct {
+ p map[int]struct{}
+ defaultRange string
+ portRanges map[string]*portRange
+ }
+ protoMap map[string]*portMap
+)
+
+// Get returns the default instance of PortAllocator
+func Get() *PortAllocator {
+ // Port Allocator is a singleton
+ // Note: Long term solution will be each PortAllocator will have access to
+ // the OS so that it can have up to date view of the OS port allocation.
+ // When this happens singleton behavior will be removed. Clients do not
+ // need to worry about this, they will not see a change in behavior.
+ once.Do(createInstance)
+ return instance
+}
+
+func newInstance() *PortAllocator {
+ start, end, err := getDynamicPortRange()
+ if err != nil {
+ start, end = DefaultPortRangeStart, DefaultPortRangeEnd
+ }
+ return &PortAllocator{
+ ipMap: ipMapping{},
+ Begin: start,
+ End: end,
+ }
+}
+
+// RequestPort requests new port from global ports pool for specified ip and proto.
+// If port is 0 it returns first free port. Otherwise it checks port availability
+// in proto's pool and returns that port or error if port is already busy.
+func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
+ return p.RequestPortInRange(ip, proto, port, port)
+}
+
+// RequestPortInRange requests new port from global ports pool for specified ip and proto.
+// If portStart and portEnd are 0 it returns the first free port in the default ephemeral range.
+// If portStart != portEnd it returns the first free port in the requested range.
+// Otherwise (portStart == portEnd) it checks port availability in the requested proto's port-pool
+// and returns that port or error if port is already busy.
+func (p *PortAllocator) RequestPortInRange(ip net.IP, proto string, portStart, portEnd int) (int, error) {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+
+ if proto != "tcp" && proto != "udp" && proto != "sctp" {
+ return 0, ErrUnknownProtocol
+ }
+
+ if ip == nil {
+ ip = defaultIP
+ }
+ ipstr := ip.String()
+ protomap, ok := p.ipMap[ipstr]
+ if !ok {
+ protomap = protoMap{
+ "tcp": p.newPortMap(),
+ "udp": p.newPortMap(),
+ "sctp": p.newPortMap(),
+ }
+
+ p.ipMap[ipstr] = protomap
+ }
+ mapping := protomap[proto]
+ if portStart > 0 && portStart == portEnd {
+ if _, ok := mapping.p[portStart]; !ok {
+ mapping.p[portStart] = struct{}{}
+ return portStart, nil
+ }
+ return 0, newErrPortAlreadyAllocated(ipstr, portStart)
+ }
+
+ port, err := mapping.findPort(portStart, portEnd)
+ if err != nil {
+ return 0, err
+ }
+ return port, nil
+}
+
+// ReleasePort releases port from global ports pool for specified ip and proto.
+func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+
+ if ip == nil {
+ ip = defaultIP
+ }
+ protomap, ok := p.ipMap[ip.String()]
+ if !ok {
+ return nil
+ }
+ delete(protomap[proto].p, port)
+ return nil
+}
+
+func (p *PortAllocator) newPortMap() *portMap {
+ defaultKey := getRangeKey(p.Begin, p.End)
+ pm := &portMap{
+ p: map[int]struct{}{},
+ defaultRange: defaultKey,
+ portRanges: map[string]*portRange{
+ defaultKey: newPortRange(p.Begin, p.End),
+ },
+ }
+ return pm
+}
+
+// ReleaseAll releases all ports for all ips.
+func (p *PortAllocator) ReleaseAll() error {
+ p.mutex.Lock()
+ p.ipMap = ipMapping{}
+ p.mutex.Unlock()
+ return nil
+}
+
+func getRangeKey(portStart, portEnd int) string {
+ return fmt.Sprintf("%d-%d", portStart, portEnd)
+}
+
+func newPortRange(portStart, portEnd int) *portRange {
+ return &portRange{
+ begin: portStart,
+ end: portEnd,
+ last: portEnd,
+ }
+}
+
+func (pm *portMap) getPortRange(portStart, portEnd int) (*portRange, error) {
+ var key string
+ if portStart == 0 && portEnd == 0 {
+ key = pm.defaultRange
+ } else {
+ key = getRangeKey(portStart, portEnd)
+ if portStart == portEnd ||
+ portStart == 0 || portEnd == 0 ||
+ portEnd < portStart {
+ return nil, fmt.Errorf("invalid port range: %s", key)
+ }
+ }
+
+ // Return existing port range, if already known.
+ if pr, exists := pm.portRanges[key]; exists {
+ return pr, nil
+ }
+
+ // Otherwise create a new port range.
+ pr := newPortRange(portStart, portEnd)
+ pm.portRanges[key] = pr
+ return pr, nil
+}
+
+func (pm *portMap) findPort(portStart, portEnd int) (int, error) {
+ pr, err := pm.getPortRange(portStart, portEnd)
+ if err != nil {
+ return 0, err
+ }
+ port := pr.last
+
+ for i := 0; i <= pr.end-pr.begin; i++ {
+ port++
+ if port > pr.end {
+ port = pr.begin
+ }
+
+ if _, ok := pm.p[port]; !ok {
+ pm.p[port] = struct{}{}
+ pr.last = port
+ return port, nil
+ }
+ }
+ return 0, ErrAllPortsAllocated
+}
--- /dev/null
+package portallocator
+
+import (
+ "bytes"
+ "fmt"
+ "os/exec"
+)
+
+func getDynamicPortRange() (start int, end int, err error) {
+ portRangeKernelSysctl := []string{"net.inet.ip.portrange.hifirst", "net.ip.portrange.hilast"}
+ portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
+ portRangeLowCmd := exec.Command("/sbin/sysctl", portRangeKernelSysctl[0])
+ var portRangeLowOut bytes.Buffer
+ portRangeLowCmd.Stdout = &portRangeLowOut
+ cmdErr := portRangeLowCmd.Run()
+ if cmdErr != nil {
+ return 0, 0, fmt.Errorf("port allocator - sysctl net.inet.ip.portrange.hifirst failed - %s: %v", portRangeFallback, err)
+ }
+ n, err := fmt.Sscanf(portRangeLowOut.String(), "%d", &start)
+ if n != 1 || err != nil {
+ if err == nil {
+ err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
+ }
+ return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range start from %s - %s: %v", portRangeLowOut.String(), portRangeFallback, err)
+ }
+
+ portRangeHighCmd := exec.Command("/sbin/sysctl", portRangeKernelSysctl[1])
+ var portRangeHighOut bytes.Buffer
+ portRangeHighCmd.Stdout = &portRangeHighOut
+ cmdErr = portRangeHighCmd.Run()
+ if cmdErr != nil {
+ return 0, 0, fmt.Errorf("port allocator - sysctl net.inet.ip.portrange.hilast failed - %s: %v", portRangeFallback, err)
+ }
+ n, err = fmt.Sscanf(portRangeHighOut.String(), "%d", &end)
+ if n != 1 || err != nil {
+ if err == nil {
+ err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
+ }
+ return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range end from %s - %s: %v", portRangeHighOut.String(), portRangeFallback, err)
+ }
+ return start, end, nil
+}
--- /dev/null
+package portallocator
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+)
+
+func getDynamicPortRange() (start int, end int, err error) {
+ const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
+ portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
+ file, err := os.Open(portRangeKernelParam)
+ if err != nil {
+ return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
+ }
+
+ defer file.Close()
+
+ n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
+ if n != 2 || err != nil {
+ if err == nil {
+ err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
+ }
+ return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
+ }
+ return start, end, nil
+}
--- /dev/null
+package portallocator
+
+import (
+ "net"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func resetPortAllocator() {
+ instance = newInstance()
+}
+
+func TestRequestNewPort(t *testing.T) {
+ p := Get()
+ defer resetPortAllocator()
+
+ port, err := p.RequestPort(defaultIP, "tcp", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := p.Begin; port != expected {
+ t.Fatalf("Expected port %d got %d", expected, port)
+ }
+}
+
+func TestRequestSpecificPort(t *testing.T) {
+ p := Get()
+ defer resetPortAllocator()
+
+ port, err := p.RequestPort(defaultIP, "tcp", 5000)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if port != 5000 {
+ t.Fatalf("Expected port 5000 got %d", port)
+ }
+}
+
+func TestReleasePort(t *testing.T) {
+ p := Get()
+
+ port, err := p.RequestPort(defaultIP, "tcp", 5000)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port != 5000 {
+ t.Fatalf("Expected port 5000 got %d", port)
+ }
+
+ if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestReuseReleasedPort(t *testing.T) {
+ p := Get()
+ defer resetPortAllocator()
+
+ port, err := p.RequestPort(defaultIP, "tcp", 5000)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port != 5000 {
+ t.Fatalf("Expected port 5000 got %d", port)
+ }
+
+ if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
+ t.Fatal(err)
+ }
+
+ port, err = p.RequestPort(defaultIP, "tcp", 5000)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port != 5000 {
+ t.Fatalf("Expected port 5000 got %d", port)
+ }
+}
+
+func TestReleaseUnreadledPort(t *testing.T) {
+ p := Get()
+ defer resetPortAllocator()
+
+ port, err := p.RequestPort(defaultIP, "tcp", 5000)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port != 5000 {
+ t.Fatalf("Expected port 5000 got %d", port)
+ }
+
+ _, err = p.RequestPort(defaultIP, "tcp", 5000)
+
+ switch err.(type) {
+ case ErrPortAlreadyAllocated:
+ default:
+ t.Fatalf("Expected port allocation error got %s", err)
+ }
+}
+
+func TestUnknowProtocol(t *testing.T) {
+ if _, err := Get().RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
+ t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err)
+ }
+}
+
+func TestAllocateAllPorts(t *testing.T) {
+ p := Get()
+ defer resetPortAllocator()
+
+ for i := 0; i <= p.End-p.Begin; i++ {
+ port, err := p.RequestPort(defaultIP, "tcp", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := p.Begin + i; port != expected {
+ t.Fatalf("Expected port %d got %d", expected, port)
+ }
+ }
+
+ if _, err := p.RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
+ t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
+ }
+
+ _, err := p.RequestPort(defaultIP, "udp", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // release a port in the middle and ensure we get another tcp port
+ port := p.Begin + 5
+ if err := p.ReleasePort(defaultIP, "tcp", port); err != nil {
+ t.Fatal(err)
+ }
+ newPort, err := p.RequestPort(defaultIP, "tcp", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if newPort != port {
+ t.Fatalf("Expected port %d got %d", port, newPort)
+ }
+
+ // now pm.last == newPort, release it so that it's the only free port of
+ // the range, and ensure we get it back
+ if err := p.ReleasePort(defaultIP, "tcp", newPort); err != nil {
+ t.Fatal(err)
+ }
+ port, err = p.RequestPort(defaultIP, "tcp", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if newPort != port {
+ t.Fatalf("Expected port %d got %d", newPort, port)
+ }
+}
+
+func BenchmarkAllocatePorts(b *testing.B) {
+ p := Get()
+ defer resetPortAllocator()
+
+ for i := 0; i < b.N; i++ {
+ for i := 0; i <= p.End-p.Begin; i++ {
+ port, err := p.RequestPort(defaultIP, "tcp", 0)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ if expected := p.Begin + i; port != expected {
+ b.Fatalf("Expected port %d got %d", expected, port)
+ }
+ }
+ p.ReleaseAll()
+ }
+}
+
+func TestPortAllocation(t *testing.T) {
+ p := Get()
+ defer resetPortAllocator()
+
+ ip := net.ParseIP("192.168.0.1")
+ ip2 := net.ParseIP("192.168.0.2")
+ if port, err := p.RequestPort(ip, "tcp", 80); err != nil {
+ t.Fatal(err)
+ } else if port != 80 {
+ t.Fatalf("Acquire(80) should return 80, not %d", port)
+ }
+ port, err := p.RequestPort(ip, "tcp", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port <= 0 {
+ t.Fatalf("Acquire(0) should return a non-zero port")
+ }
+
+ if _, err := p.RequestPort(ip, "tcp", port); err == nil {
+ t.Fatalf("Acquiring a port already in use should return an error")
+ }
+
+ if newPort, err := p.RequestPort(ip, "tcp", 0); err != nil {
+ t.Fatal(err)
+ } else if newPort == port {
+ t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
+ }
+
+ if _, err := p.RequestPort(ip, "tcp", 80); err == nil {
+ t.Fatalf("Acquiring a port already in use should return an error")
+ }
+ if _, err := p.RequestPort(ip2, "tcp", 80); err != nil {
+ t.Fatalf("It should be possible to allocate the same port on a different interface")
+ }
+ if _, err := p.RequestPort(ip2, "tcp", 80); err == nil {
+ t.Fatalf("Acquiring a port already in use should return an error")
+ }
+ if err := p.ReleasePort(ip, "tcp", 80); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := p.RequestPort(ip, "tcp", 80); err != nil {
+ t.Fatal(err)
+ }
+
+ port, err = p.RequestPort(ip, "tcp", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ port2, err := p.RequestPort(ip, "tcp", port+1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ port3, err := p.RequestPort(ip, "tcp", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port3 == port2 {
+ t.Fatal("Requesting a dynamic port should never allocate a used port")
+ }
+}
+
+func TestPortAllocationWithCustomRange(t *testing.T) {
+ p := Get()
+ defer resetPortAllocator()
+
+ start, end := 8081, 8082
+ specificPort := 8000
+
+ //get an ephemeral port.
+ port1, err := p.RequestPortInRange(defaultIP, "tcp", 0, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ //request invalid ranges
+ if _, err := p.RequestPortInRange(defaultIP, "tcp", 0, end); err == nil {
+ t.Fatalf("Expected error for invalid range %d-%d", 0, end)
+ }
+ if _, err := p.RequestPortInRange(defaultIP, "tcp", start, 0); err == nil {
+ t.Fatalf("Expected error for invalid range %d-%d", 0, end)
+ }
+ if _, err := p.RequestPortInRange(defaultIP, "tcp", 8081, 8080); err == nil {
+ t.Fatalf("Expected error for invalid range %d-%d", 0, end)
+ }
+
+ //request a single port
+ port, err := p.RequestPortInRange(defaultIP, "tcp", specificPort, specificPort)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port != specificPort {
+ t.Fatalf("Expected port %d, got %d", specificPort, port)
+ }
+
+ //get a port from the range
+ port2, err := p.RequestPortInRange(defaultIP, "tcp", start, end)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port2 < start || port2 > end {
+ t.Fatalf("Expected a port between %d and %d, got %d", start, end, port2)
+ }
+ //get another ephemeral port (should be > port1)
+ port3, err := p.RequestPortInRange(defaultIP, "tcp", 0, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port3 < port1 {
+ t.Fatalf("Expected new port > %d in the ephemeral range, got %d", port1, port3)
+ }
+ //get another (and in this case the only other) port from the range
+ port4, err := p.RequestPortInRange(defaultIP, "tcp", start, end)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if port4 < start || port4 > end {
+ t.Fatalf("Expected a port between %d and %d, got %d", start, end, port4)
+ }
+ if port4 == port2 {
+ t.Fatal("Allocated the same port from a custom range")
+ }
+ //request 3rd port from the range of 2
+ if _, err := p.RequestPortInRange(defaultIP, "tcp", start, end); err != ErrAllPortsAllocated {
+ t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
+ }
+}
+
+func TestNoDuplicateBPR(t *testing.T) {
+ p := Get()
+ defer resetPortAllocator()
+
+ if port, err := p.RequestPort(defaultIP, "tcp", p.Begin); err != nil {
+ t.Fatal(err)
+ } else if port != p.Begin {
+ t.Fatalf("Expected port %d got %d", p.Begin, port)
+ }
+
+ if port, err := p.RequestPort(defaultIP, "tcp", 0); err != nil {
+ t.Fatal(err)
+ } else if port == p.Begin {
+ t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
+ }
+}
--- /dev/null
+package portallocator
+
+const (
+ StartPortRange = 60000
+ EndPortRange = 65000
+)
+
+func getDynamicPortRange() (start int, end int, err error) {
+ return StartPortRange, EndPortRange, nil
+}
--- /dev/null
+package portmapper
+
+import (
+ "errors"
+ "fmt"
+ "net"
+
+ "github.com/docker/libnetwork/portallocator"
+ "github.com/ishidawataru/sctp"
+ "github.com/sirupsen/logrus"
+)
+
+type mapping struct {
+ proto string
+ userlandProxy userlandProxy
+ host net.Addr
+ container net.Addr
+}
+
+var newProxy = newProxyCommand
+
+var (
+ // ErrUnknownBackendAddressType refers to an unknown container or unsupported address type
+ ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
+ // ErrPortMappedForIP refers to a port already mapped to an ip address
+ ErrPortMappedForIP = errors.New("port is already mapped to ip")
+ // ErrPortNotMapped refers to an unmapped port
+ ErrPortNotMapped = errors.New("port is not mapped")
+ // ErrSCTPAddrNoIP refers to a SCTP address without IP address.
+ ErrSCTPAddrNoIP = errors.New("sctp address does not contain any IP address")
+)
+
+// New returns a new instance of PortMapper
+func New(proxyPath string) *PortMapper {
+ return NewWithPortAllocator(portallocator.Get(), proxyPath)
+}
+
+// NewWithPortAllocator returns a new instance of PortMapper which will use the specified PortAllocator
+func NewWithPortAllocator(allocator *portallocator.PortAllocator, proxyPath string) *PortMapper {
+ return &PortMapper{
+ currentMappings: make(map[string]*mapping),
+ Allocator: allocator,
+ proxyPath: proxyPath,
+ }
+}
+
+// Map maps the specified container transport address to the host's network address and transport port
+func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) {
+ return pm.MapRange(container, hostIP, hostPort, hostPort, useProxy)
+}
+
+// MapRange maps the specified container transport address to the host's network address and transport port range
+func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, hostPortEnd int, useProxy bool) (host net.Addr, err error) {
+ pm.lock.Lock()
+ defer pm.lock.Unlock()
+
+ var (
+ m *mapping
+ proto string
+ allocatedHostPort int
+ )
+
+ switch container.(type) {
+ case *net.TCPAddr:
+ proto = "tcp"
+ if allocatedHostPort, err = pm.Allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
+ return nil, err
+ }
+
+ m = &mapping{
+ proto: proto,
+ host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
+ container: container,
+ }
+
+ if useProxy {
+ m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port, pm.proxyPath)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
+ if err != nil {
+ return nil, err
+ }
+ }
+ case *net.UDPAddr:
+ proto = "udp"
+ if allocatedHostPort, err = pm.Allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
+ return nil, err
+ }
+
+ m = &mapping{
+ proto: proto,
+ host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
+ container: container,
+ }
+
+ if useProxy {
+ m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port, pm.proxyPath)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
+ if err != nil {
+ return nil, err
+ }
+ }
+ case *sctp.SCTPAddr:
+ proto = "sctp"
+ if allocatedHostPort, err = pm.Allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
+ return nil, err
+ }
+
+ m = &mapping{
+ proto: proto,
+ host: &sctp.SCTPAddr{IP: []net.IP{hostIP}, Port: allocatedHostPort},
+ container: container,
+ }
+
+ if useProxy {
+ sctpAddr := container.(*sctp.SCTPAddr)
+ if len(sctpAddr.IP) == 0 {
+ return nil, ErrSCTPAddrNoIP
+ }
+ m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, sctpAddr.IP[0], sctpAddr.Port, pm.proxyPath)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
+ if err != nil {
+ return nil, err
+ }
+ }
+ default:
+ return nil, ErrUnknownBackendAddressType
+ }
+
+ // release the allocated port on any further error during return.
+ defer func() {
+ if err != nil {
+ pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort)
+ }
+ }()
+
+ key := getKey(m.host)
+ if _, exists := pm.currentMappings[key]; exists {
+ return nil, ErrPortMappedForIP
+ }
+
+ containerIP, containerPort := getIPAndPort(m.container)
+ if hostIP.To4() != nil {
+ if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
+ return nil, err
+ }
+ }
+
+ cleanup := func() error {
+ // need to undo the iptables rules before we return
+ m.userlandProxy.Stop()
+ if hostIP.To4() != nil {
+ pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
+ if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }
+
+ if err := m.userlandProxy.Start(); err != nil {
+ if err := cleanup(); err != nil {
+ return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
+ }
+ return nil, err
+ }
+
+ pm.currentMappings[key] = m
+ return m.host, nil
+}
+
+// Unmap removes stored mapping for the specified host transport address
+func (pm *PortMapper) Unmap(host net.Addr) error {
+ pm.lock.Lock()
+ defer pm.lock.Unlock()
+
+ key := getKey(host)
+ data, exists := pm.currentMappings[key]
+ if !exists {
+ return ErrPortNotMapped
+ }
+
+ if data.userlandProxy != nil {
+ data.userlandProxy.Stop()
+ }
+
+ delete(pm.currentMappings, key)
+
+ containerIP, containerPort := getIPAndPort(data.container)
+ hostIP, hostPort := getIPAndPort(data.host)
+ if err := pm.DeleteForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
+ logrus.Errorf("Error on iptables delete: %s", err)
+ }
+
+ switch a := host.(type) {
+ case *net.TCPAddr:
+ return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port)
+ case *net.UDPAddr:
+ return pm.Allocator.ReleasePort(a.IP, "udp", a.Port)
+ case *sctp.SCTPAddr:
+ if len(a.IP) == 0 {
+ return ErrSCTPAddrNoIP
+ }
+ return pm.Allocator.ReleasePort(a.IP[0], "sctp", a.Port)
+ }
+ return ErrUnknownBackendAddressType
+}
+
+//ReMapAll will re-apply all port mappings
+func (pm *PortMapper) ReMapAll() {
+ pm.lock.Lock()
+ defer pm.lock.Unlock()
+ logrus.Debugln("Re-applying all port mappings.")
+ for _, data := range pm.currentMappings {
+ containerIP, containerPort := getIPAndPort(data.container)
+ hostIP, hostPort := getIPAndPort(data.host)
+ if err := pm.AppendForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
+ logrus.Errorf("Error on iptables add: %s", err)
+ }
+ }
+}
+
+func getKey(a net.Addr) string {
+ switch t := a.(type) {
+ case *net.TCPAddr:
+ return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
+ case *net.UDPAddr:
+ return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
+ case *sctp.SCTPAddr:
+ if len(t.IP) == 0 {
+ logrus.Error(ErrSCTPAddrNoIP)
+ return ""
+ }
+ return fmt.Sprintf("%s:%d/%s", t.IP[0].String(), t.Port, "sctp")
+ }
+ return ""
+}
+
+func getIPAndPort(a net.Addr) (net.IP, int) {
+ switch t := a.(type) {
+ case *net.TCPAddr:
+ return t.IP, t.Port
+ case *net.UDPAddr:
+ return t.IP, t.Port
+ case *sctp.SCTPAddr:
+ if len(t.IP) == 0 {
+ logrus.Error(ErrSCTPAddrNoIP)
+ return nil, 0
+ }
+ return t.IP[0], t.Port
+ }
+ return nil, 0
+}
--- /dev/null
+package portmapper
+
+import (
+ "net"
+ "sync"
+
+ "github.com/docker/libnetwork/iptables"
+ "github.com/docker/libnetwork/portallocator"
+)
+
+// PortMapper manages the network address translation
+type PortMapper struct {
+ bridgeName string
+
+ // udp:ip:port
+ currentMappings map[string]*mapping
+ lock sync.Mutex
+
+ proxyPath string
+
+ Allocator *portallocator.PortAllocator
+ chain *iptables.ChainInfo
+}
+
+// SetIptablesChain sets the specified chain into portmapper
+func (pm *PortMapper) SetIptablesChain(c *iptables.ChainInfo, bridgeName string) {
+ pm.chain = c
+ pm.bridgeName = bridgeName
+}
+
+// AppendForwardingTableEntry adds a port mapping to the forwarding table
+func (pm *PortMapper) AppendForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
+ return pm.forward(iptables.Append, proto, sourceIP, sourcePort, containerIP, containerPort)
+}
+
+// DeleteForwardingTableEntry removes a port mapping from the forwarding table
+func (pm *PortMapper) DeleteForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
+ return pm.forward(iptables.Delete, proto, sourceIP, sourcePort, containerIP, containerPort)
+}
+
+func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
+ if pm.chain == nil {
+ return nil
+ }
+ return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort, pm.bridgeName)
+}
--- /dev/null
+package portmapper
+
+import (
+ "net"
+ "strings"
+ "testing"
+
+ "github.com/docker/libnetwork/iptables"
+ _ "github.com/docker/libnetwork/testutils"
+)
+
+func init() {
+ // override this func to mock out the proxy server
+ newProxy = newMockProxyCommand
+}
+
+func TestSetIptablesChain(t *testing.T) {
+ pm := New("")
+
+ c := &iptables.ChainInfo{
+ Name: "TEST",
+ }
+
+ if pm.chain != nil {
+ t.Fatal("chain should be nil at init")
+ }
+
+ pm.SetIptablesChain(c, "lo")
+ if pm.chain == nil {
+ t.Fatal("chain should not be nil after set")
+ }
+}
+
+func TestMapTCPPorts(t *testing.T) {
+ pm := New("")
+ dstIP1 := net.ParseIP("192.168.0.1")
+ dstIP2 := net.ParseIP("192.168.0.2")
+ dstAddr1 := &net.TCPAddr{IP: dstIP1, Port: 80}
+ dstAddr2 := &net.TCPAddr{IP: dstIP2, Port: 80}
+
+ srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+ srcAddr2 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.2")}
+
+ addrEqual := func(addr1, addr2 net.Addr) bool {
+ return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+ }
+
+ if host, err := pm.Map(srcAddr1, dstIP1, 80, true); err != nil {
+ t.Fatalf("Failed to allocate port: %s", err)
+ } else if !addrEqual(dstAddr1, host) {
+ t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+ dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network())
+ }
+
+ if _, err := pm.Map(srcAddr1, dstIP1, 80, true); err == nil {
+ t.Fatalf("Port is in use - mapping should have failed")
+ }
+
+ if _, err := pm.Map(srcAddr2, dstIP1, 80, true); err == nil {
+ t.Fatalf("Port is in use - mapping should have failed")
+ }
+
+ if _, err := pm.Map(srcAddr2, dstIP2, 80, true); err != nil {
+ t.Fatalf("Failed to allocate port: %s", err)
+ }
+
+ if pm.Unmap(dstAddr1) != nil {
+ t.Fatalf("Failed to release port")
+ }
+
+ if pm.Unmap(dstAddr2) != nil {
+ t.Fatalf("Failed to release port")
+ }
+
+ if pm.Unmap(dstAddr2) == nil {
+ t.Fatalf("Port already released, but no error reported")
+ }
+}
+
+func TestGetUDPKey(t *testing.T) {
+ addr := &net.UDPAddr{IP: net.ParseIP("192.168.1.5"), Port: 53}
+
+ key := getKey(addr)
+
+ if expected := "192.168.1.5:53/udp"; key != expected {
+ t.Fatalf("expected key %s got %s", expected, key)
+ }
+}
+
+func TestGetTCPKey(t *testing.T) {
+ addr := &net.TCPAddr{IP: net.ParseIP("192.168.1.5"), Port: 80}
+
+ key := getKey(addr)
+
+ if expected := "192.168.1.5:80/tcp"; key != expected {
+ t.Fatalf("expected key %s got %s", expected, key)
+ }
+}
+
+func TestGetUDPIPAndPort(t *testing.T) {
+ addr := &net.UDPAddr{IP: net.ParseIP("192.168.1.5"), Port: 53}
+
+ ip, port := getIPAndPort(addr)
+ if expected := "192.168.1.5"; ip.String() != expected {
+ t.Fatalf("expected ip %s got %s", expected, ip)
+ }
+
+ if ep := 53; port != ep {
+ t.Fatalf("expected port %d got %d", ep, port)
+ }
+}
+
+func TestMapUDPPorts(t *testing.T) {
+ pm := New("")
+ dstIP1 := net.ParseIP("192.168.0.1")
+ dstIP2 := net.ParseIP("192.168.0.2")
+ dstAddr1 := &net.UDPAddr{IP: dstIP1, Port: 80}
+ dstAddr2 := &net.UDPAddr{IP: dstIP2, Port: 80}
+
+ srcAddr1 := &net.UDPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+ srcAddr2 := &net.UDPAddr{Port: 1080, IP: net.ParseIP("172.16.0.2")}
+
+ addrEqual := func(addr1, addr2 net.Addr) bool {
+ return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+ }
+
+ if host, err := pm.Map(srcAddr1, dstIP1, 80, true); err != nil {
+ t.Fatalf("Failed to allocate port: %s", err)
+ } else if !addrEqual(dstAddr1, host) {
+ t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+ dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network())
+ }
+
+ if _, err := pm.Map(srcAddr1, dstIP1, 80, true); err == nil {
+ t.Fatalf("Port is in use - mapping should have failed")
+ }
+
+ if _, err := pm.Map(srcAddr2, dstIP1, 80, true); err == nil {
+ t.Fatalf("Port is in use - mapping should have failed")
+ }
+
+ if _, err := pm.Map(srcAddr2, dstIP2, 80, true); err != nil {
+ t.Fatalf("Failed to allocate port: %s", err)
+ }
+
+ if pm.Unmap(dstAddr1) != nil {
+ t.Fatalf("Failed to release port")
+ }
+
+ if pm.Unmap(dstAddr2) != nil {
+ t.Fatalf("Failed to release port")
+ }
+
+ if pm.Unmap(dstAddr2) == nil {
+ t.Fatalf("Port already released, but no error reported")
+ }
+}
+
+func TestMapAllPortsSingleInterface(t *testing.T) {
+ pm := New("")
+ dstIP1 := net.ParseIP("0.0.0.0")
+ srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+
+ hosts := []net.Addr{}
+ var host net.Addr
+ var err error
+
+ defer func() {
+ for _, val := range hosts {
+ pm.Unmap(val)
+ }
+ }()
+
+ for i := 0; i < 10; i++ {
+ start, end := pm.Allocator.Begin, pm.Allocator.End
+ for i := start; i < end; i++ {
+ if host, err = pm.Map(srcAddr1, dstIP1, 0, true); err != nil {
+ t.Fatal(err)
+ }
+
+ hosts = append(hosts, host)
+ }
+
+ if _, err := pm.Map(srcAddr1, dstIP1, start, true); err == nil {
+ t.Fatalf("Port %d should be bound but is not", start)
+ }
+
+ for _, val := range hosts {
+ if err := pm.Unmap(val); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ hosts = []net.Addr{}
+ }
+}
+
+func TestMapTCPDummyListen(t *testing.T) {
+ pm := New("")
+ dstIP := net.ParseIP("0.0.0.0")
+ dstAddr := &net.TCPAddr{IP: dstIP, Port: 80}
+
+ // no-op for dummy
+ srcAddr := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+
+ addrEqual := func(addr1, addr2 net.Addr) bool {
+ return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+ }
+
+ if host, err := pm.Map(srcAddr, dstIP, 80, false); err != nil {
+ t.Fatalf("Failed to allocate port: %s", err)
+ } else if !addrEqual(dstAddr, host) {
+ t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+ dstAddr.String(), dstAddr.Network(), host.String(), host.Network())
+ }
+ if _, err := net.Listen("tcp", "0.0.0.0:80"); err == nil {
+ t.Fatal("Listen on mapped port without proxy should fail")
+ } else {
+ if !strings.Contains(err.Error(), "address already in use") {
+ t.Fatalf("Error should be about address already in use, got %v", err)
+ }
+ }
+ if _, err := net.Listen("tcp", "0.0.0.0:81"); err != nil {
+ t.Fatal(err)
+ }
+ if host, err := pm.Map(srcAddr, dstIP, 81, false); err == nil {
+ t.Fatalf("Bound port shouldn't be allocated, but it was on: %v", host)
+ } else {
+ if !strings.Contains(err.Error(), "address already in use") {
+ t.Fatalf("Error should be about address already in use, got %v", err)
+ }
+ }
+}
+
+func TestMapUDPDummyListen(t *testing.T) {
+ pm := New("")
+ dstIP := net.ParseIP("0.0.0.0")
+ dstAddr := &net.UDPAddr{IP: dstIP, Port: 80}
+
+ // no-op for dummy
+ srcAddr := &net.UDPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+
+ addrEqual := func(addr1, addr2 net.Addr) bool {
+ return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+ }
+
+ if host, err := pm.Map(srcAddr, dstIP, 80, false); err != nil {
+ t.Fatalf("Failed to allocate port: %s", err)
+ } else if !addrEqual(dstAddr, host) {
+ t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+ dstAddr.String(), dstAddr.Network(), host.String(), host.Network())
+ }
+ if _, err := net.ListenUDP("udp", &net.UDPAddr{IP: dstIP, Port: 80}); err == nil {
+ t.Fatal("Listen on mapped port without proxy should fail")
+ } else {
+ if !strings.Contains(err.Error(), "address already in use") {
+ t.Fatalf("Error should be about address already in use, got %v", err)
+ }
+ }
+ if _, err := net.ListenUDP("udp", &net.UDPAddr{IP: dstIP, Port: 81}); err != nil {
+ t.Fatal(err)
+ }
+ if host, err := pm.Map(srcAddr, dstIP, 81, false); err == nil {
+ t.Fatalf("Bound port shouldn't be allocated, but it was on: %v", host)
+ } else {
+ if !strings.Contains(err.Error(), "address already in use") {
+ t.Fatalf("Error should be about address already in use, got %v", err)
+ }
+ }
+}
--- /dev/null
+package portmapper
+
+import (
+ "net"
+ "sync"
+
+ "github.com/docker/libnetwork/portallocator"
+)
+
+// PortMapper manages the network address translation
+type PortMapper struct {
+ bridgeName string
+
+ // udp:ip:port
+ currentMappings map[string]*mapping
+ lock sync.Mutex
+
+ proxyPath string
+
+ Allocator *portallocator.PortAllocator
+}
+
+// AppendForwardingTableEntry adds a port mapping to the forwarding table
+func (pm *PortMapper) AppendForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
+ return nil
+}
+
+// DeleteForwardingTableEntry removes a port mapping from the forwarding table
+func (pm *PortMapper) DeleteForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
+ return nil
+}
--- /dev/null
+package portmapper
+
+import "net"
+
+func newMockProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, userlandProxyPath string) (userlandProxy, error) {
+ return &mockProxyCommand{}, nil
+}
+
+type mockProxyCommand struct {
+}
+
+func (p *mockProxyCommand) Start() error {
+ return nil
+}
+
+func (p *mockProxyCommand) Stop() error {
+ return nil
+}
--- /dev/null
+package portmapper
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/exec"
+ "time"
+
+ "github.com/ishidawataru/sctp"
+)
+
+var userlandProxyCommandName = "docker-proxy"
+
+type userlandProxy interface {
+ Start() error
+ Stop() error
+}
+
+// proxyCommand wraps an exec.Cmd to run the userland TCP and UDP
+// proxies as separate processes.
+type proxyCommand struct {
+ cmd *exec.Cmd
+}
+
+func (p *proxyCommand) Start() error {
+ r, w, err := os.Pipe()
+ if err != nil {
+ return fmt.Errorf("proxy unable to open os.Pipe %s", err)
+ }
+ defer r.Close()
+ p.cmd.ExtraFiles = []*os.File{w}
+ if err := p.cmd.Start(); err != nil {
+ return err
+ }
+ w.Close()
+
+ errchan := make(chan error, 1)
+ go func() {
+ buf := make([]byte, 2)
+ r.Read(buf)
+
+ if string(buf) != "0\n" {
+ errStr, err := ioutil.ReadAll(r)
+ if err != nil {
+ errchan <- fmt.Errorf("Error reading exit status from userland proxy: %v", err)
+ return
+ }
+
+ errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr)
+ return
+ }
+ errchan <- nil
+ }()
+
+ select {
+ case err := <-errchan:
+ return err
+ case <-time.After(16 * time.Second):
+ return fmt.Errorf("Timed out proxy starting the userland proxy")
+ }
+}
+
+func (p *proxyCommand) Stop() error {
+ if p.cmd.Process != nil {
+ if err := p.cmd.Process.Signal(os.Interrupt); err != nil {
+ return err
+ }
+ return p.cmd.Wait()
+ }
+ return nil
+}
+
+// dummyProxy just listen on some port, it is needed to prevent accidental
+// port allocations on bound port, because without userland proxy we using
+// iptables rules and not net.Listen
+type dummyProxy struct {
+ listener io.Closer
+ addr net.Addr
+}
+
+func newDummyProxy(proto string, hostIP net.IP, hostPort int) (userlandProxy, error) {
+ switch proto {
+ case "tcp":
+ addr := &net.TCPAddr{IP: hostIP, Port: hostPort}
+ return &dummyProxy{addr: addr}, nil
+ case "udp":
+ addr := &net.UDPAddr{IP: hostIP, Port: hostPort}
+ return &dummyProxy{addr: addr}, nil
+ case "sctp":
+ addr := &sctp.SCTPAddr{IP: []net.IP{hostIP}, Port: hostPort}
+ return &dummyProxy{addr: addr}, nil
+ default:
+ return nil, fmt.Errorf("Unknown addr type: %s", proto)
+ }
+}
+
+func (p *dummyProxy) Start() error {
+ switch addr := p.addr.(type) {
+ case *net.TCPAddr:
+ l, err := net.ListenTCP("tcp", addr)
+ if err != nil {
+ return err
+ }
+ p.listener = l
+ case *net.UDPAddr:
+ l, err := net.ListenUDP("udp", addr)
+ if err != nil {
+ return err
+ }
+ p.listener = l
+ case *sctp.SCTPAddr:
+ l, err := sctp.ListenSCTP("sctp", addr)
+ if err != nil {
+ return err
+ }
+ p.listener = l
+ default:
+ return fmt.Errorf("Unknown addr type: %T", p.addr)
+ }
+ return nil
+}
+
+func (p *dummyProxy) Stop() error {
+ if p.listener != nil {
+ return p.listener.Close()
+ }
+ return nil
+}
--- /dev/null
+package portmapper
+
+import (
+ "net"
+ "os/exec"
+ "strconv"
+ "syscall"
+)
+
+func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) {
+ path := proxyPath
+ if proxyPath == "" {
+ cmd, err := exec.LookPath(userlandProxyCommandName)
+ if err != nil {
+ return nil, err
+ }
+ path = cmd
+ }
+
+ args := []string{
+ path,
+ "-proto", proto,
+ "-host-ip", hostIP.String(),
+ "-host-port", strconv.Itoa(hostPort),
+ "-container-ip", containerIP.String(),
+ "-container-port", strconv.Itoa(containerPort),
+ }
+
+ return &proxyCommand{
+ cmd: &exec.Cmd{
+ Path: path,
+ Args: args,
+ SysProcAttr: &syscall.SysProcAttr{
+ Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies
+ },
+ },
+ }, nil
+}
--- /dev/null
+package portmapper
+
+import (
+ "errors"
+ "net"
+)
+
+func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) {
+ return nil, errors.New("proxy is unsupported on windows")
+}
--- /dev/null
+Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf
--- /dev/null
+package dns
+
+import (
+ "regexp"
+)
+
+// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range.
+const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)`
+
+// IPv4Localhost is a regex pattern for IPv4 localhost address range.
+const IPv4Localhost = `(127\.([0-9]{1,3}\.){2}[0-9]{1,3})`
+
+var localhostIPRegexp = regexp.MustCompile(IPLocalhost)
+var localhostIPv4Regexp = regexp.MustCompile(IPv4Localhost)
+
+// IsLocalhost returns true if ip matches the localhost IP regular expression.
+// Used for determining if nameserver settings are being passed which are
+// localhost addresses
+func IsLocalhost(ip string) bool {
+ return localhostIPRegexp.MatchString(ip)
+}
+
+// IsIPv4Localhost returns true if ip matches the IPv4 localhost regular expression.
+func IsIPv4Localhost(ip string) bool {
+ return localhostIPv4Regexp.MatchString(ip)
+}
--- /dev/null
+// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf
+package resolvconf
+
+import (
+ "bytes"
+ "io/ioutil"
+ "regexp"
+ "strings"
+ "sync"
+
+ "github.com/docker/docker/pkg/ioutils"
+ "github.com/docker/libnetwork/resolvconf/dns"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // DefaultResolvConf points to the default file used for dns configuration on a linux machine
+ DefaultResolvConf = "/etc/resolv.conf"
+)
+
+var (
+ // Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS
+ defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"}
+ defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"}
+ ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
+ ipv4Address = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
+ // This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
+ // will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
+ // -- e.g. other link-local types -- either won't work in containers or are unnecessary.
+ // For readability and sufficiency for Docker purposes this seemed more reasonable than a
+ // 1000+ character regexp with exact and complete IPv6 validation
+ ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})(%\w+)?`
+
+ localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + dns.IPLocalhost + `\s*\n*`)
+ nsIPv6Regexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
+ nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
+ nsIPv6Regexpmatch = regexp.MustCompile(`^\s*nameserver\s*((` + ipv6Address + `))\s*$`)
+ nsIPv4Regexpmatch = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `))\s*$`)
+ searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
+ optionsRegexp = regexp.MustCompile(`^\s*options\s*(([^\s]+\s*)*)$`)
+)
+
+var lastModified struct {
+ sync.Mutex
+ sha256 string
+ contents []byte
+}
+
+// File contains the resolv.conf content and its hash
+type File struct {
+ Content []byte
+ Hash string
+}
+
+// Get returns the contents of /etc/resolv.conf and its hash
+func Get() (*File, error) {
+ return GetSpecific(DefaultResolvConf)
+}
+
+// GetSpecific returns the contents of the user specified resolv.conf file and its hash
+func GetSpecific(path string) (*File, error) {
+ resolv, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ hash, err := ioutils.HashData(bytes.NewReader(resolv))
+ if err != nil {
+ return nil, err
+ }
+ return &File{Content: resolv, Hash: hash}, nil
+}
+
+// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash
+// and, if modified since last check, returns the bytes and new hash.
+// This feature is used by the resolv.conf updater for containers
+func GetIfChanged() (*File, error) {
+ lastModified.Lock()
+ defer lastModified.Unlock()
+
+ resolv, err := ioutil.ReadFile("/etc/resolv.conf")
+ if err != nil {
+ return nil, err
+ }
+ newHash, err := ioutils.HashData(bytes.NewReader(resolv))
+ if err != nil {
+ return nil, err
+ }
+ if lastModified.sha256 != newHash {
+ lastModified.sha256 = newHash
+ lastModified.contents = resolv
+ return &File{Content: resolv, Hash: newHash}, nil
+ }
+ // nothing changed, so return no data
+ return nil, nil
+}
+
+// GetLastModified retrieves the last used contents and hash of the host resolv.conf.
+// Used by containers updating on restart
+func GetLastModified() *File {
+ lastModified.Lock()
+ defer lastModified.Unlock()
+
+ return &File{Content: lastModified.contents, Hash: lastModified.sha256}
+}
+
+// FilterResolvDNS cleans up the config in resolvConf. It has two main jobs:
+// 1. It looks for localhost (127.*|::1) entries in the provided
+// resolv.conf, removing local nameserver entries, and, if the resulting
+// cleaned config has no defined nameservers left, adds default DNS entries
+// 2. Given the caller provides the enable/disable state of IPv6, the filter
+// code will remove all IPv6 nameservers if it is not enabled for containers
+//
+func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) {
+ cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
+ // if IPv6 is not enabled, also clean out any IPv6 address nameserver
+ if !ipv6Enabled {
+ cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})
+ }
+ // if the resulting resolvConf has no more nameservers defined, add appropriate
+ // default DNS servers for IPv4 and (optionally) IPv6
+ if len(GetNameservers(cleanedResolvConf, types.IP)) == 0 {
+ logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers: %v", defaultIPv4Dns)
+ dns := defaultIPv4Dns
+ if ipv6Enabled {
+ logrus.Infof("IPv6 enabled; Adding default IPv6 external servers: %v", defaultIPv6Dns)
+ dns = append(dns, defaultIPv6Dns...)
+ }
+ cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...)
+ }
+ hash, err := ioutils.HashData(bytes.NewReader(cleanedResolvConf))
+ if err != nil {
+ return nil, err
+ }
+ return &File{Content: cleanedResolvConf, Hash: hash}, nil
+}
+
+// getLines parses input into lines and strips away comments.
+func getLines(input []byte, commentMarker []byte) [][]byte {
+ lines := bytes.Split(input, []byte("\n"))
+ var output [][]byte
+ for _, currentLine := range lines {
+ var commentIndex = bytes.Index(currentLine, commentMarker)
+ if commentIndex == -1 {
+ output = append(output, currentLine)
+ } else {
+ output = append(output, currentLine[:commentIndex])
+ }
+ }
+ return output
+}
+
+// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
+func GetNameservers(resolvConf []byte, kind int) []string {
+ nameservers := []string{}
+ for _, line := range getLines(resolvConf, []byte("#")) {
+ var ns [][]byte
+ if kind == types.IP {
+ ns = nsRegexp.FindSubmatch(line)
+ } else if kind == types.IPv4 {
+ ns = nsIPv4Regexpmatch.FindSubmatch(line)
+ } else if kind == types.IPv6 {
+ ns = nsIPv6Regexpmatch.FindSubmatch(line)
+ }
+ if len(ns) > 0 {
+ nameservers = append(nameservers, string(ns[1]))
+ }
+ }
+ return nameservers
+}
+
+// GetNameserversAsCIDR returns nameservers (if any) listed in
+// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
+// This function's output is intended for net.ParseCIDR
+func GetNameserversAsCIDR(resolvConf []byte) []string {
+ nameservers := []string{}
+ for _, nameserver := range GetNameservers(resolvConf, types.IP) {
+ var address string
+ // If IPv6, strip zone if present
+ if strings.Contains(nameserver, ":") {
+ address = strings.Split(nameserver, "%")[0] + "/128"
+ } else {
+ address = nameserver + "/32"
+ }
+ nameservers = append(nameservers, address)
+ }
+ return nameservers
+}
+
+// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf
+// If more than one search line is encountered, only the contents of the last
+// one is returned.
+func GetSearchDomains(resolvConf []byte) []string {
+ domains := []string{}
+ for _, line := range getLines(resolvConf, []byte("#")) {
+ match := searchRegexp.FindSubmatch(line)
+ if match == nil {
+ continue
+ }
+ domains = strings.Fields(string(match[1]))
+ }
+ return domains
+}
+
+// GetOptions returns options (if any) listed in /etc/resolv.conf
+// If more than one options line is encountered, only the contents of the last
+// one is returned.
+func GetOptions(resolvConf []byte) []string {
+ options := []string{}
+ for _, line := range getLines(resolvConf, []byte("#")) {
+ match := optionsRegexp.FindSubmatch(line)
+ if match == nil {
+ continue
+ }
+ options = strings.Fields(string(match[1]))
+ }
+ return options
+}
+
+// Build writes a configuration file to path containing a "nameserver" entry
+// for every element in dns, a "search" entry for every element in
+// dnsSearch, and an "options" entry for every element in dnsOptions.
+func Build(path string, dns, dnsSearch, dnsOptions []string) (*File, error) {
+ content := bytes.NewBuffer(nil)
+ if len(dnsSearch) > 0 {
+ if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
+ if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
+ return nil, err
+ }
+ }
+ }
+ for _, dns := range dns {
+ if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
+ return nil, err
+ }
+ }
+ if len(dnsOptions) > 0 {
+ if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" {
+ if _, err := content.WriteString("options " + optsString + "\n"); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ hash, err := ioutils.HashData(bytes.NewReader(content.Bytes()))
+ if err != nil {
+ return nil, err
+ }
+
+ return &File{Content: content.Bytes(), Hash: hash}, ioutil.WriteFile(path, content.Bytes(), 0644)
+}
--- /dev/null
+package resolvconf
+
+import (
+ "bytes"
+ "io/ioutil"
+ "os"
+ "testing"
+
+ "github.com/docker/docker/pkg/ioutils"
+ _ "github.com/docker/libnetwork/testutils"
+ "github.com/docker/libnetwork/types"
+)
+
+func TestGet(t *testing.T) {
+ resolvConfUtils, err := Get()
+ if err != nil {
+ t.Fatal(err)
+ }
+ resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(resolvConfUtils.Content) != string(resolvConfSystem) {
+ t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.")
+ }
+ hashSystem, err := ioutils.HashData(bytes.NewReader(resolvConfSystem))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if resolvConfUtils.Hash != hashSystem {
+ t.Fatalf("/etc/resolv.conf and GetResolvConf have different hashes.")
+ }
+}
+
+func TestGetNameservers(t *testing.T) {
+ for resolv, result := range map[string][]string{`
+nameserver 1.2.3.4
+nameserver 40.3.200.10
+search example.com`: {"1.2.3.4", "40.3.200.10"},
+ `search example.com`: {},
+ `nameserver 1.2.3.4
+search example.com
+nameserver 4.30.20.100`: {"1.2.3.4", "4.30.20.100"},
+ ``: {},
+ ` nameserver 1.2.3.4 `: {"1.2.3.4"},
+ `search example.com
+nameserver 1.2.3.4
+#nameserver 4.3.2.1`: {"1.2.3.4"},
+ `search example.com
+nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4"},
+ } {
+ test := GetNameservers([]byte(resolv), types.IP)
+ if !strSlicesEqual(test, result) {
+ t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
+ }
+ }
+}
+
+func TestGetNameserversAsCIDR(t *testing.T) {
+ for resolv, result := range map[string][]string{`
+nameserver 1.2.3.4
+nameserver 40.3.200.10
+search example.com`: {"1.2.3.4/32", "40.3.200.10/32"},
+ `search example.com`: {},
+ `nameserver 1.2.3.4
+search example.com
+nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"},
+ ``: {},
+ ` nameserver 1.2.3.4 `: {"1.2.3.4/32"},
+ `search example.com
+nameserver 1.2.3.4
+#nameserver 4.3.2.1`: {"1.2.3.4/32"},
+ `search example.com
+nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"},
+ } {
+ test := GetNameserversAsCIDR([]byte(resolv))
+ if !strSlicesEqual(test, result) {
+ t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
+ }
+ }
+}
+
+func TestGetSearchDomains(t *testing.T) {
+ for resolv, result := range map[string][]string{
+ `search example.com`: {"example.com"},
+ `search example.com # ignored`: {"example.com"},
+ ` search example.com `: {"example.com"},
+ ` search example.com # ignored`: {"example.com"},
+ `search foo.example.com example.com`: {"foo.example.com", "example.com"},
+ ` search foo.example.com example.com `: {"foo.example.com", "example.com"},
+ ` search foo.example.com example.com # ignored`: {"foo.example.com", "example.com"},
+ ``: {},
+ `# ignored`: {},
+ `nameserver 1.2.3.4
+search foo.example.com example.com`: {"foo.example.com", "example.com"},
+ `nameserver 1.2.3.4
+search dup1.example.com dup2.example.com
+search foo.example.com example.com`: {"foo.example.com", "example.com"},
+ `nameserver 1.2.3.4
+search foo.example.com example.com
+nameserver 4.30.20.100`: {"foo.example.com", "example.com"},
+ } {
+ test := GetSearchDomains([]byte(resolv))
+ if !strSlicesEqual(test, result) {
+ t.Fatalf("Wrong search domain string {%s} should be %v. Input: %s", test, result, resolv)
+ }
+ }
+}
+
+func TestGetOptions(t *testing.T) {
+ for resolv, result := range map[string][]string{
+ `options opt1`: {"opt1"},
+ `options opt1 # ignored`: {"opt1"},
+ ` options opt1 `: {"opt1"},
+ ` options opt1 # ignored`: {"opt1"},
+ `options opt1 opt2 opt3`: {"opt1", "opt2", "opt3"},
+ `options opt1 opt2 opt3 # ignored`: {"opt1", "opt2", "opt3"},
+ ` options opt1 opt2 opt3 `: {"opt1", "opt2", "opt3"},
+ ` options opt1 opt2 opt3 # ignored`: {"opt1", "opt2", "opt3"},
+ ``: {},
+ `# ignored`: {},
+ `nameserver 1.2.3.4`: {},
+ `nameserver 1.2.3.4
+options opt1 opt2 opt3`: {"opt1", "opt2", "opt3"},
+ `nameserver 1.2.3.4
+options opt1 opt2
+options opt3 opt4`: {"opt3", "opt4"},
+ } {
+ test := GetOptions([]byte(resolv))
+ if !strSlicesEqual(test, result) {
+ t.Fatalf("Wrong options string {%s} should be %v. Input: %s", test, result, resolv)
+ }
+ }
+}
+
+func strSlicesEqual(a, b []string) bool {
+ if len(a) != len(b) {
+ return false
+ }
+
+ for i, v := range a {
+ if v != b[i] {
+ return false
+ }
+ }
+
+ return true
+}
+
+func TestBuild(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ _, err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"}, []string{"opt1"})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "search search1\nnameserver ns1\nnameserver ns2\nnameserver ns3\noptions opt1\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+}
+
+func TestBuildWithZeroLengthDomainSearch(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ _, err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."}, []string{"opt1"})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\noptions opt1\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+ if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) {
+ t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content)
+ }
+}
+
+func TestBuildWithNoOptions(t *testing.T) {
+ file, err := ioutil.TempFile("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ _, err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"}, []string{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ content, err := ioutil.ReadFile(file.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected := "search search1\nnameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) {
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+ }
+ if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) {
+ t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content)
+ }
+}
+
+func TestFilterResolvDns(t *testing.T) {
+ ns0 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\n"
+
+ if result, _ := FilterResolvDNS([]byte(ns0), false); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ ns1 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n"
+ if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ ns1 = "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n"
+ if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ ns1 = "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n"
+ if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ ns1 = "nameserver ::1\nnameserver 10.16.60.14\nnameserver 127.0.2.1\nnameserver 10.16.60.21\n"
+ if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ ns1 = "nameserver 10.16.60.14\nnameserver ::1\nnameserver 10.16.60.21\nnameserver ::1"
+ if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ // with IPv6 disabled (false param), the IPv6 nameserver should be removed
+ ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
+ if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed Localhost+IPv6 off: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ // with IPv6 disabled (false param), the IPv6 link-local nameserver with zone ID should be removed
+ ns1 = "nameserver 10.16.60.14\nnameserver FE80::BB1%1\nnameserver FE80::BB1%eth0\nnameserver 10.16.60.21\n"
+ if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed Localhost+IPv6 off: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ // with IPv6 enabled, the IPv6 nameserver should be preserved
+ ns0 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\n"
+ ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
+ if result, _ := FilterResolvDNS([]byte(ns1), true); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed Localhost+IPv6 on: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ // with IPv6 enabled, and no non-localhost servers, Google defaults (both IPv4+IPv6) should be added
+ ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nnameserver 2001:4860:4860::8888\nnameserver 2001:4860:4860::8844"
+ ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
+ if result, _ := FilterResolvDNS([]byte(ns1), true); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+
+ // with IPv6 disabled, and no non-localhost servers, Google defaults (only IPv4) should be added
+ ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4"
+ ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
+ if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+ if ns0 != string(result.Content) {
+ t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result.Content))
+ }
+ }
+}
--- /dev/null
+package libnetwork
+
+import (
+ "fmt"
+ "math/rand"
+ "net"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/docker/libnetwork/types"
+ "github.com/miekg/dns"
+ "github.com/sirupsen/logrus"
+)
+
+// Resolver represents the embedded DNS server in Docker. It operates
+// by listening on container's loopback interface for DNS queries.
+type Resolver interface {
+ // Start starts the name server for the container
+ Start() error
+ // Stop stops the name server for the container. Stopped resolver
+ // can be reused after running the SetupFunc again.
+ Stop()
+ // SetupFunc() provides the setup function that should be run
+ // in the container's network namespace.
+ SetupFunc(int) func()
+ // NameServer() returns the IP of the DNS resolver for the
+ // containers.
+ NameServer() string
+ // SetExtServers configures the external nameservers the resolver
+ // should use to forward queries
+ SetExtServers([]extDNSEntry)
+ // ResolverOptions returns resolv.conf options that should be set
+ ResolverOptions() []string
+}
+
+// DNSBackend represents a backend DNS resolver used for DNS name
+// resolution. All the queries to the resolver are forwarded to the
+// backend resolver.
+type DNSBackend interface {
+ // ResolveName resolves a service name to an IPv4 or IPv6 address by searching
+ // the networks the sandbox is connected to. For IPv6 queries, second return
+ // value will be true if the name exists in docker domain but doesn't have an
+ // IPv6 address. Such queries shouldn't be forwarded to external nameservers.
+ ResolveName(name string, iplen int) ([]net.IP, bool)
+ // ResolveIP returns the service name for the passed in IP. IP is in reverse dotted
+ // notation; the format used for DNS PTR records
+ ResolveIP(name string) string
+ // ResolveService returns all the backend details about the containers or hosts
+ // backing a service. Its purpose is to satisfy an SRV query
+ ResolveService(name string) ([]*net.SRV, []net.IP)
+ // ExecFunc allows a function to be executed in the context of the backend
+ // on behalf of the resolver.
+ ExecFunc(f func()) error
+ //NdotsSet queries the backends ndots dns option settings
+ NdotsSet() bool
+ // HandleQueryResp passes the name & IP from a response to the backend. backend
+ // can use it to maintain any required state about the resolution
+ HandleQueryResp(name string, ip net.IP)
+}
+
+const (
+ dnsPort = "53"
+ ptrIPv4domain = ".in-addr.arpa."
+ ptrIPv6domain = ".ip6.arpa."
+ respTTL = 600
+ maxExtDNS = 3 //max number of external servers to try
+ extIOTimeout = 4 * time.Second
+ defaultRespSize = 512
+ maxConcurrent = 100
+ logInterval = 2 * time.Second
+)
+
+type extDNSEntry struct {
+ IPStr string
+ HostLoopback bool
+}
+
+// resolver implements the Resolver interface
+type resolver struct {
+ backend DNSBackend
+ extDNSList [maxExtDNS]extDNSEntry
+ server *dns.Server
+ conn *net.UDPConn
+ tcpServer *dns.Server
+ tcpListen *net.TCPListener
+ err error
+ count int32
+ tStamp time.Time
+ queryLock sync.Mutex
+ listenAddress string
+ proxyDNS bool
+ resolverKey string
+ startCh chan struct{}
+}
+
+func init() {
+ rand.Seed(time.Now().Unix())
+}
+
+// NewResolver creates a new instance of the Resolver
+func NewResolver(address string, proxyDNS bool, resolverKey string, backend DNSBackend) Resolver {
+ return &resolver{
+ backend: backend,
+ proxyDNS: proxyDNS,
+ listenAddress: address,
+ resolverKey: resolverKey,
+ err: fmt.Errorf("setup not done yet"),
+ startCh: make(chan struct{}, 1),
+ }
+}
+
+func (r *resolver) SetupFunc(port int) func() {
+ return (func() {
+ var err error
+
+ // DNS operates primarily on UDP
+ addr := &net.UDPAddr{
+ IP: net.ParseIP(r.listenAddress),
+ Port: port,
+ }
+
+ r.conn, err = net.ListenUDP("udp", addr)
+ if err != nil {
+ r.err = fmt.Errorf("error in opening name server socket %v", err)
+ return
+ }
+
+ // Listen on a TCP as well
+ tcpaddr := &net.TCPAddr{
+ IP: net.ParseIP(r.listenAddress),
+ Port: port,
+ }
+
+ r.tcpListen, err = net.ListenTCP("tcp", tcpaddr)
+ if err != nil {
+ r.err = fmt.Errorf("error in opening name TCP server socket %v", err)
+ return
+ }
+ r.err = nil
+ })
+}
+
+func (r *resolver) Start() error {
+ r.startCh <- struct{}{}
+ defer func() { <-r.startCh }()
+
+ // make sure the resolver has been setup before starting
+ if r.err != nil {
+ return r.err
+ }
+
+ if err := r.setupIPTable(); err != nil {
+ return fmt.Errorf("setting up IP table rules failed: %v", err)
+ }
+
+ s := &dns.Server{Handler: r, PacketConn: r.conn}
+ r.server = s
+ go func() {
+ s.ActivateAndServe()
+ }()
+
+ tcpServer := &dns.Server{Handler: r, Listener: r.tcpListen}
+ r.tcpServer = tcpServer
+ go func() {
+ tcpServer.ActivateAndServe()
+ }()
+ return nil
+}
+
+func (r *resolver) Stop() {
+ r.startCh <- struct{}{}
+ defer func() { <-r.startCh }()
+
+ if r.server != nil {
+ r.server.Shutdown()
+ }
+ if r.tcpServer != nil {
+ r.tcpServer.Shutdown()
+ }
+ r.conn = nil
+ r.tcpServer = nil
+ r.err = fmt.Errorf("setup not done yet")
+ r.tStamp = time.Time{}
+ r.count = 0
+ r.queryLock = sync.Mutex{}
+}
+
+func (r *resolver) SetExtServers(extDNS []extDNSEntry) {
+ l := len(extDNS)
+ if l > maxExtDNS {
+ l = maxExtDNS
+ }
+ for i := 0; i < l; i++ {
+ r.extDNSList[i] = extDNS[i]
+ }
+}
+
+func (r *resolver) NameServer() string {
+ return r.listenAddress
+}
+
+func (r *resolver) ResolverOptions() []string {
+ return []string{"ndots:0"}
+}
+
+func setCommonFlags(msg *dns.Msg) {
+ msg.RecursionAvailable = true
+}
+
+func shuffleAddr(addr []net.IP) []net.IP {
+ for i := len(addr) - 1; i > 0; i-- {
+ r := rand.Intn(i + 1)
+ addr[i], addr[r] = addr[r], addr[i]
+ }
+ return addr
+}
+
+func createRespMsg(query *dns.Msg) *dns.Msg {
+ resp := new(dns.Msg)
+ resp.SetReply(query)
+ setCommonFlags(resp)
+
+ return resp
+}
+
+func (r *resolver) handleMXQuery(name string, query *dns.Msg) (*dns.Msg, error) {
+ addrv4, _ := r.backend.ResolveName(name, types.IPv4)
+ addrv6, _ := r.backend.ResolveName(name, types.IPv6)
+
+ if addrv4 == nil && addrv6 == nil {
+ return nil, nil
+ }
+
+ // We were able to resolve the name. Respond with an empty list with
+ // RcodeSuccess/NOERROR so that email clients can treat it as "implicit MX"
+ // [RFC 5321 Section-5.1] and issue a Type A/AAAA query for the name.
+
+ resp := createRespMsg(query)
+ return resp, nil
+}
+
+func (r *resolver) handleIPQuery(name string, query *dns.Msg, ipType int) (*dns.Msg, error) {
+ var addr []net.IP
+ var ipv6Miss bool
+ addr, ipv6Miss = r.backend.ResolveName(name, ipType)
+
+ if addr == nil && ipv6Miss {
+ // Send a reply without any Answer sections
+ logrus.Debugf("[resolver] lookup name %s present without IPv6 address", name)
+ resp := createRespMsg(query)
+ return resp, nil
+ }
+ if addr == nil {
+ return nil, nil
+ }
+
+ logrus.Debugf("[resolver] lookup for %s: IP %v", name, addr)
+
+ resp := createRespMsg(query)
+ if len(addr) > 1 {
+ addr = shuffleAddr(addr)
+ }
+ if ipType == types.IPv4 {
+ for _, ip := range addr {
+ rr := new(dns.A)
+ rr.Hdr = dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: respTTL}
+ rr.A = ip
+ resp.Answer = append(resp.Answer, rr)
+ }
+ } else {
+ for _, ip := range addr {
+ rr := new(dns.AAAA)
+ rr.Hdr = dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: respTTL}
+ rr.AAAA = ip
+ resp.Answer = append(resp.Answer, rr)
+ }
+ }
+ return resp, nil
+}
+
+func (r *resolver) handlePTRQuery(ptr string, query *dns.Msg) (*dns.Msg, error) {
+ var parts []string
+
+ if strings.HasSuffix(ptr, ptrIPv4domain) {
+ parts = strings.Split(ptr, ptrIPv4domain)
+ } else if strings.HasSuffix(ptr, ptrIPv6domain) {
+ parts = strings.Split(ptr, ptrIPv6domain)
+ } else {
+ return nil, fmt.Errorf("invalid PTR query, %v", ptr)
+ }
+
+ host := r.backend.ResolveIP(parts[0])
+
+ if len(host) == 0 {
+ return nil, nil
+ }
+
+ logrus.Debugf("[resolver] lookup for IP %s: name %s", parts[0], host)
+ fqdn := dns.Fqdn(host)
+
+ resp := new(dns.Msg)
+ resp.SetReply(query)
+ setCommonFlags(resp)
+
+ rr := new(dns.PTR)
+ rr.Hdr = dns.RR_Header{Name: ptr, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: respTTL}
+ rr.Ptr = fqdn
+ resp.Answer = append(resp.Answer, rr)
+ return resp, nil
+}
+
+func (r *resolver) handleSRVQuery(svc string, query *dns.Msg) (*dns.Msg, error) {
+
+ srv, ip := r.backend.ResolveService(svc)
+
+ if len(srv) == 0 {
+ return nil, nil
+ }
+ if len(srv) != len(ip) {
+ return nil, fmt.Errorf("invalid reply for SRV query %s", svc)
+ }
+
+ resp := createRespMsg(query)
+
+ for i, r := range srv {
+ rr := new(dns.SRV)
+ rr.Hdr = dns.RR_Header{Name: svc, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: respTTL}
+ rr.Port = r.Port
+ rr.Target = r.Target
+ resp.Answer = append(resp.Answer, rr)
+
+ rr1 := new(dns.A)
+ rr1.Hdr = dns.RR_Header{Name: r.Target, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: respTTL}
+ rr1.A = ip[i]
+ resp.Extra = append(resp.Extra, rr1)
+ }
+ return resp, nil
+
+}
+
+func truncateResp(resp *dns.Msg, maxSize int, isTCP bool) {
+ if !isTCP {
+ resp.Truncated = true
+ }
+
+ srv := resp.Question[0].Qtype == dns.TypeSRV
+ // trim the Answer RRs one by one till the whole message fits
+ // within the reply size
+ for resp.Len() > maxSize {
+ resp.Answer = resp.Answer[:len(resp.Answer)-1]
+
+ if srv && len(resp.Extra) > 0 {
+ resp.Extra = resp.Extra[:len(resp.Extra)-1]
+ }
+ }
+}
+
+func (r *resolver) ServeDNS(w dns.ResponseWriter, query *dns.Msg) {
+ var (
+ extConn net.Conn
+ resp *dns.Msg
+ err error
+ )
+
+ if query == nil || len(query.Question) == 0 {
+ return
+ }
+ name := query.Question[0].Name
+
+ switch query.Question[0].Qtype {
+ case dns.TypeA:
+ resp, err = r.handleIPQuery(name, query, types.IPv4)
+ case dns.TypeAAAA:
+ resp, err = r.handleIPQuery(name, query, types.IPv6)
+ case dns.TypeMX:
+ resp, err = r.handleMXQuery(name, query)
+ case dns.TypePTR:
+ resp, err = r.handlePTRQuery(name, query)
+ case dns.TypeSRV:
+ resp, err = r.handleSRVQuery(name, query)
+ }
+
+ if err != nil {
+ logrus.Error(err)
+ return
+ }
+
+ if resp == nil {
+ // If the backend doesn't support proxying dns request
+ // fail the response
+ if !r.proxyDNS {
+ resp = new(dns.Msg)
+ resp.SetRcode(query, dns.RcodeServerFailure)
+ w.WriteMsg(resp)
+ return
+ }
+
+ // If the user sets ndots > 0 explicitly and the query is
+ // in the root domain don't forward it out. We will return
+ // failure and let the client retry with the search domain
+ // attached
+ switch query.Question[0].Qtype {
+ case dns.TypeA:
+ fallthrough
+ case dns.TypeAAAA:
+ if r.backend.NdotsSet() && !strings.Contains(strings.TrimSuffix(name, "."), ".") {
+ resp = createRespMsg(query)
+ }
+ }
+ }
+
+ proto := w.LocalAddr().Network()
+ maxSize := 0
+ if proto == "tcp" {
+ maxSize = dns.MaxMsgSize - 1
+ } else if proto == "udp" {
+ optRR := query.IsEdns0()
+ if optRR != nil {
+ maxSize = int(optRR.UDPSize())
+ }
+ if maxSize < defaultRespSize {
+ maxSize = defaultRespSize
+ }
+ }
+
+ if resp != nil {
+ if resp.Len() > maxSize {
+ truncateResp(resp, maxSize, proto == "tcp")
+ }
+ } else {
+ for i := 0; i < maxExtDNS; i++ {
+ extDNS := &r.extDNSList[i]
+ if extDNS.IPStr == "" {
+ break
+ }
+ extConnect := func() {
+ addr := fmt.Sprintf("%s:%d", extDNS.IPStr, 53)
+ extConn, err = net.DialTimeout(proto, addr, extIOTimeout)
+ }
+
+ if extDNS.HostLoopback {
+ extConnect()
+ } else {
+ execErr := r.backend.ExecFunc(extConnect)
+ if execErr != nil {
+ logrus.Warn(execErr)
+ continue
+ }
+ }
+ if err != nil {
+ logrus.Warnf("[resolver] connect failed: %s", err)
+ continue
+ }
+ queryType := dns.TypeToString[query.Question[0].Qtype]
+ logrus.Debugf("[resolver] query %s (%s) from %s, forwarding to %s:%s", name, queryType,
+ extConn.LocalAddr().String(), proto, extDNS.IPStr)
+
+ // Timeout has to be set for every IO operation.
+ extConn.SetDeadline(time.Now().Add(extIOTimeout))
+ co := &dns.Conn{
+ Conn: extConn,
+ UDPSize: uint16(maxSize),
+ }
+ defer co.Close()
+
+ // limits the number of outstanding concurrent queries.
+ if !r.forwardQueryStart() {
+ old := r.tStamp
+ r.tStamp = time.Now()
+ if r.tStamp.Sub(old) > logInterval {
+ logrus.Errorf("[resolver] more than %v concurrent queries from %s", maxConcurrent, extConn.LocalAddr().String())
+ }
+ continue
+ }
+
+ err = co.WriteMsg(query)
+ if err != nil {
+ r.forwardQueryEnd()
+ logrus.Debugf("[resolver] send to DNS server failed, %s", err)
+ continue
+ }
+
+ resp, err = co.ReadMsg()
+ // Truncated DNS replies should be sent to the client so that the
+ // client can retry over TCP
+ if err != nil && err != dns.ErrTruncated {
+ r.forwardQueryEnd()
+ logrus.Debugf("[resolver] read from DNS server failed, %s", err)
+ continue
+ }
+ r.forwardQueryEnd()
+ if resp != nil {
+ if resp.Rcode == dns.RcodeServerFailure {
+ // for Server Failure response, continue to the next external DNS server
+ logrus.Debugf("[resolver] external DNS %s:%s responded with ServFail for %q", proto, extDNS.IPStr, name)
+ continue
+ }
+ answers := 0
+ for _, rr := range resp.Answer {
+ h := rr.Header()
+ switch h.Rrtype {
+ case dns.TypeA:
+ answers++
+ ip := rr.(*dns.A).A
+ logrus.Debugf("[resolver] received A record %q for %q from %s:%s", ip, h.Name, proto, extDNS.IPStr)
+ r.backend.HandleQueryResp(h.Name, ip)
+ case dns.TypeAAAA:
+ answers++
+ ip := rr.(*dns.AAAA).AAAA
+ logrus.Debugf("[resolver] received AAAA record %q for %q from %s:%s", ip, h.Name, proto, extDNS.IPStr)
+ r.backend.HandleQueryResp(h.Name, ip)
+ }
+ }
+ if resp.Answer == nil || answers == 0 {
+ logrus.Debugf("[resolver] external DNS %s:%s did not return any %s records for %q", proto, extDNS.IPStr, queryType, name)
+ }
+ resp.Compress = true
+ } else {
+ logrus.Debugf("[resolver] external DNS %s:%s returned empty response for %q", proto, extDNS.IPStr, name)
+ }
+ break
+ }
+ if resp == nil {
+ return
+ }
+ }
+
+ if err = w.WriteMsg(resp); err != nil {
+ logrus.Errorf("[resolver] error writing resolver resp, %s", err)
+ }
+}
+
+func (r *resolver) forwardQueryStart() bool {
+ r.queryLock.Lock()
+ defer r.queryLock.Unlock()
+
+ if r.count == maxConcurrent {
+ return false
+ }
+ r.count++
+
+ return true
+}
+
+func (r *resolver) forwardQueryEnd() {
+ r.queryLock.Lock()
+ defer r.queryLock.Unlock()
+
+ if r.count == 0 {
+ logrus.Error("[resolver] invalid concurrent query count")
+ } else {
+ r.count--
+ }
+}
--- /dev/null
+package libnetwork
+
+import (
+ "bytes"
+ "net"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/miekg/dns"
+)
+
+// a simple/null address type that will be used to fake a local address for unit testing
+type tstaddr struct {
+ net string
+}
+
+func (a *tstaddr) Network() string { return "tcp" }
+
+func (a *tstaddr) String() string { return "127.0.0.1" }
+
+// a simple writer that implements dns.ResponseWriter for unit testing purposes
+type tstwriter struct {
+ msg *dns.Msg
+}
+
+func (w *tstwriter) WriteMsg(m *dns.Msg) (err error) {
+ w.msg = m
+ return nil
+}
+
+func (w *tstwriter) Write(m []byte) (int, error) { return 0, nil }
+
+func (w *tstwriter) LocalAddr() net.Addr { return new(tstaddr) }
+
+func (w *tstwriter) RemoteAddr() net.Addr { return new(tstaddr) }
+
+func (w *tstwriter) TsigStatus() error { return nil }
+
+func (w *tstwriter) TsigTimersOnly(b bool) {}
+
+func (w *tstwriter) Hijack() {}
+
+func (w *tstwriter) Close() error { return nil }
+
+func (w *tstwriter) GetResponse() *dns.Msg { return w.msg }
+
+func (w *tstwriter) ClearResponse() { w.msg = nil }
+
+func checkNonNullResponse(t *testing.T, m *dns.Msg) {
+ if m == nil {
+ t.Fatal("Null DNS response found. Non Null response msg expected.")
+ }
+}
+
+func checkNullResponse(t *testing.T, m *dns.Msg) {
+ if m != nil {
+ t.Fatal("Non Null DNS response found. Null response msg expected.")
+ }
+}
+
+func checkDNSAnswersCount(t *testing.T, m *dns.Msg, expected int) {
+ answers := len(m.Answer)
+ if answers != expected {
+ t.Fatalf("Expected number of answers in response: %d. Found: %d", expected, answers)
+ }
+}
+
+func checkDNSResponseCode(t *testing.T, m *dns.Msg, expected int) {
+ if m.MsgHdr.Rcode != expected {
+ t.Fatalf("Expected DNS response code: %d. Found: %d", expected, m.MsgHdr.Rcode)
+ }
+}
+
+func checkDNSRRType(t *testing.T, actual, expected uint16) {
+ if actual != expected {
+ t.Fatalf("Expected DNS Rrtype: %d. Found: %d", expected, actual)
+ }
+}
+
+func TestDNSIPQuery(t *testing.T) {
+ c, err := New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ n, err := c.NewNetwork("bridge", "dtnet1", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ ep, err := n.CreateEndpoint("testep")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sb, err := c.NewSandbox("c1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer func() {
+ if err := sb.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ // we need the endpoint only to populate ep_list for the sandbox as part of resolve_name
+ // it is not set as a target for name resolution and does not serve any other purpose
+ err = ep.Join(sb)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // add service records which are used to resolve names. These are the real targets for the DNS querries
+ n.(*network).addSvcRecords("ep1", "name1", "svc1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
+
+ w := new(tstwriter)
+ // the unit tests right now will focus on non-proxyed DNS requests
+ r := NewResolver(resolverIPSandbox, false, sb.Key(), sb.(*sandbox))
+
+ // test name1's IP is resolved correctly with the default A type query
+ q := new(dns.Msg)
+ q.SetQuestion("name1", dns.TypeA)
+ r.(*resolver).ServeDNS(w, q)
+ resp := w.GetResponse()
+ checkNonNullResponse(t, resp)
+ t.Log("Response: ", resp.String())
+ checkDNSResponseCode(t, resp, dns.RcodeSuccess)
+ checkDNSAnswersCount(t, resp, 1)
+ checkDNSRRType(t, resp.Answer[0].Header().Rrtype, dns.TypeA)
+ if answer, ok := resp.Answer[0].(*dns.A); ok {
+ if !bytes.Equal(answer.A, net.ParseIP("192.168.0.1")) {
+ t.Fatalf("IP response in Answer %v does not match 192.168.0.1", answer.A)
+ }
+ } else {
+ t.Fatal("Answer of type A not found")
+ }
+ w.ClearResponse()
+
+ // test MX query with name1 results in Success response with 0 answer records
+ q = new(dns.Msg)
+ q.SetQuestion("name1", dns.TypeMX)
+ r.(*resolver).ServeDNS(w, q)
+ resp = w.GetResponse()
+ checkNonNullResponse(t, resp)
+ t.Log("Response: ", resp.String())
+ checkDNSResponseCode(t, resp, dns.RcodeSuccess)
+ checkDNSAnswersCount(t, resp, 0)
+ w.ClearResponse()
+
+ // test MX query with non existent name results in ServFail response with 0 answer records
+ // since this is a unit test env, we disable proxying DNS above which results in ServFail rather than NXDOMAIN
+ q = new(dns.Msg)
+ q.SetQuestion("nonexistent", dns.TypeMX)
+ r.(*resolver).ServeDNS(w, q)
+ resp = w.GetResponse()
+ checkNonNullResponse(t, resp)
+ t.Log("Response: ", resp.String())
+ checkDNSResponseCode(t, resp, dns.RcodeServerFailure)
+ w.ClearResponse()
+
+}
+
+func newDNSHandlerServFailOnce(requests *int) func(w dns.ResponseWriter, r *dns.Msg) {
+ return func(w dns.ResponseWriter, r *dns.Msg) {
+ m := new(dns.Msg)
+ m.SetReply(r)
+ m.Compress = false
+ if *requests == 0 {
+ m.SetRcode(r, dns.RcodeServerFailure)
+ }
+ *requests = *requests + 1
+ w.WriteMsg(m)
+ }
+}
+
+func waitForLocalDNSServer(t *testing.T) {
+ retries := 0
+ maxRetries := 10
+
+ for retries < maxRetries {
+ t.Log("Try connecting to DNS server ...")
+ // this test and retry mechanism only works for TCP. With UDP there is no
+ // connection and the test becomes inaccurate leading to unpredictable results
+ tconn, err := net.DialTimeout("tcp", "127.0.0.1:53", 10*time.Second)
+ retries = retries + 1
+ if err != nil {
+ if oerr, ok := err.(*net.OpError); ok {
+ // server is probably initializing
+ if oerr.Err == syscall.ECONNREFUSED {
+ continue
+ }
+ } else {
+ // something is wrong: we should stop for analysis
+ t.Fatal(err)
+ }
+ }
+ if tconn != nil {
+ tconn.Close()
+ break
+ }
+ }
+}
+
+func TestDNSProxyServFail(t *testing.T) {
+ c, err := New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Stop()
+
+ n, err := c.NewNetwork("bridge", "dtnet2", "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := n.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ sb, err := c.NewSandbox("c1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer func() {
+ if err := sb.Delete(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ var nRequests int
+ // initialize a local DNS server and configure it to fail the first query
+ dns.HandleFunc(".", newDNSHandlerServFailOnce(&nRequests))
+ // use TCP for predictable results. Connection tests (to figure out DNS server initialization) don't work with UDP
+ server := &dns.Server{Addr: ":53", Net: "tcp"}
+ go server.ListenAndServe()
+ defer server.Shutdown()
+
+ waitForLocalDNSServer(t)
+ t.Log("DNS Server can be reached")
+
+ w := new(tstwriter)
+ r := NewResolver(resolverIPSandbox, true, sb.Key(), sb.(*sandbox))
+ q := new(dns.Msg)
+ q.SetQuestion("name1.", dns.TypeA)
+
+ var localDNSEntries []extDNSEntry
+ extTestDNSEntry := extDNSEntry{IPStr: "127.0.0.1", HostLoopback: true}
+
+ // configure two external DNS entries and point both to local DNS server thread
+ localDNSEntries = append(localDNSEntries, extTestDNSEntry)
+ localDNSEntries = append(localDNSEntries, extTestDNSEntry)
+
+ // this should generate two requests: the first will fail leading to a retry
+ r.(*resolver).SetExtServers(localDNSEntries)
+ r.(*resolver).ServeDNS(w, q)
+ if nRequests != 2 {
+ t.Fatalf("Expected 2 DNS querries. Found: %d", nRequests)
+ }
+ t.Logf("Expected number of DNS requests generated")
+}
--- /dev/null
+// +build !windows
+
+package libnetwork
+
+import (
+ "fmt"
+ "net"
+ "os"
+ "os/exec"
+ "runtime"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork/iptables"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netns"
+)
+
+func init() {
+ reexec.Register("setup-resolver", reexecSetupResolver)
+}
+
+const (
+ // outputChain used for docker embed dns
+ outputChain = "DOCKER_OUTPUT"
+ //postroutingchain used for docker embed dns
+ postroutingchain = "DOCKER_POSTROUTING"
+)
+
+func reexecSetupResolver() {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ if len(os.Args) < 4 {
+ logrus.Error("invalid number of arguments..")
+ os.Exit(1)
+ }
+
+ resolverIP, ipPort, _ := net.SplitHostPort(os.Args[2])
+ _, tcpPort, _ := net.SplitHostPort(os.Args[3])
+ rules := [][]string{
+ {"-t", "nat", "-I", outputChain, "-d", resolverIP, "-p", "udp", "--dport", dnsPort, "-j", "DNAT", "--to-destination", os.Args[2]},
+ {"-t", "nat", "-I", postroutingchain, "-s", resolverIP, "-p", "udp", "--sport", ipPort, "-j", "SNAT", "--to-source", ":" + dnsPort},
+ {"-t", "nat", "-I", outputChain, "-d", resolverIP, "-p", "tcp", "--dport", dnsPort, "-j", "DNAT", "--to-destination", os.Args[3]},
+ {"-t", "nat", "-I", postroutingchain, "-s", resolverIP, "-p", "tcp", "--sport", tcpPort, "-j", "SNAT", "--to-source", ":" + dnsPort},
+ }
+
+ f, err := os.OpenFile(os.Args[1], os.O_RDONLY, 0)
+ if err != nil {
+ logrus.Errorf("failed get network namespace %q: %v", os.Args[1], err)
+ os.Exit(2)
+ }
+ defer f.Close()
+
+ nsFD := f.Fd()
+ if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
+ logrus.Errorf("setting into container net ns %v failed, %v", os.Args[1], err)
+ os.Exit(3)
+ }
+
+ // insert outputChain and postroutingchain
+ err = iptables.RawCombinedOutputNative("-t", "nat", "-C", "OUTPUT", "-d", resolverIP, "-j", outputChain)
+ if err == nil {
+ iptables.RawCombinedOutputNative("-t", "nat", "-F", outputChain)
+ } else {
+ iptables.RawCombinedOutputNative("-t", "nat", "-N", outputChain)
+ iptables.RawCombinedOutputNative("-t", "nat", "-I", "OUTPUT", "-d", resolverIP, "-j", outputChain)
+ }
+
+ err = iptables.RawCombinedOutputNative("-t", "nat", "-C", "POSTROUTING", "-d", resolverIP, "-j", postroutingchain)
+ if err == nil {
+ iptables.RawCombinedOutputNative("-t", "nat", "-F", postroutingchain)
+ } else {
+ iptables.RawCombinedOutputNative("-t", "nat", "-N", postroutingchain)
+ iptables.RawCombinedOutputNative("-t", "nat", "-I", "POSTROUTING", "-d", resolverIP, "-j", postroutingchain)
+ }
+
+ for _, rule := range rules {
+ if iptables.RawCombinedOutputNative(rule...) != nil {
+ logrus.Errorf("set up rule failed, %v", rule)
+ }
+ }
+}
+
+func (r *resolver) setupIPTable() error {
+ if r.err != nil {
+ return r.err
+ }
+ laddr := r.conn.LocalAddr().String()
+ ltcpaddr := r.tcpListen.Addr().String()
+
+ cmd := &exec.Cmd{
+ Path: reexec.Self(),
+ Args: append([]string{"setup-resolver"}, r.resolverKey, laddr, ltcpaddr),
+ Stdout: os.Stdout,
+ Stderr: os.Stderr,
+ }
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("reexec failed: %v", err)
+ }
+ return nil
+}
--- /dev/null
+// +build windows
+
+package libnetwork
+
+func (r *resolver) setupIPTable() error {
+ return nil
+}
--- /dev/null
+package libnetwork
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/docker/libnetwork/etchosts"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+// Sandbox provides the control over the network container entity. It is a one to one mapping with the container.
+type Sandbox interface {
+ // ID returns the ID of the sandbox
+ ID() string
+ // Key returns the sandbox's key
+ Key() string
+ // ContainerID returns the container id associated to this sandbox
+ ContainerID() string
+ // Labels returns the sandbox's labels
+ Labels() map[string]interface{}
+ // Statistics retrieves the interfaces' statistics for the sandbox
+ Statistics() (map[string]*types.InterfaceStatistics, error)
+ // Refresh leaves all the endpoints, resets and re-applies the options,
+ // re-joins all the endpoints without destroying the osl sandbox
+ Refresh(options ...SandboxOption) error
+ // SetKey updates the Sandbox Key
+ SetKey(key string) error
+ // Rename changes the name of all attached Endpoints
+ Rename(name string) error
+ // Delete destroys this container after detaching it from all connected endpoints.
+ Delete() error
+ // Endpoints returns all the endpoints connected to the sandbox
+ Endpoints() []Endpoint
+ // ResolveService returns all the backend details about the containers or hosts
+ // backing a service. Its purpose is to satisfy an SRV query
+ ResolveService(name string) ([]*net.SRV, []net.IP)
+ // EnableService makes a managed container's service available by adding the
+ // endpoint to the service load balancer and service discovery
+ EnableService() error
+ // DisableService removes a managed container's endpoints from the load balancer
+ // and service discovery
+ DisableService() error
+}
+
+// SandboxOption is an option setter function type used to pass various options to
+// NewNetContainer method. The various setter functions of type SandboxOption are
+// provided by libnetwork, they look like ContainerOptionXXXX(...)
+type SandboxOption func(sb *sandbox)
+
+func (sb *sandbox) processOptions(options ...SandboxOption) {
+ for _, opt := range options {
+ if opt != nil {
+ opt(sb)
+ }
+ }
+}
+
+type sandbox struct {
+ id string
+ containerID string
+ config containerConfig
+ extDNS []extDNSEntry
+ osSbox osl.Sandbox
+ controller *controller
+ resolver Resolver
+ resolverOnce sync.Once
+ refCnt int
+ endpoints []*endpoint
+ epPriority map[string]int
+ populatedEndpoints map[string]struct{}
+ joinLeaveDone chan struct{}
+ dbIndex uint64
+ dbExists bool
+ isStub bool
+ inDelete bool
+ ingress bool
+ ndotsSet bool
+ oslTypes []osl.SandboxType // slice of properties of this sandbox
+ loadBalancerNID string // NID that this SB is a load balancer for
+ sync.Mutex
+ // This mutex is used to serialize service related operation for an endpoint
+ // The lock is here because the endpoint is saved into the store so is not unique
+ Service sync.Mutex
+}
+
+// These are the container configs used to customize container /etc/hosts file.
+type hostsPathConfig struct {
+ hostName string
+ domainName string
+ hostsPath string
+ originHostsPath string
+ extraHosts []extraHost
+ parentUpdates []parentUpdate
+}
+
+type parentUpdate struct {
+ cid string
+ name string
+ ip string
+}
+
+type extraHost struct {
+ name string
+ IP string
+}
+
+// These are the container configs used to customize container /etc/resolv.conf file.
+type resolvConfPathConfig struct {
+ resolvConfPath string
+ originResolvConfPath string
+ resolvConfHashFile string
+ dnsList []string
+ dnsSearchList []string
+ dnsOptionsList []string
+}
+
+type containerConfig struct {
+ hostsPathConfig
+ resolvConfPathConfig
+ generic map[string]interface{}
+ useDefaultSandBox bool
+ useExternalKey bool
+ prio int // higher the value, more the priority
+ exposedPorts []types.TransportPort
+}
+
+const (
+ resolverIPSandbox = "127.0.0.11"
+)
+
+func (sb *sandbox) ID() string {
+ return sb.id
+}
+
+func (sb *sandbox) ContainerID() string {
+ return sb.containerID
+}
+
+func (sb *sandbox) Key() string {
+ if sb.config.useDefaultSandBox {
+ return osl.GenerateKey("default")
+ }
+ return osl.GenerateKey(sb.id)
+}
+
+func (sb *sandbox) Labels() map[string]interface{} {
+ sb.Lock()
+ defer sb.Unlock()
+ opts := make(map[string]interface{}, len(sb.config.generic))
+ for k, v := range sb.config.generic {
+ opts[k] = v
+ }
+ return opts
+}
+
+func (sb *sandbox) Statistics() (map[string]*types.InterfaceStatistics, error) {
+ m := make(map[string]*types.InterfaceStatistics)
+
+ sb.Lock()
+ osb := sb.osSbox
+ sb.Unlock()
+ if osb == nil {
+ return m, nil
+ }
+
+ var err error
+ for _, i := range osb.Info().Interfaces() {
+ if m[i.DstName()], err = i.Statistics(); err != nil {
+ return m, err
+ }
+ }
+
+ return m, nil
+}
+
+func (sb *sandbox) Delete() error {
+ return sb.delete(false)
+}
+
+func (sb *sandbox) delete(force bool) error {
+ sb.Lock()
+ if sb.inDelete {
+ sb.Unlock()
+ return types.ForbiddenErrorf("another sandbox delete in progress")
+ }
+ // Set the inDelete flag. This will ensure that we don't
+ // update the store until we have completed all the endpoint
+ // leaves and deletes. And when endpoint leaves and deletes
+ // are completed then we can finally delete the sandbox object
+ // altogether from the data store. If the daemon exits
+ // ungracefully in the middle of a sandbox delete this way we
+ // will have all the references to the endpoints in the
+ // sandbox so that we can clean them up when we restart
+ sb.inDelete = true
+ sb.Unlock()
+
+ c := sb.controller
+
+ // Detach from all endpoints
+ retain := false
+ for _, ep := range sb.getConnectedEndpoints() {
+ // gw network endpoint detach and removal are automatic
+ if ep.endpointInGWNetwork() && !force {
+ continue
+ }
+ // Retain the sanbdox if we can't obtain the network from store.
+ if _, err := c.getNetworkFromStore(ep.getNetwork().ID()); err != nil {
+ if c.isDistributedControl() {
+ retain = true
+ }
+ logrus.Warnf("Failed getting network for ep %s during sandbox %s delete: %v", ep.ID(), sb.ID(), err)
+ continue
+ }
+
+ if !force {
+ if err := ep.Leave(sb); err != nil {
+ logrus.Warnf("Failed detaching sandbox %s from endpoint %s: %v\n", sb.ID(), ep.ID(), err)
+ }
+ }
+
+ if err := ep.Delete(force); err != nil {
+ logrus.Warnf("Failed deleting endpoint %s: %v\n", ep.ID(), err)
+ }
+ }
+
+ if retain {
+ sb.Lock()
+ sb.inDelete = false
+ sb.Unlock()
+ return fmt.Errorf("could not cleanup all the endpoints in container %s / sandbox %s", sb.containerID, sb.id)
+ }
+ // Container is going away. Path cache in etchosts is most
+ // likely not required any more. Drop it.
+ etchosts.Drop(sb.config.hostsPath)
+
+ if sb.resolver != nil {
+ sb.resolver.Stop()
+ }
+
+ if sb.osSbox != nil && !sb.config.useDefaultSandBox {
+ sb.osSbox.Destroy()
+ }
+
+ if err := sb.storeDelete(); err != nil {
+ logrus.Warnf("Failed to delete sandbox %s from store: %v", sb.ID(), err)
+ }
+
+ c.Lock()
+ if sb.ingress {
+ c.ingressSandbox = nil
+ }
+ delete(c.sandboxes, sb.ID())
+ c.Unlock()
+
+ return nil
+}
+
+func (sb *sandbox) Rename(name string) error {
+ var err error
+
+ for _, ep := range sb.getConnectedEndpoints() {
+ if ep.endpointInGWNetwork() {
+ continue
+ }
+
+ oldName := ep.Name()
+ lEp := ep
+ if err = ep.rename(name); err != nil {
+ break
+ }
+
+ defer func() {
+ if err != nil {
+ lEp.rename(oldName)
+ }
+ }()
+ }
+
+ return err
+}
+
+func (sb *sandbox) Refresh(options ...SandboxOption) error {
+ // Store connected endpoints
+ epList := sb.getConnectedEndpoints()
+
+ // Detach from all endpoints
+ for _, ep := range epList {
+ if err := ep.Leave(sb); err != nil {
+ logrus.Warnf("Failed detaching sandbox %s from endpoint %s: %v\n", sb.ID(), ep.ID(), err)
+ }
+ }
+
+ // Re-apply options
+ sb.config = containerConfig{}
+ sb.processOptions(options...)
+
+ // Setup discovery files
+ if err := sb.setupResolutionFiles(); err != nil {
+ return err
+ }
+
+ // Re-connect to all endpoints
+ for _, ep := range epList {
+ if err := ep.Join(sb); err != nil {
+ logrus.Warnf("Failed attach sandbox %s to endpoint %s: %v\n", sb.ID(), ep.ID(), err)
+ }
+ }
+
+ return nil
+}
+
+func (sb *sandbox) MarshalJSON() ([]byte, error) {
+ sb.Lock()
+ defer sb.Unlock()
+
+ // We are just interested in the container ID. This can be expanded to include all of containerInfo if there is a need
+ return json.Marshal(sb.id)
+}
+
+func (sb *sandbox) UnmarshalJSON(b []byte) (err error) {
+ sb.Lock()
+ defer sb.Unlock()
+
+ var id string
+ if err := json.Unmarshal(b, &id); err != nil {
+ return err
+ }
+ sb.id = id
+ return nil
+}
+
+func (sb *sandbox) Endpoints() []Endpoint {
+ sb.Lock()
+ defer sb.Unlock()
+
+ endpoints := make([]Endpoint, len(sb.endpoints))
+ for i, ep := range sb.endpoints {
+ endpoints[i] = ep
+ }
+ return endpoints
+}
+
+func (sb *sandbox) getConnectedEndpoints() []*endpoint {
+ sb.Lock()
+ defer sb.Unlock()
+
+ eps := make([]*endpoint, len(sb.endpoints))
+ copy(eps, sb.endpoints)
+
+ return eps
+}
+
+func (sb *sandbox) addEndpoint(ep *endpoint) {
+ sb.Lock()
+ defer sb.Unlock()
+
+ l := len(sb.endpoints)
+ i := sort.Search(l, func(j int) bool {
+ return ep.Less(sb.endpoints[j])
+ })
+
+ sb.endpoints = append(sb.endpoints, nil)
+ copy(sb.endpoints[i+1:], sb.endpoints[i:])
+ sb.endpoints[i] = ep
+}
+
+func (sb *sandbox) removeEndpoint(ep *endpoint) {
+ sb.Lock()
+ defer sb.Unlock()
+
+ sb.removeEndpointRaw(ep)
+}
+
+func (sb *sandbox) removeEndpointRaw(ep *endpoint) {
+ for i, e := range sb.endpoints {
+ if e == ep {
+ sb.endpoints = append(sb.endpoints[:i], sb.endpoints[i+1:]...)
+ return
+ }
+ }
+}
+
+func (sb *sandbox) getEndpoint(id string) *endpoint {
+ sb.Lock()
+ defer sb.Unlock()
+
+ for _, ep := range sb.endpoints {
+ if ep.id == id {
+ return ep
+ }
+ }
+
+ return nil
+}
+
+func (sb *sandbox) updateGateway(ep *endpoint) error {
+ sb.Lock()
+ osSbox := sb.osSbox
+ sb.Unlock()
+ if osSbox == nil {
+ return nil
+ }
+ osSbox.UnsetGateway()
+ osSbox.UnsetGatewayIPv6()
+
+ if ep == nil {
+ return nil
+ }
+
+ ep.Lock()
+ joinInfo := ep.joinInfo
+ ep.Unlock()
+
+ if err := osSbox.SetGateway(joinInfo.gw); err != nil {
+ return fmt.Errorf("failed to set gateway while updating gateway: %v", err)
+ }
+
+ if err := osSbox.SetGatewayIPv6(joinInfo.gw6); err != nil {
+ return fmt.Errorf("failed to set IPv6 gateway while updating gateway: %v", err)
+ }
+
+ return nil
+}
+
+func (sb *sandbox) HandleQueryResp(name string, ip net.IP) {
+ for _, ep := range sb.getConnectedEndpoints() {
+ n := ep.getNetwork()
+ n.HandleQueryResp(name, ip)
+ }
+}
+
+func (sb *sandbox) ResolveIP(ip string) string {
+ var svc string
+ logrus.Debugf("IP To resolve %v", ip)
+
+ for _, ep := range sb.getConnectedEndpoints() {
+ n := ep.getNetwork()
+ svc = n.ResolveIP(ip)
+ if len(svc) != 0 {
+ return svc
+ }
+ }
+
+ return svc
+}
+
+func (sb *sandbox) ExecFunc(f func()) error {
+ sb.Lock()
+ osSbox := sb.osSbox
+ sb.Unlock()
+ if osSbox != nil {
+ return osSbox.InvokeFunc(f)
+ }
+ return fmt.Errorf("osl sandbox unavailable in ExecFunc for %v", sb.ContainerID())
+}
+
+func (sb *sandbox) ResolveService(name string) ([]*net.SRV, []net.IP) {
+ srv := []*net.SRV{}
+ ip := []net.IP{}
+
+ logrus.Debugf("Service name To resolve: %v", name)
+
+ // There are DNS implementations that allow SRV queries for names not in
+ // the format defined by RFC 2782. Hence specific validations checks are
+ // not done
+ parts := strings.Split(name, ".")
+ if len(parts) < 3 {
+ return nil, nil
+ }
+
+ for _, ep := range sb.getConnectedEndpoints() {
+ n := ep.getNetwork()
+
+ srv, ip = n.ResolveService(name)
+ if len(srv) > 0 {
+ break
+ }
+ }
+ return srv, ip
+}
+
+func getDynamicNwEndpoints(epList []*endpoint) []*endpoint {
+ eps := []*endpoint{}
+ for _, ep := range epList {
+ n := ep.getNetwork()
+ if n.dynamic && !n.ingress {
+ eps = append(eps, ep)
+ }
+ }
+ return eps
+}
+
+func getIngressNwEndpoint(epList []*endpoint) *endpoint {
+ for _, ep := range epList {
+ n := ep.getNetwork()
+ if n.ingress {
+ return ep
+ }
+ }
+ return nil
+}
+
+func getLocalNwEndpoints(epList []*endpoint) []*endpoint {
+ eps := []*endpoint{}
+ for _, ep := range epList {
+ n := ep.getNetwork()
+ if !n.dynamic && !n.ingress {
+ eps = append(eps, ep)
+ }
+ }
+ return eps
+}
+
+func (sb *sandbox) ResolveName(name string, ipType int) ([]net.IP, bool) {
+ // Embedded server owns the docker network domain. Resolution should work
+ // for both container_name and container_name.network_name
+ // We allow '.' in service name and network name. For a name a.b.c.d the
+ // following have to tried;
+ // {a.b.c.d in the networks container is connected to}
+ // {a.b.c in network d},
+ // {a.b in network c.d},
+ // {a in network b.c.d},
+
+ logrus.Debugf("Name To resolve: %v", name)
+ name = strings.TrimSuffix(name, ".")
+ reqName := []string{name}
+ networkName := []string{""}
+
+ if strings.Contains(name, ".") {
+ var i int
+ dup := name
+ for {
+ if i = strings.LastIndex(dup, "."); i == -1 {
+ break
+ }
+ networkName = append(networkName, name[i+1:])
+ reqName = append(reqName, name[:i])
+
+ dup = dup[:i]
+ }
+ }
+
+ epList := sb.getConnectedEndpoints()
+
+ // In swarm mode services with exposed ports are connected to user overlay
+ // network, ingress network and docker_gwbridge network. Name resolution
+ // should prioritize returning the VIP/IPs on user overlay network.
+ newList := []*endpoint{}
+ if !sb.controller.isDistributedControl() {
+ newList = append(newList, getDynamicNwEndpoints(epList)...)
+ ingressEP := getIngressNwEndpoint(epList)
+ if ingressEP != nil {
+ newList = append(newList, ingressEP)
+ }
+ newList = append(newList, getLocalNwEndpoints(epList)...)
+ epList = newList
+ }
+
+ for i := 0; i < len(reqName); i++ {
+
+ // First check for local container alias
+ ip, ipv6Miss := sb.resolveName(reqName[i], networkName[i], epList, true, ipType)
+ if ip != nil {
+ return ip, false
+ }
+ if ipv6Miss {
+ return ip, ipv6Miss
+ }
+
+ // Resolve the actual container name
+ ip, ipv6Miss = sb.resolveName(reqName[i], networkName[i], epList, false, ipType)
+ if ip != nil {
+ return ip, false
+ }
+ if ipv6Miss {
+ return ip, ipv6Miss
+ }
+ }
+ return nil, false
+}
+
+func (sb *sandbox) resolveName(req string, networkName string, epList []*endpoint, alias bool, ipType int) ([]net.IP, bool) {
+ var ipv6Miss bool
+
+ for _, ep := range epList {
+ name := req
+ n := ep.getNetwork()
+
+ if networkName != "" && networkName != n.Name() {
+ continue
+ }
+
+ if alias {
+ if ep.aliases == nil {
+ continue
+ }
+
+ var ok bool
+ ep.Lock()
+ name, ok = ep.aliases[req]
+ ep.Unlock()
+ if !ok {
+ continue
+ }
+ } else {
+ // If it is a regular lookup and if the requested name is an alias
+ // don't perform a svc lookup for this endpoint.
+ ep.Lock()
+ if _, ok := ep.aliases[req]; ok {
+ ep.Unlock()
+ continue
+ }
+ ep.Unlock()
+ }
+
+ ip, miss := n.ResolveName(name, ipType)
+
+ if ip != nil {
+ return ip, false
+ }
+
+ if miss {
+ ipv6Miss = miss
+ }
+ }
+ return nil, ipv6Miss
+}
+
+func (sb *sandbox) SetKey(basePath string) error {
+ start := time.Now()
+ defer func() {
+ logrus.Debugf("sandbox set key processing took %s for container %s", time.Since(start), sb.ContainerID())
+ }()
+
+ if basePath == "" {
+ return types.BadRequestErrorf("invalid sandbox key")
+ }
+
+ sb.Lock()
+ if sb.inDelete {
+ sb.Unlock()
+ return types.ForbiddenErrorf("failed to SetKey: sandbox %q delete in progress", sb.id)
+ }
+ oldosSbox := sb.osSbox
+ sb.Unlock()
+
+ if oldosSbox != nil {
+ // If we already have an OS sandbox, release the network resources from that
+ // and destroy the OS snab. We are moving into a new home further down. Note that none
+ // of the network resources gets destroyed during the move.
+ sb.releaseOSSbox()
+ }
+
+ osSbox, err := osl.GetSandboxForExternalKey(basePath, sb.Key())
+ if err != nil {
+ return err
+ }
+
+ sb.Lock()
+ sb.osSbox = osSbox
+ sb.Unlock()
+
+ // If the resolver was setup before stop it and set it up in the
+ // new osl sandbox.
+ if oldosSbox != nil && sb.resolver != nil {
+ sb.resolver.Stop()
+
+ if err := sb.osSbox.InvokeFunc(sb.resolver.SetupFunc(0)); err == nil {
+ if err := sb.resolver.Start(); err != nil {
+ logrus.Errorf("Resolver Start failed for container %s, %q", sb.ContainerID(), err)
+ }
+ } else {
+ logrus.Errorf("Resolver Setup Function failed for container %s, %q", sb.ContainerID(), err)
+ }
+ }
+
+ for _, ep := range sb.getConnectedEndpoints() {
+ if err = sb.populateNetworkResources(ep); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (sb *sandbox) EnableService() (err error) {
+ logrus.Debugf("EnableService %s START", sb.containerID)
+ defer func() {
+ if err != nil {
+ sb.DisableService()
+ }
+ }()
+ for _, ep := range sb.getConnectedEndpoints() {
+ if !ep.isServiceEnabled() {
+ if err := ep.addServiceInfoToCluster(sb); err != nil {
+ return fmt.Errorf("could not update state for endpoint %s into cluster: %v", ep.Name(), err)
+ }
+ ep.enableService()
+ }
+ }
+ logrus.Debugf("EnableService %s DONE", sb.containerID)
+ return nil
+}
+
+func (sb *sandbox) DisableService() (err error) {
+ logrus.Debugf("DisableService %s START", sb.containerID)
+ failedEps := []string{}
+ defer func() {
+ if len(failedEps) > 0 {
+ err = fmt.Errorf("failed to disable service on sandbox:%s, for endpoints %s", sb.ID(), strings.Join(failedEps, ","))
+ }
+ }()
+ for _, ep := range sb.getConnectedEndpoints() {
+ if ep.isServiceEnabled() {
+ if err := ep.deleteServiceInfoFromCluster(sb, false, "DisableService"); err != nil {
+ failedEps = append(failedEps, ep.Name())
+ logrus.Warnf("failed update state for endpoint %s into cluster: %v", ep.Name(), err)
+ }
+ ep.disableService()
+ }
+ }
+ logrus.Debugf("DisableService %s DONE", sb.containerID)
+ return nil
+}
+
+func releaseOSSboxResources(osSbox osl.Sandbox, ep *endpoint) {
+ for _, i := range osSbox.Info().Interfaces() {
+ // Only remove the interfaces owned by this endpoint from the sandbox.
+ if ep.hasInterface(i.SrcName()) {
+ if err := i.Remove(); err != nil {
+ logrus.Debugf("Remove interface %s failed: %v", i.SrcName(), err)
+ }
+ }
+ }
+
+ ep.Lock()
+ joinInfo := ep.joinInfo
+ vip := ep.virtualIP
+ lbModeIsDSR := ep.network.loadBalancerMode == loadBalancerModeDSR
+ ep.Unlock()
+
+ if len(vip) > 0 && lbModeIsDSR {
+ ipNet := &net.IPNet{IP: vip, Mask: net.CIDRMask(32, 32)}
+ if err := osSbox.RemoveAliasIP(osSbox.GetLoopbackIfaceName(), ipNet); err != nil {
+ logrus.WithError(err).Debugf("failed to remove virtual ip %v to loopback", ipNet)
+ }
+ }
+
+ if joinInfo == nil {
+ return
+ }
+
+ // Remove non-interface routes.
+ for _, r := range joinInfo.StaticRoutes {
+ if err := osSbox.RemoveStaticRoute(r); err != nil {
+ logrus.Debugf("Remove route failed: %v", err)
+ }
+ }
+}
+
+func (sb *sandbox) releaseOSSbox() {
+ sb.Lock()
+ osSbox := sb.osSbox
+ sb.osSbox = nil
+ sb.Unlock()
+
+ if osSbox == nil {
+ return
+ }
+
+ for _, ep := range sb.getConnectedEndpoints() {
+ releaseOSSboxResources(osSbox, ep)
+ }
+
+ osSbox.Destroy()
+}
+
+func (sb *sandbox) restoreOslSandbox() error {
+ var routes []*types.StaticRoute
+
+ // restore osl sandbox
+ Ifaces := make(map[string][]osl.IfaceOption)
+ for _, ep := range sb.endpoints {
+ var ifaceOptions []osl.IfaceOption
+ ep.Lock()
+ joinInfo := ep.joinInfo
+ i := ep.iface
+ ep.Unlock()
+
+ if i == nil {
+ logrus.Errorf("error restoring endpoint %s for container %s", ep.Name(), sb.ContainerID())
+ continue
+ }
+
+ ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().Address(i.addr), sb.osSbox.InterfaceOptions().Routes(i.routes))
+ if i.addrv6 != nil && i.addrv6.IP.To16() != nil {
+ ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().AddressIPv6(i.addrv6))
+ }
+ if i.mac != nil {
+ ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().MacAddress(i.mac))
+ }
+ if len(i.llAddrs) != 0 {
+ ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().LinkLocalAddresses(i.llAddrs))
+ }
+ Ifaces[fmt.Sprintf("%s+%s", i.srcName, i.dstPrefix)] = ifaceOptions
+ if joinInfo != nil {
+ routes = append(routes, joinInfo.StaticRoutes...)
+ }
+ if ep.needResolver() {
+ sb.startResolver(true)
+ }
+ }
+
+ gwep := sb.getGatewayEndpoint()
+ if gwep == nil {
+ return nil
+ }
+
+ // restore osl sandbox
+ err := sb.osSbox.Restore(Ifaces, routes, gwep.joinInfo.gw, gwep.joinInfo.gw6)
+ return err
+}
+
+func (sb *sandbox) populateNetworkResources(ep *endpoint) error {
+ sb.Lock()
+ if sb.osSbox == nil {
+ sb.Unlock()
+ return nil
+ }
+ inDelete := sb.inDelete
+ sb.Unlock()
+
+ ep.Lock()
+ joinInfo := ep.joinInfo
+ i := ep.iface
+ lbModeIsDSR := ep.network.loadBalancerMode == loadBalancerModeDSR
+ ep.Unlock()
+
+ if ep.needResolver() {
+ sb.startResolver(false)
+ }
+
+ if i != nil && i.srcName != "" {
+ var ifaceOptions []osl.IfaceOption
+
+ ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().Address(i.addr), sb.osSbox.InterfaceOptions().Routes(i.routes))
+ if i.addrv6 != nil && i.addrv6.IP.To16() != nil {
+ ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().AddressIPv6(i.addrv6))
+ }
+ if len(i.llAddrs) != 0 {
+ ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().LinkLocalAddresses(i.llAddrs))
+ }
+ if i.mac != nil {
+ ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().MacAddress(i.mac))
+ }
+
+ if err := sb.osSbox.AddInterface(i.srcName, i.dstPrefix, ifaceOptions...); err != nil {
+ return fmt.Errorf("failed to add interface %s to sandbox: %v", i.srcName, err)
+ }
+
+ if len(ep.virtualIP) > 0 && lbModeIsDSR {
+ if sb.loadBalancerNID == "" {
+ if err := sb.osSbox.DisableARPForVIP(i.srcName); err != nil {
+ return fmt.Errorf("failed disable ARP for VIP: %v", err)
+ }
+ }
+ ipNet := &net.IPNet{IP: ep.virtualIP, Mask: net.CIDRMask(32, 32)}
+ if err := sb.osSbox.AddAliasIP(sb.osSbox.GetLoopbackIfaceName(), ipNet); err != nil {
+ return fmt.Errorf("failed to add virtual ip %v to loopback: %v", ipNet, err)
+ }
+ }
+ }
+
+ if joinInfo != nil {
+ // Set up non-interface routes.
+ for _, r := range joinInfo.StaticRoutes {
+ if err := sb.osSbox.AddStaticRoute(r); err != nil {
+ return fmt.Errorf("failed to add static route %s: %v", r.Destination.String(), err)
+ }
+ }
+ }
+
+ if ep == sb.getGatewayEndpoint() {
+ if err := sb.updateGateway(ep); err != nil {
+ return err
+ }
+ }
+
+ // Make sure to add the endpoint to the populated endpoint set
+ // before populating loadbalancers.
+ sb.Lock()
+ sb.populatedEndpoints[ep.ID()] = struct{}{}
+ sb.Unlock()
+
+ // Populate load balancer only after updating all the other
+ // information including gateway and other routes so that
+ // loadbalancers are populated all the network state is in
+ // place in the sandbox.
+ sb.populateLoadBalancers(ep)
+
+ // Only update the store if we did not come here as part of
+ // sandbox delete. If we came here as part of delete then do
+ // not bother updating the store. The sandbox object will be
+ // deleted anyway
+ if !inDelete {
+ return sb.storeUpdate()
+ }
+
+ return nil
+}
+
+func (sb *sandbox) clearNetworkResources(origEp *endpoint) error {
+ ep := sb.getEndpoint(origEp.id)
+ if ep == nil {
+ return fmt.Errorf("could not find the sandbox endpoint data for endpoint %s",
+ origEp.id)
+ }
+
+ sb.Lock()
+ osSbox := sb.osSbox
+ inDelete := sb.inDelete
+ sb.Unlock()
+ if osSbox != nil {
+ releaseOSSboxResources(osSbox, ep)
+ }
+
+ sb.Lock()
+ delete(sb.populatedEndpoints, ep.ID())
+
+ if len(sb.endpoints) == 0 {
+ // sb.endpoints should never be empty and this is unexpected error condition
+ // We log an error message to note this down for debugging purposes.
+ logrus.Errorf("No endpoints in sandbox while trying to remove endpoint %s", ep.Name())
+ sb.Unlock()
+ return nil
+ }
+
+ var (
+ gwepBefore, gwepAfter *endpoint
+ index = -1
+ )
+ for i, e := range sb.endpoints {
+ if e == ep {
+ index = i
+ }
+ if len(e.Gateway()) > 0 && gwepBefore == nil {
+ gwepBefore = e
+ }
+ if index != -1 && gwepBefore != nil {
+ break
+ }
+ }
+
+ if index == -1 {
+ logrus.Warnf("Endpoint %s has already been deleted", ep.Name())
+ sb.Unlock()
+ return nil
+ }
+
+ sb.removeEndpointRaw(ep)
+ for _, e := range sb.endpoints {
+ if len(e.Gateway()) > 0 {
+ gwepAfter = e
+ break
+ }
+ }
+ delete(sb.epPriority, ep.ID())
+ sb.Unlock()
+
+ if gwepAfter != nil && gwepBefore != gwepAfter {
+ sb.updateGateway(gwepAfter)
+ }
+
+ // Only update the store if we did not come here as part of
+ // sandbox delete. If we came here as part of delete then do
+ // not bother updating the store. The sandbox object will be
+ // deleted anyway
+ if !inDelete {
+ return sb.storeUpdate()
+ }
+
+ return nil
+}
+
+func (sb *sandbox) isEndpointPopulated(ep *endpoint) bool {
+ sb.Lock()
+ _, ok := sb.populatedEndpoints[ep.ID()]
+ sb.Unlock()
+ return ok
+}
+
+// joinLeaveStart waits to ensure there are no joins or leaves in progress and
+// marks this join/leave in progress without race
+func (sb *sandbox) joinLeaveStart() {
+ sb.Lock()
+ defer sb.Unlock()
+
+ for sb.joinLeaveDone != nil {
+ joinLeaveDone := sb.joinLeaveDone
+ sb.Unlock()
+
+ <-joinLeaveDone
+
+ sb.Lock()
+ }
+
+ sb.joinLeaveDone = make(chan struct{})
+}
+
+// joinLeaveEnd marks the end of this join/leave operation and
+// signals the same without race to other join and leave waiters
+func (sb *sandbox) joinLeaveEnd() {
+ sb.Lock()
+ defer sb.Unlock()
+
+ if sb.joinLeaveDone != nil {
+ close(sb.joinLeaveDone)
+ sb.joinLeaveDone = nil
+ }
+}
+
+func (sb *sandbox) hasPortConfigs() bool {
+ opts := sb.Labels()
+ _, hasExpPorts := opts[netlabel.ExposedPorts]
+ _, hasPortMaps := opts[netlabel.PortMap]
+ return hasExpPorts || hasPortMaps
+}
+
+// OptionHostname function returns an option setter for hostname option to
+// be passed to NewSandbox method.
+func OptionHostname(name string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.hostName = name
+ }
+}
+
+// OptionDomainname function returns an option setter for domainname option to
+// be passed to NewSandbox method.
+func OptionDomainname(name string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.domainName = name
+ }
+}
+
+// OptionHostsPath function returns an option setter for hostspath option to
+// be passed to NewSandbox method.
+func OptionHostsPath(path string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.hostsPath = path
+ }
+}
+
+// OptionOriginHostsPath function returns an option setter for origin hosts file path
+// to be passed to NewSandbox method.
+func OptionOriginHostsPath(path string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.originHostsPath = path
+ }
+}
+
+// OptionExtraHost function returns an option setter for extra /etc/hosts options
+// which is a name and IP as strings.
+func OptionExtraHost(name string, IP string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.extraHosts = append(sb.config.extraHosts, extraHost{name: name, IP: IP})
+ }
+}
+
+// OptionParentUpdate function returns an option setter for parent container
+// which needs to update the IP address for the linked container.
+func OptionParentUpdate(cid string, name, ip string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.parentUpdates = append(sb.config.parentUpdates, parentUpdate{cid: cid, name: name, ip: ip})
+ }
+}
+
+// OptionResolvConfPath function returns an option setter for resolvconfpath option to
+// be passed to net container methods.
+func OptionResolvConfPath(path string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.resolvConfPath = path
+ }
+}
+
+// OptionOriginResolvConfPath function returns an option setter to set the path to the
+// origin resolv.conf file to be passed to net container methods.
+func OptionOriginResolvConfPath(path string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.originResolvConfPath = path
+ }
+}
+
+// OptionDNS function returns an option setter for dns entry option to
+// be passed to container Create method.
+func OptionDNS(dns string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.dnsList = append(sb.config.dnsList, dns)
+ }
+}
+
+// OptionDNSSearch function returns an option setter for dns search entry option to
+// be passed to container Create method.
+func OptionDNSSearch(search string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.dnsSearchList = append(sb.config.dnsSearchList, search)
+ }
+}
+
+// OptionDNSOptions function returns an option setter for dns options entry option to
+// be passed to container Create method.
+func OptionDNSOptions(options string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.dnsOptionsList = append(sb.config.dnsOptionsList, options)
+ }
+}
+
+// OptionUseDefaultSandbox function returns an option setter for using default sandbox
+// (host namespace) to be passed to container Create method.
+func OptionUseDefaultSandbox() SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.useDefaultSandBox = true
+ }
+}
+
+// OptionUseExternalKey function returns an option setter for using provided namespace
+// instead of creating one.
+func OptionUseExternalKey() SandboxOption {
+ return func(sb *sandbox) {
+ sb.config.useExternalKey = true
+ }
+}
+
+// OptionGeneric function returns an option setter for Generic configuration
+// that is not managed by libNetwork but can be used by the Drivers during the call to
+// net container creation method. Container Labels are a good example.
+func OptionGeneric(generic map[string]interface{}) SandboxOption {
+ return func(sb *sandbox) {
+ if sb.config.generic == nil {
+ sb.config.generic = make(map[string]interface{}, len(generic))
+ }
+ for k, v := range generic {
+ sb.config.generic[k] = v
+ }
+ }
+}
+
+// OptionExposedPorts function returns an option setter for the container exposed
+// ports option to be passed to container Create method.
+func OptionExposedPorts(exposedPorts []types.TransportPort) SandboxOption {
+ return func(sb *sandbox) {
+ if sb.config.generic == nil {
+ sb.config.generic = make(map[string]interface{})
+ }
+ // Defensive copy
+ eps := make([]types.TransportPort, len(exposedPorts))
+ copy(eps, exposedPorts)
+ // Store endpoint label and in generic because driver needs it
+ sb.config.exposedPorts = eps
+ sb.config.generic[netlabel.ExposedPorts] = eps
+ }
+}
+
+// OptionPortMapping function returns an option setter for the mapping
+// ports option to be passed to container Create method.
+func OptionPortMapping(portBindings []types.PortBinding) SandboxOption {
+ return func(sb *sandbox) {
+ if sb.config.generic == nil {
+ sb.config.generic = make(map[string]interface{})
+ }
+ // Store a copy of the bindings as generic data to pass to the driver
+ pbs := make([]types.PortBinding, len(portBindings))
+ copy(pbs, portBindings)
+ sb.config.generic[netlabel.PortMap] = pbs
+ }
+}
+
+// OptionIngress function returns an option setter for marking a
+// sandbox as the controller's ingress sandbox.
+func OptionIngress() SandboxOption {
+ return func(sb *sandbox) {
+ sb.ingress = true
+ sb.oslTypes = append(sb.oslTypes, osl.SandboxTypeIngress)
+ }
+}
+
+// OptionLoadBalancer function returns an option setter for marking a
+// sandbox as a load balancer sandbox.
+func OptionLoadBalancer(nid string) SandboxOption {
+ return func(sb *sandbox) {
+ sb.loadBalancerNID = nid
+ sb.oslTypes = append(sb.oslTypes, osl.SandboxTypeLoadBalancer)
+ }
+}
+
+// <=> Returns true if a < b, false if a > b and advances to next level if a == b
+// epi.prio <=> epj.prio # 2 < 1
+// epi.gw <=> epj.gw # non-gw < gw
+// epi.internal <=> epj.internal # non-internal < internal
+// epi.joininfo <=> epj.joininfo # ipv6 < ipv4
+// epi.name <=> epj.name # bar < foo
+func (epi *endpoint) Less(epj *endpoint) bool {
+ var (
+ prioi, prioj int
+ )
+
+ sbi, _ := epi.getSandbox()
+ sbj, _ := epj.getSandbox()
+
+ // Prio defaults to 0
+ if sbi != nil {
+ prioi = sbi.epPriority[epi.ID()]
+ }
+ if sbj != nil {
+ prioj = sbj.epPriority[epj.ID()]
+ }
+
+ if prioi != prioj {
+ return prioi > prioj
+ }
+
+ gwi := epi.endpointInGWNetwork()
+ gwj := epj.endpointInGWNetwork()
+ if gwi != gwj {
+ return gwj
+ }
+
+ inti := epi.getNetwork().Internal()
+ intj := epj.getNetwork().Internal()
+ if inti != intj {
+ return intj
+ }
+
+ jii := 0
+ if epi.joinInfo != nil {
+ if epi.joinInfo.gw != nil {
+ jii = jii + 1
+ }
+ if epi.joinInfo.gw6 != nil {
+ jii = jii + 2
+ }
+ }
+
+ jij := 0
+ if epj.joinInfo != nil {
+ if epj.joinInfo.gw != nil {
+ jij = jij + 1
+ }
+ if epj.joinInfo.gw6 != nil {
+ jij = jij + 2
+ }
+ }
+
+ if jii != jij {
+ return jii > jij
+ }
+
+ return epi.network.Name() < epj.network.Name()
+}
+
+func (sb *sandbox) NdotsSet() bool {
+ return sb.ndotsSet
+}
--- /dev/null
+// +build !windows
+
+package libnetwork
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/docker/libnetwork/etchosts"
+ "github.com/docker/libnetwork/resolvconf"
+ "github.com/docker/libnetwork/resolvconf/dns"
+ "github.com/docker/libnetwork/types"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ defaultPrefix = "/var/lib/docker/network/files"
+ dirPerm = 0755
+ filePerm = 0644
+)
+
+func (sb *sandbox) startResolver(restore bool) {
+ sb.resolverOnce.Do(func() {
+ var err error
+ sb.resolver = NewResolver(resolverIPSandbox, true, sb.Key(), sb)
+ defer func() {
+ if err != nil {
+ sb.resolver = nil
+ }
+ }()
+
+ // In the case of live restore container is already running with
+ // right resolv.conf contents created before. Just update the
+ // external DNS servers from the restored sandbox for embedded
+ // server to use.
+ if !restore {
+ err = sb.rebuildDNS()
+ if err != nil {
+ logrus.Errorf("Updating resolv.conf failed for container %s, %q", sb.ContainerID(), err)
+ return
+ }
+ }
+ sb.resolver.SetExtServers(sb.extDNS)
+
+ if err = sb.osSbox.InvokeFunc(sb.resolver.SetupFunc(0)); err != nil {
+ logrus.Errorf("Resolver Setup function failed for container %s, %q", sb.ContainerID(), err)
+ return
+ }
+
+ if err = sb.resolver.Start(); err != nil {
+ logrus.Errorf("Resolver Start failed for container %s, %q", sb.ContainerID(), err)
+ }
+ })
+}
+
+func (sb *sandbox) setupResolutionFiles() error {
+ if err := sb.buildHostsFile(); err != nil {
+ return err
+ }
+
+ if err := sb.updateParentHosts(); err != nil {
+ return err
+ }
+
+ return sb.setupDNS()
+}
+
+func (sb *sandbox) buildHostsFile() error {
+ if sb.config.hostsPath == "" {
+ sb.config.hostsPath = defaultPrefix + "/" + sb.id + "/hosts"
+ }
+
+ dir, _ := filepath.Split(sb.config.hostsPath)
+ if err := createBasePath(dir); err != nil {
+ return err
+ }
+
+ // This is for the host mode networking
+ if sb.config.useDefaultSandBox && len(sb.config.extraHosts) == 0 {
+ // We are working under the assumption that the origin file option had been properly expressed by the upper layer
+ // if not here we are going to error out
+ if err := copyFile(sb.config.originHostsPath, sb.config.hostsPath); err != nil && !os.IsNotExist(err) {
+ return types.InternalErrorf("could not copy source hosts file %s to %s: %v", sb.config.originHostsPath, sb.config.hostsPath, err)
+ }
+ return nil
+ }
+
+ extraContent := make([]etchosts.Record, 0, len(sb.config.extraHosts))
+ for _, extraHost := range sb.config.extraHosts {
+ extraContent = append(extraContent, etchosts.Record{Hosts: extraHost.name, IP: extraHost.IP})
+ }
+
+ return etchosts.Build(sb.config.hostsPath, "", sb.config.hostName, sb.config.domainName, extraContent)
+}
+
+func (sb *sandbox) updateHostsFile(ifaceIP string) error {
+ if ifaceIP == "" {
+ return nil
+ }
+
+ if sb.config.originHostsPath != "" {
+ return nil
+ }
+
+ // User might have provided a FQDN in hostname or split it across hostname
+ // and domainname. We want the FQDN and the bare hostname.
+ fqdn := sb.config.hostName
+ mhost := sb.config.hostName
+ if sb.config.domainName != "" {
+ fqdn = fmt.Sprintf("%s.%s", fqdn, sb.config.domainName)
+ }
+
+ parts := strings.SplitN(fqdn, ".", 2)
+ if len(parts) == 2 {
+ mhost = fmt.Sprintf("%s %s", fqdn, parts[0])
+ }
+
+ extraContent := []etchosts.Record{{Hosts: mhost, IP: ifaceIP}}
+
+ sb.addHostsEntries(extraContent)
+ return nil
+}
+
+func (sb *sandbox) addHostsEntries(recs []etchosts.Record) {
+ if err := etchosts.Add(sb.config.hostsPath, recs); err != nil {
+ logrus.Warnf("Failed adding service host entries to the running container: %v", err)
+ }
+}
+
+func (sb *sandbox) deleteHostsEntries(recs []etchosts.Record) {
+ if err := etchosts.Delete(sb.config.hostsPath, recs); err != nil {
+ logrus.Warnf("Failed deleting service host entries to the running container: %v", err)
+ }
+}
+
+func (sb *sandbox) updateParentHosts() error {
+ var pSb Sandbox
+
+ for _, update := range sb.config.parentUpdates {
+ sb.controller.WalkSandboxes(SandboxContainerWalker(&pSb, update.cid))
+ if pSb == nil {
+ continue
+ }
+ if err := etchosts.Update(pSb.(*sandbox).config.hostsPath, update.ip, update.name); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (sb *sandbox) restorePath() {
+ if sb.config.resolvConfPath == "" {
+ sb.config.resolvConfPath = defaultPrefix + "/" + sb.id + "/resolv.conf"
+ }
+ sb.config.resolvConfHashFile = sb.config.resolvConfPath + ".hash"
+ if sb.config.hostsPath == "" {
+ sb.config.hostsPath = defaultPrefix + "/" + sb.id + "/hosts"
+ }
+}
+
+func (sb *sandbox) setExternalResolvers(content []byte, addrType int, checkLoopback bool) {
+ servers := resolvconf.GetNameservers(content, addrType)
+ for _, ip := range servers {
+ hostLoopback := false
+ if checkLoopback {
+ hostLoopback = dns.IsIPv4Localhost(ip)
+ }
+ sb.extDNS = append(sb.extDNS, extDNSEntry{
+ IPStr: ip,
+ HostLoopback: hostLoopback,
+ })
+ }
+}
+
+func (sb *sandbox) setupDNS() error {
+ var newRC *resolvconf.File
+
+ if sb.config.resolvConfPath == "" {
+ sb.config.resolvConfPath = defaultPrefix + "/" + sb.id + "/resolv.conf"
+ }
+
+ sb.config.resolvConfHashFile = sb.config.resolvConfPath + ".hash"
+
+ dir, _ := filepath.Split(sb.config.resolvConfPath)
+ if err := createBasePath(dir); err != nil {
+ return err
+ }
+
+ // When the user specify a conainter in the host namespace and do no have any dns option specified
+ // we just copy the host resolv.conf from the host itself
+ if sb.config.useDefaultSandBox &&
+ len(sb.config.dnsList) == 0 && len(sb.config.dnsSearchList) == 0 && len(sb.config.dnsOptionsList) == 0 {
+
+ // We are working under the assumption that the origin file option had been properly expressed by the upper layer
+ // if not here we are going to error out
+ if err := copyFile(sb.config.originResolvConfPath, sb.config.resolvConfPath); err != nil {
+ if !os.IsNotExist(err) {
+ return fmt.Errorf("could not copy source resolv.conf file %s to %s: %v", sb.config.originResolvConfPath, sb.config.resolvConfPath, err)
+ }
+ logrus.Infof("%s does not exist, we create an empty resolv.conf for container", sb.config.originResolvConfPath)
+ if err := createFile(sb.config.resolvConfPath); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ originResolvConfPath := sb.config.originResolvConfPath
+ if originResolvConfPath == "" {
+ // if not specified fallback to default /etc/resolv.conf
+ originResolvConfPath = resolvconf.DefaultResolvConf
+ }
+ currRC, err := resolvconf.GetSpecific(originResolvConfPath)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return err
+ }
+ // it's ok to continue if /etc/resolv.conf doesn't exist, default resolvers (Google's Public DNS)
+ // will be used
+ currRC = &resolvconf.File{}
+ logrus.Infof("/etc/resolv.conf does not exist")
+ }
+
+ if len(sb.config.dnsList) > 0 || len(sb.config.dnsSearchList) > 0 || len(sb.config.dnsOptionsList) > 0 {
+ var (
+ err error
+ dnsList = resolvconf.GetNameservers(currRC.Content, types.IP)
+ dnsSearchList = resolvconf.GetSearchDomains(currRC.Content)
+ dnsOptionsList = resolvconf.GetOptions(currRC.Content)
+ )
+ if len(sb.config.dnsList) > 0 {
+ dnsList = sb.config.dnsList
+ }
+ if len(sb.config.dnsSearchList) > 0 {
+ dnsSearchList = sb.config.dnsSearchList
+ }
+ if len(sb.config.dnsOptionsList) > 0 {
+ dnsOptionsList = sb.config.dnsOptionsList
+ }
+ newRC, err = resolvconf.Build(sb.config.resolvConfPath, dnsList, dnsSearchList, dnsOptionsList)
+ if err != nil {
+ return err
+ }
+ // After building the resolv.conf from the user config save the
+ // external resolvers in the sandbox. Note that --dns 127.0.0.x
+ // config refers to the loopback in the container namespace
+ sb.setExternalResolvers(newRC.Content, types.IPv4, false)
+ } else {
+ // If the host resolv.conf file has 127.0.0.x container should
+ // use the host resolver for queries. This is supported by the
+ // docker embedded DNS server. Hence save the external resolvers
+ // before filtering it out.
+ sb.setExternalResolvers(currRC.Content, types.IPv4, true)
+
+ // Replace any localhost/127.* (at this point we have no info about ipv6, pass it as true)
+ if newRC, err = resolvconf.FilterResolvDNS(currRC.Content, true); err != nil {
+ return err
+ }
+ // No contention on container resolv.conf file at sandbox creation
+ if err := ioutil.WriteFile(sb.config.resolvConfPath, newRC.Content, filePerm); err != nil {
+ return types.InternalErrorf("failed to write unhaltered resolv.conf file content when setting up dns for sandbox %s: %v", sb.ID(), err)
+ }
+ }
+
+ // Write hash
+ if err := ioutil.WriteFile(sb.config.resolvConfHashFile, []byte(newRC.Hash), filePerm); err != nil {
+ return types.InternalErrorf("failed to write resolv.conf hash file when setting up dns for sandbox %s: %v", sb.ID(), err)
+ }
+
+ return nil
+}
+
+func (sb *sandbox) updateDNS(ipv6Enabled bool) error {
+ var (
+ currHash string
+ hashFile = sb.config.resolvConfHashFile
+ )
+
+ // This is for the host mode networking
+ if sb.config.useDefaultSandBox {
+ return nil
+ }
+
+ if len(sb.config.dnsList) > 0 || len(sb.config.dnsSearchList) > 0 || len(sb.config.dnsOptionsList) > 0 {
+ return nil
+ }
+
+ currRC, err := resolvconf.GetSpecific(sb.config.resolvConfPath)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return err
+ }
+ } else {
+ h, err := ioutil.ReadFile(hashFile)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return err
+ }
+ } else {
+ currHash = string(h)
+ }
+ }
+
+ if currHash != "" && currHash != currRC.Hash {
+ // Seems the user has changed the container resolv.conf since the last time
+ // we checked so return without doing anything.
+ //logrus.Infof("Skipping update of resolv.conf file with ipv6Enabled: %t because file was touched by user", ipv6Enabled)
+ return nil
+ }
+
+ // replace any localhost/127.* and remove IPv6 nameservers if IPv6 disabled.
+ newRC, err := resolvconf.FilterResolvDNS(currRC.Content, ipv6Enabled)
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(sb.config.resolvConfPath, newRC.Content, 0644)
+ if err != nil {
+ return err
+ }
+
+ // write the new hash in a temp file and rename it to make the update atomic
+ dir := path.Dir(sb.config.resolvConfPath)
+ tmpHashFile, err := ioutil.TempFile(dir, "hash")
+ if err != nil {
+ return err
+ }
+ if err = tmpHashFile.Chmod(filePerm); err != nil {
+ tmpHashFile.Close()
+ return err
+ }
+ _, err = tmpHashFile.Write([]byte(newRC.Hash))
+ if err1 := tmpHashFile.Close(); err == nil {
+ err = err1
+ }
+ if err != nil {
+ return err
+ }
+ return os.Rename(tmpHashFile.Name(), hashFile)
+}
+
+// Embedded DNS server has to be enabled for this sandbox. Rebuild the container's
+// resolv.conf by doing the following
+// - Add only the embedded server's IP to container's resolv.conf
+// - If the embedded server needs any resolv.conf options add it to the current list
+func (sb *sandbox) rebuildDNS() error {
+ currRC, err := resolvconf.GetSpecific(sb.config.resolvConfPath)
+ if err != nil {
+ return err
+ }
+
+ if len(sb.extDNS) == 0 {
+ sb.setExternalResolvers(currRC.Content, types.IPv4, false)
+ }
+ var (
+ dnsList = []string{sb.resolver.NameServer()}
+ dnsOptionsList = resolvconf.GetOptions(currRC.Content)
+ dnsSearchList = resolvconf.GetSearchDomains(currRC.Content)
+ )
+
+ // external v6 DNS servers has to be listed in resolv.conf
+ dnsList = append(dnsList, resolvconf.GetNameservers(currRC.Content, types.IPv6)...)
+
+ // If the user config and embedded DNS server both have ndots option set,
+ // remember the user's config so that unqualified names not in the docker
+ // domain can be dropped.
+ resOptions := sb.resolver.ResolverOptions()
+
+dnsOpt:
+ for _, resOpt := range resOptions {
+ if strings.Contains(resOpt, "ndots") {
+ for _, option := range dnsOptionsList {
+ if strings.Contains(option, "ndots") {
+ parts := strings.Split(option, ":")
+ if len(parts) != 2 {
+ return fmt.Errorf("invalid ndots option %v", option)
+ }
+ if num, err := strconv.Atoi(parts[1]); err != nil {
+ return fmt.Errorf("invalid number for ndots option: %v", parts[1])
+ } else if num >= 0 {
+ // if the user sets ndots, use the user setting
+ sb.ndotsSet = true
+ break dnsOpt
+ } else {
+ return fmt.Errorf("invalid number for ndots option: %v", num)
+ }
+ }
+ }
+ }
+ }
+
+ if !sb.ndotsSet {
+ // if the user did not set the ndots, set it to 0 to prioritize the service name resolution
+ // Ref: https://linux.die.net/man/5/resolv.conf
+ dnsOptionsList = append(dnsOptionsList, resOptions...)
+ }
+
+ _, err = resolvconf.Build(sb.config.resolvConfPath, dnsList, dnsSearchList, dnsOptionsList)
+ return err
+}
+
+func createBasePath(dir string) error {
+ return os.MkdirAll(dir, dirPerm)
+}
+
+func createFile(path string) error {
+ var f *os.File
+
+ dir, _ := filepath.Split(path)
+ err := createBasePath(dir)
+ if err != nil {
+ return err
+ }
+
+ f, err = os.Create(path)
+ if err == nil {
+ f.Close()
+ }
+
+ return err
+}
+
+func copyFile(src, dst string) error {
+ sBytes, err := ioutil.ReadFile(src)
+ if err != nil {
+ return err
+ }
+ return ioutil.WriteFile(dst, sBytes, filePerm)
+}
--- /dev/null
+// +build windows
+
+package libnetwork
+
+import (
+ "github.com/docker/libnetwork/etchosts"
+)
+
+// Stub implementations for DNS related functions
+
+func (sb *sandbox) startResolver(bool) {
+}
+
+func (sb *sandbox) setupResolutionFiles() error {
+ return nil
+}
+
+func (sb *sandbox) restorePath() {
+}
+
+func (sb *sandbox) updateHostsFile(ifaceIP string) error {
+ return nil
+}
+
+func (sb *sandbox) addHostsEntries(recs []etchosts.Record) {
+
+}
+
+func (sb *sandbox) deleteHostsEntries(recs []etchosts.Record) {
+
+}
+
+func (sb *sandbox) updateDNS(ipv6Enabled bool) error {
+ return nil
+}
--- /dev/null
+package libnetwork
+
+import "github.com/docker/docker/pkg/reexec"
+
+type setKeyData struct {
+ ContainerID string
+ Key string
+}
+
+func init() {
+ reexec.Register("libnetwork-setkey", processSetKeyReexec)
+}
--- /dev/null
+// +build linux freebsd
+
+package libnetwork
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "os"
+
+ "github.com/docker/libnetwork/types"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/sirupsen/logrus"
+)
+
+const udsBase = "/run/docker/libnetwork/"
+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] = <container-id>, [2] = <controller-id> }
+// It also expects specs.State as a json string in <stdin>
+// 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]=<container-id>, [2]=<controller-id> }
+ 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 specs.State as a json string in <stdin>
+ stateBuf, err := ioutil.ReadAll(os.Stdin)
+ if err != nil {
+ return
+ }
+ var state specs.State
+ if err = json.Unmarshal(stateBuf, &state); err != nil {
+ return
+ }
+
+ controllerID := os.Args[2]
+
+ err = SetExternalKey(controllerID, containerID, fmt.Sprintf("/proc/%d/ns/net", state.Pid))
+}
+
+// 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 doesn't exist. cannot accept client connections", sock)
+ return
+ }
+ logrus.Errorf("Error accepting connection %v", err)
+ continue
+ }
+ go func() {
+ defer conn.Close()
+
+ 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()
+}
--- /dev/null
+// +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] = <container-id>, [2] = <controller-id> }
+// It also expects configs.HookState as a json string in <stdin>
+// 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() {
+}
--- /dev/null
+package libnetwork
+
+import (
+ "encoding/json"
+ "sync"
+
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/osl"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ sandboxPrefix = "sandbox"
+)
+
+type epState struct {
+ Eid string
+ Nid string
+}
+
+type sbState struct {
+ ID string
+ Cid string
+ c *controller
+ dbIndex uint64
+ dbExists bool
+ Eps []epState
+ EpPriority map[string]int
+ // external servers have to be persisted so that on restart of a live-restore
+ // enabled daemon we get the external servers for the running containers.
+ // We have two versions of ExtDNS to support upgrade & downgrade of the daemon
+ // between >=1.14 and <1.14 versions.
+ ExtDNS []string
+ ExtDNS2 []extDNSEntry
+}
+
+func (sbs *sbState) Key() []string {
+ return []string{sandboxPrefix, sbs.ID}
+}
+
+func (sbs *sbState) KeyPrefix() []string {
+ return []string{sandboxPrefix}
+}
+
+func (sbs *sbState) Value() []byte {
+ b, err := json.Marshal(sbs)
+ if err != nil {
+ return nil
+ }
+ return b
+}
+
+func (sbs *sbState) SetValue(value []byte) error {
+ return json.Unmarshal(value, sbs)
+}
+
+func (sbs *sbState) Index() uint64 {
+ sbi, err := sbs.c.SandboxByID(sbs.ID)
+ if err != nil {
+ return sbs.dbIndex
+ }
+
+ sb := sbi.(*sandbox)
+ maxIndex := sb.dbIndex
+ if sbs.dbIndex > maxIndex {
+ maxIndex = sbs.dbIndex
+ }
+
+ return maxIndex
+}
+
+func (sbs *sbState) SetIndex(index uint64) {
+ sbs.dbIndex = index
+ sbs.dbExists = true
+
+ sbi, err := sbs.c.SandboxByID(sbs.ID)
+ if err != nil {
+ return
+ }
+
+ sb := sbi.(*sandbox)
+ sb.dbIndex = index
+ sb.dbExists = true
+}
+
+func (sbs *sbState) Exists() bool {
+ if sbs.dbExists {
+ return sbs.dbExists
+ }
+
+ sbi, err := sbs.c.SandboxByID(sbs.ID)
+ if err != nil {
+ return false
+ }
+
+ sb := sbi.(*sandbox)
+ return sb.dbExists
+}
+
+func (sbs *sbState) Skip() bool {
+ return false
+}
+
+func (sbs *sbState) New() datastore.KVObject {
+ return &sbState{c: sbs.c}
+}
+
+func (sbs *sbState) CopyTo(o datastore.KVObject) error {
+ dstSbs := o.(*sbState)
+ dstSbs.c = sbs.c
+ dstSbs.ID = sbs.ID
+ dstSbs.Cid = sbs.Cid
+ dstSbs.dbIndex = sbs.dbIndex
+ dstSbs.dbExists = sbs.dbExists
+ dstSbs.EpPriority = sbs.EpPriority
+
+ dstSbs.Eps = append(dstSbs.Eps, sbs.Eps...)
+
+ if len(sbs.ExtDNS2) > 0 {
+ for _, dns := range sbs.ExtDNS2 {
+ dstSbs.ExtDNS2 = append(dstSbs.ExtDNS2, dns)
+ dstSbs.ExtDNS = append(dstSbs.ExtDNS, dns.IPStr)
+ }
+ return nil
+ }
+ for _, dns := range sbs.ExtDNS {
+ dstSbs.ExtDNS = append(dstSbs.ExtDNS, dns)
+ dstSbs.ExtDNS2 = append(dstSbs.ExtDNS2, extDNSEntry{IPStr: dns})
+ }
+
+ return nil
+}
+
+func (sbs *sbState) DataScope() string {
+ return datastore.LocalScope
+}
+
+func (sb *sandbox) storeUpdate() error {
+ sbs := &sbState{
+ c: sb.controller,
+ ID: sb.id,
+ Cid: sb.containerID,
+ EpPriority: sb.epPriority,
+ ExtDNS2: sb.extDNS,
+ }
+
+ for _, ext := range sb.extDNS {
+ sbs.ExtDNS = append(sbs.ExtDNS, ext.IPStr)
+ }
+
+retry:
+ sbs.Eps = nil
+ for _, ep := range sb.getConnectedEndpoints() {
+ // If the endpoint is not persisted then do not add it to
+ // the sandbox checkpoint
+ if ep.Skip() {
+ continue
+ }
+
+ eps := epState{
+ Nid: ep.getNetwork().ID(),
+ Eid: ep.ID(),
+ }
+
+ sbs.Eps = append(sbs.Eps, eps)
+ }
+
+ err := sb.controller.updateToStore(sbs)
+ if err == datastore.ErrKeyModified {
+ // When we get ErrKeyModified it is sufficient to just
+ // go back and retry. No need to get the object from
+ // the store because we always regenerate the store
+ // state from in memory sandbox state
+ goto retry
+ }
+
+ return err
+}
+
+func (sb *sandbox) storeDelete() error {
+ sbs := &sbState{
+ c: sb.controller,
+ ID: sb.id,
+ Cid: sb.containerID,
+ dbIndex: sb.dbIndex,
+ dbExists: sb.dbExists,
+ }
+
+ return sb.controller.deleteFromStore(sbs)
+}
+
+func (c *controller) sandboxCleanup(activeSandboxes map[string]interface{}) {
+ store := c.getStore(datastore.LocalScope)
+ if store == nil {
+ logrus.Error("Could not find local scope store while trying to cleanup sandboxes")
+ return
+ }
+
+ kvol, err := store.List(datastore.Key(sandboxPrefix), &sbState{c: c})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ logrus.Errorf("failed to get sandboxes for scope %s: %v", store.Scope(), err)
+ return
+ }
+
+ // It's normal for no sandboxes to be found. Just bail out.
+ if err == datastore.ErrKeyNotFound {
+ return
+ }
+
+ for _, kvo := range kvol {
+ sbs := kvo.(*sbState)
+
+ sb := &sandbox{
+ id: sbs.ID,
+ controller: sbs.c,
+ containerID: sbs.Cid,
+ endpoints: []*endpoint{},
+ populatedEndpoints: map[string]struct{}{},
+ dbIndex: sbs.dbIndex,
+ isStub: true,
+ dbExists: true,
+ }
+ // If we are restoring from a older version extDNSEntry won't have the
+ // HostLoopback field
+ if len(sbs.ExtDNS2) > 0 {
+ sb.extDNS = sbs.ExtDNS2
+ } else {
+ for _, dns := range sbs.ExtDNS {
+ sb.extDNS = append(sb.extDNS, extDNSEntry{IPStr: dns})
+ }
+ }
+
+ msg := " for cleanup"
+ create := true
+ isRestore := false
+ if val, ok := activeSandboxes[sb.ID()]; ok {
+ msg = ""
+ sb.isStub = false
+ isRestore = true
+ opts := val.([]SandboxOption)
+ sb.processOptions(opts...)
+ sb.restorePath()
+ create = !sb.config.useDefaultSandBox
+ }
+ sb.osSbox, err = osl.NewSandbox(sb.Key(), create, isRestore)
+ if err != nil {
+ logrus.Errorf("failed to create osl sandbox while trying to restore sandbox %.7s%s: %v", sb.ID(), msg, err)
+ continue
+ }
+
+ c.Lock()
+ c.sandboxes[sb.id] = sb
+ c.Unlock()
+
+ for _, eps := range sbs.Eps {
+ n, err := c.getNetworkFromStore(eps.Nid)
+ var ep *endpoint
+ if err != nil {
+ logrus.Errorf("getNetworkFromStore for nid %s failed while trying to build sandbox for cleanup: %v", eps.Nid, err)
+ n = &network{id: eps.Nid, ctrlr: c, drvOnce: &sync.Once{}, persist: true}
+ ep = &endpoint{id: eps.Eid, network: n, sandboxID: sbs.ID}
+ } else {
+ ep, err = n.getEndpointFromStore(eps.Eid)
+ if err != nil {
+ logrus.Errorf("getEndpointFromStore for eid %s failed while trying to build sandbox for cleanup: %v", eps.Eid, err)
+ ep = &endpoint{id: eps.Eid, network: n, sandboxID: sbs.ID}
+ }
+ }
+ if _, ok := activeSandboxes[sb.ID()]; ok && err != nil {
+ logrus.Errorf("failed to restore endpoint %s in %s for container %s due to %v", eps.Eid, eps.Nid, sb.ContainerID(), err)
+ continue
+ }
+ sb.addEndpoint(ep)
+ }
+
+ if _, ok := activeSandboxes[sb.ID()]; !ok {
+ logrus.Infof("Removing stale sandbox %s (%s)", sb.id, sb.containerID)
+ if err := sb.delete(true); err != nil {
+ logrus.Errorf("Failed to delete sandbox %s while trying to cleanup: %v", sb.id, err)
+ }
+ continue
+ }
+
+ // reconstruct osl sandbox field
+ if !sb.config.useDefaultSandBox {
+ if err := sb.restoreOslSandbox(); err != nil {
+ logrus.Errorf("failed to populate fields for osl sandbox %s", sb.ID())
+ continue
+ }
+ } else {
+ c.sboxOnce.Do(func() {
+ c.defOsSbox = sb.osSbox
+ })
+ }
+
+ for _, ep := range sb.endpoints {
+ // Watch for service records
+ if !c.isAgent() {
+ c.watchSvcRecord(ep)
+ }
+ }
+ }
+}
--- /dev/null
+package libnetwork
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/docker/libnetwork/config"
+ "github.com/docker/libnetwork/ipamapi"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/options"
+ "github.com/docker/libnetwork/osl"
+ "github.com/docker/libnetwork/testutils"
+)
+
+func getTestEnv(t *testing.T, opts ...[]NetworkOption) (NetworkController, []Network) {
+ netType := "bridge"
+
+ option := options.Generic{
+ "EnableIPForwarding": true,
+ }
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = option
+
+ cfgOptions, err := OptionBoltdbWithRandomDBFile()
+ if err != nil {
+ t.Fatal(err)
+ }
+ c, err := New(append(cfgOptions, config.OptionDriverConfig(netType, genericOption))...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(opts) == 0 {
+ return c, nil
+ }
+
+ nwList := make([]Network, 0, len(opts))
+ for i, opt := range opts {
+ name := fmt.Sprintf("test_nw_%d", i)
+ netOption := options.Generic{
+ netlabel.GenericData: options.Generic{
+ "BridgeName": name,
+ },
+ }
+ newOptions := make([]NetworkOption, 1, len(opt)+1)
+ newOptions[0] = NetworkOptionGeneric(netOption)
+ newOptions = append(newOptions, opt...)
+ n, err := c.NewNetwork(netType, name, "", newOptions...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ nwList = append(nwList, n)
+ }
+
+ return c, nwList
+}
+
+func TestSandboxAddEmpty(t *testing.T) {
+ c, _ := getTestEnv(t)
+ ctrlr := c.(*controller)
+
+ sbx, err := ctrlr.NewSandbox("sandbox0")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := sbx.Delete(); err != nil {
+ t.Fatal(err)
+ }
+
+ if len(ctrlr.sandboxes) != 0 {
+ t.Fatalf("controller sandboxes is not empty. len = %d", len(ctrlr.sandboxes))
+ }
+
+ osl.GC()
+}
+
+// // If different priorities are specified, internal option and ipv6 addresses mustn't influence endpoint order
+func TestSandboxAddMultiPrio(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ opts := [][]NetworkOption{
+ {NetworkOptionEnableIPv6(true), NetworkOptionIpam(ipamapi.DefaultIPAM, "", nil, []*IpamConf{{PreferredPool: "fe90::/64"}}, nil)},
+ {NetworkOptionInternalNetwork()},
+ {},
+ }
+
+ c, nws := getTestEnv(t, opts...)
+ ctrlr := c.(*controller)
+
+ sbx, err := ctrlr.NewSandbox("sandbox1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ sid := sbx.ID()
+
+ ep1, err := nws[0].CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ ep2, err := nws[1].CreateEndpoint("ep2")
+ if err != nil {
+ t.Fatal(err)
+ }
+ ep3, err := nws[2].CreateEndpoint("ep3")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep1.Join(sbx, JoinOptionPriority(ep1, 1)); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep2.Join(sbx, JoinOptionPriority(ep2, 2)); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ep3.Join(sbx, JoinOptionPriority(ep3, 3)); err != nil {
+ t.Fatal(err)
+ }
+
+ if ctrlr.sandboxes[sid].endpoints[0].ID() != ep3.ID() {
+ t.Fatal("Expected ep3 to be at the top of the heap. But did not find ep3 at the top of the heap")
+ }
+
+ if len(sbx.Endpoints()) != 3 {
+ t.Fatal("Expected 3 endpoints to be connected to the sandbox.")
+ }
+
+ if err := ep3.Leave(sbx); err != nil {
+ t.Fatal(err)
+ }
+ if ctrlr.sandboxes[sid].endpoints[0].ID() != ep2.ID() {
+ t.Fatal("Expected ep2 to be at the top of the heap after removing ep3. But did not find ep2 at the top of the heap")
+ }
+
+ if err := ep2.Leave(sbx); err != nil {
+ t.Fatal(err)
+ }
+ if ctrlr.sandboxes[sid].endpoints[0].ID() != ep1.ID() {
+ t.Fatal("Expected ep1 to be at the top of the heap after removing ep2. But did not find ep1 at the top of the heap")
+ }
+
+ // Re-add ep3 back
+ if err := ep3.Join(sbx, JoinOptionPriority(ep3, 3)); err != nil {
+ t.Fatal(err)
+ }
+
+ if ctrlr.sandboxes[sid].endpoints[0].ID() != ep3.ID() {
+ t.Fatal("Expected ep3 to be at the top of the heap after adding ep3 back. But did not find ep3 at the top of the heap")
+ }
+
+ if err := sbx.Delete(); err != nil {
+ t.Fatal(err)
+ }
+
+ if len(ctrlr.sandboxes) != 0 {
+ t.Fatalf("controller sandboxes is not empty. len = %d", len(ctrlr.sandboxes))
+ }
+
+ osl.GC()
+}
+
+func TestSandboxAddSamePrio(t *testing.T) {
+ if !testutils.IsRunningInContainer() {
+ defer testutils.SetupTestOSContext(t)()
+ }
+
+ opts := [][]NetworkOption{
+ {},
+ {},
+ {NetworkOptionEnableIPv6(true), NetworkOptionIpam(ipamapi.DefaultIPAM, "", nil, []*IpamConf{{PreferredPool: "fe90::/64"}}, nil)},
+ {NetworkOptionInternalNetwork()},
+ }
+
+ c, nws := getTestEnv(t, opts...)
+
+ ctrlr := c.(*controller)
+
+ sbx, err := ctrlr.NewSandbox("sandbox1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ sid := sbx.ID()
+
+ epNw1, err := nws[1].CreateEndpoint("ep1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ epIPv6, err := nws[2].CreateEndpoint("ep2")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ epInternal, err := nws[3].CreateEndpoint("ep3")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ epNw0, err := nws[0].CreateEndpoint("ep4")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := epNw1.Join(sbx); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := epIPv6.Join(sbx); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := epInternal.Join(sbx); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := epNw0.Join(sbx); err != nil {
+ t.Fatal(err)
+ }
+
+ // order should now be: epIPv6, epNw0, epNw1, epInternal
+ if len(sbx.Endpoints()) != 4 {
+ t.Fatal("Expected 4 endpoints to be connected to the sandbox.")
+ }
+
+ // IPv6 has precedence over IPv4
+ if ctrlr.sandboxes[sid].endpoints[0].ID() != epIPv6.ID() {
+ t.Fatal("Expected epIPv6 to be at the top of the heap. But did not find epIPv6 at the top of the heap")
+ }
+
+ // internal network has lowest precedence
+ if ctrlr.sandboxes[sid].endpoints[3].ID() != epInternal.ID() {
+ t.Fatal("Expected epInternal to be at the bottom of the heap. But did not find epInternal at the bottom of the heap")
+ }
+
+ if err := epIPv6.Leave(sbx); err != nil {
+ t.Fatal(err)
+ }
+
+ // 'test_nw_0' has precedence over 'test_nw_1'
+ if ctrlr.sandboxes[sid].endpoints[0].ID() != epNw0.ID() {
+ t.Fatal("Expected epNw0 to be at the top of the heap after removing epIPv6. But did not find epNw0 at the top of the heap")
+ }
+
+ if err := epNw1.Leave(sbx); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := sbx.Delete(); err != nil {
+ t.Fatal(err)
+ }
+
+ if len(ctrlr.sandboxes) != 0 {
+ t.Fatalf("controller containers is not empty. len = %d", len(ctrlr.sandboxes))
+ }
+
+ osl.GC()
+}
--- /dev/null
+package libnetwork
+
+import (
+ "fmt"
+ "net"
+ "sync"
+
+ "github.com/docker/libnetwork/internal/setmatrix"
+)
+
+var (
+ // A global monotonic counter to assign firewall marks to
+ // services.
+ fwMarkCtr uint32 = 256
+ fwMarkCtrMu sync.Mutex
+)
+
+type portConfigs []*PortConfig
+
+func (p portConfigs) String() string {
+ if len(p) == 0 {
+ return ""
+ }
+
+ pc := p[0]
+ str := fmt.Sprintf("%d:%d/%s", pc.PublishedPort, pc.TargetPort, PortConfig_Protocol_name[int32(pc.Protocol)])
+ for _, pc := range p[1:] {
+ str = str + fmt.Sprintf(",%d:%d/%s", pc.PublishedPort, pc.TargetPort, PortConfig_Protocol_name[int32(pc.Protocol)])
+ }
+
+ return str
+}
+
+type serviceKey struct {
+ id string
+ ports string
+}
+
+type service struct {
+ name string // Service Name
+ id string // Service ID
+
+ // Map of loadbalancers for the service one-per attached
+ // network. It is keyed with network ID.
+ loadBalancers map[string]*loadBalancer
+
+ // List of ingress ports exposed by the service
+ ingressPorts portConfigs
+
+ // Service aliases
+ aliases []string
+
+ // This maps tracks for each IP address the list of endpoints ID
+ // associated with it. At stable state the endpoint ID expected is 1
+ // but during transition and service change it is possible to have
+ // temporary more than 1
+ ipToEndpoint setmatrix.SetMatrix
+
+ deleted bool
+
+ sync.Mutex
+}
+
+// assignIPToEndpoint inserts the mapping between the IP and the endpoint identifier
+// returns true if the mapping was not present, false otherwise
+// returns also the number of endpoints associated to the IP
+func (s *service) assignIPToEndpoint(ip, eID string) (bool, int) {
+ return s.ipToEndpoint.Insert(ip, eID)
+}
+
+// removeIPToEndpoint removes the mapping between the IP and the endpoint identifier
+// returns true if the mapping was deleted, false otherwise
+// returns also the number of endpoints associated to the IP
+func (s *service) removeIPToEndpoint(ip, eID string) (bool, int) {
+ return s.ipToEndpoint.Remove(ip, eID)
+}
+
+func (s *service) printIPToEndpoint(ip string) (string, bool) {
+ return s.ipToEndpoint.String(ip)
+}
+
+type lbBackend struct {
+ ip net.IP
+ disabled bool
+}
+
+type loadBalancer struct {
+ vip net.IP
+ fwMark uint32
+
+ // Map of backend IPs backing this loadbalancer on this
+ // network. It is keyed with endpoint ID.
+ backEnds map[string]*lbBackend
+
+ // Back pointer to service to which the loadbalancer belongs.
+ service *service
+ sync.Mutex
+}
--- /dev/null
+// +build linux windows
+
+package libnetwork
+
+import (
+ "net"
+
+ "github.com/docker/libnetwork/internal/setmatrix"
+ "github.com/sirupsen/logrus"
+)
+
+const maxSetStringLen = 350
+
+func (c *controller) addEndpointNameResolution(svcName, svcID, nID, eID, containerName string, vip net.IP, serviceAliases, taskAliases []string, ip net.IP, addService bool, method string) error {
+ n, err := c.NetworkByID(nID)
+ if err != nil {
+ return err
+ }
+
+ logrus.Debugf("addEndpointNameResolution %s %s add_service:%t sAliases:%v tAliases:%v", eID, svcName, addService, serviceAliases, taskAliases)
+
+ // Add container resolution mappings
+ c.addContainerNameResolution(nID, eID, containerName, taskAliases, ip, method)
+
+ serviceID := svcID
+ if serviceID == "" {
+ // This is the case of a normal container not part of a service
+ serviceID = eID
+ }
+
+ // Add endpoint IP to special "tasks.svc_name" so that the applications have access to DNS RR.
+ n.(*network).addSvcRecords(eID, "tasks."+svcName, serviceID, ip, nil, false, method)
+ for _, alias := range serviceAliases {
+ n.(*network).addSvcRecords(eID, "tasks."+alias, serviceID, ip, nil, false, method)
+ }
+
+ // Add service name to vip in DNS, if vip is valid. Otherwise resort to DNS RR
+ if len(vip) == 0 {
+ n.(*network).addSvcRecords(eID, svcName, serviceID, ip, nil, false, method)
+ for _, alias := range serviceAliases {
+ n.(*network).addSvcRecords(eID, alias, serviceID, ip, nil, false, method)
+ }
+ }
+
+ if addService && len(vip) != 0 {
+ n.(*network).addSvcRecords(eID, svcName, serviceID, vip, nil, false, method)
+ for _, alias := range serviceAliases {
+ n.(*network).addSvcRecords(eID, alias, serviceID, vip, nil, false, method)
+ }
+ }
+
+ return nil
+}
+
+func (c *controller) addContainerNameResolution(nID, eID, containerName string, taskAliases []string, ip net.IP, method string) error {
+ n, err := c.NetworkByID(nID)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("addContainerNameResolution %s %s", eID, containerName)
+
+ // Add resolution for container name
+ n.(*network).addSvcRecords(eID, containerName, eID, ip, nil, true, method)
+
+ // Add resolution for taskaliases
+ for _, alias := range taskAliases {
+ n.(*network).addSvcRecords(eID, alias, eID, ip, nil, true, method)
+ }
+
+ return nil
+}
+
+func (c *controller) deleteEndpointNameResolution(svcName, svcID, nID, eID, containerName string, vip net.IP, serviceAliases, taskAliases []string, ip net.IP, rmService, multipleEntries bool, method string) error {
+ n, err := c.NetworkByID(nID)
+ if err != nil {
+ return err
+ }
+
+ logrus.Debugf("deleteEndpointNameResolution %s %s rm_service:%t suppress:%t sAliases:%v tAliases:%v", eID, svcName, rmService, multipleEntries, serviceAliases, taskAliases)
+
+ // Delete container resolution mappings
+ c.delContainerNameResolution(nID, eID, containerName, taskAliases, ip, method)
+
+ serviceID := svcID
+ if serviceID == "" {
+ // This is the case of a normal container not part of a service
+ serviceID = eID
+ }
+
+ // Delete the special "tasks.svc_name" backend record.
+ if !multipleEntries {
+ n.(*network).deleteSvcRecords(eID, "tasks."+svcName, serviceID, ip, nil, false, method)
+ for _, alias := range serviceAliases {
+ n.(*network).deleteSvcRecords(eID, "tasks."+alias, serviceID, ip, nil, false, method)
+ }
+ }
+
+ // If we are doing DNS RR delete the endpoint IP from DNS record right away.
+ if !multipleEntries && len(vip) == 0 {
+ n.(*network).deleteSvcRecords(eID, svcName, serviceID, ip, nil, false, method)
+ for _, alias := range serviceAliases {
+ n.(*network).deleteSvcRecords(eID, alias, serviceID, ip, nil, false, method)
+ }
+ }
+
+ // Remove the DNS record for VIP only if we are removing the service
+ if rmService && len(vip) != 0 && !multipleEntries {
+ n.(*network).deleteSvcRecords(eID, svcName, serviceID, vip, nil, false, method)
+ for _, alias := range serviceAliases {
+ n.(*network).deleteSvcRecords(eID, alias, serviceID, vip, nil, false, method)
+ }
+ }
+
+ return nil
+}
+
+func (c *controller) delContainerNameResolution(nID, eID, containerName string, taskAliases []string, ip net.IP, method string) error {
+ n, err := c.NetworkByID(nID)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("delContainerNameResolution %s %s", eID, containerName)
+
+ // Delete resolution for container name
+ n.(*network).deleteSvcRecords(eID, containerName, eID, ip, nil, true, method)
+
+ // Delete resolution for taskaliases
+ for _, alias := range taskAliases {
+ n.(*network).deleteSvcRecords(eID, alias, eID, ip, nil, true, method)
+ }
+
+ return nil
+}
+
+func newService(name string, id string, ingressPorts []*PortConfig, serviceAliases []string) *service {
+ return &service{
+ name: name,
+ id: id,
+ ingressPorts: ingressPorts,
+ loadBalancers: make(map[string]*loadBalancer),
+ aliases: serviceAliases,
+ ipToEndpoint: setmatrix.NewSetMatrix(),
+ }
+}
+
+func (c *controller) getLBIndex(sid, nid string, ingressPorts []*PortConfig) int {
+ skey := serviceKey{
+ id: sid,
+ ports: portConfigs(ingressPorts).String(),
+ }
+ c.Lock()
+ s, ok := c.serviceBindings[skey]
+ c.Unlock()
+
+ if !ok {
+ return 0
+ }
+
+ s.Lock()
+ lb := s.loadBalancers[nid]
+ s.Unlock()
+
+ return int(lb.fwMark)
+}
+
+// cleanupServiceDiscovery when the network is being deleted, erase all the associated service discovery records
+func (c *controller) cleanupServiceDiscovery(cleanupNID string) {
+ c.Lock()
+ defer c.Unlock()
+ if cleanupNID == "" {
+ logrus.Debugf("cleanupServiceDiscovery for all networks")
+ c.svcRecords = make(map[string]svcInfo)
+ return
+ }
+ logrus.Debugf("cleanupServiceDiscovery for network:%s", cleanupNID)
+ delete(c.svcRecords, cleanupNID)
+}
+
+func (c *controller) cleanupServiceBindings(cleanupNID string) {
+ var cleanupFuncs []func()
+
+ logrus.Debugf("cleanupServiceBindings for %s", cleanupNID)
+ c.Lock()
+ services := make([]*service, 0, len(c.serviceBindings))
+ for _, s := range c.serviceBindings {
+ services = append(services, s)
+ }
+ c.Unlock()
+
+ for _, s := range services {
+ s.Lock()
+ // Skip the serviceBindings that got deleted
+ if s.deleted {
+ s.Unlock()
+ continue
+ }
+ for nid, lb := range s.loadBalancers {
+ if cleanupNID != "" && nid != cleanupNID {
+ continue
+ }
+ for eid, be := range lb.backEnds {
+ cleanupFuncs = append(cleanupFuncs, makeServiceCleanupFunc(c, s, nid, eid, lb.vip, be.ip))
+ }
+ }
+ s.Unlock()
+ }
+
+ for _, f := range cleanupFuncs {
+ f()
+ }
+
+}
+
+func makeServiceCleanupFunc(c *controller, s *service, nID, eID string, vip net.IP, ip net.IP) func() {
+ // ContainerName and taskAliases are not available here, this is still fine because the Service discovery
+ // cleanup already happened before. The only thing that rmServiceBinding is still doing here a part from the Load
+ // Balancer bookeeping, is to keep consistent the mapping of endpoint to IP.
+ return func() {
+ if err := c.rmServiceBinding(s.name, s.id, nID, eID, "", vip, s.ingressPorts, s.aliases, []string{}, ip, "cleanupServiceBindings", false, true); err != nil {
+ logrus.Errorf("Failed to remove service bindings for service %s network %s endpoint %s while cleanup: %v", s.id, nID, eID, err)
+ }
+ }
+}
+
+func (c *controller) addServiceBinding(svcName, svcID, nID, eID, containerName string, vip net.IP, ingressPorts []*PortConfig, serviceAliases, taskAliases []string, ip net.IP, method string) error {
+ var addService bool
+
+ // Failure to lock the network ID on add can result in racing
+ // racing against network deletion resulting in inconsistent
+ // state in the c.serviceBindings map and it's sub-maps. Also,
+ // always lock network ID before services to avoid deadlock.
+ c.networkLocker.Lock(nID)
+ defer c.networkLocker.Unlock(nID)
+
+ n, err := c.NetworkByID(nID)
+ if err != nil {
+ return err
+ }
+
+ skey := serviceKey{
+ id: svcID,
+ ports: portConfigs(ingressPorts).String(),
+ }
+
+ var s *service
+ for {
+ c.Lock()
+ var ok bool
+ s, ok = c.serviceBindings[skey]
+ if !ok {
+ // Create a new service if we are seeing this service
+ // for the first time.
+ s = newService(svcName, svcID, ingressPorts, serviceAliases)
+ c.serviceBindings[skey] = s
+ }
+ c.Unlock()
+ s.Lock()
+ if !s.deleted {
+ // ok the object is good to be used
+ break
+ }
+ s.Unlock()
+ }
+ logrus.Debugf("addServiceBinding from %s START for %s %s p:%p nid:%s skey:%v", method, svcName, eID, s, nID, skey)
+ defer s.Unlock()
+
+ lb, ok := s.loadBalancers[nID]
+ if !ok {
+ // Create a new load balancer if we are seeing this
+ // network attachment on the service for the first
+ // time.
+ fwMarkCtrMu.Lock()
+
+ lb = &loadBalancer{
+ vip: vip,
+ fwMark: fwMarkCtr,
+ backEnds: make(map[string]*lbBackend),
+ service: s,
+ }
+
+ fwMarkCtr++
+ fwMarkCtrMu.Unlock()
+
+ s.loadBalancers[nID] = lb
+ addService = true
+ }
+
+ lb.backEnds[eID] = &lbBackend{ip, false}
+
+ ok, entries := s.assignIPToEndpoint(ip.String(), eID)
+ if !ok || entries > 1 {
+ setStr, b := s.printIPToEndpoint(ip.String())
+ if len(setStr) > maxSetStringLen {
+ setStr = setStr[:maxSetStringLen]
+ }
+ logrus.Warnf("addServiceBinding %s possible transient state ok:%t entries:%d set:%t %s", eID, ok, entries, b, setStr)
+ }
+
+ // Add loadbalancer service and backend to the network
+ n.(*network).addLBBackend(ip, lb)
+
+ // Add the appropriate name resolutions
+ c.addEndpointNameResolution(svcName, svcID, nID, eID, containerName, vip, serviceAliases, taskAliases, ip, addService, "addServiceBinding")
+
+ logrus.Debugf("addServiceBinding from %s END for %s %s", method, svcName, eID)
+
+ return nil
+}
+
+func (c *controller) rmServiceBinding(svcName, svcID, nID, eID, containerName string, vip net.IP, ingressPorts []*PortConfig, serviceAliases []string, taskAliases []string, ip net.IP, method string, deleteSvcRecords bool, fullRemove bool) error {
+
+ var rmService bool
+
+ skey := serviceKey{
+ id: svcID,
+ ports: portConfigs(ingressPorts).String(),
+ }
+
+ c.Lock()
+ s, ok := c.serviceBindings[skey]
+ c.Unlock()
+ if !ok {
+ logrus.Warnf("rmServiceBinding %s %s %s aborted c.serviceBindings[skey] !ok", method, svcName, eID)
+ return nil
+ }
+
+ s.Lock()
+ defer s.Unlock()
+ logrus.Debugf("rmServiceBinding from %s START for %s %s p:%p nid:%s sKey:%v deleteSvc:%t", method, svcName, eID, s, nID, skey, deleteSvcRecords)
+ lb, ok := s.loadBalancers[nID]
+ if !ok {
+ logrus.Warnf("rmServiceBinding %s %s %s aborted s.loadBalancers[nid] !ok", method, svcName, eID)
+ return nil
+ }
+
+ be, ok := lb.backEnds[eID]
+ if !ok {
+ logrus.Warnf("rmServiceBinding %s %s %s aborted lb.backEnds[eid] && lb.disabled[eid] !ok", method, svcName, eID)
+ return nil
+ }
+
+ if fullRemove {
+ // delete regardless
+ delete(lb.backEnds, eID)
+ } else {
+ be.disabled = true
+ }
+
+ if len(lb.backEnds) == 0 {
+ // All the backends for this service have been
+ // removed. Time to remove the load balancer and also
+ // remove the service entry in IPVS.
+ rmService = true
+
+ delete(s.loadBalancers, nID)
+ logrus.Debugf("rmServiceBinding %s delete %s, p:%p in loadbalancers len:%d", eID, nID, lb, len(s.loadBalancers))
+ }
+
+ ok, entries := s.removeIPToEndpoint(ip.String(), eID)
+ if !ok || entries > 0 {
+ setStr, b := s.printIPToEndpoint(ip.String())
+ if len(setStr) > maxSetStringLen {
+ setStr = setStr[:maxSetStringLen]
+ }
+ logrus.Warnf("rmServiceBinding %s possible transient state ok:%t entries:%d set:%t %s", eID, ok, entries, b, setStr)
+ }
+
+ // Remove loadbalancer service(if needed) and backend in all
+ // sandboxes in the network only if the vip is valid.
+ if entries == 0 {
+ // The network may well have been deleted before the last
+ // of the service bindings. That's ok, because removing
+ // the network sandbox implicitly removes the backend
+ // service bindings.
+ n, err := c.NetworkByID(nID)
+ if err == nil {
+ n.(*network).rmLBBackend(ip, lb, rmService, fullRemove)
+ }
+ }
+
+ // Delete the name resolutions
+ if deleteSvcRecords {
+ c.deleteEndpointNameResolution(svcName, svcID, nID, eID, containerName, vip, serviceAliases, taskAliases, ip, rmService, entries > 0, "rmServiceBinding")
+ }
+
+ if len(s.loadBalancers) == 0 {
+ // All loadbalancers for the service removed. Time to
+ // remove the service itself.
+ c.Lock()
+
+ // Mark the object as deleted so that the add won't use it wrongly
+ s.deleted = true
+ // NOTE The delete from the serviceBindings map has to be the last operation else we are allowing a race between this service
+ // that is getting deleted and a new service that will be created if the entry is not anymore there
+ delete(c.serviceBindings, skey)
+ c.Unlock()
+ }
+
+ logrus.Debugf("rmServiceBinding from %s END for %s %s", method, svcName, eID)
+ return nil
+}
--- /dev/null
+package libnetwork
+
+import (
+ "net"
+ "testing"
+
+ "github.com/docker/libnetwork/resolvconf"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+)
+
+func TestCleanupServiceDiscovery(t *testing.T) {
+ c, err := New()
+ assert.NilError(t, err)
+ defer c.Stop()
+
+ n1, err := c.NewNetwork("bridge", "net1", "", nil)
+ assert.NilError(t, err)
+ defer n1.Delete()
+
+ n2, err := c.NewNetwork("bridge", "net2", "", nil)
+ assert.NilError(t, err)
+ defer n2.Delete()
+
+ n1.(*network).addSvcRecords("N1ep1", "service_test", "serviceID1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
+ n1.(*network).addSvcRecords("N2ep2", "service_test", "serviceID2", net.ParseIP("192.168.0.2"), net.IP{}, true, "test")
+
+ n2.(*network).addSvcRecords("N2ep1", "service_test", "serviceID1", net.ParseIP("192.168.1.1"), net.IP{}, true, "test")
+ n2.(*network).addSvcRecords("N2ep2", "service_test", "serviceID2", net.ParseIP("192.168.1.2"), net.IP{}, true, "test")
+
+ if len(c.(*controller).svcRecords) != 2 {
+ t.Fatalf("Service record not added correctly:%v", c.(*controller).svcRecords)
+ }
+
+ // cleanup net1
+ c.(*controller).cleanupServiceDiscovery(n1.ID())
+
+ if len(c.(*controller).svcRecords) != 1 {
+ t.Fatalf("Service record not cleaned correctly:%v", c.(*controller).svcRecords)
+ }
+
+ c.(*controller).cleanupServiceDiscovery("")
+
+ if len(c.(*controller).svcRecords) != 0 {
+ t.Fatalf("Service record not cleaned correctly:%v", c.(*controller).svcRecords)
+ }
+}
+
+func TestDNSOptions(t *testing.T) {
+ c, err := New()
+ assert.NilError(t, err)
+
+ sb, err := c.(*controller).NewSandbox("cnt1", nil)
+ assert.NilError(t, err)
+ defer sb.Delete()
+ sb.(*sandbox).startResolver(false)
+
+ err = sb.(*sandbox).setupDNS()
+ assert.NilError(t, err)
+ err = sb.(*sandbox).rebuildDNS()
+ assert.NilError(t, err)
+ currRC, err := resolvconf.GetSpecific(sb.(*sandbox).config.resolvConfPath)
+ assert.NilError(t, err)
+ dnsOptionsList := resolvconf.GetOptions(currRC.Content)
+ assert.Check(t, is.Len(dnsOptionsList, 1))
+ assert.Check(t, is.Equal("ndots:0", dnsOptionsList[0]))
+
+ sb.(*sandbox).config.dnsOptionsList = []string{"ndots:5"}
+ err = sb.(*sandbox).setupDNS()
+ assert.NilError(t, err)
+ currRC, err = resolvconf.GetSpecific(sb.(*sandbox).config.resolvConfPath)
+ assert.NilError(t, err)
+ dnsOptionsList = resolvconf.GetOptions(currRC.Content)
+ assert.Check(t, is.Len(dnsOptionsList, 1))
+ assert.Check(t, is.Equal("ndots:5", dnsOptionsList[0]))
+
+ err = sb.(*sandbox).rebuildDNS()
+ assert.NilError(t, err)
+ currRC, err = resolvconf.GetSpecific(sb.(*sandbox).config.resolvConfPath)
+ assert.NilError(t, err)
+ dnsOptionsList = resolvconf.GetOptions(currRC.Content)
+ assert.Check(t, is.Len(dnsOptionsList, 1))
+ assert.Check(t, is.Equal("ndots:5", dnsOptionsList[0]))
+
+ sb2, err := c.(*controller).NewSandbox("cnt2", nil)
+ assert.NilError(t, err)
+ defer sb2.Delete()
+ sb2.(*sandbox).startResolver(false)
+
+ sb2.(*sandbox).config.dnsOptionsList = []string{"ndots:0"}
+ err = sb2.(*sandbox).setupDNS()
+ assert.NilError(t, err)
+ err = sb2.(*sandbox).rebuildDNS()
+ assert.NilError(t, err)
+ currRC, err = resolvconf.GetSpecific(sb2.(*sandbox).config.resolvConfPath)
+ assert.NilError(t, err)
+ dnsOptionsList = resolvconf.GetOptions(currRC.Content)
+ assert.Check(t, is.Len(dnsOptionsList, 1))
+ assert.Check(t, is.Equal("ndots:0", dnsOptionsList[0]))
+
+ sb2.(*sandbox).config.dnsOptionsList = []string{"ndots:foobar"}
+ err = sb2.(*sandbox).setupDNS()
+ assert.NilError(t, err)
+ err = sb2.(*sandbox).rebuildDNS()
+ assert.Error(t, err, "invalid number for ndots option: foobar")
+
+ sb2.(*sandbox).config.dnsOptionsList = []string{"ndots:-1"}
+ err = sb2.(*sandbox).setupDNS()
+ assert.NilError(t, err)
+ err = sb2.(*sandbox).rebuildDNS()
+ assert.Error(t, err, "invalid number for ndots option: -1")
+}
--- /dev/null
+package libnetwork
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/docker/libnetwork/iptables"
+ "github.com/docker/libnetwork/ipvs"
+ "github.com/docker/libnetwork/ns"
+ "github.com/gogo/protobuf/proto"
+ "github.com/ishidawataru/sctp"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink/nl"
+ "github.com/vishvananda/netns"
+)
+
+func init() {
+ reexec.Register("fwmarker", fwMarker)
+ reexec.Register("redirector", redirector)
+}
+
+// Populate all loadbalancers on the network that the passed endpoint
+// belongs to, into this sandbox.
+func (sb *sandbox) populateLoadBalancers(ep *endpoint) {
+ // This is an interface less endpoint. Nothing to do.
+ if ep.Iface() == nil {
+ return
+ }
+
+ n := ep.getNetwork()
+ eIP := ep.Iface().Address()
+
+ if n.ingress {
+ if err := addRedirectRules(sb.Key(), eIP, ep.ingressPorts); err != nil {
+ logrus.Errorf("Failed to add redirect rules for ep %s (%.7s): %v", ep.Name(), ep.ID(), err)
+ }
+ }
+}
+
+func (n *network) findLBEndpointSandbox() (*endpoint, *sandbox, error) {
+ // TODO: get endpoint from store? See EndpointInfo()
+ var ep *endpoint
+ // Find this node's LB sandbox endpoint: there should be exactly one
+ for _, e := range n.Endpoints() {
+ epi := e.Info()
+ if epi != nil && epi.LoadBalancer() {
+ ep = e.(*endpoint)
+ break
+ }
+ }
+ if ep == nil {
+ return nil, nil, fmt.Errorf("Unable to find load balancing endpoint for network %s", n.ID())
+ }
+ // Get the load balancer sandbox itself as well
+ sb, ok := ep.getSandbox()
+ if !ok {
+ return nil, nil, fmt.Errorf("Unable to get sandbox for %s(%s) in for %s", ep.Name(), ep.ID(), n.ID())
+ }
+ ep = sb.getEndpoint(ep.ID())
+ if ep == nil {
+ return nil, nil, fmt.Errorf("Load balancing endpoint %s(%s) removed from %s", ep.Name(), ep.ID(), n.ID())
+ }
+ return ep, sb, nil
+}
+
+// Searches the OS sandbox for the name of the endpoint interface
+// within the sandbox. This is required for adding/removing IP
+// aliases to the interface.
+func findIfaceDstName(sb *sandbox, ep *endpoint) string {
+ srcName := ep.Iface().SrcName()
+ for _, i := range sb.osSbox.Info().Interfaces() {
+ if i.SrcName() == srcName {
+ return i.DstName()
+ }
+ }
+ return ""
+}
+
+// Add loadbalancer backend to the loadbalncer sandbox for the network.
+// If needed add the service as well.
+func (n *network) addLBBackend(ip net.IP, lb *loadBalancer) {
+ if len(lb.vip) == 0 {
+ return
+ }
+ ep, sb, err := n.findLBEndpointSandbox()
+ if err != nil {
+ logrus.Errorf("addLBBackend %s/%s: %v", n.ID(), n.Name(), err)
+ return
+ }
+ if sb.osSbox == nil {
+ return
+ }
+
+ eIP := ep.Iface().Address()
+
+ i, err := ipvs.New(sb.Key())
+ if err != nil {
+ logrus.Errorf("Failed to create an ipvs handle for sbox %.7s (%.7s,%s) for lb addition: %v", sb.ID(), sb.ContainerID(), sb.Key(), err)
+ return
+ }
+ defer i.Close()
+
+ s := &ipvs.Service{
+ AddressFamily: nl.FAMILY_V4,
+ FWMark: lb.fwMark,
+ SchedName: ipvs.RoundRobin,
+ }
+
+ if !i.IsServicePresent(s) {
+ // Add IP alias for the VIP to the endpoint
+ ifName := findIfaceDstName(sb, ep)
+ if ifName == "" {
+ logrus.Errorf("Failed find interface name for endpoint %s(%s) to create LB alias", ep.ID(), ep.Name())
+ return
+ }
+ err := sb.osSbox.AddAliasIP(ifName, &net.IPNet{IP: lb.vip, Mask: net.CIDRMask(32, 32)})
+ if err != nil {
+ logrus.Errorf("Failed add IP alias %s to network %s LB endpoint interface %s: %v", lb.vip, n.ID(), ifName, err)
+ return
+ }
+
+ if sb.ingress {
+ var gwIP net.IP
+ if ep := sb.getGatewayEndpoint(); ep != nil {
+ gwIP = ep.Iface().Address().IP
+ }
+ if err := programIngress(gwIP, lb.service.ingressPorts, false); err != nil {
+ logrus.Errorf("Failed to add ingress: %v", err)
+ return
+ }
+ }
+
+ logrus.Debugf("Creating service for vip %s fwMark %d ingressPorts %#v in sbox %.7s (%.7s)", lb.vip, lb.fwMark, lb.service.ingressPorts, sb.ID(), sb.ContainerID())
+ if err := invokeFWMarker(sb.Key(), lb.vip, lb.fwMark, lb.service.ingressPorts, eIP, false, n.loadBalancerMode); err != nil {
+ logrus.Errorf("Failed to add firewall mark rule in sbox %.7s (%.7s): %v", sb.ID(), sb.ContainerID(), err)
+ return
+ }
+
+ if err := i.NewService(s); err != nil && err != syscall.EEXIST {
+ logrus.Errorf("Failed to create a new service for vip %s fwmark %d in sbox %.7s (%.7s): %v", lb.vip, lb.fwMark, sb.ID(), sb.ContainerID(), err)
+ return
+ }
+ }
+
+ d := &ipvs.Destination{
+ AddressFamily: nl.FAMILY_V4,
+ Address: ip,
+ Weight: 1,
+ }
+ if n.loadBalancerMode == loadBalancerModeDSR {
+ d.ConnectionFlags = ipvs.ConnFwdDirectRoute
+ }
+
+ // Remove the sched name before using the service to add
+ // destination.
+ s.SchedName = ""
+ if err := i.NewDestination(s, d); err != nil && err != syscall.EEXIST {
+ logrus.Errorf("Failed to create real server %s for vip %s fwmark %d in sbox %.7s (%.7s): %v", ip, lb.vip, lb.fwMark, sb.ID(), sb.ContainerID(), err)
+ }
+}
+
+// Remove loadbalancer backend the load balancing endpoint for this
+// network. If 'rmService' is true, then remove the service entry as well.
+// If 'fullRemove' is true then completely remove the entry, otherwise
+// just deweight it for now.
+func (n *network) rmLBBackend(ip net.IP, lb *loadBalancer, rmService bool, fullRemove bool) {
+ if len(lb.vip) == 0 {
+ return
+ }
+ ep, sb, err := n.findLBEndpointSandbox()
+ if err != nil {
+ logrus.Debugf("rmLBBackend for %s/%s: %v -- probably transient state", n.ID(), n.Name(), err)
+ return
+ }
+ if sb.osSbox == nil {
+ return
+ }
+
+ eIP := ep.Iface().Address()
+
+ i, err := ipvs.New(sb.Key())
+ if err != nil {
+ logrus.Errorf("Failed to create an ipvs handle for sbox %.7s (%.7s,%s) for lb removal: %v", sb.ID(), sb.ContainerID(), sb.Key(), err)
+ return
+ }
+ defer i.Close()
+
+ s := &ipvs.Service{
+ AddressFamily: nl.FAMILY_V4,
+ FWMark: lb.fwMark,
+ }
+
+ d := &ipvs.Destination{
+ AddressFamily: nl.FAMILY_V4,
+ Address: ip,
+ Weight: 1,
+ }
+ if n.loadBalancerMode == loadBalancerModeDSR {
+ d.ConnectionFlags = ipvs.ConnFwdDirectRoute
+ }
+
+ if fullRemove {
+ if err := i.DelDestination(s, d); err != nil && err != syscall.ENOENT {
+ logrus.Errorf("Failed to delete real server %s for vip %s fwmark %d in sbox %.7s (%.7s): %v", ip, lb.vip, lb.fwMark, sb.ID(), sb.ContainerID(), err)
+ }
+ } else {
+ d.Weight = 0
+ if err := i.UpdateDestination(s, d); err != nil && err != syscall.ENOENT {
+ logrus.Errorf("Failed to set LB weight of real server %s to 0 for vip %s fwmark %d in sbox %.7s (%.7s): %v", ip, lb.vip, lb.fwMark, sb.ID(), sb.ContainerID(), err)
+ }
+ }
+
+ if rmService {
+ s.SchedName = ipvs.RoundRobin
+ if err := i.DelService(s); err != nil && err != syscall.ENOENT {
+ logrus.Errorf("Failed to delete service for vip %s fwmark %d in sbox %.7s (%.7s): %v", lb.vip, lb.fwMark, sb.ID(), sb.ContainerID(), err)
+ }
+
+ if sb.ingress {
+ var gwIP net.IP
+ if ep := sb.getGatewayEndpoint(); ep != nil {
+ gwIP = ep.Iface().Address().IP
+ }
+ if err := programIngress(gwIP, lb.service.ingressPorts, true); err != nil {
+ logrus.Errorf("Failed to delete ingress: %v", err)
+ }
+ }
+
+ if err := invokeFWMarker(sb.Key(), lb.vip, lb.fwMark, lb.service.ingressPorts, eIP, true, n.loadBalancerMode); err != nil {
+ logrus.Errorf("Failed to delete firewall mark rule in sbox %.7s (%.7s): %v", sb.ID(), sb.ContainerID(), err)
+ }
+
+ // Remove IP alias from the VIP to the endpoint
+ ifName := findIfaceDstName(sb, ep)
+ if ifName == "" {
+ logrus.Errorf("Failed find interface name for endpoint %s(%s) to create LB alias", ep.ID(), ep.Name())
+ return
+ }
+ err := sb.osSbox.RemoveAliasIP(ifName, &net.IPNet{IP: lb.vip, Mask: net.CIDRMask(32, 32)})
+ if err != nil {
+ logrus.Errorf("Failed add IP alias %s to network %s LB endpoint interface %s: %v", lb.vip, n.ID(), ifName, err)
+ }
+ }
+}
+
+const ingressChain = "DOCKER-INGRESS"
+
+var (
+ ingressOnce sync.Once
+ ingressMu sync.Mutex // lock for operations on ingress
+ ingressProxyTbl = make(map[string]io.Closer)
+ portConfigMu sync.Mutex
+ portConfigTbl = make(map[PortConfig]int)
+)
+
+func filterPortConfigs(ingressPorts []*PortConfig, isDelete bool) []*PortConfig {
+ portConfigMu.Lock()
+ iPorts := make([]*PortConfig, 0, len(ingressPorts))
+ for _, pc := range ingressPorts {
+ if isDelete {
+ if cnt, ok := portConfigTbl[*pc]; ok {
+ // This is the last reference to this
+ // port config. Delete the port config
+ // and add it to filtered list to be
+ // plumbed.
+ if cnt == 1 {
+ delete(portConfigTbl, *pc)
+ iPorts = append(iPorts, pc)
+ continue
+ }
+
+ portConfigTbl[*pc] = cnt - 1
+ }
+
+ continue
+ }
+
+ if cnt, ok := portConfigTbl[*pc]; ok {
+ portConfigTbl[*pc] = cnt + 1
+ continue
+ }
+
+ // We are adding it for the first time. Add it to the
+ // filter list to be plumbed.
+ portConfigTbl[*pc] = 1
+ iPorts = append(iPorts, pc)
+ }
+ portConfigMu.Unlock()
+
+ return iPorts
+}
+
+func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) error {
+ addDelOpt := "-I"
+ rollbackAddDelOpt := "-D"
+ if isDelete {
+ addDelOpt = "-D"
+ rollbackAddDelOpt = "-I"
+ }
+
+ ingressMu.Lock()
+ defer ingressMu.Unlock()
+
+ chainExists := iptables.ExistChain(ingressChain, iptables.Nat)
+ filterChainExists := iptables.ExistChain(ingressChain, iptables.Filter)
+
+ ingressOnce.Do(func() {
+ // Flush nat table and filter table ingress chain rules during init if it
+ // exists. It might contain stale rules from previous life.
+ if chainExists {
+ if err := iptables.RawCombinedOutput("-t", "nat", "-F", ingressChain); err != nil {
+ logrus.Errorf("Could not flush nat table ingress chain rules during init: %v", err)
+ }
+ }
+ if filterChainExists {
+ if err := iptables.RawCombinedOutput("-F", ingressChain); err != nil {
+ logrus.Errorf("Could not flush filter table ingress chain rules during init: %v", err)
+ }
+ }
+ })
+
+ if !isDelete {
+ if !chainExists {
+ if err := iptables.RawCombinedOutput("-t", "nat", "-N", ingressChain); err != nil {
+ return fmt.Errorf("failed to create ingress chain: %v", err)
+ }
+ }
+ if !filterChainExists {
+ if err := iptables.RawCombinedOutput("-N", ingressChain); err != nil {
+ return fmt.Errorf("failed to create filter table ingress chain: %v", err)
+ }
+ }
+
+ if !iptables.Exists(iptables.Nat, ingressChain, "-j", "RETURN") {
+ if err := iptables.RawCombinedOutput("-t", "nat", "-A", ingressChain, "-j", "RETURN"); err != nil {
+ return fmt.Errorf("failed to add return rule in nat table ingress chain: %v", err)
+ }
+ }
+
+ if !iptables.Exists(iptables.Filter, ingressChain, "-j", "RETURN") {
+ if err := iptables.RawCombinedOutput("-A", ingressChain, "-j", "RETURN"); err != nil {
+ return fmt.Errorf("failed to add return rule to filter table ingress chain: %v", err)
+ }
+ }
+
+ for _, chain := range []string{"OUTPUT", "PREROUTING"} {
+ if !iptables.Exists(iptables.Nat, chain, "-m", "addrtype", "--dst-type", "LOCAL", "-j", ingressChain) {
+ if err := iptables.RawCombinedOutput("-t", "nat", "-I", chain, "-m", "addrtype", "--dst-type", "LOCAL", "-j", ingressChain); err != nil {
+ return fmt.Errorf("failed to add jump rule in %s to ingress chain: %v", chain, err)
+ }
+ }
+ }
+
+ if !iptables.Exists(iptables.Filter, "FORWARD", "-j", ingressChain) {
+ if err := iptables.RawCombinedOutput("-I", "FORWARD", "-j", ingressChain); err != nil {
+ return fmt.Errorf("failed to add jump rule to %s in filter table forward chain: %v", ingressChain, err)
+ }
+ arrangeUserFilterRule()
+ }
+
+ oifName, err := findOIFName(gwIP)
+ if err != nil {
+ return fmt.Errorf("failed to find gateway bridge interface name for %s: %v", gwIP, err)
+ }
+
+ path := filepath.Join("/proc/sys/net/ipv4/conf", oifName, "route_localnet")
+ if err := ioutil.WriteFile(path, []byte{'1', '\n'}, 0644); err != nil {
+ return fmt.Errorf("could not write to %s: %v", path, err)
+ }
+
+ ruleArgs := strings.Fields(fmt.Sprintf("-m addrtype --src-type LOCAL -o %s -j MASQUERADE", oifName))
+ if !iptables.Exists(iptables.Nat, "POSTROUTING", ruleArgs...) {
+ if err := iptables.RawCombinedOutput(append([]string{"-t", "nat", "-I", "POSTROUTING"}, ruleArgs...)...); err != nil {
+ return fmt.Errorf("failed to add ingress localhost POSTROUTING rule for %s: %v", oifName, err)
+ }
+ }
+ }
+
+ //Filter the ingress ports until port rules start to be added/deleted
+ filteredPorts := filterPortConfigs(ingressPorts, isDelete)
+ rollbackRules := make([][]string, 0, len(filteredPorts)*3)
+ var portErr error
+ defer func() {
+ if portErr != nil && !isDelete {
+ filterPortConfigs(filteredPorts, !isDelete)
+ for _, rule := range rollbackRules {
+ if err := iptables.RawCombinedOutput(rule...); err != nil {
+ logrus.Warnf("roll back rule failed, %v: %v", rule, err)
+ }
+ }
+ }
+ }()
+
+ for _, iPort := range filteredPorts {
+ if iptables.ExistChain(ingressChain, iptables.Nat) {
+ rule := strings.Fields(fmt.Sprintf("-t nat %s %s -p %s --dport %d -j DNAT --to-destination %s:%d",
+ addDelOpt, ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort, gwIP, iPort.PublishedPort))
+ if portErr = iptables.RawCombinedOutput(rule...); portErr != nil {
+ errStr := fmt.Sprintf("set up rule failed, %v: %v", rule, portErr)
+ if !isDelete {
+ return fmt.Errorf("%s", errStr)
+ }
+ logrus.Infof("%s", errStr)
+ }
+ rollbackRule := strings.Fields(fmt.Sprintf("-t nat %s %s -p %s --dport %d -j DNAT --to-destination %s:%d", rollbackAddDelOpt,
+ ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort, gwIP, iPort.PublishedPort))
+ rollbackRules = append(rollbackRules, rollbackRule)
+ }
+
+ // Filter table rules to allow a published service to be accessible in the local node from..
+ // 1) service tasks attached to other networks
+ // 2) unmanaged containers on bridge networks
+ rule := strings.Fields(fmt.Sprintf("%s %s -m state -p %s --sport %d --state ESTABLISHED,RELATED -j ACCEPT",
+ addDelOpt, ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort))
+ if portErr = iptables.RawCombinedOutput(rule...); portErr != nil {
+ errStr := fmt.Sprintf("set up rule failed, %v: %v", rule, portErr)
+ if !isDelete {
+ return fmt.Errorf("%s", errStr)
+ }
+ logrus.Warnf("%s", errStr)
+ }
+ rollbackRule := strings.Fields(fmt.Sprintf("%s %s -m state -p %s --sport %d --state ESTABLISHED,RELATED -j ACCEPT", rollbackAddDelOpt,
+ ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort))
+ rollbackRules = append(rollbackRules, rollbackRule)
+
+ rule = strings.Fields(fmt.Sprintf("%s %s -p %s --dport %d -j ACCEPT",
+ addDelOpt, ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort))
+ if portErr = iptables.RawCombinedOutput(rule...); portErr != nil {
+ errStr := fmt.Sprintf("set up rule failed, %v: %v", rule, portErr)
+ if !isDelete {
+ return fmt.Errorf("%s", errStr)
+ }
+ logrus.Warnf("%s", errStr)
+ }
+ rollbackRule = strings.Fields(fmt.Sprintf("%s %s -p %s --dport %d -j ACCEPT", rollbackAddDelOpt,
+ ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort))
+ rollbackRules = append(rollbackRules, rollbackRule)
+
+ if err := plumbProxy(iPort, isDelete); err != nil {
+ logrus.Warnf("failed to create proxy for port %d: %v", iPort.PublishedPort, err)
+ }
+ }
+
+ return nil
+}
+
+// In the filter table FORWARD chain the first rule should be to jump to
+// DOCKER-USER so the user is able to filter packet first.
+// The second rule should be jump to INGRESS-CHAIN.
+// This chain has the rules to allow access to the published ports for swarm tasks
+// from local bridge networks and docker_gwbridge (ie:taks on other swarm networks)
+func arrangeIngressFilterRule() {
+ if iptables.ExistChain(ingressChain, iptables.Filter) {
+ if iptables.Exists(iptables.Filter, "FORWARD", "-j", ingressChain) {
+ if err := iptables.RawCombinedOutput("-D", "FORWARD", "-j", ingressChain); err != nil {
+ logrus.Warnf("failed to delete jump rule to ingressChain in filter table: %v", err)
+ }
+ }
+ if err := iptables.RawCombinedOutput("-I", "FORWARD", "-j", ingressChain); err != nil {
+ logrus.Warnf("failed to add jump rule to ingressChain in filter table: %v", err)
+ }
+ }
+}
+
+func findOIFName(ip net.IP) (string, error) {
+ nlh := ns.NlHandle()
+
+ routes, err := nlh.RouteGet(ip)
+ if err != nil {
+ return "", err
+ }
+
+ if len(routes) == 0 {
+ return "", fmt.Errorf("no route to %s", ip)
+ }
+
+ // Pick the first route(typically there is only one route). We
+ // don't support multipath.
+ link, err := nlh.LinkByIndex(routes[0].LinkIndex)
+ if err != nil {
+ return "", err
+ }
+
+ return link.Attrs().Name, nil
+}
+
+func plumbProxy(iPort *PortConfig, isDelete bool) error {
+ var (
+ err error
+ l io.Closer
+ )
+
+ portSpec := fmt.Sprintf("%d/%s", iPort.PublishedPort, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]))
+ if isDelete {
+ if listener, ok := ingressProxyTbl[portSpec]; ok {
+ if listener != nil {
+ listener.Close()
+ }
+ }
+
+ return nil
+ }
+
+ switch iPort.Protocol {
+ case ProtocolTCP:
+ l, err = net.ListenTCP("tcp", &net.TCPAddr{Port: int(iPort.PublishedPort)})
+ case ProtocolUDP:
+ l, err = net.ListenUDP("udp", &net.UDPAddr{Port: int(iPort.PublishedPort)})
+ case ProtocolSCTP:
+ l, err = sctp.ListenSCTP("sctp", &sctp.SCTPAddr{Port: int(iPort.PublishedPort)})
+ default:
+ err = fmt.Errorf("unknown protocol %v", iPort.Protocol)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ ingressProxyTbl[portSpec] = l
+
+ return nil
+}
+
+func writePortsToFile(ports []*PortConfig) (string, error) {
+ f, err := ioutil.TempFile("", "port_configs")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ buf, _ := proto.Marshal(&EndpointRecord{
+ IngressPorts: ports,
+ })
+
+ n, err := f.Write(buf)
+ if err != nil {
+ return "", err
+ }
+
+ if n < len(buf) {
+ return "", io.ErrShortWrite
+ }
+
+ return f.Name(), nil
+}
+
+func readPortsFromFile(fileName string) ([]*PortConfig, error) {
+ buf, err := ioutil.ReadFile(fileName)
+ if err != nil {
+ return nil, err
+ }
+
+ var epRec EndpointRecord
+ err = proto.Unmarshal(buf, &epRec)
+ if err != nil {
+ return nil, err
+ }
+
+ return epRec.IngressPorts, nil
+}
+
+// Invoke fwmarker reexec routine to mark vip destined packets with
+// the passed firewall mark.
+func invokeFWMarker(path string, vip net.IP, fwMark uint32, ingressPorts []*PortConfig, eIP *net.IPNet, isDelete bool, lbMode string) error {
+ var ingressPortsFile string
+
+ if len(ingressPorts) != 0 {
+ var err error
+ ingressPortsFile, err = writePortsToFile(ingressPorts)
+ if err != nil {
+ return err
+ }
+
+ defer os.Remove(ingressPortsFile)
+ }
+
+ addDelOpt := "-A"
+ if isDelete {
+ addDelOpt = "-D"
+ }
+
+ cmd := &exec.Cmd{
+ Path: reexec.Self(),
+ Args: append([]string{"fwmarker"}, path, vip.String(), fmt.Sprintf("%d", fwMark), addDelOpt, ingressPortsFile, eIP.String(), lbMode),
+ Stdout: os.Stdout,
+ Stderr: os.Stderr,
+ }
+
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("reexec failed: %v", err)
+ }
+
+ return nil
+}
+
+// Firewall marker reexec function.
+func fwMarker() {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ if len(os.Args) < 8 {
+ logrus.Error("invalid number of arguments..")
+ os.Exit(1)
+ }
+
+ var ingressPorts []*PortConfig
+ if os.Args[5] != "" {
+ var err error
+ ingressPorts, err = readPortsFromFile(os.Args[5])
+ if err != nil {
+ logrus.Errorf("Failed reading ingress ports file: %v", err)
+ os.Exit(2)
+ }
+ }
+
+ vip := os.Args[2]
+ fwMark, err := strconv.ParseUint(os.Args[3], 10, 32)
+ if err != nil {
+ logrus.Errorf("bad fwmark value(%s) passed: %v", os.Args[3], err)
+ os.Exit(3)
+ }
+ addDelOpt := os.Args[4]
+
+ rules := [][]string{}
+ for _, iPort := range ingressPorts {
+ rule := strings.Fields(fmt.Sprintf("-t mangle %s PREROUTING -p %s --dport %d -j MARK --set-mark %d",
+ addDelOpt, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort, fwMark))
+ rules = append(rules, rule)
+ }
+
+ ns, err := netns.GetFromPath(os.Args[1])
+ if err != nil {
+ logrus.Errorf("failed get network namespace %q: %v", os.Args[1], err)
+ os.Exit(4)
+ }
+ defer ns.Close()
+
+ if err := netns.Set(ns); err != nil {
+ logrus.Errorf("setting into container net ns %v failed, %v", os.Args[1], err)
+ os.Exit(5)
+ }
+
+ lbMode := os.Args[7]
+ if addDelOpt == "-A" && lbMode == loadBalancerModeNAT {
+ eIP, subnet, err := net.ParseCIDR(os.Args[6])
+ if err != nil {
+ logrus.Errorf("Failed to parse endpoint IP %s: %v", os.Args[6], err)
+ os.Exit(6)
+ }
+
+ ruleParams := strings.Fields(fmt.Sprintf("-m ipvs --ipvs -d %s -j SNAT --to-source %s", subnet, eIP))
+ if !iptables.Exists("nat", "POSTROUTING", ruleParams...) {
+ rule := append(strings.Fields("-t nat -A POSTROUTING"), ruleParams...)
+ rules = append(rules, rule)
+
+ err := ioutil.WriteFile("/proc/sys/net/ipv4/vs/conntrack", []byte{'1', '\n'}, 0644)
+ if err != nil {
+ logrus.Errorf("Failed to write to /proc/sys/net/ipv4/vs/conntrack: %v", err)
+ os.Exit(7)
+ }
+ }
+ }
+
+ rule := strings.Fields(fmt.Sprintf("-t mangle %s INPUT -d %s/32 -j MARK --set-mark %d", addDelOpt, vip, fwMark))
+ rules = append(rules, rule)
+
+ for _, rule := range rules {
+ if err := iptables.RawCombinedOutputNative(rule...); err != nil {
+ logrus.Errorf("set up rule failed, %v: %v", rule, err)
+ os.Exit(8)
+ }
+ }
+}
+
+func addRedirectRules(path string, eIP *net.IPNet, ingressPorts []*PortConfig) error {
+ var ingressPortsFile string
+
+ if len(ingressPorts) != 0 {
+ var err error
+ ingressPortsFile, err = writePortsToFile(ingressPorts)
+ if err != nil {
+ return err
+ }
+ defer os.Remove(ingressPortsFile)
+ }
+
+ cmd := &exec.Cmd{
+ Path: reexec.Self(),
+ Args: append([]string{"redirector"}, path, eIP.String(), ingressPortsFile),
+ Stdout: os.Stdout,
+ Stderr: os.Stderr,
+ }
+
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("reexec failed: %v", err)
+ }
+
+ return nil
+}
+
+// Redirector reexec function.
+func redirector() {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ if len(os.Args) < 4 {
+ logrus.Error("invalid number of arguments..")
+ os.Exit(1)
+ }
+
+ var ingressPorts []*PortConfig
+ if os.Args[3] != "" {
+ var err error
+ ingressPorts, err = readPortsFromFile(os.Args[3])
+ if err != nil {
+ logrus.Errorf("Failed reading ingress ports file: %v", err)
+ os.Exit(2)
+ }
+ }
+
+ eIP, _, err := net.ParseCIDR(os.Args[2])
+ if err != nil {
+ logrus.Errorf("Failed to parse endpoint IP %s: %v", os.Args[2], err)
+ os.Exit(3)
+ }
+
+ rules := [][]string{}
+ for _, iPort := range ingressPorts {
+ rule := strings.Fields(fmt.Sprintf("-t nat -A PREROUTING -d %s -p %s --dport %d -j REDIRECT --to-port %d",
+ eIP.String(), strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort, iPort.TargetPort))
+ rules = append(rules, rule)
+ // Allow only incoming connections to exposed ports
+ iRule := strings.Fields(fmt.Sprintf("-I INPUT -d %s -p %s --dport %d -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT",
+ eIP.String(), strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.TargetPort))
+ rules = append(rules, iRule)
+ // Allow only outgoing connections from exposed ports
+ oRule := strings.Fields(fmt.Sprintf("-I OUTPUT -s %s -p %s --sport %d -m conntrack --ctstate ESTABLISHED -j ACCEPT",
+ eIP.String(), strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.TargetPort))
+ rules = append(rules, oRule)
+ }
+
+ ns, err := netns.GetFromPath(os.Args[1])
+ if err != nil {
+ logrus.Errorf("failed get network namespace %q: %v", os.Args[1], err)
+ os.Exit(4)
+ }
+ defer ns.Close()
+
+ if err := netns.Set(ns); err != nil {
+ logrus.Errorf("setting into container net ns %v failed, %v", os.Args[1], err)
+ os.Exit(5)
+ }
+
+ for _, rule := range rules {
+ if err := iptables.RawCombinedOutputNative(rule...); err != nil {
+ logrus.Errorf("set up rule failed, %v: %v", rule, err)
+ os.Exit(6)
+ }
+ }
+
+ if len(ingressPorts) == 0 {
+ return
+ }
+
+ // Ensure blocking rules for anything else in/to ingress network
+ for _, rule := range [][]string{
+ {"-d", eIP.String(), "-p", "sctp", "-j", "DROP"},
+ {"-d", eIP.String(), "-p", "udp", "-j", "DROP"},
+ {"-d", eIP.String(), "-p", "tcp", "-j", "DROP"},
+ } {
+ if !iptables.ExistsNative(iptables.Filter, "INPUT", rule...) {
+ if err := iptables.RawCombinedOutputNative(append([]string{"-A", "INPUT"}, rule...)...); err != nil {
+ logrus.Errorf("set up rule failed, %v: %v", rule, err)
+ os.Exit(7)
+ }
+ }
+ rule[0] = "-s"
+ if !iptables.ExistsNative(iptables.Filter, "OUTPUT", rule...) {
+ if err := iptables.RawCombinedOutputNative(append([]string{"-A", "OUTPUT"}, rule...)...); err != nil {
+ logrus.Errorf("set up rule failed, %v: %v", rule, err)
+ os.Exit(8)
+ }
+ }
+ }
+}
--- /dev/null
+// +build !linux,!windows
+
+package libnetwork
+
+import (
+ "fmt"
+ "net"
+)
+
+func (c *controller) cleanupServiceBindings(nid string) {
+}
+
+func (c *controller) addServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, aliases []string, ip net.IP) error {
+ return fmt.Errorf("not supported")
+}
+
+func (c *controller) rmServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, aliases []string, ip net.IP) error {
+ return fmt.Errorf("not supported")
+}
+
+func (sb *sandbox) populateLoadBalancers(ep *endpoint) {
+}
+
+func arrangeIngressFilterRule() {
+}
--- /dev/null
+package libnetwork
+
+import (
+ "net"
+
+ "github.com/Microsoft/hcsshim"
+ "github.com/docker/docker/pkg/system"
+ "github.com/sirupsen/logrus"
+)
+
+type policyLists struct {
+ ilb *hcsshim.PolicyList
+ elb *hcsshim.PolicyList
+}
+
+var lbPolicylistMap map[*loadBalancer]*policyLists
+
+func init() {
+ lbPolicylistMap = make(map[*loadBalancer]*policyLists)
+}
+
+func (n *network) addLBBackend(ip net.IP, lb *loadBalancer) {
+ if len(lb.vip) == 0 {
+ return
+ }
+
+ vip := lb.vip
+ ingressPorts := lb.service.ingressPorts
+
+ if system.GetOSVersion().Build > 16236 {
+ lb.Lock()
+ defer lb.Unlock()
+ //find the load balancer IP for the network.
+ var sourceVIP string
+ for _, e := range n.Endpoints() {
+ epInfo := e.Info()
+ if epInfo == nil {
+ continue
+ }
+ if epInfo.LoadBalancer() {
+ sourceVIP = epInfo.Iface().Address().IP.String()
+ break
+ }
+ }
+
+ if sourceVIP == "" {
+ logrus.Errorf("Failed to find load balancer IP for network %s", n.Name())
+ return
+ }
+
+ var endpoints []hcsshim.HNSEndpoint
+
+ for eid, be := range lb.backEnds {
+ if be.disabled {
+ continue
+ }
+ //Call HNS to get back ID (GUID) corresponding to the endpoint.
+ hnsEndpoint, err := hcsshim.GetHNSEndpointByName(eid)
+ if err != nil {
+ logrus.Errorf("Failed to find HNS ID for endpoint %v: %v", eid, err)
+ return
+ }
+
+ endpoints = append(endpoints, *hnsEndpoint)
+ }
+
+ if policies, ok := lbPolicylistMap[lb]; ok {
+
+ if policies.ilb != nil {
+ policies.ilb.Delete()
+ policies.ilb = nil
+ }
+
+ if policies.elb != nil {
+ policies.elb.Delete()
+ policies.elb = nil
+ }
+ delete(lbPolicylistMap, lb)
+ }
+
+ ilbPolicy, err := hcsshim.AddLoadBalancer(endpoints, true, sourceVIP, vip.String(), 0, 0, 0)
+ if err != nil {
+ logrus.Errorf("Failed to add ILB policy for service %s (%s) with endpoints %v using load balancer IP %s on network %s: %v",
+ lb.service.name, vip.String(), endpoints, sourceVIP, n.Name(), err)
+ return
+ }
+
+ lbPolicylistMap[lb] = &policyLists{
+ ilb: ilbPolicy,
+ }
+
+ publishedPorts := make(map[uint32]uint32)
+
+ for i, port := range ingressPorts {
+ protocol := uint16(6)
+
+ // Skip already published port
+ if publishedPorts[port.PublishedPort] == port.TargetPort {
+ continue
+ }
+
+ if port.Protocol == ProtocolUDP {
+ protocol = 17
+ }
+
+ // check if already has udp matching to add wild card publishing
+ for j := i + 1; j < len(ingressPorts); j++ {
+ if ingressPorts[j].TargetPort == port.TargetPort &&
+ ingressPorts[j].PublishedPort == port.PublishedPort {
+ protocol = 0
+ }
+ }
+
+ publishedPorts[port.PublishedPort] = port.TargetPort
+
+ lbPolicylistMap[lb].elb, err = hcsshim.AddLoadBalancer(endpoints, false, sourceVIP, "", protocol, uint16(port.TargetPort), uint16(port.PublishedPort))
+ if err != nil {
+ logrus.Errorf("Failed to add ELB policy for service %s (ip:%s target port:%v published port:%v) with endpoints %v using load balancer IP %s on network %s: %v",
+ lb.service.name, vip.String(), uint16(port.TargetPort), uint16(port.PublishedPort), endpoints, sourceVIP, n.Name(), err)
+ return
+ }
+ }
+ }
+}
+
+func (n *network) rmLBBackend(ip net.IP, lb *loadBalancer, rmService bool, fullRemove bool) {
+ if len(lb.vip) == 0 {
+ return
+ }
+
+ if system.GetOSVersion().Build > 16236 {
+ if numEnabledBackends(lb) > 0 {
+ //Reprogram HNS (actually VFP) with the existing backends.
+ n.addLBBackend(ip, lb)
+ } else {
+ lb.Lock()
+ defer lb.Unlock()
+ logrus.Debugf("No more backends for service %s (ip:%s). Removing all policies", lb.service.name, lb.vip.String())
+
+ if policyLists, ok := lbPolicylistMap[lb]; ok {
+ if policyLists.ilb != nil {
+ policyLists.ilb.Delete()
+ policyLists.ilb = nil
+ }
+
+ if policyLists.elb != nil {
+ policyLists.elb.Delete()
+ policyLists.elb = nil
+ }
+ delete(lbPolicylistMap, lb)
+
+ } else {
+ logrus.Errorf("Failed to find policies for service %s (%s)", lb.service.name, lb.vip.String())
+ }
+ }
+ }
+}
+
+func numEnabledBackends(lb *loadBalancer) int {
+ nEnabled := 0
+ for _, be := range lb.backEnds {
+ if !be.disabled {
+ nEnabled++
+ }
+ }
+ return nEnabled
+}
+
+func (sb *sandbox) populateLoadBalancers(ep *endpoint) {
+}
+
+func arrangeIngressFilterRule() {
+}
--- /dev/null
+package libnetwork
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/docker/libkv/store/boltdb"
+ "github.com/docker/libkv/store/consul"
+ "github.com/docker/libkv/store/etcd"
+ "github.com/docker/libkv/store/zookeeper"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/sirupsen/logrus"
+)
+
+func registerKVStores() {
+ consul.Register()
+ zookeeper.Register()
+ etcd.Register()
+ boltdb.Register()
+}
+
+func (c *controller) initScopedStore(scope string, scfg *datastore.ScopeCfg) error {
+ store, err := datastore.NewDataStore(scope, scfg)
+ if err != nil {
+ return err
+ }
+ c.Lock()
+ c.stores = append(c.stores, store)
+ c.Unlock()
+
+ return nil
+}
+
+func (c *controller) initStores() error {
+ registerKVStores()
+
+ c.Lock()
+ if c.cfg == nil {
+ c.Unlock()
+ return nil
+ }
+ scopeConfigs := c.cfg.Scopes
+ c.stores = nil
+ c.Unlock()
+
+ for scope, scfg := range scopeConfigs {
+ if err := c.initScopedStore(scope, scfg); err != nil {
+ return err
+ }
+ }
+
+ c.startWatch()
+ return nil
+}
+
+func (c *controller) closeStores() {
+ for _, store := range c.getStores() {
+ store.Close()
+ }
+}
+
+func (c *controller) getStore(scope string) datastore.DataStore {
+ c.Lock()
+ defer c.Unlock()
+
+ for _, store := range c.stores {
+ if store.Scope() == scope {
+ return store
+ }
+ }
+
+ return nil
+}
+
+func (c *controller) getStores() []datastore.DataStore {
+ c.Lock()
+ defer c.Unlock()
+
+ return c.stores
+}
+
+func (c *controller) getNetworkFromStore(nid string) (*network, error) {
+ for _, store := range c.getStores() {
+ n := &network{id: nid, ctrlr: c}
+ err := store.GetObject(datastore.Key(n.Key()...), n)
+ // Continue searching in the next store if the key is not found in this store
+ if err != nil {
+ if err != datastore.ErrKeyNotFound {
+ logrus.Debugf("could not find network %s: %v", nid, err)
+ }
+ continue
+ }
+
+ ec := &endpointCnt{n: n}
+ err = store.GetObject(datastore.Key(ec.Key()...), ec)
+ if err != nil && !n.inDelete {
+ return nil, fmt.Errorf("could not find endpoint count for network %s: %v", n.Name(), err)
+ }
+
+ n.epCnt = ec
+ if n.scope == "" {
+ n.scope = store.Scope()
+ }
+ return n, nil
+ }
+
+ return nil, fmt.Errorf("network %s not found", nid)
+}
+
+func (c *controller) getNetworksForScope(scope string) ([]*network, error) {
+ var nl []*network
+
+ store := c.getStore(scope)
+ if store == nil {
+ return nil, nil
+ }
+
+ kvol, err := store.List(datastore.Key(datastore.NetworkKeyPrefix),
+ &network{ctrlr: c})
+ if err != nil && err != datastore.ErrKeyNotFound {
+ return nil, fmt.Errorf("failed to get networks for scope %s: %v",
+ scope, err)
+ }
+
+ for _, kvo := range kvol {
+ n := kvo.(*network)
+ n.ctrlr = c
+
+ ec := &endpointCnt{n: n}
+ err = store.GetObject(datastore.Key(ec.Key()...), ec)
+ if err != nil && !n.inDelete {
+ logrus.Warnf("Could not find endpoint count key %s for network %s while listing: %v", datastore.Key(ec.Key()...), n.Name(), err)
+ continue
+ }
+
+ n.epCnt = ec
+ if n.scope == "" {
+ n.scope = scope
+ }
+ nl = append(nl, n)
+ }
+
+ return nl, nil
+}
+
+func (c *controller) getNetworksFromStore() ([]*network, error) {
+ var nl []*network
+
+ for _, store := range c.getStores() {
+ kvol, err := store.List(datastore.Key(datastore.NetworkKeyPrefix),
+ &network{ctrlr: c})
+ // Continue searching in the next store if no keys found in this store
+ if err != nil {
+ if err != datastore.ErrKeyNotFound {
+ logrus.Debugf("failed to get networks for scope %s: %v", store.Scope(), err)
+ }
+ continue
+ }
+
+ kvep, err := store.Map(datastore.Key(epCntKeyPrefix), &endpointCnt{})
+ if err != nil {
+ if err != datastore.ErrKeyNotFound {
+ logrus.Warnf("failed to get endpoint_count map for scope %s: %v", store.Scope(), err)
+ }
+ }
+
+ for _, kvo := range kvol {
+ n := kvo.(*network)
+ n.Lock()
+ n.ctrlr = c
+ ec := &endpointCnt{n: n}
+ // Trim the leading & trailing "/" to make it consistent across all stores
+ if val, ok := kvep[strings.Trim(datastore.Key(ec.Key()...), "/")]; ok {
+ ec = val.(*endpointCnt)
+ ec.n = n
+ n.epCnt = ec
+ }
+ if n.scope == "" {
+ n.scope = store.Scope()
+ }
+ n.Unlock()
+ nl = append(nl, n)
+ }
+ }
+
+ return nl, nil
+}
+
+func (n *network) getEndpointFromStore(eid string) (*endpoint, error) {
+ var errors []string
+ for _, store := range n.ctrlr.getStores() {
+ ep := &endpoint{id: eid, network: n}
+ err := store.GetObject(datastore.Key(ep.Key()...), ep)
+ // Continue searching in the next store if the key is not found in this store
+ if err != nil {
+ if err != datastore.ErrKeyNotFound {
+ errors = append(errors, fmt.Sprintf("{%s:%v}, ", store.Scope(), err))
+ logrus.Debugf("could not find endpoint %s in %s: %v", eid, store.Scope(), err)
+ }
+ continue
+ }
+ return ep, nil
+ }
+ return nil, fmt.Errorf("could not find endpoint %s: %v", eid, errors)
+}
+
+func (n *network) getEndpointsFromStore() ([]*endpoint, error) {
+ var epl []*endpoint
+
+ tmp := endpoint{network: n}
+ for _, store := range n.getController().getStores() {
+ kvol, err := store.List(datastore.Key(tmp.KeyPrefix()...), &endpoint{network: n})
+ // Continue searching in the next store if no keys found in this store
+ if err != nil {
+ if err != datastore.ErrKeyNotFound {
+ logrus.Debugf("failed to get endpoints for network %s scope %s: %v",
+ n.Name(), store.Scope(), err)
+ }
+ continue
+ }
+
+ for _, kvo := range kvol {
+ ep := kvo.(*endpoint)
+ epl = append(epl, ep)
+ }
+ }
+
+ return epl, nil
+}
+
+func (c *controller) updateToStore(kvObject datastore.KVObject) error {
+ cs := c.getStore(kvObject.DataScope())
+ if cs == nil {
+ return ErrDataStoreNotInitialized(kvObject.DataScope())
+ }
+
+ if err := cs.PutObjectAtomic(kvObject); err != nil {
+ if err == datastore.ErrKeyModified {
+ return err
+ }
+ return fmt.Errorf("failed to update store for object type %T: %v", kvObject, err)
+ }
+
+ return nil
+}
+
+func (c *controller) deleteFromStore(kvObject datastore.KVObject) error {
+ cs := c.getStore(kvObject.DataScope())
+ if cs == nil {
+ return ErrDataStoreNotInitialized(kvObject.DataScope())
+ }
+
+retry:
+ if err := cs.DeleteObjectAtomic(kvObject); err != nil {
+ if err == datastore.ErrKeyModified {
+ if err := cs.GetObject(datastore.Key(kvObject.Key()...), kvObject); err != nil {
+ return fmt.Errorf("could not update the kvobject to latest when trying to delete: %v", err)
+ }
+ logrus.Warnf("Error (%v) deleting object %v, retrying....", err, kvObject.Key())
+ goto retry
+ }
+ return err
+ }
+
+ return nil
+}
+
+type netWatch struct {
+ localEps map[string]*endpoint
+ remoteEps map[string]*endpoint
+ stopCh chan struct{}
+}
+
+func (c *controller) getLocalEps(nw *netWatch) []*endpoint {
+ c.Lock()
+ defer c.Unlock()
+
+ var epl []*endpoint
+ for _, ep := range nw.localEps {
+ epl = append(epl, ep)
+ }
+
+ return epl
+}
+
+func (c *controller) watchSvcRecord(ep *endpoint) {
+ c.watchCh <- ep
+}
+
+func (c *controller) unWatchSvcRecord(ep *endpoint) {
+ c.unWatchCh <- ep
+}
+
+func (c *controller) networkWatchLoop(nw *netWatch, ep *endpoint, ecCh <-chan datastore.KVObject) {
+ for {
+ select {
+ case <-nw.stopCh:
+ return
+ case o := <-ecCh:
+ ec := o.(*endpointCnt)
+
+ epl, err := ec.n.getEndpointsFromStore()
+ if err != nil {
+ break
+ }
+
+ c.Lock()
+ var addEp []*endpoint
+
+ delEpMap := make(map[string]*endpoint)
+ renameEpMap := make(map[string]bool)
+ for k, v := range nw.remoteEps {
+ delEpMap[k] = v
+ }
+
+ for _, lEp := range epl {
+ if _, ok := nw.localEps[lEp.ID()]; ok {
+ continue
+ }
+
+ if ep, ok := nw.remoteEps[lEp.ID()]; ok {
+ // On a container rename EP ID will remain
+ // the same but the name will change. service
+ // records should reflect the change.
+ // Keep old EP entry in the delEpMap and add
+ // EP from the store (which has the new name)
+ // into the new list
+ if lEp.name == ep.name {
+ delete(delEpMap, lEp.ID())
+ continue
+ }
+ renameEpMap[lEp.ID()] = true
+ }
+ nw.remoteEps[lEp.ID()] = lEp
+ addEp = append(addEp, lEp)
+ }
+
+ // EPs whose name are to be deleted from the svc records
+ // should also be removed from nw's remote EP list, except
+ // the ones that are getting renamed.
+ for _, lEp := range delEpMap {
+ if !renameEpMap[lEp.ID()] {
+ delete(nw.remoteEps, lEp.ID())
+ }
+ }
+ c.Unlock()
+
+ for _, lEp := range delEpMap {
+ ep.getNetwork().updateSvcRecord(lEp, c.getLocalEps(nw), false)
+
+ }
+ for _, lEp := range addEp {
+ ep.getNetwork().updateSvcRecord(lEp, c.getLocalEps(nw), true)
+ }
+ }
+ }
+}
+
+func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoint) {
+ n := ep.getNetwork()
+ if !c.isDistributedControl() && n.Scope() == datastore.SwarmScope && n.driverIsMultihost() {
+ return
+ }
+
+ c.Lock()
+ nw, ok := nmap[n.ID()]
+ c.Unlock()
+
+ if ok {
+ // Update the svc db for the local endpoint join right away
+ n.updateSvcRecord(ep, c.getLocalEps(nw), true)
+
+ c.Lock()
+ nw.localEps[ep.ID()] = ep
+
+ // If we had learned that from the kv store remove it
+ // from remote ep list now that we know that this is
+ // indeed a local endpoint
+ delete(nw.remoteEps, ep.ID())
+ c.Unlock()
+ return
+ }
+
+ nw = &netWatch{
+ localEps: make(map[string]*endpoint),
+ remoteEps: make(map[string]*endpoint),
+ }
+
+ // Update the svc db for the local endpoint join right away
+ // Do this before adding this ep to localEps so that we don't
+ // try to update this ep's container's svc records
+ n.updateSvcRecord(ep, c.getLocalEps(nw), true)
+
+ c.Lock()
+ nw.localEps[ep.ID()] = ep
+ nmap[n.ID()] = nw
+ nw.stopCh = make(chan struct{})
+ c.Unlock()
+
+ store := c.getStore(n.DataScope())
+ if store == nil {
+ return
+ }
+
+ if !store.Watchable() {
+ return
+ }
+
+ ch, err := store.Watch(n.getEpCnt(), nw.stopCh)
+ if err != nil {
+ logrus.Warnf("Error creating watch for network: %v", err)
+ return
+ }
+
+ go c.networkWatchLoop(nw, ep, ch)
+}
+
+func (c *controller) processEndpointDelete(nmap map[string]*netWatch, ep *endpoint) {
+ n := ep.getNetwork()
+ if !c.isDistributedControl() && n.Scope() == datastore.SwarmScope && n.driverIsMultihost() {
+ return
+ }
+
+ c.Lock()
+ nw, ok := nmap[n.ID()]
+
+ if ok {
+ delete(nw.localEps, ep.ID())
+ c.Unlock()
+
+ // Update the svc db about local endpoint leave right away
+ // Do this after we remove this ep from localEps so that we
+ // don't try to remove this svc record from this ep's container.
+ n.updateSvcRecord(ep, c.getLocalEps(nw), false)
+
+ c.Lock()
+ if len(nw.localEps) == 0 {
+ close(nw.stopCh)
+
+ // This is the last container going away for the network. Destroy
+ // this network's svc db entry
+ delete(c.svcRecords, n.ID())
+
+ delete(nmap, n.ID())
+ }
+ }
+ c.Unlock()
+}
+
+func (c *controller) watchLoop() {
+ for {
+ select {
+ case ep := <-c.watchCh:
+ c.processEndpointCreate(c.nmap, ep)
+ case ep := <-c.unWatchCh:
+ c.processEndpointDelete(c.nmap, ep)
+ }
+ }
+}
+
+func (c *controller) startWatch() {
+ if c.watchCh != nil {
+ return
+ }
+ c.watchCh = make(chan *endpoint)
+ c.unWatchCh = make(chan *endpoint)
+ c.nmap = make(map[string]*netWatch)
+
+ go c.watchLoop()
+}
+
+func (c *controller) networkCleanup() {
+ networks, err := c.getNetworksFromStore()
+ if err != nil {
+ logrus.Warnf("Could not retrieve networks from store(s) during network cleanup: %v", err)
+ return
+ }
+
+ for _, n := range networks {
+ if n.inDelete {
+ logrus.Infof("Removing stale network %s (%s)", n.Name(), n.ID())
+ if err := n.delete(true, true); err != nil {
+ logrus.Debugf("Error while removing stale network: %v", err)
+ }
+ }
+ }
+}
+
+var populateSpecial NetworkWalker = func(nw Network) bool {
+ if n := nw.(*network); n.hasSpecialDriver() && !n.ConfigOnly() {
+ if err := n.getController().addNetwork(n); err != nil {
+ logrus.Warnf("Failed to populate network %q with driver %q", nw.Name(), nw.Type())
+ }
+ }
+ return false
+}
--- /dev/null
+package libnetwork
+
+import (
+ "os"
+ "testing"
+
+ "github.com/docker/libkv/store"
+ "github.com/docker/libnetwork/datastore"
+)
+
+func TestBoltdbBackend(t *testing.T) {
+ defer os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+ testLocalBackend(t, "", "", nil)
+ defer os.Remove("/tmp/boltdb.db")
+ config := &store.Config{Bucket: "testBackend"}
+ testLocalBackend(t, "boltdb", "/tmp/boltdb.db", config)
+
+}
+
+func TestNoPersist(t *testing.T) {
+ cfgOptions, err := OptionBoltdbWithRandomDBFile()
+ if err != nil {
+ t.Fatalf("Error creating random boltdb file : %v", err)
+ }
+ ctrl, err := New(cfgOptions...)
+ if err != nil {
+ t.Fatalf("Error new controller: %v", err)
+ }
+ nw, err := ctrl.NewNetwork("host", "host", "", NetworkOptionPersist(false))
+ if err != nil {
+ t.Fatalf("Error creating default \"host\" network: %v", err)
+ }
+ ep, err := nw.CreateEndpoint("newendpoint", []EndpointOption{}...)
+ if err != nil {
+ t.Fatalf("Error creating endpoint: %v", err)
+ }
+ store := ctrl.(*controller).getStore(datastore.LocalScope).KVStore()
+ if exists, _ := store.Exists(datastore.Key(datastore.NetworkKeyPrefix, string(nw.ID()))); exists {
+ t.Fatalf("Network with persist=false should not be stored in KV Store")
+ }
+ if exists, _ := store.Exists(datastore.Key([]string{datastore.EndpointKeyPrefix, string(nw.ID()), string(ep.ID())}...)); exists {
+ t.Fatalf("Endpoint in Network with persist=false should not be stored in KV Store")
+ }
+ store.Close()
+}
--- /dev/null
+package libnetwork
+
+import (
+ "fmt"
+ "io/ioutil"
+ "testing"
+
+ "github.com/docker/libkv/store"
+ "github.com/docker/libnetwork/config"
+ "github.com/docker/libnetwork/datastore"
+ "github.com/docker/libnetwork/netlabel"
+ "github.com/docker/libnetwork/options"
+)
+
+func testZooKeeperBackend(t *testing.T) {
+ c, err := testNewController(t, "zk", "127.0.0.1:2181/custom_prefix")
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.Stop()
+}
+
+func testNewController(t *testing.T, provider, url string) (NetworkController, error) {
+ cfgOptions, err := OptionBoltdbWithRandomDBFile()
+ if err != nil {
+ return nil, err
+ }
+ cfgOptions = append(cfgOptions, config.OptionKVProvider(provider))
+ cfgOptions = append(cfgOptions, config.OptionKVProviderURL(url))
+ return New(cfgOptions...)
+}
+
+func testLocalBackend(t *testing.T, provider, url string, storeConfig *store.Config) {
+ cfgOptions := []config.Option{}
+ cfgOptions = append(cfgOptions, config.OptionLocalKVProvider(provider))
+ cfgOptions = append(cfgOptions, config.OptionLocalKVProviderURL(url))
+ cfgOptions = append(cfgOptions, config.OptionLocalKVProviderConfig(storeConfig))
+
+ driverOptions := options.Generic{}
+ genericOption := make(map[string]interface{})
+ genericOption[netlabel.GenericData] = driverOptions
+ cfgOptions = append(cfgOptions, config.OptionDriverConfig("host", genericOption))
+
+ ctrl, err := New(cfgOptions...)
+ if err != nil {
+ t.Fatalf("Error new controller: %v", err)
+ }
+ nw, err := ctrl.NewNetwork("host", "host", "")
+ if err != nil {
+ t.Fatalf("Error creating default \"host\" network: %v", err)
+ }
+ ep, err := nw.CreateEndpoint("newendpoint", []EndpointOption{}...)
+ if err != nil {
+ t.Fatalf("Error creating endpoint: %v", err)
+ }
+ store := ctrl.(*controller).getStore(datastore.LocalScope).KVStore()
+ if exists, err := store.Exists(datastore.Key(datastore.NetworkKeyPrefix, string(nw.ID()))); !exists || err != nil {
+ t.Fatalf("Network key should have been created.")
+ }
+ if exists, err := store.Exists(datastore.Key([]string{datastore.EndpointKeyPrefix, string(nw.ID()), string(ep.ID())}...)); !exists || err != nil {
+ t.Fatalf("Endpoint key should have been created.")
+ }
+ store.Close()
+
+ // test restore of local store
+ ctrl, err = New(cfgOptions...)
+ if err != nil {
+ t.Fatalf("Error creating controller: %v", err)
+ }
+ if _, err = ctrl.NetworkByID(nw.ID()); err != nil {
+ t.Fatalf("Error getting network %v", err)
+ }
+}
+
+// OptionBoltdbWithRandomDBFile function returns a random dir for local store backend
+func OptionBoltdbWithRandomDBFile() ([]config.Option, error) {
+ tmp, err := ioutil.TempFile("", "libnetwork-")
+ if err != nil {
+ return nil, fmt.Errorf("Error creating temp file: %v", err)
+ }
+ if err := tmp.Close(); err != nil {
+ return nil, fmt.Errorf("Error closing temp file: %v", err)
+ }
+ cfgOptions := []config.Option{}
+ cfgOptions = append(cfgOptions, config.OptionLocalKVProvider("boltdb"))
+ cfgOptions = append(cfgOptions, config.OptionLocalKVProviderURL(tmp.Name()))
+ sCfg := &store.Config{Bucket: "testBackend"}
+ cfgOptions = append(cfgOptions, config.OptionLocalKVProviderConfig(sCfg))
+ return cfgOptions, nil
+}
+
+func TestMultipleControllersWithSameStore(t *testing.T) {
+ cfgOptions, err := OptionBoltdbWithRandomDBFile()
+ if err != nil {
+ t.Fatalf("Error getting random boltdb configs %v", err)
+ }
+ ctrl1, err := New(cfgOptions...)
+ if err != nil {
+ t.Fatalf("Error new controller: %v", err)
+ }
+ defer ctrl1.Stop()
+ // Use the same boltdb file without closing the previous controller
+ _, err = New(cfgOptions...)
+ if err != nil {
+ t.Fatalf("Local store must support concurrent controllers")
+ }
+}
--- /dev/null
+FROM docker:18-dind
+
+RUN set -ex \
+ && echo "http://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \
+ && echo "http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
+RUN apk add --no-cache \
+ util-linux \
+ bridge-utils \
+ iptables \
+ iputils \
+ iproute2 \
+ ipvsadm \
+ conntrack-tools \
+ bash
+
+WORKDIR /bin
+COPY *.sh /bin/
+
+CMD /bin/run.sh
--- /dev/null
+Usage: docker run -v /var/run:/var/run --network host --privileged dockereng/network-diagnostic:support.sh
--- /dev/null
+#!/bin/sh
+
+# try to fetch the latest version from github
+wget -O support.sh.new https://raw.githubusercontent.com/docker/libnetwork/master/support/support.sh
+
+if [ "$?" -eq "0" ]; then
+ mv support.sh.new support.sh
+ chmod +x support.sh
+else
+ echo "issue fetching the latest support.sh, will use the container version"
+fi
+
+echo "run the support script"
+./support.sh
--- /dev/null
+#!/usr/bin/env bash
+
+# Required tools
+DOCKER="${DOCKER:-docker}"
+NSENTER="${NSENTER:-nsenter}"
+BRIDGE="${BRIDGE:-bridge}"
+IPTABLES="${IPTABLES:-iptables}"
+IPVSADM="${IPVSADM:-ipvsadm}"
+IP="${IP:-ip}"
+
+networks=0
+containers=0
+ip_overlap=0
+
+NSDIR=/var/run/docker/netns
+
+function die {
+ echo $*
+ exit 1
+}
+
+function echo_and_run {
+ echo "#" "$@"
+ eval $(printf '%q ' "$@") < /dev/stdout
+}
+
+function check_ip_overlap {
+ inspect=$1
+ overlap=$(echo "$inspect_output" | grep "EndpointIP\|VIP" | cut -d':' -f2 | sort | uniq -c | grep -v "1 ")
+ if [ ! -z "$overlap" ]; then
+ echo -e "\n\n*** OVERLAP on Network ${networkID} ***";
+ echo -e "${overlap} \n\n"
+ ((ip_overlap++))
+ else
+ echo "No overlap"
+ fi
+}
+
+type -P ${DOCKER} > /dev/null || echo "This tool requires the docker binary"
+type -P ${NSENTER} > /dev/null || echo "This tool requires nsenter"
+type -P ${BRIDGE} > /dev/null || echo "This tool requires bridge"
+type -P ${IPTABLES} > /dev/null || echo "This tool requires iptables"
+type -P ${IPVSADM} > /dev/null || echo "This tool requires ipvsadm"
+type -P ${IP} > /dev/null || echo "This tool requires ip"
+
+if ${DOCKER} network inspect --help | grep -q -- --verbose; then
+ NETINSPECT_VERBOSE_SUPPORT="--verbose"
+else
+ NETINSPECT_VERBOSE_SUPPORT=""
+fi
+
+echo "Host iptables"
+echo_and_run ${IPTABLES} -w1 -n -v -L -t filter | grep -v '^$'
+echo_and_run ${IPTABLES} -w1 -n -v -L -t nat | grep -v '^$'
+echo_and_run ${IPTABLES} -w1 -n -v -L -t mangle | grep -v '^$'
+printf "\n"
+
+echo "Host links addresses and routes"
+echo_and_run ${IP} -o link show
+echo_and_run ${IP} -o -4 address show
+echo_and_run ${IP} -4 route show
+printf "\n"
+
+echo "Overlay network configuration"
+for networkID in $(${DOCKER} network ls --no-trunc --filter driver=overlay -q) "ingress_sbox"; do
+ echo "nnn Network ${networkID}"
+ if [ "${networkID}" != "ingress_sbox" ]; then
+ nspath=(${NSDIR}/*-${networkID:0:10})
+ inspect_output=$(${DOCKER} network inspect ${NETINSPECT_VERBOSE_SUPPORT} ${networkID})
+ echo "$inspect_output"
+ check_ip_overlap $inspect_output
+ else
+ nspath=(${NSDIR}/${networkID})
+ fi
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IP} -o -4 address show
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IP} -4 route show
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IP} -4 neigh show
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${BRIDGE} fdb show
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IPTABLES} -w1 -n -v -L -t filter | grep -v '^$'
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IPTABLES} -w1 -n -v -L -t nat | grep -v '^$'
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IPTABLES} -w1 -n -v -L -t mangle | grep -v '^$'
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IPVSADM} -l -n
+ printf "\n"
+ ((networks++))
+done
+
+echo "Container network configuration"
+while read containerID status; do
+ echo "ccc Container ${containerID} state: ${status}"
+ ${DOCKER} container inspect ${containerID} --format 'Name:{{json .Name | printf "%s\n"}}Id:{{json .Id | printf "%s\n"}}Hostname:{{json .Config.Hostname | printf "%s\n"}}CreatedAt:{{json .Created | printf "%s\n"}}State:{{json .State|printf "%s\n"}}RestartCount:{{json .RestartCount | printf "%s\n" }}Labels:{{json .Config.Labels | printf "%s\n"}}NetworkSettings:{{json .NetworkSettings}}' | sed '/^State:/ {s/\\"/QUOTE/g; s/,"Output":"[^"]*"//g;}'
+ if [ ${status} = "Up" ]; then
+ nspath=$(docker container inspect --format {{.NetworkSettings.SandboxKey}} ${containerID})
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IP} -o -4 address show
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IP} -4 route show
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IP} -4 neigh show
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IPTABLES} -w1 -n -v -L -t nat | grep -v '^$'
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IPTABLES} -w1 -n -v -L -t mangle | grep -v '^$'
+ echo_and_run ${NSENTER} --net=${nspath[0]} ${IPVSADM} -l -n
+ ((containers++))
+ fi
+ printf "\n"
+done < <(${DOCKER} container ls -a --format '{{.ID}} {{.Status}}' |cut -d' ' -f1,2)
+
+echo -e "\n\n==SUMMARY=="
+echo -e "\t Processed $networks networks"
+echo -e "\t IP overlap found: $ip_overlap"
+echo -e "\t Processed $containers running containers"
--- /dev/null
+# LibNetwork Integration Tests
+
+Integration tests provide end-to-end testing of LibNetwork and Drivers.
+
+While unit tests verify the code is working as expected by relying on mocks and
+artificially created fixtures, integration tests actually use real docker
+engines and communicate to it through the CLI.
+
+Note that integration tests do **not** replace unit tests and Docker is used as a good use-case.
+
+As a rule of thumb, code should be tested thoroughly with unit tests.
+Integration tests on the other hand are meant to test a specific feature end to end.
+
+Integration tests are written in *bash* using the
+[bats](https://github.com/sstephenson/bats) framework.
+
+## Pre-Requisites
+
+1. Bats (https://github.com/sstephenson/bats#installing-bats-from-source)
+2. Docker Machine (https://github.com/docker/machine)
+3. Virtualbox (as a Docker machine driver)
+
+## Running integration tests
+
+* Start by [installing] (https://github.com/sstephenson/bats#installing-bats-from-source) *bats* on your system.
+* If not done already, [install](https://docs.docker.com/machine/) *docker-machine* into /usr/bin
+* Make sure Virtualbox is installed as well, which will be used by docker-machine as a driver to launch VMs
+
+In order to run all integration tests, pass *bats* the test path:
+```
+$ bats test/integration/daemon-configs.bats
+```
+
+
--- /dev/null
+#!/usr/bin/env bats
+
+load helpers
+
+export DRIVER=virtualbox
+export NAME="bats-$DRIVER-daemon-configs"
+export MACHINE_STORAGE_PATH=/tmp/machine-bats-daemon-test-$DRIVER
+# Default memsize is 1024MB and disksize is 20000MB
+# These values are defined in drivers/virtualbox/virtualbox.go
+export DEFAULT_MEMSIZE=1024
+export DEFAULT_DISKSIZE=20000
+export CUSTOM_MEMSIZE=1536
+export CUSTOM_DISKSIZE=10000
+export CUSTOM_CPUCOUNT=1
+export BAD_URL="http://dev.null:9111/bad.iso"
+
+function setup() {
+ # add sleep because vbox; ugh
+ sleep 1
+}
+
+findDiskSize() {
+ # SATA-0-0 is usually the boot2disk.iso image
+ # We assume that SATA 1-0 is root disk VMDK and grab this UUID
+ # e.g. "SATA-ImageUUID-1-0"="fb5f33a7-e4e3-4cb9-877c-f9415ae2adea"
+ # TODO(slashk): does this work on Windows ?
+ run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep SATA-ImageUUID-1-0 | cut -d'=' -f2"
+ run bash -c "VBoxManage showhdinfo $output | grep "Capacity:" | awk -F' ' '{ print $2 }'"
+}
+
+findMemorySize() {
+ run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep memory= | cut -d'=' -f2"
+}
+
+findCPUCount() {
+ run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep cpus= | cut -d'=' -f2"
+}
+
+buildMachineWithOldIsoCheckUpgrade() {
+ run wget https://github.com/boot2docker/boot2docker/releases/download/v1.4.1/boot2docker.iso -O $MACHINE_STORAGE_PATH/cache/boot2docker.iso
+ run machine create -d virtualbox $NAME
+ run machine upgrade $NAME
+}
+
+@test "$DRIVER: machine should not exist" {
+ run machine active $NAME
+ [ "$status" -eq 1 ]
+}
+
+@test "$DRIVER: VM should not exist" {
+ run VBoxManage showvminfo $NAME
+ [ "$status" -eq 1 ]
+}
+
+@test "$DRIVER: create" {
+ run machine create -d $DRIVER $NAME
+ [ "$status" -eq 0 ]
+}
+
+@test "$DRIVER: active" {
+ run machine active $NAME
+ [ "$status" -eq 0 ]
+}
+
+@test "$DRIVER: check default machine memory size" {
+ findMemorySize
+ [[ ${output} == "${DEFAULT_MEMSIZE}" ]]
+}
+
+@test "$DRIVER: check default machine disksize" {
+ findDiskSize
+ [[ ${output} == *"$DEFAULT_DISKSIZE"* ]]
+}
+
+@test "$DRIVER: test bridge-ip" {
+ run machine ssh $NAME sudo /etc/init.d/docker stop
+ run machine ssh $NAME sudo ifconfig docker0 down
+ run machine ssh $NAME sudo ip link delete docker0
+ BIP='--bip=172.168.45.1/24'
+ set_extra_config $BIP
+ cat ${TMP_EXTRA_ARGS_FILE} | machine ssh $NAME sudo tee /var/lib/boot2docker/profile
+ cat ${DAEMON_CFG_FILE} | machine ssh $NAME "sudo tee -a /var/lib/boot2docker/profile"
+ run machine ssh $NAME sudo /etc/init.d/docker start
+ run machine ssh $NAME ifconfig docker0
+ [ "$status" -eq 0 ]
+ [[ ${lines[1]} =~ "172.168.45.1" ]]
+}
+
+@test "$DRIVER: run busybox container" {
+ run machine ssh $NAME sudo cat /var/lib/boot2docker/profile
+ run docker $(machine config $NAME) run busybox echo hello world
+ [ "$status" -eq 0 ]
+}
+
+@test "$DRIVER: remove machine" {
+ run machine rm -f $NAME
+}
+
+# Cleanup of machine store should always be the last 'test'
+@test "$DRIVER: cleanup" {
+ run rm -rf $MACHINE_STORAGE_PATH
+ [ "$status" -eq 0 ]
+}
+
--- /dev/null
+CACERT=/var/lib/boot2docker/ca.pem
+SERVERCERT=/var/lib/boot2docker/server-key.pem
+SERVERKEY=/var/lib/boot2docker/server.pem
+DOCKER_TLS=no
--- /dev/null
+# -*- mode: sh -*-
+#!/usr/bin/env bats
+
+load helpers
+
+function test_single_network_connectivity() {
+ local nw_name start end
+
+ nw_name=${1}
+ start=1
+ end=${2}
+
+ # Create containers and connect them to the network
+ for i in `seq ${start} ${end}`;
+ do
+ dnet_cmd $(inst_id2port 1) container create container_${i}
+ net_connect 1 container_${i} ${nw_name}
+ done
+
+ # Now test connectivity between all the containers using service names
+ for i in `seq ${start} ${end}`;
+ do
+ if [ "${nw_name}" != "internal" ]; then
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_${i}) \
+ "ping -c 1 www.google.com"
+ fi
+ for j in `seq ${start} ${end}`;
+ do
+ if [ "$i" -eq "$j" ]; then
+ continue
+ fi
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_${i}) \
+ "ping -c 1 container_${j}"
+ done
+ done
+
+ if [ -n "$3" ]; then
+ return
+ fi
+
+ # Teardown the container connections and the network
+ for i in `seq ${start} ${end}`;
+ do
+ net_disconnect 1 container_${i} ${nw_name}
+ dnet_cmd $(inst_id2port 1) container rm container_${i}
+ done
+}
+
+@test "Test default bridge network" {
+ skip_for_circleci
+
+ echo $(docker ps)
+ test_single_network_connectivity bridge 3
+}
+
+
+@test "Test default network dnet restart" {
+ skip_for_circleci
+
+ echo $(docker ps)
+
+ for iter in `seq 1 2`;
+ do
+ test_single_network_connectivity bridge 3
+ if [ "$iter" -eq 1 ]; then
+ docker restart dnet-1-bridge
+ wait_for_dnet $(inst_id2port 1) dnet-1-bridge
+ fi
+ done
+}
+
+@test "Test default network dnet ungraceful restart" {
+ skip_for_circleci
+
+ echo $(docker ps)
+
+ for iter in `seq 1 2`;
+ do
+ if [ "$iter" -eq 1 ]; then
+ test_single_network_connectivity bridge 3 skip
+ docker restart dnet-1-bridge
+ wait_for_dnet $(inst_id2port 1) dnet-1-bridge
+ else
+ test_single_network_connectivity bridge 3
+ fi
+ done
+}
+
+@test "Test bridge network" {
+ skip_for_circleci
+
+ echo $(docker ps)
+ dnet_cmd $(inst_id2port 1) network create -d bridge singlehost
+ test_single_network_connectivity singlehost 3
+ dnet_cmd $(inst_id2port 1) network rm singlehost
+}
+
+@test "Test bridge network dnet restart" {
+ skip_for_circleci
+
+ echo $(docker ps)
+ dnet_cmd $(inst_id2port 1) network create -d bridge singlehost
+
+ for iter in `seq 1 2`;
+ do
+ test_single_network_connectivity singlehost 3
+ if [ "$iter" -eq 1 ]; then
+ docker restart dnet-1-bridge
+ wait_for_dnet $(inst_id2port 1) dnet-1-bridge
+ fi
+ done
+
+ dnet_cmd $(inst_id2port 1) network rm singlehost
+}
+
+@test "Test bridge network dnet ungraceful restart" {
+ skip_for_circleci
+
+ echo $(docker ps)
+ dnet_cmd $(inst_id2port 1) network create -d bridge singlehost
+
+ for iter in `seq 1 2`;
+ do
+ if [ "$iter" -eq 1 ]; then
+ test_single_network_connectivity singlehost 3 skip
+ docker restart dnet-1-bridge
+ wait_for_dnet $(inst_id2port 1) dnet-1-bridge
+ else
+ test_single_network_connectivity singlehost 3
+ fi
+ done
+
+ dnet_cmd $(inst_id2port 1) network rm singlehost
+}
+
+@test "Test multiple bridge networks" {
+ skip_for_circleci
+
+ echo $(docker ps)
+
+ start=1
+ end=3
+
+ for i in `seq ${start} ${end}`;
+ do
+ dnet_cmd $(inst_id2port 1) container create container_${i}
+ for j in `seq ${start} ${end}`;
+ do
+ if [ "$i" -eq "$j" ]; then
+ continue
+ fi
+
+ if [ "$i" -lt "$j" ]; then
+ dnet_cmd $(inst_id2port 1) network create -d bridge sh${i}${j}
+ nw=sh${i}${j}
+ else
+ nw=sh${j}${i}
+ fi
+
+ osvc="svc${i}${j}"
+ dnet_cmd $(inst_id2port 1) service publish ${osvc}.${nw}
+ dnet_cmd $(inst_id2port 1) service attach container_${i} ${osvc}.${nw}
+ done
+ done
+
+ for i in `seq ${start} ${end}`;
+ do
+ echo ${i1}
+ for j in `seq ${start} ${end}`;
+ do
+ echo ${j1}
+ if [ "$i" -eq "$j" ]; then
+ continue
+ fi
+
+ osvc="svc${j}${i}"
+ echo "pinging ${osvc}"
+ dnet_cmd $(inst_id2port 1) service ls
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_${i}) "cat /etc/hosts"
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_${i}) "ping -c 1 ${osvc}"
+ done
+ done
+
+ svcs=(
+ 0,0
+ 2,3
+ 1,3
+ 1,2
+ )
+
+ echo "Test connectivity failure"
+ for i in `seq ${start} ${end}`;
+ do
+ IFS=, read a b <<<"${svcs[$i]}"
+ osvc="svc${a}${b}"
+ echo "pinging ${osvc}"
+ runc_nofail $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_${i}) "ping -c 1 ${osvc}"
+ [ "${status}" -ne 0 ]
+ done
+
+ for i in `seq ${start} ${end}`;
+ do
+ for j in `seq ${start} ${end}`;
+ do
+ if [ "$i" -eq "$j" ]; then
+ continue
+ fi
+
+ if [ "$i" -lt "$j" ]; then
+ nw=sh${i}${j}
+ else
+ nw=sh${j}${i}
+ fi
+
+ osvc="svc${i}${j}"
+ dnet_cmd $(inst_id2port 1) service detach container_${i} ${osvc}.${nw}
+ dnet_cmd $(inst_id2port 1) service unpublish ${osvc}.${nw}
+
+ done
+ dnet_cmd $(inst_id2port 1) container rm container_${i}
+ done
+
+ for i in `seq ${start} ${end}`;
+ do
+ for j in `seq ${start} ${end}`;
+ do
+ if [ "$i" -eq "$j" ]; then
+ continue
+ fi
+
+ if [ "$i" -lt "$j" ]; then
+ dnet_cmd $(inst_id2port 1) network rm sh${i}${j}
+ fi
+ done
+ done
+
+}
+
+@test "Test bridge network alias support" {
+ skip_for_circleci
+ dnet_cmd $(inst_id2port 1) network create -d bridge br1
+ dnet_cmd $(inst_id2port 1) container create container_1
+ net_connect 1 container_1 br1 container_2:c2
+ dnet_cmd $(inst_id2port 1) container create container_2
+ net_connect 1 container_2 br1
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 container_2"
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 c2"
+ net_disconnect 1 container_1 br1
+ net_disconnect 1 container_2 br1
+ dnet_cmd $(inst_id2port 1) container rm container_1
+ dnet_cmd $(inst_id2port 1) container rm container_2
+ dnet_cmd $(inst_id2port 1) network rm br1
+}
+
+
+@test "Test bridge network global alias support" {
+ skip_for_circleci
+ dnet_cmd $(inst_id2port 1) network create -d bridge br1
+ dnet_cmd $(inst_id2port 1) network create -d bridge br2
+ dnet_cmd $(inst_id2port 1) container create container_1
+ net_connect 1 container_1 br1 : c1
+ dnet_cmd $(inst_id2port 1) container create container_2
+ net_connect 1 container_2 br1 : shared
+ dnet_cmd $(inst_id2port 1) container create container_3
+ net_connect 1 container_3 br1 : shared
+
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_2) "ping -c 1 container_1"
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_2) "ping -c 1 c1"
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 container_2"
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 shared"
+
+ net_disconnect 1 container_2 br1
+ dnet_cmd $(inst_id2port 1) container rm container_2
+
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 container_3"
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 shared"
+
+ net_disconnect 1 container_1 br1
+ dnet_cmd $(inst_id2port 1) container rm container_1
+ net_disconnect 1 container_3 br1
+ dnet_cmd $(inst_id2port 1) container rm container_3
+
+ dnet_cmd $(inst_id2port 1) network rm br1
+}
+
+@test "Test bridge network internal network" {
+ skip_for_circleci
+
+ echo $(docker ps)
+ dnet_cmd $(inst_id2port 1) network create -d bridge --internal internal
+ dnet_cmd $(inst_id2port 1) container create container_1
+ # connects to internal network, confirm it can't communicate with outside world
+ net_connect 1 container_1 internal
+ run runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 8.8.8.8"
+ [[ "$output" == *"1 packets transmitted, 0 packets received, 100% packet loss"* ]]
+ net_disconnect 1 container_1 internal
+ # connects to bridge network, confirm it can communicate with outside world
+ net_connect 1 container_1 bridge
+ runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 8.8.8.8"
+ net_disconnect 1 container_1 bridge
+ dnet_cmd $(inst_id2port 1) container rm container_1
+ # test communications within internal network
+ test_single_network_connectivity internal 3
+ dnet_cmd $(inst_id2port 1) network rm internal
+}
--- /dev/null
+#!/usr/bin/env bats
+
+load helpers
+
+@test "Test dnet custom port" {
+ start_dnet 1 a 4567
+ dnet_cmd 4567 network ls
+ stop_dnet 1 a
+}
+
+@test "Test dnet invalid custom port" {
+ start_dnet 1 b 4567
+ run dnet_cmd 4568 network ls
+ echo ${output}
+ [ "$status" -ne 0 ]
+ stop_dnet 1 b
+}
+
+@test "Test dnet invalid params" {
+ start_dnet 1 c
+ run dnet_cmd 8080 network ls
+ echo ${output}
+ [ "$status" -ne 0 ]
+ run ./bin/dnet -H=unix://var/run/dnet.sock network ls
+ echo ${output}
+ [ "$status" -ne 0 ]
+ run ./bin/dnet -H= -l=invalid network ls
+ echo ${output}
+ [ "$status" -ne 0 ]
+ stop_dnet 1 c
+}
--- /dev/null
+function get_docker_bridge_ip() {
+ echo $(docker run --rm -it busybox ip route show | grep default | cut -d" " -f3)
+}
+
+function inst_id2port() {
+ echo $((41000+${1}-1))
+}
+
+function dnet_container_name() {
+ echo dnet-$1-$2
+}
+
+function dnet_container_ip() {
+ docker inspect --format '{{.NetworkSettings.IPAddress}}' dnet-$1-$2
+}
+
+function get_sbox_id() {
+ local line
+
+ line=$(dnet_cmd $(inst_id2port ${1}) service ls | grep ${2})
+ echo ${line} | cut -d" " -f5
+}
+
+function net_connect() {
+ local al gl
+ if [ -n "$4" ]; then
+ if [ "${4}" != ":" ]; then
+ al="--alias=${4}"
+ fi
+ fi
+ if [ -n "$5" ]; then
+ gl="--alias=${5}"
+ fi
+ dnet_cmd $(inst_id2port ${1}) service publish $gl ${2}.${3}
+ dnet_cmd $(inst_id2port ${1}) service attach $al ${2} ${2}.${3}
+}
+
+function net_disconnect() {
+ dnet_cmd $(inst_id2port ${1}) service detach ${2} ${2}.${3}
+ dnet_cmd $(inst_id2port ${1}) service unpublish ${2}.${3}
+}
+
+function start_consul() {
+ stop_consul
+ docker run -d \
+ --name=pr_consul \
+ -p 8500:8500 \
+ -p 8300-8302:8300-8302/tcp \
+ -p 8300-8302:8300-8302/udp \
+ -h consul \
+ progrium/consul -server -bootstrap
+ sleep 2
+}
+
+function stop_consul() {
+ echo "consul started"
+ docker stop pr_consul || true
+ # You cannot destroy a container in Circle CI. So do not attempt destroy in circleci
+ if [ -z "$CIRCLECI" ]; then
+ docker rm -f pr_consul || true
+ fi
+}
+
+hrun() {
+ local e E T oldIFS
+ [[ ! "$-" =~ e ]] || e=1
+ [[ ! "$-" =~ E ]] || E=1
+ [[ ! "$-" =~ T ]] || T=1
+ set +e
+ set +E
+ set +T
+ output="$("$@" 2>&1)"
+ status="$?"
+ oldIFS=$IFS
+ IFS=$'\n' lines=($output)
+ [ -z "$e" ] || set -e
+ [ -z "$E" ] || set -E
+ [ -z "$T" ] || set -T
+ IFS=$oldIFS
+}
+
+function wait_for_dnet() {
+ local hport
+
+ hport=$1
+ echo "waiting on dnet to come up ..."
+ for i in `seq 1 10`;
+ do
+ hrun ./bin/dnet -H tcp://127.0.0.1:${hport} network ls
+ echo ${output}
+ if [ "$status" -eq 0 ]; then
+ return
+ fi
+
+ if [[ "${lines[1]}" =~ .*EOF.* ]]
+ then
+ docker logs ${2}
+ fi
+ echo "still waiting after ${i} seconds"
+ sleep 1
+ done
+}
+
+function parse_discovery_str() {
+ local d provider address
+ discovery=$1
+ provider=$(echo ${discovery} | cut -d":" -f1)
+ address=$(echo ${discovery} | cut -d":" -f2):$(echo ${discovery} | cut -d":" -f3)
+ address=${address:2}
+ echo "${discovery} ${provider} ${address}"
+}
+
+function start_dnet() {
+ local inst suffix name hport cport hopt store bridge_ip labels tomlfile nip
+ local discovery provider address
+
+ inst=$1
+ shift
+ suffix=$1
+ shift
+
+ store=$(echo $suffix | cut -d":" -f1)
+ nip=$(echo $suffix | cut -s -d":" -f2)
+
+
+ stop_dnet ${inst} ${store}
+ name=$(dnet_container_name ${inst} ${store})
+
+ hport=$((41000+${inst}-1))
+ cport=2385
+ hopt=""
+
+ while [ -n "$1" ]
+ do
+ if [[ "$1" =~ ^[0-9]+$ ]]
+ then
+ hport=$1
+ cport=$1
+ hopt="-H tcp://0.0.0.0:${cport}"
+ else
+ store=$1
+ fi
+ shift
+ done
+
+ bridge_ip=$(get_docker_bridge_ip)
+
+ echo "start_dnet parsed values: " ${inst} ${suffix} ${name} ${hport} ${cport} ${hopt} ${store}
+
+ mkdir -p /tmp/dnet/${name}
+ tomlfile="/tmp/dnet/${name}/libnetwork.toml"
+
+ # Try discovery URLs with or without path
+ neigh_ip=""
+ neighbors=""
+ if [ "$store" = "zookeeper" ]; then
+ read discovery provider address < <(parse_discovery_str zk://${bridge_ip}:2182)
+ elif [ "$store" = "etcd" ]; then
+ read discovery provider address < <(parse_discovery_str etcd://${bridge_ip}:42000/custom_prefix)
+ elif [ "$store" = "consul" ]; then
+ read discovery provider address < <(parse_discovery_str consul://${bridge_ip}:8500/custom_prefix)
+ else
+ if [ "$nip" != "" ]; then
+ neighbors=${nip}
+ fi
+
+ discovery=""
+ provider=""
+ address=""
+ fi
+
+ if [ "$discovery" != "" ]; then
+ cat > ${tomlfile} <<EOF
+title = "LibNetwork Configuration file for ${name}"
+
+[daemon]
+ debug = false
+[cluster]
+ discovery = "${discovery}"
+ Heartbeat = 10
+[scopes]
+ [scopes.global]
+ [scopes.global.client]
+ provider = "${provider}"
+ address = "${address}"
+EOF
+ else
+ cat > ${tomlfile} <<EOF
+title = "LibNetwork Configuration file for ${name}"
+
+[daemon]
+ debug = false
+[orchestration]
+ agent = true
+ bind = "eth0"
+ peer = "${neighbors}"
+EOF
+ fi
+
+ cat ${tomlfile}
+ docker run \
+ -d \
+ --hostname=$(echo ${name} | sed s/_/-/g) \
+ --name=${name} \
+ --privileged \
+ -p ${hport}:${cport} \
+ -e _OVERLAY_HOST_MODE \
+ -v $(pwd)/:/go/src/github.com/docker/libnetwork \
+ -v /tmp:/tmp \
+ -v $(pwd)/${TMPC_ROOT}:/scratch \
+ -v /usr/local/bin/runc:/usr/local/bin/runc \
+ -w /go/src/github.com/docker/libnetwork \
+ mrjana/golang ./bin/dnet -d -D ${hopt} -c ${tomlfile}
+
+ wait_for_dnet $(inst_id2port ${inst}) ${name}
+}
+
+function start_ovrouter() {
+ local name=${1}
+ local parent=${2}
+
+ docker run \
+ -d \
+ --name=${name} \
+ --net=container:${parent} \
+ --volumes-from ${parent} \
+ -w /go/src/github.com/docker/libnetwork \
+ mrjana/golang ./cmd/ovrouter/ovrouter eth0
+}
+
+function skip_for_circleci() {
+ if [ -n "$CIRCLECI" ]; then
+ skip
+ fi
+}
+
+function stop_dnet() {
+ local name
+
+ name=$(dnet_container_name $1 $2)
+ rm -rf /tmp/dnet/${name} || true
+ docker stop ${name} || true
+ # You cannot destroy a container in Circle CI. So do not attempt destroy in circleci
+ if [ -z "$CIRCLECI" ]; then
+ docker rm -f ${name} || true
+ fi
+}
+
+function dnet_cmd() {
+ local hport
+
+ hport=$1
+ shift
+ ./bin/dnet -H tcp://127.0.0.1:${hport} $*
+}
+
+function dnet_exec() {
+ docker exec -it ${1} bash -c "trap \"echo SIGHUP\" SIGHUP; $2"
+}
+
+function runc() {
+ local dnet
+
+ dnet=${1}
+ shift
+ dnet_exec ${dnet} "cp /var/lib/docker/network/files/${1}*/* /scratch/rootfs/etc"
+ dnet_exec ${dnet} "mkdir -p /var/run/netns"
+ dnet_exec ${dnet} "touch /var/run/netns/c && mount -o bind /var/run/docker/netns/${1} /var/run/netns/c"
+ dnet_exec ${dnet} "ip netns exec c unshare -fmuip --mount-proc chroot \"/scratch/rootfs\" /bin/sh -c \"/bin/mount -t proc proc /proc && ${2}\""
+ dnet_exec ${dnet} "umount /var/run/netns/c && rm /var/run/netns/c"
+}
+
+function runc_nofail() {
+ local dnet
+
+ dnet=${1}
+ shift
+ dnet_exec ${dnet} "cp /var/lib/docker/network/files/${1}*/* /scratch/rootfs/etc"
+ dnet_exec ${dnet} "mkdir -p /var/run/netns"
+ dnet_exec ${dnet} "touch /var/run/netns/c && mount -o bind /var/run/docker/netns/${1} /var/run/netns/c"
+ set +e
+ dnet_exec ${dnet} "ip netns exec c unshare -fmuip --mount-proc chroot \"/scratch/rootfs\" /bin/sh -c \"/bin/mount -t proc proc /proc && ${2}\""
+ status="$?"
+ set -e
+ dnet_exec ${dnet} "umount /var/run/netns/c && rm /var/run/netns/c"
+}
+
+function start_etcd() {
+ local bridge_ip
+ stop_etcd
+
+ bridge_ip=$(get_docker_bridge_ip)
+ docker run -d \
+ --net=host \
+ --name=dn_etcd \
+ mrjana/etcd --listen-client-urls http://0.0.0.0:42000 \
+ --advertise-client-urls http://${bridge_ip}:42000
+ sleep 2
+}
+
+function stop_etcd() {
+ docker stop dn_etcd || true
+ # You cannot destroy a container in Circle CI. So do not attempt destroy in circleci
+ if [ -z "$CIRCLECI" ]; then
+ docker rm -f dn_etcd || true
+ fi
+}
+
+function start_zookeeper() {
+ stop_zookeeper
+ docker run -d \
+ --name=zookeeper_server \
+ -p 2182:2181 \
+ -h zookeeper \
+ dnephin/docker-zookeeper:3.4.6
+ sleep 2
+}
+
+function stop_zookeeper() {
+ echo "zookeeper started"
+ docker stop zookeeper_server || true
+ # You cannot destroy a container in Circle CI. So do not attempt destroy in circleci
+ if [ -z "$CIRCLECI" ]; then
+ docker rm -f zookeeper_server || true
+ fi
+}
+
+function test_overlay() {
+ dnet_suffix=$1
+
+ echo $(docker ps)
+
+ start=1
+ end=3
+ # Setup overlay network and connect containers ot it
+ if [ -z "${2}" -o "${2}" != "skip_add" ]; then
+ if [ -z "${2}" -o "${2}" != "internal" ]; then
+ dnet_cmd $(inst_id2port 1) network create -d overlay multihost
+ else
+ dnet_cmd $(inst_id2port 1) network create -d overlay --internal multihost
+ fi
+ fi
+
+ for i in `seq ${start} ${end}`;
+ do
+ dnet_cmd $(inst_id2port $i) container create container_${i}
+ net_connect ${i} container_${i} multihost
+ done
+
+ # Now test connectivity between all the containers using service names
+ for i in `seq ${start} ${end}`;
+ do
+ if [ -z "${2}" -o "${2}" != "internal" ]; then
+ runc $(dnet_container_name $i $dnet_suffix) $(get_sbox_id ${i} container_${i}) \
+ "ping -c 1 www.google.com"
+ else
+ default_route=`runc $(dnet_container_name $i $dnet_suffix) $(get_sbox_id ${i} container_${i}) "ip route | grep default"`
+ [ "$default_route" = "" ]
+ fi
+ for j in `seq ${start} ${end}`;
+ do
+ if [ "$i" -eq "$j" ]; then
+ continue
+ fi
+ runc $(dnet_container_name $i $dnet_suffix) $(get_sbox_id ${i} container_${i}) \
+ "ping -c 1 container_$j"
+ done
+ done
+
+ # Setup bridge network and connect containers ot it
+ if [ -z "${2}" -o "${2}" != "skip_add" ]; then
+ if [ -z "${2}" -o "${2}" != "internal" ]; then
+ dnet_cmd $(inst_id2port 1) network create -d bridge br1
+ dnet_cmd $(inst_id2port 1) network create -d bridge br2
+ net_connect ${start} container_${start} br1
+ net_connect ${start} container_${start} br2
+
+ # Make sure external connectivity works
+ runc $(dnet_container_name ${start} $dnet_suffix) $(get_sbox_id ${start} container_${start}) \
+ "ping -c 1 www.google.com"
+ net_disconnect ${start} container_${start} br1
+ net_disconnect ${start} container_${start} br2
+
+ # Make sure external connectivity works
+ runc $(dnet_container_name ${start} $dnet_suffix) $(get_sbox_id ${start} container_${start}) \
+ "ping -c 1 www.google.com"
+ dnet_cmd $(inst_id2port 1) network rm br1
+ dnet_cmd $(inst_id2port 1) network rm br2
+
+ # Disconnect from overlay network
+ net_disconnect ${start} container_${start} multihost
+
+ # Connect to overlay network again
+ net_connect ${start} container_${start} multihost
+
+ # Make sure external connectivity still works
+ runc $(dnet_container_name ${start} $dnet_suffix) $(get_sbox_id ${start} container_${start}) \
+ "ping -c 1 www.google.com"
+ fi
+ fi
+
+ # Teardown the container connections and the network
+ for i in `seq ${start} ${end}`;
+ do
+ net_disconnect ${i} container_${i} multihost
+ dnet_cmd $(inst_id2port $i) container rm container_${i}
+ done
+
+ if [ -z "${2}" -o "${2}" != "skip_rm" ]; then
+ dnet_cmd $(inst_id2port 2) network rm multihost
+ fi
+}
+
+function check_etchosts() {
+ local dnet sbid retval
+ dnet=${1}
+ shift
+ sbid=${1}
+ shift
+
+ retval="true"
+
+ for i in $*;
+ do
+ run runc ${dnet} ${sbid} "cat /etc/hosts"
+ if [ "$status" -ne 0 ]; then
+ retval="${output}"
+ break
+ fi
+
+ line=$(echo ${output} | grep ${i})
+ if [ "${line}" == "" ]; then
+ retval="false"
+ fi
+ done
+
+ echo ${retval}
+}
+
+function test_overlay_singlehost() {
+ dnet_suffix=$1
+ shift
+
+ echo $(docker ps)
+
+ start=1
+ end=3
+ # Setup overlay network and connect containers ot it
+ dnet_cmd $(inst_id2port 1) network create -d overlay multihost
+ for i in `seq ${start} ${end}`;
+ do
+ dnet_cmd $(inst_id2port 1) container create container_${i}
+ net_connect 1 container_${i} multihost
+ done
+
+ # Now test connectivity between all the containers using service names
+ for i in `seq ${start} ${end}`;
+ do
+ for j in `seq ${start} ${end}`;
+ do
+ if [ "$i" -eq "$j" ]; then
+ continue
+ fi
+ runc $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 container_${i}) \
+ "ping -c 1 container_$j"
+ done
+ done
+
+ # Teardown the container connections and the network
+ for i in `seq ${start} ${end}`;
+ do
+ net_disconnect 1 container_${i} multihost
+ dnet_cmd $(inst_id2port 1) container rm container_${i}
+ done
+
+ dnet_cmd $(inst_id2port 1) network rm multihost
+}
+
+function test_overlay_hostmode() {
+ dnet_suffix=$1
+ shift
+
+ echo $(docker ps)
+
+ start=1
+ end=2
+ # Setup overlay network and connect containers ot it
+ dnet_cmd $(inst_id2port 1) network create -d overlay multihost1
+ dnet_cmd $(inst_id2port 1) network create -d overlay multihost2
+ dnet_cmd $(inst_id2port 1) network ls
+
+ for i in `seq ${start} ${end}`;
+ do
+ dnet_cmd $(inst_id2port 1) container create mh1_${i}
+ net_connect 1 mh1_${i} multihost1
+ done
+
+ for i in `seq ${start} ${end}`;
+ do
+ dnet_cmd $(inst_id2port 1) container create mh2_${i}
+ net_connect 1 mh2_${i} multihost2
+ done
+
+ # Now test connectivity between all the containers using service names
+ for i in `seq ${start} ${end}`;
+ do
+ for j in `seq ${start} ${end}`;
+ do
+ if [ "$i" -eq "$j" ]; then
+ continue
+ fi
+
+ # Find the IP addresses of the j containers on both networks
+ hrun runc $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh1_${i}) "nslookup mh1_$j"
+ mh1_j_ip=$(echo ${output} | awk '{print $11}')
+
+ hrun runc $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh2_${i}) "nslookup mh2_$j"
+ mh2_j_ip=$(echo ${output} | awk '{print $11}')
+
+ # Ping the j containers in the same network and ensure they are successful
+ runc $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh1_${i}) \
+ "ping -c 1 mh1_$j"
+ runc $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh2_${i}) \
+ "ping -c 1 mh2_$j"
+
+ # Try pinging j container IPs from the container in the other network and make sure that they are not successful
+ runc_nofail $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh1_${i}) "ping -c 1 ${mh2_j_ip}"
+ [ "${status}" -ne 0 ]
+
+ runc_nofail $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh2_${i}) "ping -c 1 ${mh1_j_ip}"
+ [ "${status}" -ne 0 ]
+
+ # Try pinging the j container IPS from the host(dnet container in this case) and make syre that they are not successful
+ hrun docker exec -it $(dnet_container_name 1 $dnet_suffix) "ping -c 1 ${mh1_j_ip}"
+ [ "${status}" -ne 0 ]
+
+ hrun docker exec -it $(dnet_container_name 1 $dnet_suffix) "ping -c 1 ${mh2_j_ip}"
+ [ "${status}" -ne 0 ]
+ done
+ done
+
+ # Teardown the container connections and the network
+ for i in `seq ${start} ${end}`;
+ do
+ net_disconnect 1 mh1_${i} multihost1
+ dnet_cmd $(inst_id2port 1) container rm mh1_${i}
+ done
+
+ for i in `seq ${start} ${end}`;
+ do
+ net_disconnect 1 mh2_${i} multihost2
+ dnet_cmd $(inst_id2port 1) container rm mh2_${i}
+ done
+
+ dnet_cmd $(inst_id2port 1) network rm multihost1
+ dnet_cmd $(inst_id2port 1) network rm multihost2
+}
--- /dev/null
+# -*- mode: sh -*-
+#!/usr/bin/env bats
+
+load helpers
+
+function is_network_exist() {
+ line=$(dnet_cmd $(inst_id2port $1) network ls | grep ${2})
+ name=$(echo ${line} | cut -d" " -f2)
+ driver=$(echo ${line} | cut -d" " -f3)
+ if [ "$name" == "$2" -a "$driver" == "$3" ]; then
+ echo "true"
+ else
+ echo "false"
+ fi
+}
+
+@test "Test multinode network create" {
+ echo $(docker ps)
+ for i in `seq 1 3`;
+ do
+ oname="mh$i"
+ run dnet_cmd $(inst_id2port $i) network create -d test ${oname}
+ echo ${output}
+ [ "$status" -eq 0 ]
+
+ for j in `seq 1 3`;
+ do
+ result=$(is_network_exist $j ${oname} test)
+ [ "$result" = "true" ]
+ done
+
+ # Always try to remove the network from the second node
+ dnet_cmd $(inst_id2port 2) network rm ${oname}
+ echo "delete ${oname}"
+ nresult=$(is_network_exist 1 ${oname} test)
+ echo ${nresult}
+ dnet_cmd $(inst_id2port 1) network ls
+ [ "$nresult" = "false" ]
+ done
+}
+
+@test "Test multinode service create" {
+ echo $(docker ps)
+ dnet_cmd $(inst_id2port 1) network create -d test multihost
+ for i in `seq 1 3`;
+ do
+ oname="svc$i"
+ run dnet_cmd $(inst_id2port $i) service publish ${oname}.multihost
+ echo ${output}
+ [ "$status" -eq 0 ]
+
+ for j in `seq 1 3`;
+ do
+ run dnet_cmd $(inst_id2port $j) service ls
+ [ "$status" -eq 0 ]
+ echo ${output}
+ echo ${lines[1]}
+ svc=$(echo ${lines[1]} | cut -d" " -f2)
+ network=$(echo ${lines[1]} | cut -d" " -f3)
+ echo ${svc} ${network}
+ [ "$network" = "multihost" ]
+ [ "$svc" = "${oname}" ]
+ done
+ dnet_cmd $(inst_id2port 2) service unpublish ${oname}.multihost
+ done
+ dnet_cmd $(inst_id2port 3) network rm multihost
+}
+
+@test "Test multinode service attach" {
+ echo $(docker ps)
+ dnet_cmd $(inst_id2port 2) network create -d test multihost
+ dnet_cmd $(inst_id2port 3) service publish svc.multihost
+ for i in `seq 1 3`;
+ do
+ dnet_cmd $(inst_id2port $i) container create container_${i}
+ dnet_cmd $(inst_id2port $i) service attach container_${i} svc.multihost
+ run dnet_cmd $(inst_id2port $i) service ls
+ [ "$status" -eq 0 ]
+ echo ${output}
+ echo ${lines[1]}
+ container=$(echo ${lines[1]} | cut -d" " -f4)
+ [ "$container" = "container_$i" ]
+ for j in `seq 1 3`;
+ do
+ if [ "$j" = "$i" ]; then
+ continue
+ fi
+ dnet_cmd $(inst_id2port $j) container create container_${j}
+ run dnet_cmd $(inst_id2port $j) service attach container_${j} svc.multihost
+ echo ${output}
+ [ "$status" -ne 0 ]
+ dnet_cmd $(inst_id2port $j) container rm container_${j}
+ done
+ dnet_cmd $(inst_id2port $i) service detach container_${i} svc.multihost
+ dnet_cmd $(inst_id2port $i) container rm container_${i}
+ done
+ dnet_cmd $(inst_id2port 1) service unpublish svc.multihost
+ dnet_cmd $(inst_id2port 3) network rm multihost
+}
+
+@test "Test multinode network and service delete" {
+ echo $(docker ps)
+ for i in `seq 1 3`;
+ do
+ oname="mh$i"
+ osvc="svc$i"
+ dnet_cmd $(inst_id2port $i) network create -d test ${oname}
+ dnet_cmd $(inst_id2port $i) service publish ${osvc}.${oname}
+ dnet_cmd $(inst_id2port $i) container create container_${i}
+ dnet_cmd $(inst_id2port $i) network ls
+ dnet_cmd $(inst_id2port $i) service attach container_${i} ${osvc}.${oname}
+
+ for j in `seq 1 3`;
+ do
+ run dnet_cmd $(inst_id2port $i) service unpublish ${osvc}.${oname}
+ echo ${output}
+ [ "$status" -ne 0 ]
+ run dnet_cmd $(inst_id2port $j) network rm ${oname}
+ echo ${output}
+ [ "$status" -ne 0 ]
+ done
+
+ dnet_cmd $(inst_id2port $i) service detach container_${i} ${osvc}.${oname}
+ dnet_cmd $(inst_id2port $i) container rm container_${i}
+
+ # Always try to remove the service from different nodes
+ dnet_cmd $(inst_id2port 2) service unpublish ${osvc}.${oname}
+ dnet_cmd $(inst_id2port 3) network rm ${oname}
+ done
+}
--- /dev/null
+# -*- mode: sh -*-
+#!/usr/bin/env bats
+
+load helpers
+
+@test "Test overlay network hostmode with consul" {
+ skip_for_circleci
+ test_overlay_hostmode consul
+}
--- /dev/null
+# -*- mode: sh -*-
+#!/usr/bin/env bats
+
+load helpers
+
+@test "Test overlay network with consul" {
+ skip_for_circleci
+ test_overlay consul
+}
+
+@test "Test overlay network singlehost with consul" {
+ skip_for_circleci
+ test_overlay_singlehost consul
+}
+
+@test "Test overlay network with dnet restart" {
+ skip_for_circleci
+ test_overlay consul skip_rm
+ docker restart dnet-1-consul
+ wait_for_dnet $(inst_id2port 1) dnet-1-consul
+ docker restart dnet-2-consul
+ wait_for_dnet $(inst_id2port 2) dnet-2-consul
+ docker restart dnet-3-consul
+ wait_for_dnet $(inst_id2port 3) dnet-3-consul
+ test_overlay consul skip_add
+}
+
+@test "Test overlay network internal network with consul" {
+ skip_for_circleci
+ test_overlay consul internal
+}
+
+@test "Test overlay network with dnet ungraceful shutdown" {
+ skip_for_circleci
+ dnet_cmd $(inst_id2port 1) network create -d overlay multihost
+ start=1
+ end=3
+ for i in `seq ${start} ${end}`;
+ do
+ dnet_cmd $(inst_id2port $i) container create container_${i}
+ net_connect ${i} container_${i} multihost
+ done
+
+ hrun runc $(dnet_container_name 1 consul) $(get_sbox_id 1 container_1) "ifconfig eth0"
+ container_1_ip=$(echo ${output} | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}')
+
+ # ungracefully kill dnet-1-consul container
+ docker rm -f dnet-1-consul
+
+ # forcefully unpublish the service from dnet2 when dnet1 is dead.
+ dnet_cmd $(inst_id2port 2) service unpublish -f container_1.multihost
+ dnet_cmd $(inst_id2port 2) container create container_1
+ net_connect 2 container_1 multihost
+
+ hrun runc $(dnet_container_name 2 consul) $(get_sbox_id 2 container_1) "ifconfig eth0"
+ container_1_new_ip=$(echo ${output} | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}')
+
+ if [ "$container_1_ip" != "$container_1_new_ip" ]; then
+ exit 1
+ fi
+}
--- /dev/null
+# -*- mode: sh -*-
+#!/usr/bin/env bats
+
+load helpers
+
+@test "Test overlay network with etcd" {
+ skip_for_circleci
+ test_overlay etcd
+}
--- /dev/null
+# -*- mode: sh -*-
+#!/usr/bin/env bats
+
+load helpers
+
+function test_overlay_local() {
+ dnet_suffix=$1
+
+ echo $(docker ps)
+
+ start=1
+ end=3
+ for i in `seq ${start} ${end}`;
+ do
+ echo "iteration count ${i}"
+ dnet_cmd $(inst_id2port $i) network create -d overlay --id=mhid --subnet=10.1.0.0/16 --ip-range=10.1.${i}.0/24 --opt=com.docker.network.driver.overlay.vxlanid_list=1024 multihost
+ dnet_cmd $(inst_id2port $i) container create container_${i}
+ net_connect ${i} container_${i} multihost
+ done
+
+ # Now test connectivity between all the containers using service names
+ for i in `seq ${start} ${end}`;
+ do
+ if [ -z "${2}" -o "${2}" != "internal" ]; then
+ runc $(dnet_container_name $i $dnet_suffix) $(get_sbox_id ${i} container_${i}) \
+ "ping -c 1 www.google.com"
+ else
+ default_route=`runc $(dnet_container_name $i $dnet_suffix) $(get_sbox_id ${i} container_${i}) "ip route | grep default"`
+ [ "$default_route" = "" ]
+ fi
+ for j in `seq ${start} ${end}`;
+ do
+ if [ "$i" -eq "$j" ]; then
+ continue
+ fi
+ #runc $(dnet_container_name $i $dnet_suffix) $(get_sbox_id ${i} container_${i}) "ping -c 1 10.1.${j}.1"
+ runc $(dnet_container_name $i $dnet_suffix) $(get_sbox_id ${i} container_${i}) "ping -c 1 container_${j}"
+ done
+ done
+
+ # Teardown the container connections and the network
+ for i in `seq ${start} ${end}`;
+ do
+ net_disconnect ${i} container_${i} multihost
+ dnet_cmd $(inst_id2port $i) container rm container_${i}
+ done
+
+ if [ -z "${2}" -o "${2}" != "skip_rm" ]; then
+ dnet_cmd $(inst_id2port 2) network rm multihost
+ fi
+}
+
+@test "Test overlay network in local scope" {
+ skip_for_circleci
+ test_overlay_local local
+}
+
+#"ping -c 1 10.1.${j}.1"
--- /dev/null
+# -*- mode: sh -*-
+#!/usr/bin/env bats
+
+load helpers
+
+@test "Test overlay network with zookeeper" {
+ skip_for_circleci
+ test_overlay zookeeper
+}
--- /dev/null
+#!/usr/bin/env bash
+
+set -e
+
+export INTEGRATION_ROOT=./integration-tmp
+export TMPC_ROOT=./integration-tmp/tmpc
+
+declare -A cmap
+
+trap "cleanup_containers" EXIT SIGINT
+
+function cleanup_containers() {
+ for c in "${!cmap[@]}";
+ do
+ docker stop $c 1>>${INTEGRATION_ROOT}/test.log 2>&1 || true
+ if [ -z "$CIRCLECI" ]; then
+ docker rm -f $c 1>>${INTEGRATION_ROOT}/test.log 2>&1 || true
+ fi
+ done
+
+ unset cmap
+}
+
+function run_bridge_tests() {
+ ## Setup
+ start_dnet 1 bridge 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-bridge]=dnet-1-bridge
+
+ ## Run the test cases
+ ./integration-tmp/bin/bats ./test/integration/dnet/bridge.bats
+
+ ## Teardown
+ stop_dnet 1 bridge 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-bridge]
+}
+
+function run_overlay_local_tests() {
+ ## Test overlay network in local scope
+ ## Setup
+ start_dnet 1 local 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-local]=dnet-1-local
+ start_dnet 2 local:$(dnet_container_ip 1 local) 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-2-local]=dnet-2-local
+ start_dnet 3 local:$(dnet_container_ip 1 local) 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-3-local]=dnet-3-local
+
+ ## Run the test cases
+ ./integration-tmp/bin/bats ./test/integration/dnet/overlay-local.bats
+
+ ## Teardown
+ stop_dnet 1 local 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-local]
+ stop_dnet 2 local 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-2-local]
+ stop_dnet 3 local 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-3-local]
+}
+
+function run_overlay_consul_tests() {
+ ## Test overlay network with consul
+ ## Setup
+ start_dnet 1 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-consul]=dnet-1-consul
+ start_dnet 2 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-2-consul]=dnet-2-consul
+ start_dnet 3 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-3-consul]=dnet-3-consul
+
+ ## Run the test cases
+ ./integration-tmp/bin/bats ./test/integration/dnet/overlay-consul.bats
+
+ ## Teardown
+ stop_dnet 1 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-consul]
+ stop_dnet 2 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-2-consul]
+ stop_dnet 3 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-3-consul]
+}
+
+function run_overlay_consul_host_tests() {
+ export _OVERLAY_HOST_MODE="true"
+ ## Setup
+ start_dnet 1 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-consul]=dnet-1-consul
+
+ ## Run the test cases
+ ./integration-tmp/bin/bats ./test/integration/dnet/overlay-consul-host.bats
+
+ ## Teardown
+ stop_dnet 1 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-consul]
+ unset _OVERLAY_HOST_MODE
+}
+
+function run_overlay_zk_tests() {
+ ## Test overlay network with zookeeper
+ start_dnet 1 zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-zookeeper]=dnet-1-zookeeper
+ start_dnet 2 zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-2-zookeeper]=dnet-2-zookeeper
+ start_dnet 3 zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-3-zookeeper]=dnet-3-zookeeper
+
+ ./integration-tmp/bin/bats ./test/integration/dnet/overlay-zookeeper.bats
+
+ stop_dnet 1 zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-zookeeper]
+ stop_dnet 2 zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-2-zookeeper]
+ stop_dnet 3 zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-3-zookeeper]
+}
+
+function run_overlay_etcd_tests() {
+ ## Test overlay network with etcd
+ start_dnet 1 etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-etcd]=dnet-1-etcd
+ start_dnet 2 etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-2-etcd]=dnet-2-etcd
+ start_dnet 3 etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-3-etcd]=dnet-3-etcd
+
+ ./integration-tmp/bin/bats ./test/integration/dnet/overlay-etcd.bats
+
+ stop_dnet 1 etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-etcd]
+ stop_dnet 2 etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-2-etcd]
+ stop_dnet 3 etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-3-etcd]
+}
+
+function run_dnet_tests() {
+ # Test dnet configuration options
+ ./integration-tmp/bin/bats ./test/integration/dnet/dnet.bats
+}
+
+function run_simple_consul_tests() {
+ # Test a single node configuration with a global scope test driver
+ ## Setup
+ start_dnet 1 simple 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-simple]=dnet-1-simple
+
+ ## Run the test cases
+ ./integration-tmp/bin/bats ./test/integration/dnet/simple.bats
+
+ ## Teardown
+ stop_dnet 1 simple 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-simple]
+}
+
+function run_multi_consul_tests() {
+ # Test multi node configuration with a global scope test driver backed by consul
+
+ ## Setup
+ start_dnet 1 multi_consul consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-multi_consul]=dnet-1-multi_consul
+ start_dnet 2 multi_consul consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-2-multi_consul]=dnet-2-multi_consul
+ start_dnet 3 multi_consul consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-3-multi_consul]=dnet-3-multi_consul
+
+ ## Run the test cases
+ ./integration-tmp/bin/bats ./test/integration/dnet/multi.bats
+
+ ## Teardown
+ stop_dnet 1 multi_consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-multi_consul]
+ stop_dnet 2 multi_consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-2-multi_consul]
+ stop_dnet 3 multi_consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-3-multi_consul]
+}
+
+function run_multi_zk_tests() {
+ # Test multi node configuration with a global scope test driver backed by zookeeper
+
+ ## Setup
+ start_dnet 1 multi_zk zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-multi_zk]=dnet-1-multi_zk
+ start_dnet 2 multi_zk zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-2-multi_zk]=dnet-2-multi_zk
+ start_dnet 3 multi_zk zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-3-multi_zk]=dnet-3-multi_zk
+
+ ## Run the test cases
+ ./integration-tmp/bin/bats ./test/integration/dnet/multi.bats
+
+ ## Teardown
+ stop_dnet 1 multi_zk 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-multi_zk]
+ stop_dnet 2 multi_zk 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-2-multi_zk]
+ stop_dnet 3 multi_zk 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-3-multi_zk]
+}
+
+function run_multi_etcd_tests() {
+ # Test multi node configuration with a global scope test driver backed by etcd
+
+ ## Setup
+ start_dnet 1 multi_etcd etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-1-multi_etcd]=dnet-1-multi_etcd
+ start_dnet 2 multi_etcd etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-2-multi_etcd]=dnet-2-multi_etcd
+ start_dnet 3 multi_etcd etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dnet-3-multi_etcd]=dnet-3-multi_etcd
+
+ ## Run the test cases
+ ./integration-tmp/bin/bats ./test/integration/dnet/multi.bats
+
+ ## Teardown
+ stop_dnet 1 multi_etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-1-multi_etcd]
+ stop_dnet 2 multi_etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-2-multi_etcd]
+ stop_dnet 3 multi_etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ unset cmap[dnet-3-multi_etcd]
+}
+
+source ./test/integration/dnet/helpers.bash
+
+if [ ! -d ${INTEGRATION_ROOT} ]; then
+ mkdir -p ${INTEGRATION_ROOT}
+ git clone https://github.com/sstephenson/bats.git ${INTEGRATION_ROOT}/bats
+ ./integration-tmp/bats/install.sh ./integration-tmp
+fi
+
+if [ ! -d ${TMPC_ROOT} ]; then
+ mkdir -p ${TMPC_ROOT}
+ docker pull busybox:ubuntu
+ docker export $(docker create busybox:ubuntu) > ${TMPC_ROOT}/busybox.tar
+ mkdir -p ${TMPC_ROOT}/rootfs
+ tar -C ${TMPC_ROOT}/rootfs -xf ${TMPC_ROOT}/busybox.tar
+fi
+
+# Suite setup
+
+if [ -z "$SUITES" ]; then
+ if [ -n "$CIRCLECI" ]
+ then
+ # We can only run a limited list of suites in circleci because of the
+ # old kernel and limited docker environment.
+ suites="dnet multi_consul multi_zk multi_etcd"
+ else
+ suites="dnet multi_consul multi_zk multi_etcd bridge overlay_consul overlay_consul_host overlay_zk overlay_etcd"
+ fi
+else
+ suites="$SUITES"
+fi
+
+if [[ ( "$suites" =~ .*consul.* ) || ( "$suites" =~ .*bridge.* ) ]]; then
+ echo "Starting consul ..."
+ start_consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[pr_consul]=pr_consul
+fi
+
+if [[ "$suites" =~ .*zk.* ]]; then
+ echo "Starting zookeeper ..."
+ start_zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[zookeeper_server]=zookeeper_server
+fi
+
+if [[ "$suites" =~ .*etcd.* ]]; then
+ echo "Starting etcd ..."
+ start_etcd 1>>${INTEGRATION_ROOT}/test.log 2>&1
+ cmap[dn_etcd]=dn_etcd
+fi
+
+echo ""
+
+for suite in ${suites};
+do
+ suite_func=run_${suite}_tests
+ echo "Running ${suite}_tests ..."
+ declare -F $suite_func >/dev/null && $suite_func
+ echo ""
+done
--- /dev/null
+#!/usr/bin/env bats
+
+load helpers
+
+@test "Test network create" {
+ echo $(docker ps)
+ run dnet_cmd $(inst_id2port 1) network create -d test mh1
+ echo ${output}
+ [ "$status" -eq 0 ]
+ run dnet_cmd $(inst_id2port 1) network ls
+ echo ${output}
+ line=$(dnet_cmd $(inst_id2port 1) network ls | grep mh1)
+ echo ${line}
+ name=$(echo ${line} | cut -d" " -f2)
+ driver=$(echo ${line} | cut -d" " -f3)
+ echo ${name} ${driver}
+ [ "$name" = "mh1" ]
+ [ "$driver" = "test" ]
+ dnet_cmd $(inst_id2port 1) network rm mh1
+}
+
+@test "Test network delete with id" {
+ echo $(docker ps)
+ run dnet_cmd $(inst_id2port 1) network create -d test mh1
+ [ "$status" -eq 0 ]
+ echo ${output}
+ dnet_cmd $(inst_id2port 1) network rm ${output}
+}
+
+@test "Test service create" {
+ echo $(docker ps)
+ dnet_cmd $(inst_id2port 1) network create -d test multihost
+ run dnet_cmd $(inst_id2port 1) service publish svc1.multihost
+ echo ${output}
+ [ "$status" -eq 0 ]
+ run dnet_cmd $(inst_id2port 1) service ls
+ echo ${output}
+ echo ${lines[1]}
+ [ "$status" -eq 0 ]
+ svc=$(echo ${lines[1]} | cut -d" " -f2)
+ network=$(echo ${lines[1]} | cut -d" " -f3)
+ echo ${svc} ${network}
+ [ "$network" = "multihost" ]
+ [ "$svc" = "svc1" ]
+ dnet_cmd $(inst_id2port 1) service unpublish svc1.multihost
+ dnet_cmd $(inst_id2port 1) network rm multihost
+}
+
+@test "Test service delete with id" {
+ echo $(docker ps)
+ dnet_cmd $(inst_id2port 1) network create -d test multihost
+ run dnet_cmd $(inst_id2port 1) service publish svc1.multihost
+ [ "$status" -eq 0 ]
+ echo ${output}
+ run dnet_cmd $(inst_id2port 1) service ls
+ [ "$status" -eq 0 ]
+ echo ${output}
+ echo ${lines[1]}
+ id=$(echo ${lines[1]} | cut -d" " -f1)
+ dnet_cmd $(inst_id2port 1) service unpublish ${id}.multihost
+ dnet_cmd $(inst_id2port 1) network rm multihost
+}
+
+@test "Test service attach" {
+ echo $(docker ps)
+ dnet_cmd $(inst_id2port 1) network create -d test multihost
+ dnet_cmd $(inst_id2port 1) service publish svc1.multihost
+ dnet_cmd $(inst_id2port 1) container create container_1
+ dnet_cmd $(inst_id2port 1) service attach container_1 svc1.multihost
+ run dnet_cmd $(inst_id2port 1) service ls
+ [ "$status" -eq 0 ]
+ echo ${output}
+ echo ${lines[1]}
+ container=$(echo ${lines[1]} | cut -d" " -f4)
+ [ "$container" = "container_1" ]
+ dnet_cmd $(inst_id2port 1) service detach container_1 svc1.multihost
+ dnet_cmd $(inst_id2port 1) container rm container_1
+ dnet_cmd $(inst_id2port 1) service unpublish svc1.multihost
+ dnet_cmd $(inst_id2port 1) network rm multihost
+}
--- /dev/null
+#!/bin/bash
+
+# Root directory of the repository.
+MACHINE_ROOT=/usr/bin
+
+PLATFORM=`uname -s | tr '[:upper:]' '[:lower:]'`
+ARCH=`uname -m`
+
+if [ "$ARCH" = "x86_64" ]; then
+ ARCH="amd64"
+else
+ ARCH="386"
+fi
+MACHINE_BIN_NAME=docker-machine_$PLATFORM-$ARCH
+BATS_LOG=/tmp/bats.log
+
+touch ${BATS_LOG}
+rm ${BATS_LOG}
+
+teardown() {
+ echo "$BATS_TEST_NAME
+----------
+$output
+----------
+
+" >> ${BATS_LOG}
+}
+
+EXTRA_ARGS_CFG='EXTRA_ARGS'
+EXTRA_ARGS='--tlsverify --tlscacert=/var/lib/boot2docker/ca.pem --tlskey=/var/lib/boot2docker/server-key.pem --tlscert=/var/lib/boot2docker/server.pem --label=provider=virtualbox -H tcp://0.0.0.0:2376'
+TMP_EXTRA_ARGS_FILE=/tmp/tmp_extra_args
+DAEMON_CFG_FILE=${BATS_TEST_DIRNAME}/daemon.cfg
+set_extra_config() {
+ if [ -f ${TMP_EXTRA_ARGS_FILE} ];
+ then
+ rm ${TMP_EXTRA_ARGS_FILE}
+ fi
+ echo -n "${EXTRA_ARGS_CFG}='" > ${TMP_EXTRA_ARGS_FILE}
+ echo -n "$1 " >> ${TMP_EXTRA_ARGS_FILE}
+ echo "${EXTRA_ARGS}'" >> ${TMP_EXTRA_ARGS_FILE}
+}
+
+if [ ! -e $MACHINE_ROOT/$MACHINE_BIN_NAME ]; then
+ echo "${MACHINE_ROOT}/${MACHINE_BIN_NAME} not found"
+ exit 1
+fi
+
+function machine() {
+ ${MACHINE_ROOT}/$MACHINE_BIN_NAME "$@"
+}
--- /dev/null
+// +build linux freebsd
+
+package testutils
+
+import (
+ "os"
+ "runtime"
+ "syscall"
+ "testing"
+
+ "github.com/docker/libnetwork/ns"
+)
+
+// SetupTestOSContext joins a new network namespace, and returns its associated
+// teardown function.
+//
+// Example usage:
+//
+// defer SetupTestOSContext(t)()
+//
+func SetupTestOSContext(t *testing.T) func() {
+ runtime.LockOSThread()
+ if err := syscall.Unshare(syscall.CLONE_NEWNET); err != nil {
+ t.Fatalf("Failed to enter netns: %v", err)
+ }
+
+ fd, err := syscall.Open("/proc/self/ns/net", syscall.O_RDONLY, 0)
+ if err != nil {
+ t.Fatal("Failed to open netns file")
+ }
+
+ // Since we are switching to a new test namespace make
+ // sure to re-initialize initNs context
+ ns.Init()
+
+ runtime.LockOSThread()
+
+ return func() {
+ if err := syscall.Close(fd); err != nil {
+ t.Logf("Warning: netns closing failed (%v)", err)
+ }
+ runtime.UnlockOSThread()
+ }
+}
+
+// RunningOnCircleCI returns true if being executed on libnetwork Circle CI setup
+func RunningOnCircleCI() bool {
+ return os.Getenv("CIRCLECI") != ""
+}
--- /dev/null
+package testutils
+
+import (
+ "os"
+ "testing"
+)
+
+// SetupTestOSContext joins a new network namespace, and returns its associated
+// teardown function.
+//
+// Example usage:
+//
+// defer SetupTestOSContext(t)()
+//
+func SetupTestOSContext(t *testing.T) func() {
+ return func() {
+ }
+}
+
+// RunningOnCircleCI returns true if being executed on libnetwork Circle CI setup
+func RunningOnCircleCI() bool {
+ return os.Getenv("CIRCLECI") != ""
+}
--- /dev/null
+package testutils
+
+import "flag"
+
+var runningInContainer = flag.Bool("incontainer", false, "Indicates if the test is running in a container")
+
+// IsRunningInContainer returns whether the test is running inside a container.
+func IsRunningInContainer() bool {
+ return (*runningInContainer)
+}
--- /dev/null
+// Package types contains types that are common across libnetwork project
+package types
+
+import (
+ "bytes"
+ "fmt"
+ "net"
+ "strconv"
+ "strings"
+
+ "github.com/ishidawataru/sctp"
+)
+
+// constants for the IP address type
+const (
+ IP = iota // IPv4 and IPv6
+ IPv4
+ IPv6
+)
+
+// EncryptionKey is the libnetwork representation of the key distributed by the lead
+// manager.
+type EncryptionKey struct {
+ Subsystem string
+ Algorithm int32
+ Key []byte
+ LamportTime uint64
+}
+
+// UUID represents a globally unique ID of various resources like network and endpoint
+type UUID string
+
+// QosPolicy represents a quality of service policy on an endpoint
+type QosPolicy struct {
+ MaxEgressBandwidth uint64
+}
+
+// TransportPort represents a local Layer 4 endpoint
+type TransportPort struct {
+ Proto Protocol
+ Port uint16
+}
+
+// Equal checks if this instance of Transportport is equal to the passed one
+func (t *TransportPort) Equal(o *TransportPort) bool {
+ if t == o {
+ return true
+ }
+
+ if o == nil {
+ return false
+ }
+
+ if t.Proto != o.Proto || t.Port != o.Port {
+ return false
+ }
+
+ return true
+}
+
+// GetCopy returns a copy of this TransportPort structure instance
+func (t *TransportPort) GetCopy() TransportPort {
+ return TransportPort{Proto: t.Proto, Port: t.Port}
+}
+
+// String returns the TransportPort structure in string form
+func (t *TransportPort) String() string {
+ return fmt.Sprintf("%s/%d", t.Proto.String(), t.Port)
+}
+
+// FromString reads the TransportPort structure from string
+func (t *TransportPort) FromString(s string) error {
+ ps := strings.Split(s, "/")
+ if len(ps) == 2 {
+ t.Proto = ParseProtocol(ps[0])
+ if p, err := strconv.ParseUint(ps[1], 10, 16); err == nil {
+ t.Port = uint16(p)
+ return nil
+ }
+ }
+ return BadRequestErrorf("invalid format for transport port: %s", s)
+}
+
+// PortBinding represents a port binding between the container and the host
+type PortBinding struct {
+ Proto Protocol
+ IP net.IP
+ Port uint16
+ HostIP net.IP
+ HostPort uint16
+ HostPortEnd uint16
+}
+
+// HostAddr returns the host side transport address
+func (p PortBinding) HostAddr() (net.Addr, error) {
+ switch p.Proto {
+ case UDP:
+ return &net.UDPAddr{IP: p.HostIP, Port: int(p.HostPort)}, nil
+ case TCP:
+ return &net.TCPAddr{IP: p.HostIP, Port: int(p.HostPort)}, nil
+ case SCTP:
+ return &sctp.SCTPAddr{IP: []net.IP{p.HostIP}, Port: int(p.HostPort)}, nil
+ default:
+ return nil, ErrInvalidProtocolBinding(p.Proto.String())
+ }
+}
+
+// ContainerAddr returns the container side transport address
+func (p PortBinding) ContainerAddr() (net.Addr, error) {
+ switch p.Proto {
+ case UDP:
+ return &net.UDPAddr{IP: p.IP, Port: int(p.Port)}, nil
+ case TCP:
+ return &net.TCPAddr{IP: p.IP, Port: int(p.Port)}, nil
+ case SCTP:
+ return &sctp.SCTPAddr{IP: []net.IP{p.IP}, Port: int(p.Port)}, nil
+ default:
+ return nil, ErrInvalidProtocolBinding(p.Proto.String())
+ }
+}
+
+// GetCopy returns a copy of this PortBinding structure instance
+func (p *PortBinding) GetCopy() PortBinding {
+ return PortBinding{
+ Proto: p.Proto,
+ IP: GetIPCopy(p.IP),
+ Port: p.Port,
+ HostIP: GetIPCopy(p.HostIP),
+ HostPort: p.HostPort,
+ HostPortEnd: p.HostPortEnd,
+ }
+}
+
+// String returns the PortBinding structure in string form
+func (p *PortBinding) String() string {
+ ret := fmt.Sprintf("%s/", p.Proto)
+ if p.IP != nil {
+ ret += p.IP.String()
+ }
+ ret = fmt.Sprintf("%s:%d/", ret, p.Port)
+ if p.HostIP != nil {
+ ret += p.HostIP.String()
+ }
+ ret = fmt.Sprintf("%s:%d", ret, p.HostPort)
+ return ret
+}
+
+// FromString reads the PortBinding structure from string s.
+// String s is a triple of "protocol/containerIP:port/hostIP:port"
+// containerIP and hostIP can be in dotted decimal ("192.0.2.1") or IPv6 ("2001:db8::68") form.
+// Zoned addresses ("169.254.0.23%eth0" or "fe80::1ff:fe23:4567:890a%eth0") are not supported.
+// If string s is incorrectly formatted or the IP addresses or ports cannot be parsed, FromString
+// returns an error.
+func (p *PortBinding) FromString(s string) error {
+ ps := strings.Split(s, "/")
+ if len(ps) != 3 {
+ return BadRequestErrorf("invalid format for port binding: %s", s)
+ }
+
+ p.Proto = ParseProtocol(ps[0])
+
+ var err error
+ if p.IP, p.Port, err = parseIPPort(ps[1]); err != nil {
+ return BadRequestErrorf("failed to parse Container IP/Port in port binding: %s", err.Error())
+ }
+
+ if p.HostIP, p.HostPort, err = parseIPPort(ps[2]); err != nil {
+ return BadRequestErrorf("failed to parse Host IP/Port in port binding: %s", err.Error())
+ }
+
+ return nil
+}
+
+func parseIPPort(s string) (net.IP, uint16, error) {
+ hoststr, portstr, err := net.SplitHostPort(s)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ ip := net.ParseIP(hoststr)
+ if ip == nil {
+ return nil, 0, BadRequestErrorf("invalid ip: %s", hoststr)
+ }
+
+ port, err := strconv.ParseUint(portstr, 10, 16)
+ if err != nil {
+ return nil, 0, BadRequestErrorf("invalid port: %s", portstr)
+ }
+
+ return ip, uint16(port), nil
+}
+
+// Equal checks if this instance of PortBinding is equal to the passed one
+func (p *PortBinding) Equal(o *PortBinding) bool {
+ if p == o {
+ return true
+ }
+
+ if o == nil {
+ return false
+ }
+
+ if p.Proto != o.Proto || p.Port != o.Port ||
+ p.HostPort != o.HostPort || p.HostPortEnd != o.HostPortEnd {
+ return false
+ }
+
+ if p.IP != nil {
+ if !p.IP.Equal(o.IP) {
+ return false
+ }
+ } else {
+ if o.IP != nil {
+ return false
+ }
+ }
+
+ if p.HostIP != nil {
+ if !p.HostIP.Equal(o.HostIP) {
+ return false
+ }
+ } else {
+ if o.HostIP != nil {
+ return false
+ }
+ }
+
+ return true
+}
+
+// ErrInvalidProtocolBinding is returned when the port binding protocol is not valid.
+type ErrInvalidProtocolBinding string
+
+func (ipb ErrInvalidProtocolBinding) Error() string {
+ return fmt.Sprintf("invalid transport protocol: %s", string(ipb))
+}
+
+const (
+ // ICMP is for the ICMP ip protocol
+ ICMP = 1
+ // TCP is for the TCP ip protocol
+ TCP = 6
+ // UDP is for the UDP ip protocol
+ UDP = 17
+ // SCTP is for the SCTP ip protocol
+ SCTP = 132
+)
+
+// Protocol represents an IP protocol number
+type Protocol uint8
+
+func (p Protocol) String() string {
+ switch p {
+ case ICMP:
+ return "icmp"
+ case TCP:
+ return "tcp"
+ case UDP:
+ return "udp"
+ case SCTP:
+ return "sctp"
+ default:
+ return fmt.Sprintf("%d", p)
+ }
+}
+
+// ParseProtocol returns the respective Protocol type for the passed string
+func ParseProtocol(s string) Protocol {
+ switch strings.ToLower(s) {
+ case "icmp":
+ return ICMP
+ case "udp":
+ return UDP
+ case "tcp":
+ return TCP
+ case "sctp":
+ return SCTP
+ default:
+ return 0
+ }
+}
+
+// GetMacCopy returns a copy of the passed MAC address
+func GetMacCopy(from net.HardwareAddr) net.HardwareAddr {
+ if from == nil {
+ return nil
+ }
+ to := make(net.HardwareAddr, len(from))
+ copy(to, from)
+ return to
+}
+
+// GetIPCopy returns a copy of the passed IP address
+func GetIPCopy(from net.IP) net.IP {
+ if from == nil {
+ return nil
+ }
+ to := make(net.IP, len(from))
+ copy(to, from)
+ return to
+}
+
+// GetIPNetCopy returns a copy of the passed IP Network
+func GetIPNetCopy(from *net.IPNet) *net.IPNet {
+ if from == nil {
+ return nil
+ }
+ bm := make(net.IPMask, len(from.Mask))
+ copy(bm, from.Mask)
+ return &net.IPNet{IP: GetIPCopy(from.IP), Mask: bm}
+}
+
+// GetIPNetCanonical returns the canonical form for the passed network
+func GetIPNetCanonical(nw *net.IPNet) *net.IPNet {
+ if nw == nil {
+ return nil
+ }
+ c := GetIPNetCopy(nw)
+ c.IP = c.IP.Mask(nw.Mask)
+ return c
+}
+
+// CompareIPNet returns equal if the two IP Networks are equal
+func CompareIPNet(a, b *net.IPNet) bool {
+ if a == b {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask)
+}
+
+// GetMinimalIP returns the address in its shortest form
+// If ip contains an IPv4-mapped IPv6 address, the 4-octet form of the IPv4 address will be returned.
+// Otherwise ip is returned unchanged.
+func GetMinimalIP(ip net.IP) net.IP {
+ if ip != nil && ip.To4() != nil {
+ return ip.To4()
+ }
+ return ip
+}
+
+// GetMinimalIPNet returns a copy of the passed IP Network with congruent ip and mask notation
+func GetMinimalIPNet(nw *net.IPNet) *net.IPNet {
+ if nw == nil {
+ return nil
+ }
+ if len(nw.IP) == 16 && nw.IP.To4() != nil {
+ m := nw.Mask
+ if len(m) == 16 {
+ m = m[12:16]
+ }
+ return &net.IPNet{IP: nw.IP.To4(), Mask: m}
+ }
+ return nw
+}
+
+// IsIPNetValid returns true if the ipnet is a valid network/mask
+// combination. Otherwise returns false.
+func IsIPNetValid(nw *net.IPNet) bool {
+ return nw.String() != "0.0.0.0/0"
+}
+
+var v4inV6MaskPrefix = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+
+// compareIPMask checks if the passed ip and mask are semantically compatible.
+// It returns the byte indexes for the address and mask so that caller can
+// do bitwise operations without modifying address representation.
+func compareIPMask(ip net.IP, mask net.IPMask) (is int, ms int, err error) {
+ // Find the effective starting of address and mask
+ if len(ip) == net.IPv6len && ip.To4() != nil {
+ is = 12
+ }
+ if len(ip[is:]) == net.IPv4len && len(mask) == net.IPv6len && bytes.Equal(mask[:12], v4inV6MaskPrefix) {
+ ms = 12
+ }
+ // Check if address and mask are semantically compatible
+ if len(ip[is:]) != len(mask[ms:]) {
+ err = fmt.Errorf("ip and mask are not compatible: (%#v, %#v)", ip, mask)
+ }
+ return
+}
+
+// GetHostPartIP returns the host portion of the ip address identified by the mask.
+// IP address representation is not modified. If address and mask are not compatible
+// an error is returned.
+func GetHostPartIP(ip net.IP, mask net.IPMask) (net.IP, error) {
+ // Find the effective starting of address and mask
+ is, ms, err := compareIPMask(ip, mask)
+ if err != nil {
+ return nil, fmt.Errorf("cannot compute host portion ip address because %s", err)
+ }
+
+ // Compute host portion
+ out := GetIPCopy(ip)
+ for i := 0; i < len(mask[ms:]); i++ {
+ out[is+i] &= ^mask[ms+i]
+ }
+
+ return out, nil
+}
+
+// GetBroadcastIP returns the broadcast ip address for the passed network (ip and mask).
+// IP address representation is not modified. If address and mask are not compatible
+// an error is returned.
+func GetBroadcastIP(ip net.IP, mask net.IPMask) (net.IP, error) {
+ // Find the effective starting of address and mask
+ is, ms, err := compareIPMask(ip, mask)
+ if err != nil {
+ return nil, fmt.Errorf("cannot compute broadcast ip address because %s", err)
+ }
+
+ // Compute broadcast address
+ out := GetIPCopy(ip)
+ for i := 0; i < len(mask[ms:]); i++ {
+ out[is+i] |= ^mask[ms+i]
+ }
+
+ return out, nil
+}
+
+// ParseCIDR returns the *net.IPNet represented by the passed CIDR notation
+func ParseCIDR(cidr string) (n *net.IPNet, e error) {
+ var i net.IP
+ if i, n, e = net.ParseCIDR(cidr); e == nil {
+ n.IP = i
+ }
+ return
+}
+
+const (
+ // NEXTHOP indicates a StaticRoute with an IP next hop.
+ NEXTHOP = iota
+
+ // CONNECTED indicates a StaticRoute with an interface for directly connected peers.
+ CONNECTED
+)
+
+// StaticRoute is a statically-provisioned IP route.
+type StaticRoute struct {
+ Destination *net.IPNet
+
+ RouteType int // NEXT_HOP or CONNECTED
+
+ // NextHop will be resolved by the kernel (i.e. as a loose hop).
+ NextHop net.IP
+}
+
+// GetCopy returns a copy of this StaticRoute structure
+func (r *StaticRoute) GetCopy() *StaticRoute {
+ d := GetIPNetCopy(r.Destination)
+ nh := GetIPCopy(r.NextHop)
+ return &StaticRoute{Destination: d,
+ RouteType: r.RouteType,
+ NextHop: nh,
+ }
+}
+
+// InterfaceStatistics represents the interface's statistics
+type InterfaceStatistics struct {
+ RxBytes uint64
+ RxPackets uint64
+ RxErrors uint64
+ RxDropped uint64
+ TxBytes uint64
+ TxPackets uint64
+ TxErrors uint64
+ TxDropped uint64
+}
+
+func (is *InterfaceStatistics) String() string {
+ return fmt.Sprintf("\nRxBytes: %d, RxPackets: %d, RxErrors: %d, RxDropped: %d, TxBytes: %d, TxPackets: %d, TxErrors: %d, TxDropped: %d",
+ is.RxBytes, is.RxPackets, is.RxErrors, is.RxDropped, is.TxBytes, is.TxPackets, is.TxErrors, is.TxDropped)
+}
+
+/******************************
+ * Well-known Error Interfaces
+ ******************************/
+
+// MaskableError is an interface for errors which can be ignored by caller
+type MaskableError interface {
+ // Maskable makes implementer into MaskableError type
+ Maskable()
+}
+
+// RetryError is an interface for errors which might get resolved through retry
+type RetryError interface {
+ // Retry makes implementer into RetryError type
+ Retry()
+}
+
+// BadRequestError is an interface for errors originated by a bad request
+type BadRequestError interface {
+ // BadRequest makes implementer into BadRequestError type
+ BadRequest()
+}
+
+// NotFoundError is an interface for errors raised because a needed resource is not available
+type NotFoundError interface {
+ // NotFound makes implementer into NotFoundError type
+ NotFound()
+}
+
+// ForbiddenError is an interface for errors which denote a valid request that cannot be honored
+type ForbiddenError interface {
+ // Forbidden makes implementer into ForbiddenError type
+ Forbidden()
+}
+
+// NoServiceError is an interface for errors returned when the required service is not available
+type NoServiceError interface {
+ // NoService makes implementer into NoServiceError type
+ NoService()
+}
+
+// TimeoutError is an interface for errors raised because of timeout
+type TimeoutError interface {
+ // Timeout makes implementer into TimeoutError type
+ Timeout()
+}
+
+// NotImplementedError is an interface for errors raised because of requested functionality is not yet implemented
+type NotImplementedError interface {
+ // NotImplemented makes implementer into NotImplementedError type
+ NotImplemented()
+}
+
+// InternalError is an interface for errors raised because of an internal error
+type InternalError interface {
+ // Internal makes implementer into InternalError type
+ Internal()
+}
+
+/******************************
+ * Well-known Error Formatters
+ ******************************/
+
+// BadRequestErrorf creates an instance of BadRequestError
+func BadRequestErrorf(format string, params ...interface{}) error {
+ return badRequest(fmt.Sprintf(format, params...))
+}
+
+// NotFoundErrorf creates an instance of NotFoundError
+func NotFoundErrorf(format string, params ...interface{}) error {
+ return notFound(fmt.Sprintf(format, params...))
+}
+
+// ForbiddenErrorf creates an instance of ForbiddenError
+func ForbiddenErrorf(format string, params ...interface{}) error {
+ return forbidden(fmt.Sprintf(format, params...))
+}
+
+// NoServiceErrorf creates an instance of NoServiceError
+func NoServiceErrorf(format string, params ...interface{}) error {
+ return noService(fmt.Sprintf(format, params...))
+}
+
+// NotImplementedErrorf creates an instance of NotImplementedError
+func NotImplementedErrorf(format string, params ...interface{}) error {
+ return notImpl(fmt.Sprintf(format, params...))
+}
+
+// TimeoutErrorf creates an instance of TimeoutError
+func TimeoutErrorf(format string, params ...interface{}) error {
+ return timeout(fmt.Sprintf(format, params...))
+}
+
+// InternalErrorf creates an instance of InternalError
+func InternalErrorf(format string, params ...interface{}) error {
+ return internal(fmt.Sprintf(format, params...))
+}
+
+// InternalMaskableErrorf creates an instance of InternalError and MaskableError
+func InternalMaskableErrorf(format string, params ...interface{}) error {
+ return maskInternal(fmt.Sprintf(format, params...))
+}
+
+// RetryErrorf creates an instance of RetryError
+func RetryErrorf(format string, params ...interface{}) error {
+ return retry(fmt.Sprintf(format, params...))
+}
+
+/***********************
+ * Internal Error Types
+ ***********************/
+type badRequest string
+
+func (br badRequest) Error() string {
+ return string(br)
+}
+func (br badRequest) BadRequest() {}
+
+type maskBadRequest string
+
+type notFound string
+
+func (nf notFound) Error() string {
+ return string(nf)
+}
+func (nf notFound) NotFound() {}
+
+type forbidden string
+
+func (frb forbidden) Error() string {
+ return string(frb)
+}
+func (frb forbidden) Forbidden() {}
+
+type noService string
+
+func (ns noService) Error() string {
+ return string(ns)
+}
+func (ns noService) NoService() {}
+
+type maskNoService string
+
+type timeout string
+
+func (to timeout) Error() string {
+ return string(to)
+}
+func (to timeout) Timeout() {}
+
+type notImpl string
+
+func (ni notImpl) Error() string {
+ return string(ni)
+}
+func (ni notImpl) NotImplemented() {}
+
+type internal string
+
+func (nt internal) Error() string {
+ return string(nt)
+}
+func (nt internal) Internal() {}
+
+type maskInternal string
+
+func (mnt maskInternal) Error() string {
+ return string(mnt)
+}
+func (mnt maskInternal) Internal() {}
+func (mnt maskInternal) Maskable() {}
+
+type retry string
+
+func (r retry) Error() string {
+ return string(r)
+}
+func (r retry) Retry() {}
--- /dev/null
+package types
+
+import (
+ "net"
+ "testing"
+
+ _ "github.com/docker/libnetwork/testutils"
+ "gotest.tools/assert"
+ is "gotest.tools/assert/cmp"
+)
+
+func TestTransportPortConv(t *testing.T) {
+ sform := "tcp/23"
+ tp := &TransportPort{Proto: TCP, Port: uint16(23)}
+
+ if sform != tp.String() {
+ t.Fatalf("String() method failed")
+ }
+
+ rc := new(TransportPort)
+ if err := rc.FromString(sform); err != nil {
+ t.Fatal(err)
+ }
+ if !tp.Equal(rc) {
+ t.Fatalf("FromString() method failed")
+ }
+}
+
+func TestTransportPortBindingConv(t *testing.T) {
+ input := []struct {
+ sform string
+ pb PortBinding
+ shouldFail bool
+ }{
+ { // IPv4 -> IPv4
+ sform: "tcp/172.28.30.23:80/112.0.43.56:8001",
+ pb: PortBinding{
+ Proto: TCP,
+ IP: net.IPv4(172, 28, 30, 23),
+ Port: uint16(80),
+ HostIP: net.IPv4(112, 0, 43, 56),
+ HostPort: uint16(8001),
+ },
+ },
+ { // IPv6 -> IPv4
+ sform: "tcp/[2001:db8::1]:80/112.0.43.56:8001",
+ pb: PortBinding{
+ Proto: TCP,
+ IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ Port: uint16(80),
+ HostIP: net.IPv4(112, 0, 43, 56),
+ HostPort: uint16(8001),
+ },
+ },
+ { // IPv4inIPv6 -> IPv4
+ sform: "tcp/[::ffff:172.28.30.23]:80/112.0.43.56:8001",
+ pb: PortBinding{
+ Proto: TCP,
+ IP: net.IPv4(172, 28, 30, 23),
+ Port: uint16(80),
+ HostIP: net.IPv4(112, 0, 43, 56),
+ HostPort: uint16(8001),
+ },
+ },
+ { // IPv4 -> IPv4 zoned
+ sform: "tcp/172.28.30.23:80/169.254.0.23%eth0:8001",
+ shouldFail: true,
+ },
+ { // IPv4 -> IPv6 zoned
+ sform: "tcp/172.28.30.23:80/[fe80::1ff:fe23:4567:890a%eth0]:8001",
+ shouldFail: true,
+ },
+ }
+
+ for _, in := range input {
+ rc := new(PortBinding)
+ err := rc.FromString(in.sform)
+ if in.shouldFail {
+ assert.Assert(t, is.ErrorContains(err, ""), "Unexpected success parsing %s", in.sform)
+ } else {
+ assert.NilError(t, err)
+ assert.Assert(t, is.DeepEqual(in.pb, *rc), "input %s: expected %#v, got %#v", in.sform, in.pb, rc)
+ }
+ }
+}
+
+func TestErrorConstructors(t *testing.T) {
+ var err error
+
+ err = BadRequestErrorf("Io ho %d uccello", 1)
+ if err.Error() != "Io ho 1 uccello" {
+ t.Fatal(err)
+ }
+ if _, ok := err.(BadRequestError); !ok {
+ t.Fatal(err)
+ }
+ if _, ok := err.(MaskableError); ok {
+ t.Fatal(err)
+ }
+
+ err = RetryErrorf("Incy wincy %s went up the spout again", "spider")
+ if err.Error() != "Incy wincy spider went up the spout again" {
+ t.Fatal(err)
+ }
+ if _, ok := err.(RetryError); !ok {
+ t.Fatal(err)
+ }
+ if _, ok := err.(MaskableError); ok {
+ t.Fatal(err)
+ }
+
+ err = NotFoundErrorf("Can't find the %s", "keys")
+ if err.Error() != "Can't find the keys" {
+ t.Fatal(err)
+ }
+ if _, ok := err.(NotFoundError); !ok {
+ t.Fatal(err)
+ }
+ if _, ok := err.(MaskableError); ok {
+ t.Fatal(err)
+ }
+
+ err = ForbiddenErrorf("Can't open door %d", 2)
+ if err.Error() != "Can't open door 2" {
+ t.Fatal(err)
+ }
+ if _, ok := err.(ForbiddenError); !ok {
+ t.Fatal(err)
+ }
+ if _, ok := err.(MaskableError); ok {
+ t.Fatal(err)
+ }
+
+ err = NotImplementedErrorf("Functionality %s is not implemented", "x")
+ if err.Error() != "Functionality x is not implemented" {
+ t.Fatal(err)
+ }
+ if _, ok := err.(NotImplementedError); !ok {
+ t.Fatal(err)
+ }
+ if _, ok := err.(MaskableError); ok {
+ t.Fatal(err)
+ }
+
+ err = TimeoutErrorf("Process %s timed out", "abc")
+ if err.Error() != "Process abc timed out" {
+ t.Fatal(err)
+ }
+ if _, ok := err.(TimeoutError); !ok {
+ t.Fatal(err)
+ }
+ if _, ok := err.(MaskableError); ok {
+ t.Fatal(err)
+ }
+
+ err = NoServiceErrorf("Driver %s is not available", "mh")
+ if err.Error() != "Driver mh is not available" {
+ t.Fatal(err)
+ }
+ if _, ok := err.(NoServiceError); !ok {
+ t.Fatal(err)
+ }
+ if _, ok := err.(MaskableError); ok {
+ t.Fatal(err)
+ }
+
+ err = InternalErrorf("Not sure what happened")
+ if err.Error() != "Not sure what happened" {
+ t.Fatal(err)
+ }
+ if _, ok := err.(InternalError); !ok {
+ t.Fatal(err)
+ }
+ if _, ok := err.(MaskableError); ok {
+ t.Fatal(err)
+ }
+
+ err = InternalMaskableErrorf("Minor issue, it can be ignored")
+ if err.Error() != "Minor issue, it can be ignored" {
+ t.Fatal(err)
+ }
+ if _, ok := err.(InternalError); !ok {
+ t.Fatal(err)
+ }
+ if _, ok := err.(MaskableError); !ok {
+ t.Fatal(err)
+ }
+}
+
+func TestCompareIPMask(t *testing.T) {
+ input := []struct {
+ ip net.IP
+ mask net.IPMask
+ is int
+ ms int
+ isErr bool
+ }{
+ { // ip in v4Inv6 representation, mask in v4 representation
+ ip: net.IPv4(172, 28, 30, 1),
+ mask: []byte{0xff, 0xff, 0xff, 0},
+ is: 12,
+ ms: 0,
+ },
+ { // ip and mask in v4Inv6 representation
+ ip: net.IPv4(172, 28, 30, 2),
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
+ is: 12,
+ ms: 12,
+ },
+ { // ip in v4 representation, mask in v4Inv6 representation
+ ip: net.IPv4(172, 28, 30, 3)[12:],
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
+ is: 0,
+ ms: 12,
+ },
+ { // ip and mask in v4 representation
+ ip: net.IPv4(172, 28, 30, 4)[12:],
+ mask: []byte{0xff, 0xff, 0xff, 0},
+ is: 0,
+ ms: 0,
+ },
+ { // ip and mask as v6
+ ip: net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"),
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
+ is: 0,
+ ms: 0,
+ },
+ {
+ ip: net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"),
+ mask: []byte{0xff, 0xff, 0xff, 0},
+ isErr: true,
+ },
+ {
+ ip: net.ParseIP("173.32.4.5"),
+ mask: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0},
+ isErr: true,
+ },
+ {
+ ip: net.ParseIP("173.32.4.5"),
+ mask: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
+ isErr: true,
+ },
+ }
+
+ for ind, i := range input {
+ is, ms, err := compareIPMask(i.ip, i.mask)
+ if i.isErr {
+ if err == nil {
+ t.Fatalf("Incorrect error condition for element %d. is: %d, ms: %d, err: %v", ind, is, ms, err)
+ }
+ } else {
+ if i.is != is || i.ms != ms {
+ t.Fatalf("expected is: %d, ms: %d. Got is: %d, ms: %d for element %d", i.is, i.ms, is, ms, ind)
+ }
+ }
+ }
+}
+
+func TestUtilGetHostPartIP(t *testing.T) {
+ input := []struct {
+ ip net.IP
+ mask net.IPMask
+ host net.IP
+ err error
+ }{
+ { // ip in v4Inv6 representation, mask in v4 representation
+ ip: net.IPv4(172, 28, 30, 1),
+ mask: []byte{0xff, 0xff, 0xff, 0},
+ host: net.IPv4(0, 0, 0, 1),
+ },
+ { // ip and mask in v4Inv6 representation
+ ip: net.IPv4(172, 28, 30, 2),
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
+ host: net.IPv4(0, 0, 0, 2),
+ },
+ { // ip in v4 representation, mask in v4Inv6 representation
+ ip: net.IPv4(172, 28, 30, 3)[12:],
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
+ host: net.IPv4(0, 0, 0, 3)[12:],
+ },
+ { // ip and mask in v4 representation
+ ip: net.IPv4(172, 28, 30, 4)[12:],
+ mask: []byte{0xff, 0xff, 0xff, 0},
+ host: net.IPv4(0, 0, 0, 4)[12:],
+ },
+ { // ip and mask as v6
+ ip: net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"),
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
+ host: net.ParseIP("0::AB:00CD"),
+ },
+ }
+
+ for _, i := range input {
+ h, err := GetHostPartIP(i.ip, i.mask)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !i.host.Equal(h) {
+ t.Fatalf("Failed to return expected host ip. Expected: %s. Got: %s", i.host, h)
+ }
+ }
+
+ // ip as v6 and mask as v4 are not compatible
+ if _, err := GetHostPartIP(net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"), []byte{0xff, 0xff, 0xff, 0}); err == nil {
+ t.Fatalf("Unexpected success")
+ }
+ // ip as v4 and non conventional mask
+ if _, err := GetHostPartIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0}); err == nil {
+ t.Fatalf("Unexpected success")
+ }
+ // ip as v4 and non conventional mask
+ if _, err := GetHostPartIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0}); err == nil {
+ t.Fatalf("Unexpected success")
+ }
+}
+
+func TestUtilGetBroadcastIP(t *testing.T) {
+ input := []struct {
+ ip net.IP
+ mask net.IPMask
+ bcast net.IP
+ err error
+ }{
+ // ip in v4Inv6 representation, mask in v4 representation
+ {
+ ip: net.IPv4(172, 28, 30, 1),
+ mask: []byte{0xff, 0xff, 0xff, 0},
+ bcast: net.IPv4(172, 28, 30, 255),
+ },
+ {
+ ip: net.IPv4(10, 28, 30, 1),
+ mask: []byte{0xff, 0, 0, 0},
+ bcast: net.IPv4(10, 255, 255, 255),
+ },
+ // ip and mask in v4Inv6 representation
+ {
+ ip: net.IPv4(172, 28, 30, 2),
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
+ bcast: net.IPv4(172, 28, 30, 255),
+ },
+ {
+ ip: net.IPv4(172, 28, 30, 2),
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0},
+ bcast: net.IPv4(172, 28, 255, 255),
+ },
+ // ip in v4 representation, mask in v4Inv6 representation
+ {
+ ip: net.IPv4(172, 28, 30, 3)[12:],
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
+ bcast: net.IPv4(172, 28, 30, 255)[12:],
+ },
+ {
+ ip: net.IPv4(172, 28, 30, 3)[12:],
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
+ bcast: net.IPv4(172, 255, 255, 255)[12:],
+ },
+ // ip and mask in v4 representation
+ {
+ ip: net.IPv4(172, 28, 30, 4)[12:],
+ mask: []byte{0xff, 0xff, 0xff, 0},
+ bcast: net.IPv4(172, 28, 30, 255)[12:],
+ },
+ {
+ ip: net.IPv4(172, 28, 30, 4)[12:],
+ mask: []byte{0xff, 0xff, 0, 0},
+ bcast: net.IPv4(172, 28, 255, 255)[12:],
+ },
+ { // ip and mask as v6
+ ip: net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"),
+ mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
+ bcast: net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEFF:FFFF"),
+ },
+ }
+
+ for _, i := range input {
+ h, err := GetBroadcastIP(i.ip, i.mask)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !i.bcast.Equal(h) {
+ t.Fatalf("Failed to return expected host ip. Expected: %s. Got: %s", i.bcast, h)
+ }
+ }
+
+ // ip as v6 and mask as v4 are not compatible
+ if _, err := GetBroadcastIP(net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"), []byte{0xff, 0xff, 0xff, 0}); err == nil {
+ t.Fatalf("Unexpected success")
+ }
+ // ip as v4 and non conventional mask
+ if _, err := GetBroadcastIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0}); err == nil {
+ t.Fatalf("Unexpected success")
+ }
+ // ip as v4 and non conventional mask
+ if _, err := GetBroadcastIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0}); err == nil {
+ t.Fatalf("Unexpected success")
+ }
+}
+
+func TestParseCIDR(t *testing.T) {
+ input := []struct {
+ cidr string
+ ipnw *net.IPNet
+ }{
+ {"192.168.22.44/16", &net.IPNet{IP: net.IP{192, 168, 22, 44}, Mask: net.IPMask{255, 255, 0, 0}}},
+ {"10.10.2.0/24", &net.IPNet{IP: net.IP{10, 10, 2, 0}, Mask: net.IPMask{255, 255, 255, 0}}},
+ {"10.0.0.100/17", &net.IPNet{IP: net.IP{10, 0, 0, 100}, Mask: net.IPMask{255, 255, 128, 0}}},
+ }
+
+ for _, i := range input {
+ nw, err := ParseCIDR(i.cidr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !CompareIPNet(nw, i.ipnw) {
+ t.Fatalf("network differ. Expected %v. Got: %v", i.ipnw, nw)
+ }
+ }
+}
--- /dev/null
+github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
+github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
+github.com/Microsoft/go-winio 97e4973ce50b2ff5f09635a57e2b88a037aae829 # v0.4.11
+github.com/Microsoft/hcsshim 5b3eff572681588b6ce3df295d3d23b72f053f32 # v0.7.12
+github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
+github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
+github.com/codegangsta/cli a65b733b303f0055f8d324d805f393cd3e7a7904
+github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d
+github.com/coreos/etcd 61fc123e7a8b14a0a258aa3f5c4159861b1ec2e7 # v3.2.1
+github.com/coreos/go-semver 8ab6407b697782a06568d4b7f1db25550ec2e4c6 # v0.2.0
+github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d
+go.etcd.io/bbolt 7ee3ded59d4835e10f3e7d0f7603c42aa5e83820 # v1.3.1-etcd.8
+
+github.com/docker/docker 1046c6371132875d80f287950bb9e9e5cefa8a85 https://github.com/docker/engine.git # 18.09 branch
+github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0
+github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
+github.com/docker/go-units 47565b4f722fb6ceae66b95f853feed578a4a51c # v0.3.3
+github.com/docker/libkv 458977154600b9f23984d9f4b82e79570b5ae12b
+
+github.com/godbus/dbus 5f6efc7ef2759c81b7ba876593971bfce311eab3 # v4.0.0
+github.com/gogo/protobuf 1adfc126b41513cc696b209667c8656ea7aac67c # v1.0.0
+github.com/gorilla/context 1ea25387ff6f684839d82767c1733ff4d4d15d0a # v1.1
+github.com/gorilla/mux 0eeaf8392f5b04950925b8a69fe70f110fa7cbfc # v1.1
+github.com/hashicorp/consul 9a9cc9341bb487651a0399e3fc5e1e8a42e62dd9 # v0.5.2
+github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
+github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e
+github.com/hashicorp/memberlist 3d8438da9589e7b608a83ffac1ef8211486bcb7c
+github.com/sean-/seed e2103e2c35297fb7e17febb81e49b312087a2372
+github.com/hashicorp/go-sockaddr 6d291a969b86c4b633730bfc6b8b9d64c3aafed9
+github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870
+github.com/mattn/go-shellwords 02e3cf038dcea8290e44424da473dd12be796a8a # v1.0.3
+github.com/miekg/dns e57bf427e68187a27e22adceac868350d7a7079b # v1.0.7
+github.com/opencontainers/go-digest 279bed98673dd5bef374d3b6e4b09e2af76183bf # v1.0.0-rc1
+github.com/opencontainers/image-spec d60099175f88c47cd379c4738d158884749ed235 # v1.0.1
+github.com/opencontainers/runc 96ec2177ae841256168fcf76954f7177af9446eb
+github.com/opencontainers/runtime-spec 5684b8af48c1ac3b1451fa499724e30e3c20a294 # v1.0.1-49-g5684b8a
+github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374
+github.com/sirupsen/logrus f006c2ac4710855cf0f916dd6b77acf6b048dc6e # v1.0.3
+github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065
+github.com/vishvananda/netlink b2de5d10e38ecce8607e6b438b6d174f389a004e
+github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25
+golang.org/x/crypto 0709b304e793a5edb4a2c0145f281ecdc20838a4
+golang.org/x/net a680a1efc54dd51c040b3b5ce4939ea3cf2ea0d1
+golang.org/x/sys ac767d655b305d4e9612f5f6e33120b9176c4ad4
+golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca
+github.com/pkg/errors 645ef00459ed84a119197bfb8d8205042c6df63d # v0.8.0
+github.com/ishidawataru/sctp 07191f837fedd2f13d1ec7b5f885f0f3ec54b1cb
+
+gotest.tools b6e20af1ed078cd01a6413b734051a292450b4cb # v2.1.0
+github.com/google/go-cmp 3af367b6b30c263d47e8895973edcca9a49cf029 # v0.2.0
--- /dev/null
+
+ Apache License
+ Version 2.0, January 2004
+ https://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright The containerd Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
--- /dev/null
+# continuity
+
+[](https://godoc.org/github.com/containerd/continuity)
+[](https://travis-ci.org/containerd/continuity)
+
+A transport-agnostic, filesystem metadata manifest system
+
+This project is a staging area for experiments in providing transport agnostic
+metadata storage.
+
+Please see https://github.com/opencontainers/specs/issues/11 for more details.
+
+## Manifest Format
+
+A continuity manifest encodes filesystem metadata in Protocol Buffers.
+Please refer to [proto/manifest.proto](proto/manifest.proto).
+
+## Usage
+
+Build:
+
+```console
+$ make
+```
+
+Create a manifest (of this repo itself):
+
+```console
+$ ./bin/continuity build . > /tmp/a.pb
+```
+
+Dump a manifest:
+
+```console
+$ ./bin/continuity ls /tmp/a.pb
+...
+-rw-rw-r-- 270 B /.gitignore
+-rw-rw-r-- 88 B /.mailmap
+-rw-rw-r-- 187 B /.travis.yml
+-rw-rw-r-- 359 B /AUTHORS
+-rw-rw-r-- 11 kB /LICENSE
+-rw-rw-r-- 1.5 kB /Makefile
+...
+-rw-rw-r-- 986 B /testutil_test.go
+drwxrwxr-x 0 B /version
+-rw-rw-r-- 478 B /version/version.go
+```
+
+Verify a manifest:
+
+```console
+$ ./bin/continuity verify . /tmp/a.pb
+```
+
+Break the directory and restore using the manifest:
+```console
+$ chmod 777 Makefile
+$ ./bin/continuity verify . /tmp/a.pb
+2017/06/23 08:00:34 error verifying manifest: resource "/Makefile" has incorrect mode: -rwxrwxrwx != -rw-rw-r--
+$ ./bin/continuity apply . /tmp/a.pb
+$ stat -c %a Makefile
+664
+$ ./bin/continuity verify . /tmp/a.pb
+```
+
+
+## Contribution Guide
+### Building Proto Package
+
+If you change the proto file you will need to rebuild the generated Go with `go generate`.
+
+```console
+$ go generate ./proto
+```
+
+## Project details
+
+continuity is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
+As a containerd sub-project, you will find the:
+ * [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
+ * [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
+ * and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
+
+information in our [`containerd/project`](https://github.com/containerd/project) repository.
--- /dev/null
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package pathdriver
+
+import (
+ "path/filepath"
+)
+
+// PathDriver provides all of the path manipulation functions in a common
+// interface. The context should call these and never use the `filepath`
+// package or any other package to manipulate paths.
+type PathDriver interface {
+ Join(paths ...string) string
+ IsAbs(path string) bool
+ Rel(base, target string) (string, error)
+ Base(path string) string
+ Dir(path string) string
+ Clean(path string) string
+ Split(path string) (dir, file string)
+ Separator() byte
+ Abs(path string) (string, error)
+ Walk(string, filepath.WalkFunc) error
+ FromSlash(path string) string
+ ToSlash(path string) string
+ Match(pattern, name string) (matched bool, err error)
+}
+
+// pathDriver is a simple default implementation calls the filepath package.
+type pathDriver struct{}
+
+// LocalPathDriver is the exported pathDriver struct for convenience.
+var LocalPathDriver PathDriver = &pathDriver{}
+
+func (*pathDriver) Join(paths ...string) string {
+ return filepath.Join(paths...)
+}
+
+func (*pathDriver) IsAbs(path string) bool {
+ return filepath.IsAbs(path)
+}
+
+func (*pathDriver) Rel(base, target string) (string, error) {
+ return filepath.Rel(base, target)
+}
+
+func (*pathDriver) Base(path string) string {
+ return filepath.Base(path)
+}
+
+func (*pathDriver) Dir(path string) string {
+ return filepath.Dir(path)
+}
+
+func (*pathDriver) Clean(path string) string {
+ return filepath.Clean(path)
+}
+
+func (*pathDriver) Split(path string) (dir, file string) {
+ return filepath.Split(path)
+}
+
+func (*pathDriver) Separator() byte {
+ return filepath.Separator
+}
+
+func (*pathDriver) Abs(path string) (string, error) {
+ return filepath.Abs(path)
+}
+
+// Note that filepath.Walk calls os.Stat, so if the context wants to
+// to call Driver.Stat() for Walk, they need to create a new struct that
+// overrides this method.
+func (*pathDriver) Walk(root string, walkFn filepath.WalkFunc) error {
+ return filepath.Walk(root, walkFn)
+}
+
+func (*pathDriver) FromSlash(path string) string {
+ return filepath.FromSlash(path)
+}
+
+func (*pathDriver) ToSlash(path string) string {
+ return filepath.ToSlash(path)
+}
+
+func (*pathDriver) Match(pattern, name string) (bool, error) {
+ return filepath.Match(pattern, name)
+}
--- /dev/null
+bazil.org/fuse 371fbbdaa8987b715bdd21d6adc4c9b20155f748
+github.com/dustin/go-humanize bb3d318650d48840a39aa21a027c6630e198e626
+github.com/golang/protobuf 1e59b77b52bf8e4b449a57e6f79f21226d571845
+github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
+github.com/opencontainers/go-digest 279bed98673dd5bef374d3b6e4b09e2af76183bf
+github.com/pkg/errors f15c970de5b76fac0b59abb32d62c17cc7bed265
+github.com/sirupsen/logrus 89742aefa4b206dcf400792f3bd35b542998eb3b
+github.com/spf13/cobra 2da4a54c5ceefcee7ca5dd0eea1e18a3b6366489
+github.com/spf13/pflag 4c012f6dcd9546820e378d0bdda4d8fc772cdfea
+golang.org/x/crypto 9f005a07e0d31d45e6656d241bb5c0f2efd4bc94
+golang.org/x/net a337091b0525af65de94df2eb7e98bd9962dcbe2
+golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
+golang.org/x/sys 77b0e4315053a57ed2962443614bdb28db152054