Vendoring in libnetwork, netns, netlink

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
This commit is contained in:
Jana Radhakrishnan
2015-05-07 00:09:12 +00:00
parent 322f675123
commit 272f8cd4bc
176 changed files with 21776 additions and 3807 deletions

View File

@@ -46,7 +46,6 @@ clone() {
clone git github.com/Sirupsen/logrus v0.7.3 # logrus is a common dependency among multiple deps
clone git github.com/docker/libtrust 230dfd18c232
clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673
clone git github.com/go-fsnotify/fsnotify v1.2.0
clone git github.com/gorilla/context 14f550f51a
clone git github.com/gorilla/mux e444e69cbd
clone git github.com/kr/pty 5cf931ef8f
@@ -55,6 +54,11 @@ clone git github.com/tchap/go-patricia v2.1.0
clone hg code.google.com/p/go.net 84a4013f96e0
clone hg code.google.com/p/gosqlite 74691fb6f837
#get libnetwork packages
clone git github.com/docker/libnetwork v0.2
clone git github.com/vishvananda/netns 008d17ae001344769b031375bdb38a86219154c6
clone git github.com/vishvananda/netlink 8eb64238879fed52fd51c5b30ad20b928fb4c36c
# get distribution packages
clone git github.com/docker/distribution d957768537c5af40e4f4cd96871f7b2bde9e2923
mv src/github.com/docker/distribution/digest tmp-digest

View File

@@ -0,0 +1,33 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Coverage
*.tmp
*.coverprofile
# IDE files
.project
libnetwork-build.created

View File

@@ -0,0 +1,85 @@
{
"ImportPath": "github.com/docker/libnetwork",
"GoVersion": "go1.4.1",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/Sirupsen/logrus",
"Comment": "v0.6.4-12-g467d9d5",
"Rev": "467d9d55c2d2c17248441a8fc661561161f40d5e"
},
{
"ImportPath": "github.com/docker/docker/pkg/homedir",
"Comment": "v1.4.1-3479-ga9172f5",
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
},
{
"ImportPath": "github.com/docker/docker/pkg/ioutils",
"Comment": "v1.4.1-3479-ga9172f5",
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
},
{
"ImportPath": "github.com/docker/docker/pkg/mflag",
"Comment": "v1.4.1-3479-ga9172f5",
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
},
{
"ImportPath": "github.com/docker/docker/pkg/parsers",
"Comment": "v1.4.1-3479-ga9172f5",
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
},
{
"ImportPath": "github.com/docker/docker/pkg/plugins",
"Comment": "v1.4.1-3479-ga9172f5",
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
},
{
"ImportPath": "github.com/docker/docker/pkg/proxy",
"Comment": "v1.4.1-3479-ga9172f5",
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
},
{
"ImportPath": "github.com/docker/docker/pkg/reexec",
"Comment": "v1.4.1-3479-ga9172f5",
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
},
{
"ImportPath": "github.com/docker/docker/pkg/stringid",
"Comment": "v1.4.1-3479-ga9172f5",
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
},
{
"ImportPath": "github.com/docker/docker/pkg/term",
"Comment": "v1.4.1-3479-ga9172f5",
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
},
{
"ImportPath": "github.com/docker/libcontainer/user",
"Comment": "v1.4.0-495-g3e66118",
"Rev": "3e661186ba24f259d3860f067df052c7f6904bee"
},
{
"ImportPath": "github.com/godbus/dbus",
"Comment": "v2-3-g4160802",
"Rev": "41608027bdce7bfa8959d653a00b954591220e67"
},
{
"ImportPath": "github.com/gorilla/context",
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
},
{
"ImportPath": "github.com/gorilla/mux",
"Rev": "8096f47503459bcc74d1f4c487b7e6e42e5746b5"
},
{
"ImportPath": "github.com/vishvananda/netlink",
"Rev": "8eb64238879fed52fd51c5b30ad20b928fb4c36c"
},
{
"ImportPath": "github.com/vishvananda/netns",
"Rev": "008d17ae001344769b031375bdb38a86219154c6"
}
]
}

View File

@@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View File

@@ -0,0 +1,202 @@
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.

View File

@@ -0,0 +1,4 @@
Alexandr Morozov <lk4d4@docker.com> (@LK4D4)
Arnaud Porterie <arnaud@docker.com> (@icecrime)
Madhu Venugopal <madhu@docker.com> (@mavenugo)
Jana Radhakrishnan <mrjana@docker.com> (@mrjana)

View File

@@ -0,0 +1,78 @@
.PHONY: all all-local build build-local check check-code check-format run-tests check-local install-deps coveralls circle-ci
SHELL=/bin/bash
build_image=libnetwork-build
dockerargs = --privileged -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork
container_env = -e "INSIDECONTAINER=-incontainer=true"
docker = docker run --rm ${dockerargs} ${container_env} ${build_image}
ciargs = -e "COVERALLS_TOKEN=$$COVERALLS_TOKEN" -e "INSIDECONTAINER=-incontainer=true"
cidocker = docker run ${ciargs} ${dockerargs} golang:1.4
all: ${build_image}.created
${docker} make all-local
all-local: check-local build-local
${build_image}.created:
docker run --name=libnetworkbuild -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork golang:1.4 make install-deps
docker commit libnetworkbuild ${build_image}
docker rm libnetworkbuild
touch ${build_image}.created
build: ${build_image}.created
${docker} make build-local
build-local:
$(shell which godep) go build ./...
check: ${build_image}.created
${docker} make check-local
check-code:
@echo "Checking code... "
test -z "$$(golint ./... | tee /dev/stderr)"
go vet ./...
@echo "Done checking code"
check-format:
@echo "Checking format... "
test -z "$$(goimports -l . | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
@echo "Done checking format"
run-tests:
@echo "Running tests... "
@echo "mode: count" > coverage.coverprofile
@for dir in $$(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d); do \
if ls $$dir/*.go &> /dev/null; then \
pushd . &> /dev/null ; \
cd $$dir ; \
$(shell which godep) go test ${INSIDECONTAINER} -test.parallel 3 -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"
check-local: check-format check-code run-tests
install-deps:
apt-get update && apt-get -y install iptables
go get github.com/tools/godep
go get github.com/golang/lint/golint
go get golang.org/x/tools/cmd/vet
go get golang.org/x/tools/cmd/goimports
go get golang.org/x/tools/cmd/cover
go get github.com/mattn/goveralls
coveralls:
-@goveralls -service circleci -coverprofile=coverage.coverprofile -repotoken $$COVERALLS_TOKEN
# CircleCI's Docker fails when cleaning up using the --rm flag
# The following target is a workaround for this
circle-ci:
@${cidocker} make install-deps check-local coveralls

View File

@@ -0,0 +1,86 @@
# libnetwork - networking for containers
[![Circle CI](https://circleci.com/gh/docker/libnetwork/tree/master.svg?style=svg)](https://circleci.com/gh/docker/libnetwork/tree/master) [![Coverage Status](https://coveralls.io/repos/docker/libnetwork/badge.svg)](https://coveralls.io/r/docker/libnetwork) [![GoDoc](https://godoc.org/github.com/docker/libnetwork?status.svg)](https://godoc.org/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.
**NOTE**: libnetwork project is under heavy development and is not ready for general use.
#### 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
// Create a new controller instance
controller := libnetwork.New()
// Select and configure the network driver
networkType := "bridge"
driverOptions := options.Generic{}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = driverOptions
err := controller.ConfigureNetworkDriver(networkType, genericOption)
if err != nil {
return
}
// Create a network for containers to join.
// NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can make 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
}
// A container can join the endpoint by providing the container ID to the join
// api which returns the sandbox key which can be used to access the sandbox
// created for the container during join.
// Join acceps Variadic arguments which will be made use of by libnetwork and Drivers
_, err = ep.Join("container1",
libnetwork.JoinOptionHostname("test"),
libnetwork.JoinOptionDomainname("docker.io"))
if err != nil {
return
}
// libentwork client can check the endpoint's operational data via the Info() API
epInfo, err := ep.DriverInfo()
mapData, ok := epInfo[netlabel.PortMap]
if ok {
portMapping, ok := mapData.([]netutils.PortBinding)
if ok {
fmt.Printf("Current port mapping for endpoint %s: %v", ep.Name(), portMapping)
}
}
```
#### Current Status
Please watch this space for updates on the progress.
Currently libnetwork is nothing more than an attempt to modularize the Docker platform's networking subsystem by moving it into libnetwork as a library.
## 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.

View File

@@ -0,0 +1,29 @@
# Roadmap
Libnetwork is a young project and is still being defined.
This document defines the high-level goals of the project and defines the release-relationship to the Docker Platform.
* [Goals](#goals)
* [Project Planning](#project-planning): release-relationship to the Docker Platform.
## Long-term Goal
libnetwork project will follow Docker and Linux philosophy of delivering small, highly modular and composable tools that works 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
Libnetwork versions do not map 1:1 with Docker Platform releases.
Milestones and Project Pages are used to define the set of features that are included in each release.
| Platform Version | Libnetwork Version | Planning |
|------------------|--------------------|----------|
| Docker 1.7 | [0.3](https://github.com/docker/libnetwork/milestones/0.3) | [Project Page](https://github.com/docker/libnetwork/wiki/Docker-1.7-Project-Page) |
| Docker 1.8 | [1.0](https://github.com/docker/libnetwork/milestones/1.0) | [Project Page](https://github.com/docker/libnetwork/wiki/Docker-1.8-Project-Page) |

View File

@@ -0,0 +1,481 @@
package api
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/docker/libnetwork"
"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}
)
const (
urlNwName = "name"
urlNwID = "id"
urlEpName = "endpoint-name"
urlEpID = "endpoint-id"
urlCnID = "container-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]map[string]processor{
"GET": {
"/networks": procGetNetworks,
"/networks/name/{" + urlNwName + ":.*}": procGetNetwork,
"/networks/id/{" + urlNwID + ":.*}": procGetNetwork,
"/networks/name/{" + urlNwName + ":.*}/endpoints/": procGetEndpoints,
"/networks/id/{" + urlNwID + ":.*}/endpoints/": procGetEndpoints,
"/networks/name/{" + urlNwName + ":.*}/endpoints/name/{" + urlEpName + ":.*}": procGetEndpoint,
"/networks/id/{" + urlNwID + ":.*}/endpoints/name/{" + urlEpName + ":.*}": procGetEndpoint,
"/networks/name/{" + urlNwName + ":.*}/endpoints/id/{" + urlEpID + ":.*}": procGetEndpoint,
"/networks/id/{" + urlNwID + ":.*}/endpoints/id/{" + urlEpID + ":.*}": procGetEndpoint,
},
"POST": {
"/networks/name/{" + urlNwName + ":.*}": procCreateNetwork,
"/networks/name/{" + urlNwName + ":.*}/endpoint/name/{" + urlEpName + ":.*}": procCreateEndpoint,
"/networks/name/{" + urlNwName + ":.*}/endpoint/name/{" + urlEpName + ":.*}/container/{" + urlCnID + ":.*}": procJoinEndpoint,
},
"DELETE": {
"/networks/name/{" + urlNwName + ":.*}": procDeleteNetwork,
"/networks/id/{" + urlNwID + ":.*}": procDeleteNetwork,
"/networks/name/{" + urlNwName + ":.*}/endpoints/name/{" + urlEpName + ":.*}": procDeleteEndpoint,
"/networks/name/{" + urlNwName + ":.*}/endpoints/id/{" + urlEpID + ":.*}": procDeleteEndpoint,
"/networks/id/{" + urlNwID + ":.*}/endpoints/name/{" + urlEpName + ":.*}": procDeleteEndpoint,
"/networks/id/{" + urlNwID + ":.*}/endpoints/id/{" + urlEpID + ":.*}": procDeleteEndpoint,
"/networks/name/{" + urlNwName + ":.*}/endpoint/name/{" + urlEpName + ":.*}/container/{" + urlCnID + ":.*}": procLeaveEndpoint,
"/networks/name/{" + urlNwName + ":.*}/endpoint/id/{" + urlEpID + ":.*}/container/{" + urlCnID + ":.*}": procLeaveEndpoint,
"/networks/id/{" + urlNwID + ":.*}/endpoint/name/{" + urlEpName + ":.*}/container/{" + urlCnID + ":.*}": procLeaveEndpoint,
"/networks/id/{" + urlNwID + ":.*}/endpoint/id/{" + urlEpID + ":.*}/container/{" + urlCnID + ":.*}": procLeaveEndpoint,
},
}
h.r = mux.NewRouter()
for method, routes := range m {
for route, fct := range routes {
f := makeHandler(h.c, fct)
h.r.Path(route).Methods(method).HandlerFunc(f)
}
}
}
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
}
/**************
Options Parser
***************/
func (ej *endpointJoin) parseOptions() []libnetwork.EndpointOption {
var setFctList []libnetwork.EndpointOption
if ej.HostName != "" {
setFctList = append(setFctList, libnetwork.JoinOptionHostname(ej.HostName))
}
if ej.DomainName != "" {
setFctList = append(setFctList, libnetwork.JoinOptionDomainname(ej.DomainName))
}
if ej.HostsPath != "" {
setFctList = append(setFctList, libnetwork.JoinOptionHostsPath(ej.HostsPath))
}
if ej.ResolvConfPath != "" {
setFctList = append(setFctList, libnetwork.JoinOptionResolvConfPath(ej.ResolvConfPath))
}
if ej.UseDefaultSandbox {
setFctList = append(setFctList, libnetwork.JoinOptionUseDefaultSandbox())
}
if ej.DNS != nil {
for _, d := range ej.DNS {
setFctList = append(setFctList, libnetwork.JoinOptionDNS(d))
}
}
if ej.ExtraHosts != nil {
for _, e := range ej.ExtraHosts {
setFctList = append(setFctList, libnetwork.JoinOptionExtraHost(e.Name, e.Address))
}
}
if ej.ParentUpdates != nil {
for _, p := range ej.ParentUpdates {
setFctList = append(setFctList, libnetwork.JoinOptionParentUpdate(p.EndpointID, p.Name, p.Address))
}
}
return setFctList
}
/******************
Process functions
*******************/
/***************************
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 "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
name := vars[urlNwName]
if name != create.Name {
return "", &mismatchResponse
}
nw, err := c.NewNetwork(create.NetworkType, name, nil)
if err != nil {
return "", 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
for _, nw := range c.Networks() {
nwr := buildNetworkResource(nw)
list = append(list, nwr)
}
return list, &successResponse
}
/******************
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}
}
epn := vars[urlEpName]
if ec.Name != epn {
return "", &mismatchResponse
}
nwT, nwBy := detectNetworkTarget(vars)
n, errRsp := findNetwork(c, nwT, nwBy)
if !errRsp.isOK() {
return "", errRsp
}
if ec.NetworkID != n.ID() {
return "", &mismatchResponse
}
var setFctList []libnetwork.EndpointOption
if ec.ExposedPorts != nil {
setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(ec.ExposedPorts))
}
if ec.PortMapping != nil {
setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(ec.PortMapping))
}
ep, err := n.CreateEndpoint(epn, 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) {
target, by := detectNetworkTarget(vars)
nw, errRsp := findNetwork(c, target, by)
if !errRsp.isOK() {
return nil, errRsp
}
var list []*endpointResource
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
err := json.Unmarshal(body, &ej)
if err != nil {
return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
cid := vars[urlCnID]
if ej.ContainerID != cid {
return "", &mismatchResponse
}
nwT, nwBy := detectNetworkTarget(vars)
epT, epBy := detectEndpointTarget(vars)
ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
cd, err := ep.Join(ej.ContainerID, ej.parseOptions()...)
if err != nil {
return nil, convertNetworkError(err)
}
return cd, &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
}
err := ep.Leave(vars[urlCnID])
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()
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 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:
nw, err = c.NetworkByName(s)
default:
panic(fmt.Sprintf("unexpected selector for network search: %d", by))
}
if err != nil {
if err == libnetwork.ErrNoSuchNetwork {
return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound}
}
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
return nw, &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 err == libnetwork.ErrNoSuchEndpoint {
return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound}
}
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
return ep, &successResponse
}
func convertNetworkError(err error) *responseStatus {
// No real libnetwork error => http error code conversion for now.
// Will came in later when new interface for libnetwork error is vailable
return &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
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)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
package api
import "github.com/docker/libnetwork/netutils"
/***********
Resources
************/
// networkResource is the body of the "get network" http response message
type networkResource struct {
Name string
ID string
Type string
Endpoints []*endpointResource
}
// endpointResource is the body of the "get endpoint" http response message
type endpointResource struct {
Name string
ID string
Network string
}
/***********
Body types
************/
// networkCreate is the expected body of the "create network" http request message
type networkCreate struct {
Name string
NetworkType string
Options map[string]interface{}
}
// endpointCreate represents the body of the "create endpoint" http request message
type endpointCreate struct {
Name string
NetworkID string
ExposedPorts []netutils.TransportPort
PortMapping []netutils.PortBinding
}
// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages
type endpointJoin struct {
ContainerID string
HostName string
DomainName string
HostsPath string
ResolvConfPath string
DNS []string
ExtraHosts []endpointExtraHost
ParentUpdates []endpointParentUpdate
UseDefaultSandbox bool
}
// EndpointExtraHost represents the extra host object
type endpointExtraHost struct {
Name string
Address string
}
// EndpointParentUpdate is the object carrying the information about the
// endpoint parent that needs to be updated
type endpointParentUpdate struct {
EndpointID string
Name string
Address string
}

View File

@@ -0,0 +1,12 @@
machine:
services:
- docker
dependencies:
override:
- echo "Nothing to install"
test:
override:
- make circle-ci

View File

@@ -0,0 +1,105 @@
package client
import (
"fmt"
"io"
"io/ioutil"
"reflect"
"strings"
flag "github.com/docker/docker/pkg/mflag"
)
// CallFunc provides environment specific call utility to invoke backend functions from UI
type CallFunc func(string, string, interface{}, map[string][]string) (io.ReadCloser, 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) > 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'.\n", 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() {
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)
flags.PrintDefaults()
}
return flags
}
func readBody(stream io.ReadCloser, 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
}

View File

@@ -0,0 +1,154 @@
package client
import (
"bytes"
"io"
"testing"
_ "github.com/docker/libnetwork/netutils"
)
// nopCloser is used to provide a dummy CallFunc for Cmd()
type nopCloser struct {
io.Reader
}
func (nopCloser) Close() error { return nil }
func TestClientDummyCommand(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nopCloser{bytes.NewBufferString("")}, 200, nil
}
cli := NewNetworkCli(&out, &errOut, cFunc)
err := cli.Cmd("docker", "dummy")
if err == nil {
t.Fatalf("Incorrect Command must fail")
}
}
func TestClientNetworkInvalidCommand(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nopCloser{bytes.NewBufferString("")}, 200, nil
}
cli := NewNetworkCli(&out, &errOut, cFunc)
err := cli.Cmd("docker", "network", "invalid")
if err == nil {
t.Fatalf("Passing invalid commands must fail")
}
}
func TestClientNetworkCreate(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nopCloser{bytes.NewBufferString("")}, 200, nil
}
cli := NewNetworkCli(&out, &errOut, cFunc)
err := cli.Cmd("docker", "network", "create", "test")
if err != nil {
t.Fatal(err.Error())
}
}
func TestClientNetworkCreateWithDriver(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nopCloser{bytes.NewBufferString("")}, 200, nil
}
cli := NewNetworkCli(&out, &errOut, cFunc)
err := cli.Cmd("docker", "network", "create", "-f=dummy", "test")
if err == nil {
t.Fatalf("Passing incorrect flags to the create command must fail")
}
err = cli.Cmd("docker", "network", "create", "-d=dummy", "test")
if err != nil {
t.Fatalf(err.Error())
}
}
func TestClientNetworkRm(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nopCloser{bytes.NewBufferString("")}, 200, nil
}
cli := NewNetworkCli(&out, &errOut, cFunc)
err := cli.Cmd("docker", "network", "rm", "test")
if err != nil {
t.Fatal(err.Error())
}
}
func TestClientNetworkLs(t *testing.T) {
var out, errOut bytes.Buffer
networks := "db,web,test"
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nopCloser{bytes.NewBufferString(networks)}, 200, nil
}
cli := NewNetworkCli(&out, &errOut, cFunc)
err := cli.Cmd("docker", "network", "ls")
if err != nil {
t.Fatal(err.Error())
}
if out.String() != networks {
t.Fatal("Network List command fail to return the intended list")
}
}
func TestClientNetworkInfo(t *testing.T) {
var out, errOut bytes.Buffer
info := "dummy info"
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nopCloser{bytes.NewBufferString(info)}, 200, nil
}
cli := NewNetworkCli(&out, &errOut, cFunc)
err := cli.Cmd("docker", "network", "info", "test")
if err != nil {
t.Fatal(err.Error())
}
if out.String() != info {
t.Fatal("Network List command fail to return the intended list")
}
}
// 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 TestClientNetworkCreateHelp(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, cFunc)
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 TestClientNetworkCreateMissingArgument(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, cFunc)
err := cli.Cmd("docker", "network", "create")
if err != nil {
t.Fatal(err.Error())
}
}
*/

View File

@@ -0,0 +1,132 @@
package client
import (
"bytes"
"fmt"
"io"
flag "github.com/docker/docker/pkg/mflag"
)
const (
nullNetType = "null"
)
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"}, "null", "Driver to manage the Network")
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *flDriver == "" {
*flDriver = nullNetType
}
nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver}
obj, _, err := readBody(cli.call("POST", "/networks/name/"+cmd.Arg(0), nc, nil))
if err != nil {
fmt.Fprintf(cli.err, "%s", err.Error())
return err
}
if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil {
return err
}
return nil
}
// CmdNetworkRm handles Network Delete UI
func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "rm", "NETWORK-NAME", "Deletes a network", false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
obj, _, err := readBody(cli.call("DELETE", "/networks/name/"+cmd.Arg(0), nil, nil))
if err != nil {
fmt.Fprintf(cli.err, "%s", err.Error())
return err
}
if _, err := io.Copy(cli.out, bytes.NewReader(obj)); 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)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
if err != nil {
fmt.Fprintf(cli.err, "%s", err.Error())
return err
}
if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil {
return err
}
return nil
}
// CmdNetworkInfo handles Network Info UI
func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "info", "NETWORK-NAME", "Displays detailed information on a network", false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
obj, _, err := readBody(cli.call("GET", "/networks/name/"+cmd.Arg(0), nil, nil))
if err != nil {
fmt.Fprintf(cli.err, "%s", err.Error())
return err
}
if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil {
return err
}
return nil
}
func networkUsage(chain string) string {
help := "Commands:\n"
for _, cmd := range networkCommands {
help += fmt.Sprintf(" %-10.10s%s\n", cmd.name, cmd.description)
}
help += fmt.Sprintf("\nRun '%s network COMMAND --help' for more information on a command.", chain)
return help
}

View File

@@ -0,0 +1,34 @@
package client
import "github.com/docker/libnetwork/sandbox"
/***********
Resources
************/
// networkResource is the body of the "get network" http response message
type networkResource struct {
Name string
ID string
Type string
Endpoints []*endpointResource
}
// endpointResource is the body of the "get endpoint" http response message
type endpointResource struct {
Name string
ID string
Network string
Info sandbox.Info
}
/***********
Body types
************/
// networkCreate is the expected body of the "create network" http request message
type networkCreate struct {
Name string
NetworkType string
Options map[string]interface{}
}

View File

@@ -0,0 +1,206 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/term"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/api"
"github.com/docker/libnetwork/client"
"github.com/gorilla/mux"
)
var (
// DefaultHTTPHost is used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
DefaultHTTPHost = "127.0.0.1"
// DefaultHTTPPort is the default http port used by dnet
DefaultHTTPPort = 2385
// DefaultUnixSocket exported
DefaultUnixSocket = "/var/run/dnet.sock"
)
func main() {
_, stdout, stderr := term.StdStreams()
logrus.SetOutput(stderr)
err := dnetCommand(stdout, stderr)
if err != nil {
os.Exit(1)
}
}
func dnetCommand(stdout, stderr io.Writer) error {
flag.Parse()
if *flHelp {
flag.Usage()
return nil
}
if *flLogLevel != "" {
lvl, err := logrus.ParseLevel(*flLogLevel)
if err != nil {
fmt.Fprintf(stderr, "Unable to parse logging level: %s\n", *flLogLevel)
return err
}
logrus.SetLevel(lvl)
} else {
logrus.SetLevel(logrus.InfoLevel)
}
if *flDebug {
logrus.SetLevel(logrus.DebugLevel)
}
if *flHost == "" {
defaultHost := os.Getenv("DNET_HOST")
if defaultHost == "" {
// TODO : Add UDS support
defaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
}
*flHost = defaultHost
}
dc, err := newDnetConnection(*flHost)
if err != nil {
if *flDaemon {
logrus.Error(err)
} else {
fmt.Fprint(stderr, err)
}
return err
}
if *flDaemon {
err := dc.dnetDaemon()
if err != nil {
logrus.Errorf("dnet Daemon exited with an error : %v", err)
}
return err
}
cli := client.NewNetworkCli(stdout, stderr, dc.httpCall)
if err := cli.Cmd("dnet", flag.Args()...); err != nil {
fmt.Fprintln(stderr, err)
return err
}
return nil
}
type dnetConnection struct {
// proto holds the client protocol i.e. unix.
proto string
// addr holds the client address.
addr string
}
func (d *dnetConnection) dnetDaemon() error {
controller, err := libnetwork.New()
if err != nil {
fmt.Println("Error starting dnetDaemon :", err)
return err
}
httpHandler := api.NewHTTPHandler(controller)
r := mux.NewRouter().StrictSlash(false)
post := r.PathPrefix("/networks").Subrouter()
post.Methods("GET").HandlerFunc(httpHandler)
post.Methods("PUT", "POST").HandlerFunc(httpHandler)
post.Methods("DELETE").HandlerFunc(httpHandler)
return http.ListenAndServe(d.addr, r)
}
func newDnetConnection(val string) (*dnetConnection, error) {
url, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val)
if err != nil {
return nil, err
}
protoAddrParts := strings.SplitN(url, "://", 2)
if len(protoAddrParts) != 2 {
return nil, fmt.Errorf("bad format, expected tcp://ADDR")
}
if strings.ToLower(protoAddrParts[0]) != "tcp" {
return nil, fmt.Errorf("dnet currently only supports tcp transport")
}
return &dnetConnection{protoAddrParts[0], protoAddrParts[1]}, nil
}
func (d *dnetConnection) httpCall(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
var in io.Reader
in, err := encodeData(data)
if err != nil {
return nil, -1, err
}
req, err := http.NewRequest(method, fmt.Sprintf("%s", path), in)
if err != nil {
return 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, statusCode, fmt.Errorf("An error occurred trying to connect: %v", err)
}
if statusCode < 200 || statusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, statusCode, err
}
return nil, statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
}
return resp.Body, 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
}

View File

@@ -0,0 +1,132 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"testing"
"time"
"github.com/docker/libnetwork/netutils"
)
const dnetCommandName = "dnet"
var origStdOut = os.Stdout
func TestDnetDaemonCustom(t *testing.T) {
if !netutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
customPort := 4567
doneChan := make(chan bool)
go func() {
args := []string{dnetCommandName, "-d", fmt.Sprintf("-H=:%d", customPort)}
executeDnetCommand(t, args, true)
doneChan <- true
}()
select {
case <-doneChan:
t.Fatal("dnet Daemon is not supposed to exit")
case <-time.After(3 * time.Second):
args := []string{dnetCommandName, "-d=false", fmt.Sprintf("-H=:%d", customPort), "-D", "network", "ls"}
executeDnetCommand(t, args, true)
}
}
func TestDnetDaemonInvalidCustom(t *testing.T) {
if !netutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
customPort := 4668
doneChan := make(chan bool)
go func() {
args := []string{dnetCommandName, "-d=true", fmt.Sprintf("-H=:%d", customPort)}
executeDnetCommand(t, args, true)
doneChan <- true
}()
select {
case <-doneChan:
t.Fatal("dnet Daemon is not supposed to exit")
case <-time.After(3 * time.Second):
args := []string{dnetCommandName, "-d=false", "-H=:6669", "-D", "network", "ls"}
executeDnetCommand(t, args, false)
}
}
func TestDnetDaemonInvalidParams(t *testing.T) {
if !netutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
args := []string{dnetCommandName, "-d=false", "-H=tcp:/127.0.0.1:8080"}
executeDnetCommand(t, args, false)
args = []string{dnetCommandName, "-d=false", "-H=unix://var/run/dnet.sock"}
executeDnetCommand(t, args, false)
args = []string{dnetCommandName, "-d=false", "-H=", "-l=invalid"}
executeDnetCommand(t, args, false)
args = []string{dnetCommandName, "-d=false", "-H=", "-l=error", "invalid"}
executeDnetCommand(t, args, false)
}
func TestDnetDefaultsWithFlags(t *testing.T) {
if !netutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
doneChan := make(chan bool)
go func() {
args := []string{dnetCommandName, "-d=true", "-H=", "-l=error"}
executeDnetCommand(t, args, true)
doneChan <- true
}()
select {
case <-doneChan:
t.Fatal("dnet Daemon is not supposed to exit")
case <-time.After(3 * time.Second):
args := []string{dnetCommandName, "-d=false", "network", "create", "-d=null", "test"}
executeDnetCommand(t, args, true)
args = []string{dnetCommandName, "-d=false", "-D", "network", "ls"}
executeDnetCommand(t, args, true)
}
}
func TestDnetMain(t *testing.T) {
if !netutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
customPort := 4568
doneChan := make(chan bool)
go func() {
args := []string{dnetCommandName, "-d=true", "-h=false", fmt.Sprintf("-H=:%d", customPort)}
os.Args = args
main()
doneChan <- true
}()
select {
case <-doneChan:
t.Fatal("dnet Daemon is not supposed to exit")
case <-time.After(2 * time.Second):
}
}
func executeDnetCommand(t *testing.T, args []string, shouldSucced bool) {
_, w, _ := os.Pipe()
os.Stdout = w
os.Args = args
err := dnetCommand(ioutil.Discard, ioutil.Discard)
if shouldSucced && err != nil {
os.Stdout = origStdOut
t.Fatalf("cli [%v] must succeed, but failed with an error : %v", args, err)
} else if !shouldSucced && err == nil {
os.Stdout = origStdOut
t.Fatalf("cli [%v] must fail, but succeeded with an error : %v", args, err)
}
os.Stdout = origStdOut
}

View File

@@ -0,0 +1,49 @@
package main
import (
"fmt"
"os"
flag "github.com/docker/docker/pkg/mflag"
)
type command struct {
name string
description string
}
type byName []command
var (
flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
flHost = flag.String([]string{"H", "-Host"}, "", "Daemon socket to connect to")
flLogLevel = flag.String([]string{"l", "-log-level"}, "info", "Set the logging level")
flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage")
dnetCommands = []command{
{"network", "Network management commands"},
}
)
func init() {
flag.Usage = func() {
fmt.Fprint(os.Stdout, "Usage: dnet [OPTIONS] COMMAND [arg...]\n\nA self-sufficient runtime for container networking.\n\nOptions:\n")
flag.CommandLine.SetOutput(os.Stdout)
flag.PrintDefaults()
help := "\nCommands:\n"
for _, cmd := range dnetCommands {
help += fmt.Sprintf(" %-10.10s%s\n", cmd.name, cmd.description)
}
help += "\nRun 'dnet COMMAND --help' for more information on a command."
fmt.Fprintf(os.Stdout, "%s\n", help)
}
}
func printUsage() {
fmt.Println("Usage: dnet network <subcommand> <OPTIONS>")
}

View File

@@ -0,0 +1,66 @@
package main
import (
"fmt"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/options"
)
func main() {
// Create a new controller instance
controller, err := libnetwork.New()
if err != nil {
return
}
// Select and configure the network driver
networkType := "bridge"
driverOptions := options.Generic{}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = driverOptions
err = controller.ConfigureNetworkDriver(networkType, genericOption)
if err != nil {
return
}
// Create a network for containers to join.
// NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can make 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
}
// A container can join the endpoint by providing the container ID to the join
// api which returns the sandbox key which can be used to access the sandbox
// created for the container during join.
// Join acceps Variadic arguments which will be made use of by libnetwork and Drivers
_, err = ep.Join("container1",
libnetwork.JoinOptionHostname("test"),
libnetwork.JoinOptionDomainname("docker.io"))
if err != nil {
return
}
// libentwork client can check the endpoint's operational data via the Info() API
epInfo, err := ep.DriverInfo()
mapData, ok := epInfo[netlabel.PortMap]
if ok {
portMapping, ok := mapData.([]netutils.PortBinding)
if ok {
fmt.Printf("Current port mapping for endpoint %s: %v", ep.Name(), portMapping)
}
}
}

View File

@@ -0,0 +1,28 @@
package main
import (
"fmt"
"log"
"net"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/options"
)
func main() {
ip, net, _ := net.ParseCIDR("192.168.100.1/24")
net.IP = ip
options := options.Generic{"AddressIPv4": net}
controller, err := libnetwork.New()
if err != nil {
log.Fatal(err)
}
netType := "bridge"
err = controller.ConfigureNetworkDriver(netType, options)
netw, err := controller.NewNetwork(netType, "dummy")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Network=%#v\n", netw)
}

View File

@@ -0,0 +1,298 @@
/*
Package libnetwork provides the basic functionality and extension points to
create network namespaces and allocate interfaces for containers to use.
// Create a new controller instance
controller, _err := libnetwork.New()
// Select and configure the network driver
networkType := "bridge"
driverOptions := options.Generic{}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = driverOptions
err := controller.ConfigureNetworkDriver(networkType, genericOption)
if err != nil {
return
}
// Create a network for containers to join.
// NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can make 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
}
// A container can join the endpoint by providing the container ID to the join
// api which returns the sandbox key which can be used to access the sandbox
// created for the container during join.
// Join acceps Variadic arguments which will be made use of by libnetwork and Drivers
_, err = ep.Join("container1",
libnetwork.JoinOptionHostname("test"),
libnetwork.JoinOptionDomainname("docker.io"))
if err != nil {
return
}
*/
package libnetwork
import (
"sync"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/sandbox"
"github.com/docker/libnetwork/types"
)
// NetworkController provides the interface for controller instance which manages
// networks.
type NetworkController interface {
// ConfigureNetworkDriver applies the passed options to the driver instance for the specified network type
ConfigureNetworkDriver(networkType string, options map[string]interface{}) error
// Create a new network. The options parameter carries network specific options.
// Labels support will be added in the near future.
NewNetwork(networkType, name 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)
}
// 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
type sandboxData struct {
sandbox sandbox.Sandbox
refCnt int
}
type networkTable map[types.UUID]*network
type endpointTable map[types.UUID]*endpoint
type sandboxTable map[string]*sandboxData
type controller struct {
networks networkTable
drivers driverTable
sandboxes sandboxTable
sync.Mutex
}
// New creates a new instance of network controller.
func New() (NetworkController, error) {
c := &controller{
networks: networkTable{},
sandboxes: sandboxTable{},
drivers: driverTable{}}
if err := initDrivers(c); err != nil {
return nil, err
}
return c, nil
}
func (c *controller) ConfigureNetworkDriver(networkType string, options map[string]interface{}) error {
c.Lock()
d, ok := c.drivers[networkType]
c.Unlock()
if !ok {
return NetworkTypeError(networkType)
}
return d.Config(options)
}
func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver) error {
c.Lock()
defer c.Unlock()
if _, ok := c.drivers[networkType]; ok {
return driverapi.ErrActiveRegistration(networkType)
}
c.drivers[networkType] = driver
return nil
}
// 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, options ...NetworkOption) (Network, error) {
if name == "" {
return nil, ErrInvalidName
}
// Check if a driver for the specified network type is available
c.Lock()
d, ok := c.drivers[networkType]
c.Unlock()
if !ok {
var err error
d, err = c.loadDriver(networkType)
if err != nil {
return nil, err
}
}
// Check if a network already exists with the specified network name
c.Lock()
for _, n := range c.networks {
if n.name == name {
c.Unlock()
return nil, NetworkNameError(name)
}
}
c.Unlock()
// Construct the network object
network := &network{
name: name,
id: types.UUID(stringid.GenerateRandomID()),
ctrlr: c,
driver: d,
endpoints: endpointTable{},
}
network.processOptions(options...)
// Create the network
if err := d.CreateNetwork(network.id, network.generic); err != nil {
return nil, err
}
// Store the network handler in controller
c.Lock()
c.networks[network.id] = network
c.Unlock()
return network, nil
}
func (c *controller) Networks() []Network {
c.Lock()
defer c.Unlock()
list := make([]Network, 0, len(c.networks))
for _, n := range c.networks {
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
}
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
}
return n, nil
}
func (c *controller) NetworkByID(id string) (Network, error) {
if id == "" {
return nil, ErrInvalidID
}
c.Lock()
defer c.Unlock()
if n, ok := c.networks[types.UUID(id)]; ok {
return n, nil
}
return nil, ErrNoSuchNetwork
}
func (c *controller) sandboxAdd(key string, create bool) (sandbox.Sandbox, error) {
c.Lock()
defer c.Unlock()
sData, ok := c.sandboxes[key]
if !ok {
sb, err := sandbox.NewSandbox(key, create)
if err != nil {
return nil, err
}
sData = &sandboxData{sandbox: sb, refCnt: 1}
c.sandboxes[key] = sData
return sData.sandbox, nil
}
sData.refCnt++
return sData.sandbox, nil
}
func (c *controller) sandboxRm(key string) {
c.Lock()
defer c.Unlock()
sData := c.sandboxes[key]
sData.refCnt--
if sData.refCnt == 0 {
sData.sandbox.Destroy()
delete(c.sandboxes, key)
}
}
func (c *controller) sandboxGet(key string) sandbox.Sandbox {
c.Lock()
defer c.Unlock()
sData, ok := c.sandboxes[key]
if !ok {
return nil
}
return sData.sandbox
}
func (c *controller) loadDriver(networkType string) (driverapi.Driver, error) {
// Plugins pkg performs lazy loading of plugins that acts as remote drivers.
// As per the design, this Get call will result in remote driver discovery if there is a corresponding plugin available.
_, err := plugins.Get(networkType, driverapi.NetworkPluginEndpointType)
if err != nil {
return nil, err
}
c.Lock()
defer c.Unlock()
d, ok := c.drivers[networkType]
if !ok {
return nil, ErrInvalidNetworkDriver
}
return d, nil
}

View File

@@ -0,0 +1,13 @@
Bridge Driver
=============
The bridge driver is an implementation that uses Linux Bridging and iptables to provide connectvity 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.

View File

@@ -0,0 +1,148 @@
Design
======
The vision and goals of libnetwork are highlighted in [roadmap](../ROADMAP.md).
This document describes how libnetwork has been designed in order to acheive 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](https://github.com/docker/libnetwork/blob/docs/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 works 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.
**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 an 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` & infact they are just a subset of `Options`. `Labels` are typically end-user visible and are represented in the UI explicitely 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.
0. `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`.
1. `NetworkController` object is created using `libnetwork.New()` API to manage the allocation of Networks and optionally configure a `Driver` with driver specific `Options`.
2. `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`.
3. `controller.NewNetwork()` API also takes in optional `options` parameter which carries Driver-specific options and `Labels`, which the Drivers can make use for its purpose.
4. `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.
5. `endpoint.Join()` can be used to attach a container to a `Endpoint`. The Join operation will create a `Sandbox` if it doesnt 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.
6. `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.
7. `endpoint.Delete()` is used to delete an endpoint from a network. This results in deleting an endpoint and cleaning up the cached `sandbox.Info`.
8. `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 primiarly 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 realizes 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 provides the actual implementation for all of the LibNetwork APIs defined above. Hence there is an 1-1 correspondance 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 makes 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.
## 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)

View File

@@ -0,0 +1,15 @@
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.

View File

@@ -0,0 +1,6 @@
Overlay Driver
==============
## Configuration
## Usage

View File

@@ -0,0 +1,18 @@
Remote Drivers
==============
The remote driver package provides the integration point for dynamically-registered drivers.
## LibNetwork Integration
When LibNetwork initialises the `Remote` package with the `Init()` function, it passes a `DriverCallback` as a parameter, which implements the `RegisterDriver()`. The Remote Driver package can use this interface to register any of the `Dynamic` Drivers/Plugins with LibNetwork's `NetworkController`.
This design ensures that the implementation details (TBD) of Dynamic Driver Registration mechanism is completely owned by the inbuilt-Remote driver, and it doesn't expose any of the driver layer to the North of LibNetwork (none of the LibNetwork client APIs are impacted).
## Implementation
The actual implementation of how the Inbuilt Remote Driver registers with the Dynamic Driver is Work-In-Progress. But, the Design Goal is to Honor the bigger goals of LibNetwork by keeping it Highly modular and make sure that LibNetwork is fully composable in nature.
## Usage
The In-Built Remote Driver follows all the rules of any other In-Built Driver and has exactly the same Driver APIs exposed. LibNetwork will also support driver-specific `options` and User-supplied `Labels` which the Dynamic Drivers can make use for its operations.

View File

@@ -0,0 +1,139 @@
package driverapi
import (
"errors"
"fmt"
"net"
"github.com/docker/libnetwork/types"
)
var (
// ErrEndpointExists is returned if more than one endpoint is added to the network
ErrEndpointExists = errors.New("Endpoint already exists (Only one endpoint allowed)")
// ErrNoNetwork is returned if no network with the specified id exists
ErrNoNetwork = errors.New("No network exists")
// ErrNoEndpoint is returned if no endpoint with the specified id exists
ErrNoEndpoint = errors.New("No endpoint exists")
// ErrNotImplemented is returned when a Driver has not implemented an API yet
ErrNotImplemented = errors.New("The API is not implemented yet")
)
// 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 {
// Push driver specific config to the driver
Config(options map[string]interface{}) 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.
CreateNetwork(nid types.UUID, options map[string]interface{}) error
// DeleteNetwork invokes the driver method to delete network passing
// the network id.
DeleteNetwork(nid types.UUID) 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 types.UUID, epInfo EndpointInfo, options map[string]interface{}) error
// DeleteEndpoint invokes the driver method to delete an endpoint
// passing the network id and endpoint id.
DeleteEndpoint(nid, eid types.UUID) error
// EndpointOperInfo retrieves from the driver the operational data related to the specified endpoint
EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error)
// Join method is invoked when a Sandbox is attached to an endpoint.
Join(nid, eid types.UUID, sboxKey string, jinfo JoinInfo, options map[string]interface{}) error
// Leave method is invoked when a Sandbox detaches from an endpoint.
Leave(nid, eid types.UUID) error
// Type returns the the type of this driver, the network type this driver manages
Type() string
}
// EndpointInfo provides a go interface to fetch or populate endpoint assigned network resources.
type EndpointInfo interface {
// Interfaces returns a list of interfaces bound to the endpoint.
// If the list is not empty the driver is only expected to consume the interfaces.
// It is an error to try to add interfaces to a non-empty list.
// If the list is empty the driver is expected to populate with 0 or more interfaces.
Interfaces() []InterfaceInfo
// AddInterface is used by the driver to add an interface to the interface list.
// This method will return an error if the driver attempts to add interfaces
// if the Interfaces() method returned a non-empty list.
// ID field need only have significance within the endpoint so it can be a simple
// monotonically increasing number
AddInterface(ID int, mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error
}
// InterfaceInfo provides a go interface for drivers to retrive
// network information to interface resources.
type InterfaceInfo interface {
// MacAddress returns the MAC address.
MacAddress() net.HardwareAddr
// Address returns the IPv4 address.
Address() net.IPNet
// AddressIPv6 returns the IPv6 address.
AddressIPv6() net.IPNet
// ID returns the numerical id of the interface and has significance only within
// the endpoint.
ID() int
}
// InterfaceNameInfo provides a go interface for the drivers to assign names
// to interfaces.
type InterfaceNameInfo interface {
// SetNames method assigns the srcName and dstName for the interface.
SetNames(srcName, dstName string) error
// ID returns the numerical id that was assigned to the interface by the driver
// CreateEndpoint.
ID() int
}
// JoinInfo represents a set of resources that the driver has the ability to provide during
// join time.
type JoinInfo interface {
// InterfaceNames returns a list of InterfaceNameInfo go interface to facilitate
// setting the names for the interfaces.
InterfaceNames() []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
// SetHostsPath sets the overriding /etc/hosts path to use for the container.
SetHostsPath(string) error
// SetResolvConfPath sets the overriding /etc/resolv.conf path to use for the container.
SetResolvConfPath(string) error
}
// 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))
}
// DriverCallback provides a Callback interface for Drivers into LibNetwork
type DriverCallback interface {
// RegisterDriver provides a way for Remote drivers to dynamically register new NetworkType and associate with a driver instance
RegisterDriver(name string, driver Driver) error
}

View File

@@ -0,0 +1,25 @@
package libnetwork
import (
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/drivers/bridge"
"github.com/docker/libnetwork/drivers/host"
"github.com/docker/libnetwork/drivers/null"
"github.com/docker/libnetwork/drivers/remote"
)
type driverTable map[string]driverapi.Driver
func initDrivers(dc driverapi.DriverCallback) error {
for _, fn := range [](func(driverapi.DriverCallback) error){
bridge.Init,
host.Init,
null.Init,
remote.Init,
} {
if err := fn(dc); err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,928 @@
package bridge
import (
"errors"
"net"
"strings"
"sync"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/ipallocator"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/options"
"github.com/docker/libnetwork/portmapper"
"github.com/docker/libnetwork/sandbox"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)
const (
networkType = "bridge"
vethPrefix = "veth"
vethLen = 7
containerVeth = "eth0"
maxAllocatePortAttempts = 10
ifaceID = 1
)
var (
ipAllocator *ipallocator.IPAllocator
portMapper *portmapper.PortMapper
)
// Configuration info for the "bridge" driver.
type Configuration struct {
EnableIPForwarding bool
}
// NetworkConfiguration for network specific configuration
type NetworkConfiguration struct {
BridgeName string
AddressIPv4 *net.IPNet
FixedCIDR *net.IPNet
FixedCIDRv6 *net.IPNet
EnableIPv6 bool
EnableIPTables bool
EnableIPMasquerade bool
EnableICC bool
Mtu int
DefaultGatewayIPv4 net.IP
DefaultGatewayIPv6 net.IP
DefaultBindingIP net.IP
AllowNonDefaultBridge bool
EnableUserlandProxy bool
}
// EndpointConfiguration represents the user specified configuration for the sandbox endpoint
type EndpointConfiguration struct {
MacAddress net.HardwareAddr
PortBindings []netutils.PortBinding
ExposedPorts []netutils.TransportPort
}
// ContainerConfiguration represents the user specified configuration for a container
type ContainerConfiguration struct {
ParentEndpoints []string
ChildEndpoints []string
}
type bridgeEndpoint struct {
id types.UUID
intf *sandbox.Interface
macAddress net.HardwareAddr
config *EndpointConfiguration // User specified parameters
containerConfig *ContainerConfiguration
portMapping []netutils.PortBinding // Operation port bindings
}
type bridgeNetwork struct {
id types.UUID
bridge *bridgeInterface // The bridge's L3 interface
config *NetworkConfiguration
endpoints map[types.UUID]*bridgeEndpoint // key: endpoint id
sync.Mutex
}
type driver struct {
config *Configuration
network *bridgeNetwork
sync.Mutex
}
func init() {
ipAllocator = ipallocator.New()
portMapper = portmapper.New()
}
// New constructs a new bridge driver
func newDriver() driverapi.Driver {
return &driver{}
}
// Init registers a new instance of bridge driver
func Init(dc driverapi.DriverCallback) error {
return dc.RegisterDriver(networkType, newDriver())
}
// 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
}
// If bridge v4 subnet is specified
if c.AddressIPv4 != nil {
// If Container restricted subnet is specified, it must be a subset of bridge subnet
if c.FixedCIDR != nil {
// Check Network address
if !c.AddressIPv4.Contains(c.FixedCIDR.IP) {
return ErrInvalidContainerSubnet
}
// Check it is effectively a subset
brNetLen, _ := c.AddressIPv4.Mask.Size()
cnNetLen, _ := c.FixedCIDR.Mask.Size()
if brNetLen > cnNetLen {
return ErrInvalidContainerSubnet
}
}
// 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, FixedCIDRv6 must be specified and gw must belong to FixedCIDRv6 subnet
if c.EnableIPv6 && c.DefaultGatewayIPv6 != nil {
if c.FixedCIDRv6 == nil || !c.FixedCIDRv6.Contains(c.DefaultGatewayIPv6) {
return ErrInvalidGateway
}
}
return nil
}
func (n *bridgeNetwork) getEndpoint(eid types.UUID) (*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
}
func (d *driver) Config(option map[string]interface{}) error {
var config *Configuration
d.Lock()
defer d.Unlock()
if d.config != nil {
return ErrConfigExists
}
genericData, ok := option[netlabel.GenericData]
if ok && genericData != 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
}
d.config = config
} else {
config = &Configuration{}
}
if config.EnableIPForwarding {
return setupIPForwarding(config)
}
return nil
}
func (d *driver) getNetwork(id types.UUID) (*bridgeNetwork, error) {
// Just a dummy function to return the only network managed by Bridge driver.
// But this API makes the caller code unchanged when we move to support multiple networks.
d.Lock()
defer d.Unlock()
return d.network, nil
}
func parseNetworkOptions(option options.Generic) (*NetworkConfiguration, error) {
var config *NetworkConfiguration
genericData, ok := option[netlabel.GenericData]
if ok && genericData != nil {
switch opt := genericData.(type) {
case options.Generic:
opaqueConfig, err := options.GenerateFromModel(opt, &NetworkConfiguration{})
if err != nil {
return nil, err
}
config = opaqueConfig.(*NetworkConfiguration)
case *NetworkConfiguration:
config = opt
default:
return nil, ErrInvalidNetworkConfig
}
if err := config.Validate(); err != nil {
return nil, err
}
} else {
config = &NetworkConfiguration{}
}
if _, ok := option[netlabel.EnableIPv6]; ok {
config.EnableIPv6 = option[netlabel.EnableIPv6].(bool)
}
return config, nil
}
// Create a new network using bridge plugin
func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
var err error
// Driver must be configured
d.Lock()
// Sanity checks
if d.network != nil {
d.Unlock()
return ErrNetworkExists
}
// Create and set network handler in driver
d.network = &bridgeNetwork{id: id, endpoints: make(map[types.UUID]*bridgeEndpoint)}
network := d.network
d.Unlock()
// On failure make sure to reset driver network handler to nil
defer func() {
if err != nil {
d.Lock()
d.network = nil
d.Unlock()
}
}()
config, err := parseNetworkOptions(option)
if err != nil {
return err
}
network.config = config
// Create or retrieve the bridge L3 interface
bridgeIface := newInterface(config)
network.bridge = bridgeIface
// 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)
// 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},
// Setup the bridge to allocate containers IPv4 addresses in the
// specified subnet.
{config.FixedCIDR != nil, setupFixedCIDRv4},
// Setup the bridge to allocate containers global IPv6 addresses in the
// specified subnet.
{config.FixedCIDRv6 != nil, setupFixedCIDRv6},
// Setup Loopback Adresses Routing
{!config.EnableUserlandProxy, setupLoopbackAdressesRouting},
// Setup IPTables.
{config.EnableIPTables, setupIPTables},
// Setup DefaultGatewayIPv4
{config.DefaultGatewayIPv4 != nil, setupGatewayIPv4},
// Setup DefaultGatewayIPv6
{config.DefaultGatewayIPv6 != nil, setupGatewayIPv6},
} {
if step.Condition {
bridgeSetup.queueStep(step.Fn)
}
}
// Block bridge IP from being allocated.
bridgeSetup.queueStep(allocateBridgeIP)
// Apply the prepared list of steps, and abort at the first error.
bridgeSetup.queueStep(setupDeviceUp)
if err = bridgeSetup.apply(); err != nil {
return err
}
return nil
}
func (d *driver) DeleteNetwork(nid types.UUID) error {
var err error
// Get network handler and remove it from driver
d.Lock()
n := d.network
d.network = nil
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 d.network == nil {
d.network = n
}
d.Unlock()
}
}()
// Sanity check
if n == nil {
err = driverapi.ErrNoNetwork
return err
}
// Cannot remove network if endpoints are still present
if len(n.endpoints) != 0 {
err = ActiveEndpointsError(n.id)
return err
}
// Programming
err = netlink.LinkDel(n.bridge.Link)
return err
}
func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
var (
ipv6Addr *net.IPNet
err error
)
if epInfo == nil {
return errors.New("invalid endpoint info passed")
}
if len(epInfo.Interfaces()) != 0 {
return errors.New("non empty interface list passed to bridge(local) driver")
}
// Get the network handler and make sure it exists
d.Lock()
n := d.network
config := n.config
d.Unlock()
if n == nil {
return driverapi.ErrNoNetwork
}
// 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
}
// 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, 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
name1, err := generateIfaceName()
if err != nil {
return err
}
// Generate a name for what will be the sandbox side pipe interface
name2, err := generateIfaceName()
if err != nil {
return err
}
// Generate and add the interface pipe host <-> sandbox
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: name1, TxQLen: 0},
PeerName: name2}
if err = netlink.LinkAdd(veth); err != nil {
return err
}
// Get the host side pipe interface handler
host, err := netlink.LinkByName(name1)
if err != nil {
return err
}
defer func() {
if err != nil {
netlink.LinkDel(host)
}
}()
// Get the sandbox side pipe interface handler
sbox, err := netlink.LinkByName(name2)
if err != nil {
return err
}
defer func() {
if err != nil {
netlink.LinkDel(sbox)
}
}()
// Set the sbox's MAC. If specified, use the one configured by user, otherwise use a random one
mac := electMacAddress(epConfig)
err = netlink.LinkSetHardwareAddr(sbox, mac)
if err != nil {
return err
}
endpoint.macAddress = mac
// Add bridge inherited attributes to pipe interfaces
if config.Mtu != 0 {
err = netlink.LinkSetMTU(host, config.Mtu)
if err != nil {
return err
}
err = netlink.LinkSetMTU(sbox, config.Mtu)
if err != nil {
return err
}
}
// Attach host side pipe interface into the bridge
if err = netlink.LinkSetMaster(host,
&netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: config.BridgeName}}); err != nil {
return err
}
// v4 address for the sandbox side pipe interface
ip4, err := ipAllocator.RequestIP(n.bridge.bridgeIPv4, nil)
if err != nil {
return err
}
ipv4Addr := &net.IPNet{IP: ip4, Mask: n.bridge.bridgeIPv4.Mask}
// v6 address for the sandbox side pipe interface
ipv6Addr = &net.IPNet{}
if config.EnableIPv6 {
var ip6 net.IP
network := n.bridge.bridgeIPv6
if config.FixedCIDRv6 != nil {
network = config.FixedCIDRv6
}
ones, _ := network.Mask.Size()
if ones <= 80 {
ip6 = make(net.IP, len(network.IP))
copy(ip6, network.IP)
for i, h := range mac {
ip6[i+10] = h
}
}
ip6, err := ipAllocator.RequestIP(network, ip6)
if err != nil {
return err
}
ipv6Addr = &net.IPNet{IP: ip6, Mask: network.Mask}
}
// Create the sandbox side pipe interface
intf := &sandbox.Interface{}
intf.SrcName = name2
intf.DstName = containerVeth
intf.Address = ipv4Addr
if config.EnableIPv6 {
intf.AddressIPv6 = ipv6Addr
}
// Store the interface in endpoint, this is needed for cleanup on DeleteEndpoint()
endpoint.intf = intf
err = epInfo.AddInterface(ifaceID, endpoint.macAddress, *ipv4Addr, *ipv6Addr)
if err != nil {
return err
}
// Program any required port mapping and store them in the endpoint
endpoint.portMapping, err = allocatePorts(epConfig, intf, config.DefaultBindingIP, config.EnableUserlandProxy)
if err != nil {
return err
}
return nil
}
func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
var err error
// Get the network handler and make sure it exists
d.Lock()
n := d.network
config := n.config
d.Unlock()
if n == nil {
return driverapi.ErrNoNetwork
}
// 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()
}
}()
// Remove port mappings. Do not stop endpoint delete on unmap failure
releasePorts(ep)
// Release the v4 address allocated to this endpoint's sandbox interface
err = ipAllocator.ReleaseIP(n.bridge.bridgeIPv4, ep.intf.Address.IP)
if err != nil {
return err
}
// Release the v6 address allocated to this endpoint's sandbox interface
if config.EnableIPv6 {
err := ipAllocator.ReleaseIP(n.bridge.bridgeIPv6, ep.intf.AddressIPv6.IP)
if err != nil {
return err
}
}
// Try removal of link. Discard error: link pair might have
// already been deleted by sandbox delete.
link, err := netlink.LinkByName(ep.intf.SrcName)
if err == nil {
netlink.LinkDel(link)
}
return nil
}
func (d *driver) EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error) {
// Get the network handler and make sure it exists
d.Lock()
n := d.network
d.Unlock()
if n == nil {
return nil, driverapi.ErrNoNetwork
}
// 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
}
m := make(map[string]interface{})
if ep.portMapping != nil {
// Return a copy of the operational data
pmc := make([]netutils.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 types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
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)
}
for _, iNames := range jinfo.InterfaceNames() {
// Make sure to set names on the correct interface ID.
if iNames.ID() == ifaceID {
err = iNames.SetNames(endpoint.intf.SrcName, endpoint.intf.DstName)
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
}
if !network.config.EnableICC {
return d.link(network, endpoint, options, true)
}
return nil
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid types.UUID) error {
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)
}
if !network.config.EnableICC {
return d.link(network, endpoint, nil, false)
}
return nil
}
func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, options map[string]interface{}, enable bool) error {
var (
cc *ContainerConfiguration
err error
)
if enable {
cc, err = parseContainerOptions(options)
if err != nil {
return err
}
} else {
cc = endpoint.containerConfig
}
if cc == nil {
return nil
}
if endpoint.config != nil && endpoint.config.ExposedPorts != nil {
for _, p := range cc.ParentEndpoints {
var parentEndpoint *bridgeEndpoint
parentEndpoint, err = network.getEndpoint(types.UUID(p))
if err != nil {
return err
}
if parentEndpoint == nil {
err = InvalidEndpointIDError(p)
return err
}
l := newLink(parentEndpoint.intf.Address.IP.String(),
endpoint.intf.Address.IP.String(),
endpoint.config.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(types.UUID(c))
if err != nil {
return err
}
if childEndpoint == nil {
err = InvalidEndpointIDError(c)
return err
}
if childEndpoint.config == nil || childEndpoint.config.ExposedPorts == nil {
continue
}
l := newLink(endpoint.intf.Address.IP.String(),
childEndpoint.intf.Address.IP.String(),
childEndpoint.config.ExposedPorts, network.config.BridgeName)
if enable {
err = l.Enable()
if err != nil {
return err
}
defer func() {
if err != nil {
l.Disable()
}
}()
} else {
l.Disable()
}
}
if enable {
endpoint.containerConfig = cc
}
return nil
}
func (d *driver) Type() string {
return networkType
}
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
}
}
if opt, ok := epOptions[netlabel.PortMap]; ok {
if bs, ok := opt.([]netutils.PortBinding); ok {
ec.PortBindings = bs
} else {
return nil, ErrInvalidEndpointConfig
}
}
if opt, ok := epOptions[netlabel.ExposedPorts]; ok {
if ports, ok := opt.([]netutils.TransportPort); ok {
ec.ExposedPorts = ports
} 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 electMacAddress(epConfig *EndpointConfiguration) net.HardwareAddr {
if epConfig != nil && epConfig.MacAddress != nil {
return epConfig.MacAddress
}
return netutils.GenerateRandomMAC()
}
// Generates a name to be used for a virtual ethernet
// interface. The name is constructed by 'veth' appended
// by a randomly generated hex value. (example: veth0f60e2c)
func generateIfaceName() (string, error) {
for i := 0; i < 3; i++ {
name, err := netutils.GenerateRandomName(vethPrefix, vethLen)
if err != nil {
continue
}
if _, err := net.InterfaceByName(name); err != nil {
if strings.Contains(err.Error(), "no such") {
return name, nil
}
return "", err
}
}
return "", ErrIfaceName
}

View File

@@ -0,0 +1,531 @@
package bridge
import (
"bytes"
"fmt"
"net"
"regexp"
"testing"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
func TestCreateFullOptions(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
config := &Configuration{
EnableIPForwarding: true,
}
netConfig := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPv6: true,
FixedCIDR: bridgeNetworks[0],
EnableIPTables: true,
}
_, netConfig.FixedCIDRv6, _ = net.ParseCIDR("2001:db8::/48")
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
if err := d.Config(genericOption); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
netOption := make(map[string]interface{})
netOption[netlabel.GenericData] = netConfig
err := d.CreateNetwork("dummy", netOption)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
}
func TestCreate(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
config := &NetworkConfiguration{BridgeName: DefaultBridgeName}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
if err := d.CreateNetwork("dummy", genericOption); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
}
func TestCreateFail(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
config := &NetworkConfiguration{BridgeName: "dummy0"}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
if err := d.CreateNetwork("dummy", genericOption); err == nil {
t.Fatal("Bridge creation was expected to fail")
}
}
type testInterface struct {
id int
mac net.HardwareAddr
addr net.IPNet
addrv6 net.IPNet
srcName string
dstName string
}
type testEndpoint struct {
ifaces []*testInterface
gw net.IP
gw6 net.IP
hostsPath string
resolvConfPath string
}
func (te *testEndpoint) Interfaces() []driverapi.InterfaceInfo {
iList := make([]driverapi.InterfaceInfo, len(te.ifaces))
for i, iface := range te.ifaces {
iList[i] = iface
}
return iList
}
func (te *testEndpoint) AddInterface(id int, mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
iface := &testInterface{id: id, addr: ipv4, addrv6: ipv6}
te.ifaces = append(te.ifaces, iface)
return nil
}
func (i *testInterface) ID() int {
return i.id
}
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) SetNames(srcName string, dstName string) error {
i.srcName = srcName
i.dstName = dstName
return nil
}
func (te *testEndpoint) InterfaceNames() []driverapi.InterfaceNameInfo {
iList := make([]driverapi.InterfaceNameInfo, len(te.ifaces))
for i, iface := range te.ifaces {
iList[i] = iface
}
return iList
}
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) SetHostsPath(path string) error {
te.hostsPath = path
return nil
}
func (te *testEndpoint) SetResolvConfPath(path string) error {
te.resolvConfPath = path
return nil
}
func TestQueryEndpointInfo(t *testing.T) {
testQueryEndpointInfo(t, true)
}
func TestQueryEndpointInfoHairpin(t *testing.T) {
testQueryEndpointInfo(t, false)
}
func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
dd, _ := d.(*driver)
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPTables: true,
EnableICC: false,
EnableUserlandProxy: ulPxyEnabled,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("net1", genericOption)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
portMappings := getPortMapping()
epOptions := make(map[string]interface{})
epOptions[netlabel.PortMap] = portMappings
te := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("net1", "ep1", te, epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
ep, _ := dd.network.endpoints["ep1"]
data, err := d.EndpointOperInfo(dd.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.Fatalf("Endpoint operational data does not contain port mapping data")
}
pm, ok := pmd.([]netutils.PortBinding)
if !ok {
t.Fatalf("Unexpected format for port mapping in endpoint operational data")
}
if len(ep.portMapping) != len(pm) {
t.Fatalf("Incomplete data for port mapping in endpoint operational data")
}
for i, pb := range ep.portMapping {
if !pb.Equal(&pm[i]) {
t.Fatalf("Unexpected data for port mapping in endpoint operational data")
}
}
// Cleanup as host ports are there
err = releasePorts(ep)
if err != nil {
t.Fatalf("Failed to release mapped ports: %v", err)
}
}
func TestCreateLinkWithOptions(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
config := &NetworkConfiguration{BridgeName: DefaultBridgeName}
netOptions := make(map[string]interface{})
netOptions[netlabel.GenericData] = config
err := d.CreateNetwork("net1", netOptions)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
mac := net.HardwareAddr([]byte{0x1e, 0x67, 0x66, 0x44, 0x55, 0x66})
epOptions := make(map[string]interface{})
epOptions[netlabel.MacAddress] = mac
te := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("net1", "ep", te, epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint: %s", err.Error())
}
err = d.Join("net1", "ep", "sbox", te, nil)
if err != nil {
t.Fatalf("Failed to join the endpoint: %v", err)
}
ifaceName := te.ifaces[0].srcName
veth, err := netlink.LinkByName(ifaceName)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(mac, veth.Attrs().HardwareAddr) {
t.Fatalf("Failed to parse and program endpoint configuration")
}
}
func getExposedPorts() []netutils.TransportPort {
return []netutils.TransportPort{
netutils.TransportPort{Proto: netutils.TCP, Port: uint16(5000)},
netutils.TransportPort{Proto: netutils.UDP, Port: uint16(400)},
netutils.TransportPort{Proto: netutils.TCP, Port: uint16(600)},
}
}
func getPortMapping() []netutils.PortBinding {
return []netutils.PortBinding{
netutils.PortBinding{Proto: netutils.TCP, Port: uint16(230), HostPort: uint16(23000)},
netutils.PortBinding{Proto: netutils.UDP, Port: uint16(200), HostPort: uint16(22000)},
netutils.PortBinding{Proto: netutils.TCP, Port: uint16(120), HostPort: uint16(12000)},
}
}
func TestLinkContainers(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPTables: true,
EnableICC: false,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("net1", genericOption)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
exposedPorts := getExposedPorts()
epOptions := make(map[string]interface{})
epOptions[netlabel.ExposedPorts] = exposedPorts
te1 := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("net1", "ep1", te1, epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
addr1 := te1.ifaces[0].addr
if addr1.IP.To4() == nil {
t.Fatalf("No Ipv4 address assigned to the endpoint: ep1")
}
te2 := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("net1", "ep2", te2, nil)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
addr2 := te2.ifaces[0].addr
if addr2.IP.To4() == nil {
t.Fatalf("No Ipv4 address assigned to the endpoint: ep2")
}
ce := []string{"ep1"}
cConfig := &ContainerConfiguration{ChildEndpoints: ce}
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = cConfig
err = d.Join("net1", "ep2", "", te2, genericOption)
if err != nil {
t.Fatalf("Failed to link 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) != 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.Leave("net1", "ep2")
if err != nil {
t.Fatalf("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"
ce = []string{"ep1", "ep4"}
cConfig = &ContainerConfiguration{ChildEndpoints: ce}
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = cConfig
err = d.Join("net1", "ep2", "", te2, genericOption)
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.Fatalf("Expected Join to fail given link conditions are not satisfied")
}
}
func TestValidateConfig(t *testing.T) {
// Test mtu
c := NetworkConfiguration{Mtu: -2}
err := c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid MTU number")
}
c.Mtu = 9000
err = c.Validate()
if err != nil {
t.Fatalf("unexpected validation error on MTU number")
}
// Bridge network
_, network, _ := net.ParseCIDR("172.28.0.0/16")
// Test FixedCIDR
_, containerSubnet, _ := net.ParseCIDR("172.27.0.0/16")
c = NetworkConfiguration{
AddressIPv4: network,
FixedCIDR: containerSubnet,
}
err = c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid FixedCIDR network")
}
_, containerSubnet, _ = net.ParseCIDR("172.28.0.0/16")
c.FixedCIDR = containerSubnet
err = c.Validate()
if err != nil {
t.Fatalf("Unexpected validation error on FixedCIDR network")
}
_, containerSubnet, _ = net.ParseCIDR("172.28.0.0/15")
c.FixedCIDR = containerSubnet
err = c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid FixedCIDR network")
}
_, containerSubnet, _ = net.ParseCIDR("172.28.0.0/17")
c.FixedCIDR = containerSubnet
err = c.Validate()
if err != nil {
t.Fatalf("Unexpected validation error on FixedCIDR network")
}
// Test v4 gw
c.DefaultGatewayIPv4 = net.ParseIP("172.27.30.234")
err = c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid default gateway")
}
c.DefaultGatewayIPv4 = net.ParseIP("172.28.30.234")
err = c.Validate()
if err != nil {
t.Fatalf("Unexpected validation error on default gateway")
}
// Test v6 gw
_, containerSubnet, _ = net.ParseCIDR("2001:1234:ae:b004::/64")
c = NetworkConfiguration{
EnableIPv6: true,
FixedCIDRv6: containerSubnet,
DefaultGatewayIPv6: net.ParseIP("2001:1234:ac:b004::bad:a55"),
}
err = c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid v6 default gateway")
}
c.DefaultGatewayIPv6 = net.ParseIP("2001:1234:ae:b004::bad:a55")
err = c.Validate()
if err != nil {
t.Fatalf("Unexpected validation error on v6 default gateway")
}
c.FixedCIDRv6 = nil
err = c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid v6 default gateway")
}
}
func TestSetDefaultGw(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
_, subnetv6, _ := net.ParseCIDR("2001:db8:ea9:9abc:b0c4::/80")
gw4 := bridgeNetworks[0].IP.To4()
gw4[3] = 254
gw6 := net.ParseIP("2001:db8:ea9:9abc:b0c4::254")
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPv6: true,
FixedCIDRv6: subnetv6,
DefaultGatewayIPv4: gw4,
DefaultGatewayIPv6: gw6,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te, 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)
}
}

View File

@@ -0,0 +1,201 @@
package bridge
import (
"errors"
"fmt"
"net"
)
var (
// ErrConfigExists error is returned when driver already has a config applied.
ErrConfigExists = errors.New("configuration already exists, bridge configuration can be applied only once")
// ErrInvalidDriverConfig error is returned when Bridge Driver is passed an invalid config
ErrInvalidDriverConfig = errors.New("Invalid configuration passed to Bridge Driver")
// ErrInvalidNetworkConfig error is returned when a network is created on a driver without valid config.
ErrInvalidNetworkConfig = errors.New("trying to create a network on a driver without valid config")
// ErrInvalidContainerConfig error is returned when a endpoint create is attempted with an invalid configuration.
ErrInvalidContainerConfig = errors.New("Error in joining a container due to invalid configuration")
// ErrInvalidEndpointConfig error is returned when a endpoint create is attempted with an invalid endpoint configuration.
ErrInvalidEndpointConfig = errors.New("trying to create an endpoint with an invalid endpoint configuration")
// ErrNetworkExists error is returned when a network already exists and another network is created.
ErrNetworkExists = errors.New("network already exists, bridge can only have one network")
// ErrIfaceName error is returned when a new name could not be generated.
ErrIfaceName = errors.New("failed to find name for new interface")
// ErrNoIPAddr error is returned when bridge has no IPv4 address configured.
ErrNoIPAddr = errors.New("bridge has no IPv4 address configured")
// ErrInvalidGateway is returned when the user provided default gateway (v4/v6) is not not valid.
ErrInvalidGateway = errors.New("default gateway ip must be part of the network")
// ErrInvalidContainerSubnet is returned when the container subnet (FixedCIDR) is not valid.
ErrInvalidContainerSubnet = errors.New("container subnet must be a subset of bridge network")
// ErrInvalidMtu is returned when the user provided MTU is not valid.
ErrInvalidMtu = errors.New("invalid MTU number")
// ErrIPFwdCfg is returned when ip forwarding setup is invoked when the configuration
// not enabled.
ErrIPFwdCfg = errors.New("unexpected request to enable IP Forwarding")
)
// 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))
}
// 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))
}
// 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))
}
// 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))
}
// 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))
}
// 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))
}
// InvalidSandboxIDError is returned when the passed
// sandbox id valid.
type InvalidSandboxIDError string
func (isie InvalidSandboxIDError) Error() string {
return fmt.Sprintf("invalid sanbox id: %s", string(isie))
}
// 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))
}
// 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))
}
// 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)
}
// 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)
}
type ipTableCfgError string
func (name ipTableCfgError) Error() string {
return fmt.Sprintf("unexpected request to set IP tables for interface: %s", string(name))
}
type invalidIPTablesCfgError string
func (action invalidIPTablesCfgError) Error() string {
return fmt.Sprintf("Invalid IPTables action '%s'", string(action))
}
// 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))
}
// 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)
}
// 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)
}
// 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)
}
// 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())
}
// 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))
}

View File

@@ -0,0 +1,63 @@
package bridge
import (
"net"
"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
}
// 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(config *NetworkConfiguration) *bridgeInterface {
i := &bridgeInterface{}
// 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, _ = netlink.LinkByName(config.BridgeName)
return i
}
// exists indicates if the existing bridge interface exists on the system.
func (i *bridgeInterface) exists() bool {
return i.Link != nil
}
// addresses returns a single IPv4 address and all IPv6 addresses for the
// bridge interface.
func (i *bridgeInterface) addresses() (netlink.Addr, []netlink.Addr, error) {
v4addr, err := netlink.AddrList(i.Link, netlink.FAMILY_V4)
if err != nil {
return netlink.Addr{}, nil, err
}
v6addr, err := netlink.AddrList(i.Link, netlink.FAMILY_V6)
if err != nil {
return netlink.Addr{}, nil, err
}
if len(v4addr) == 0 {
return netlink.Addr{}, v6addr, nil
}
return v4addr[0], v6addr, nil
}

View File

@@ -0,0 +1,33 @@
package bridge
import (
"testing"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
func TestInterfaceDefaultName(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
config := &NetworkConfiguration{}
if _ = newInterface(config); config.BridgeName != DefaultBridgeName {
t.Fatalf("Expected default interface name %q, got %q", DefaultBridgeName, config.BridgeName)
}
}
func TestAddressesEmptyInterface(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
inf := newInterface(&NetworkConfiguration{})
addrv4, addrsv6, err := inf.addresses()
if err != nil {
t.Fatalf("Failed to get addresses of default interface: %v", err)
}
if expected := (netlink.Addr{}); addrv4 != expected {
t.Fatalf("Default interface has unexpected IPv4: %s", addrv4)
}
if len(addrsv6) != 0 {
t.Fatalf("Default interface has unexpected IPv6: %v", addrsv6)
}
}

View File

@@ -0,0 +1,80 @@
package bridge
import (
"fmt"
"net"
log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/netutils"
)
type link struct {
parentIP string
childIP string
ports []netutils.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 []netutils.TransportPort, bridge string) *link {
return &link{
childIP: childIP,
parentIP: parentIP,
ports: ports,
bridge: bridge,
}
}
func (l *link) Enable() error {
// -A == iptables append flag
return linkContainers("-A", l.parentIP, l.childIP, l.ports, l.bridge, false)
}
func (l *link) Disable() {
// -D == iptables delete flag
err := linkContainers("-D", l.parentIP, l.childIP, l.ports, l.bridge, true)
if err != nil {
log.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 []netutils.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.Chain{Name: DockerChain, Bridge: bridge}
for _, port := range ports {
err := chain.Link(nfAction, ip1, ip2, int(port.Port), port.Proto.String())
if !ignoreErrors && err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,39 @@
package bridge
import (
"testing"
"github.com/docker/libnetwork/netutils"
)
func getPorts() []netutils.TransportPort {
return []netutils.TransportPort{
netutils.TransportPort{Proto: netutils.TCP, Port: uint16(5000)},
netutils.TransportPort{Proto: netutils.UDP, Port: uint16(400)},
netutils.TransportPort{Proto: netutils.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()
}
}

View File

@@ -0,0 +1,200 @@
package bridge
import (
"testing"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
func TestLinkCreate(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
dr := d.(*driver)
mtu := 1490
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
Mtu: mtu,
EnableIPv6: true,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("dummy", "", te, nil)
if err != nil {
if _, ok := err.(InvalidEndpointIDError); !ok {
t.Fatalf("Failed with a wrong error :%s", err.Error())
}
} else {
t.Fatalf("Failed to detect invalid config")
}
// Good endpoint creation
err = d.CreateEndpoint("dummy", "ep", te, 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 endoint interface inherited MTU value from bridge config
sboxLnk, err := netlink.LinkByName(te.ifaces[0].srcName)
if err != nil {
t.Fatal(err)
}
if mtu != sboxLnk.Attrs().MTU {
t.Fatalf("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 := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te1, nil)
if err == nil {
t.Fatalf("Failed to detect duplicate endpoint id on same network")
}
if len(te.ifaces) != 1 {
t.Fatalf("Expected exactly one interface. Instead got %d interface(s)", len(te.ifaces))
}
if te.ifaces[0].dstName == "" {
t.Fatal("Invalid Dstname returned")
}
_, err = netlink.LinkByName(te.ifaces[0].srcName)
if err != nil {
t.Fatalf("Could not find source link %s: %v", te.ifaces[0].srcName, err)
}
n := dr.network
ip := te.ifaces[0].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.ifaces[0].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 netutils.SetupTestNetNS(t)()
d := newDriver()
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPv6: true}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te1 := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te1, nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
te2 := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te2, nil)
if err != nil {
if err != driverapi.ErrEndpointExists {
t.Fatalf("Failed with a wrong error :%s", err.Error())
}
} else {
t.Fatalf("Expected to fail while trying to add same endpoint twice")
}
}
func TestLinkCreateNoEnableIPv6(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te, nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
interfaces := te.ifaces
if interfaces[0].addrv6.IP.To16() != nil {
t.Fatalf("Expectd IPv6 address to be nil when IPv6 is not enabled. Got IPv6 = %s", interfaces[0].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 netutils.SetupTestNetNS(t)()
d := newDriver()
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPv6: true}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("dummy", "ep1", te, 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.Fatalf("Failed to detect invalid config")
}
err = d.DeleteEndpoint("dummy", "ep1")
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,124 @@
package bridge
import (
"bytes"
"errors"
"fmt"
"net"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/sandbox"
)
var (
defaultBindingIP = net.IPv4(0, 0, 0, 0)
)
func allocatePorts(epConfig *EndpointConfiguration, intf *sandbox.Interface, reqDefBindIP net.IP, ulPxyEnabled bool) ([]netutils.PortBinding, error) {
if epConfig == nil || epConfig.PortBindings == nil {
return nil, nil
}
defHostIP := defaultBindingIP
if reqDefBindIP != nil {
defHostIP = reqDefBindIP
}
return allocatePortsInternal(epConfig.PortBindings, intf.Address.IP, defHostIP, ulPxyEnabled)
}
func allocatePortsInternal(bindings []netutils.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]netutils.PortBinding, error) {
bs := make([]netutils.PortBinding, 0, len(bindings))
for _, c := range bindings {
b := c.GetCopy()
if err := allocatePort(&b, containerIP, defHostIP, ulPxyEnabled); err != nil {
// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
if cuErr := 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 allocatePort(bnd *netutils.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
}
// 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.Map(container, bnd.HostIP, int(bnd.HostPort), 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: %s", bnd.HostPort, 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
default:
// For completeness
return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
}
}
func releasePorts(ep *bridgeEndpoint) error {
return releasePortsInternal(ep.portMapping)
}
func releasePortsInternal(bindings []netutils.PortBinding) error {
var errorBuf bytes.Buffer
// Attempt to release all port bindings, do not stop on failure
for _, m := range bindings {
if err := 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 releasePort(bnd netutils.PortBinding) error {
// Construct the host side transport address
host, err := bnd.HostAddr()
if err != nil {
return err
}
return portMapper.Unmap(host)
}

View File

@@ -0,0 +1,66 @@
package bridge
import (
"os"
"testing"
"github.com/docker/docker/pkg/reexec"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/netutils"
)
func TestMain(m *testing.M) {
if reexec.Init() {
return
}
os.Exit(m.Run())
}
func TestPortMappingConfig(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
binding1 := netutils.PortBinding{Proto: netutils.UDP, Port: uint16(400), HostPort: uint16(54000)}
binding2 := netutils.PortBinding{Proto: netutils.TCP, Port: uint16(500), HostPort: uint16(65000)}
portBindings := []netutils.PortBinding{binding1, binding2}
epOptions := make(map[string]interface{})
epOptions[netlabel.PortMap] = portBindings
netConfig := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPTables: true,
}
netOptions := make(map[string]interface{})
netOptions[netlabel.GenericData] = netConfig
err := d.CreateNetwork("dummy", netOptions)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{ifaces: []*testInterface{}}
err = d.CreateEndpoint("dummy", "ep1", te, epOptions)
if err != nil {
t.Fatalf("Failed to create the endpoint: %s", err.Error())
}
dd := d.(*driver)
ep, _ := dd.network.endpoints["ep1"]
if len(ep.portMapping) != 2 {
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 {
t.Fatalf("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 {
t.Fatalf("operational port mapping data not found on bridgeEndpoint")
}
err = releasePorts(ep)
if err != nil {
t.Fatalf("Failed to release mapped ports: %v", err)
}
}

View File

@@ -0,0 +1,67 @@
package bridge
import (
"bytes"
"io/ioutil"
"regexp"
)
const (
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})`
)
var nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
func readResolvConf() ([]byte, error) {
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return nil, err
}
return resolv, 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
}
// 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) {
nameservers = append(nameservers, nameserver+"/32")
}
return nameservers
}
// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
func getNameservers(resolvConf []byte) []string {
nameservers := []string{}
for _, line := range getLines(resolvConf, []byte("#")) {
var ns = nsRegexp.FindSubmatch(line)
if len(ns) > 0 {
nameservers = append(nameservers, string(ns[1]))
}
}
return nameservers
}

View File

@@ -0,0 +1,53 @@
package bridge
import (
"bytes"
"testing"
)
func TestResolveConfRead(t *testing.T) {
b, err := readResolvConf()
if err != nil {
t.Fatalf("Failed to read resolv.conf: %v", err)
}
if b == nil {
t.Fatal("Reading resolv.conf returned no content")
}
}
func TestResolveConfReadLines(t *testing.T) {
commentChar := []byte("#")
b, _ := readResolvConf()
lines := getLines(b, commentChar)
if lines == nil {
t.Fatal("Failed to read resolv.conf lines")
}
for _, line := range lines {
if bytes.Index(line, commentChar) != -1 {
t.Fatal("Returned comment content from resolv.conf")
}
}
}
func TestResolvConfNameserversAsCIDR(t *testing.T) {
resolvConf := `# Commented line
nameserver 1.2.3.4
nameserver 5.6.7.8 # Test
`
cidrs := getNameserversAsCIDR([]byte(resolvConf))
if expected := 2; len(cidrs) != expected {
t.Fatalf("Expected %d nameservers, got %d", expected, len(cidrs))
}
expected := []string{"1.2.3.4/32", "5.6.7.8/32"}
for i, exp := range expected {
if cidrs[i] != exp {
t.Fatalf("Expected nameservers %s, got %s", exp, cidrs[i])
}
}
}

View File

@@ -0,0 +1,26 @@
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)
}

View File

@@ -0,0 +1,50 @@
package bridge
import (
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
// SetupDevice create a new bridge interface/
func setupDevice(config *NetworkConfiguration, i *bridgeInterface) error {
// We only attempt to create the bridge when the requested device name is
// the default one.
if config.BridgeName != DefaultBridgeName && !config.AllowNonDefaultBridge {
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 && (kv.Kernel >= 3 && kv.Major >= 3) {
i.Link.Attrs().HardwareAddr = netutils.GenerateRandomMAC()
log.Debugf("Setting bridge mac address to %s", i.Link.Attrs().HardwareAddr)
}
// Call out to netlink to create the device.
return netlink.LinkAdd(i.Link)
}
// SetupDeviceUp ups the given bridge interface.
func setupDeviceUp(config *NetworkConfiguration, i *bridgeInterface) error {
err := netlink.LinkSetUp(i.Link)
if err != nil {
return err
}
// Attempt to update the bridge interface to refresh the flags status,
// ignoring any failure to do so.
if lnk, err := netlink.LinkByName(config.BridgeName); err == nil {
i.Link = lnk
}
return nil
}

View File

@@ -0,0 +1,75 @@
package bridge
import (
"bytes"
"net"
"testing"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
func TestSetupNewBridge(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
config := &NetworkConfiguration{BridgeName: DefaultBridgeName}
br := &bridgeInterface{}
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 := netlink.LinkByName(DefaultBridgeName); err != nil {
t.Fatalf("Failed to retrieve bridge device: %v", err)
}
if br.Link.Attrs().Flags&net.FlagUp == net.FlagUp {
t.Fatalf("bridgeInterface should be created down")
}
}
func TestSetupNewNonDefaultBridge(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
config := &NetworkConfiguration{BridgeName: "test0"}
br := &bridgeInterface{}
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 netutils.SetupTestNetNS(t)()
config := &NetworkConfiguration{BridgeName: DefaultBridgeName}
br := &bridgeInterface{}
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, _ := netlink.LinkByName(DefaultBridgeName)
if lnk.Attrs().Flags&net.FlagUp != net.FlagUp {
t.Fatalf("bridgeInterface should be up")
}
}
func TestGenerateRandomMAC(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
mac1 := netutils.GenerateRandomMAC()
mac2 := netutils.GenerateRandomMAC()
if bytes.Compare(mac1, mac2) == 0 {
t.Fatalf("Generated twice the same MAC address %v", mac1)
}
}

View File

@@ -0,0 +1,17 @@
package bridge
import log "github.com/Sirupsen/logrus"
func setupFixedCIDRv4(config *NetworkConfiguration, i *bridgeInterface) error {
addrv4, _, err := i.addresses()
if err != nil {
return err
}
log.Debugf("Using IPv4 subnet: %v", config.FixedCIDR)
if err := ipAllocator.RegisterSubnet(addrv4.IPNet, config.FixedCIDR); err != nil {
return &FixedCIDRv4Error{subnet: config.FixedCIDR, net: addrv4.IPNet, err: err}
}
return nil
}

View File

@@ -0,0 +1,62 @@
package bridge
import (
"net"
"testing"
"github.com/docker/libnetwork/netutils"
)
func TestSetupFixedCIDRv4(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
AddressIPv4: &net.IPNet{IP: net.ParseIP("192.168.1.1"), Mask: net.CIDRMask(16, 32)},
FixedCIDR: &net.IPNet{IP: net.ParseIP("192.168.2.0"), Mask: net.CIDRMask(24, 32)}}
br := &bridgeInterface{}
if err := setupDevice(config, br); err != nil {
t.Fatalf("Bridge creation failed: %v", err)
}
if err := setupBridgeIPv4(config, br); err != nil {
t.Fatalf("Assign IPv4 to bridge failed: %v", err)
}
if err := setupFixedCIDRv4(config, br); err != nil {
t.Fatalf("Failed to setup bridge FixedCIDRv4: %v", err)
}
if ip, err := ipAllocator.RequestIP(config.FixedCIDR, nil); err != nil {
t.Fatalf("Failed to request IP to allocator: %v", err)
} else if expected := "192.168.2.1"; ip.String() != expected {
t.Fatalf("Expected allocated IP %s, got %s", expected, ip)
}
}
func TestSetupBadFixedCIDRv4(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
AddressIPv4: &net.IPNet{IP: net.ParseIP("192.168.1.1"), Mask: net.CIDRMask(24, 32)},
FixedCIDR: &net.IPNet{IP: net.ParseIP("192.168.2.0"), Mask: net.CIDRMask(24, 32)}}
br := &bridgeInterface{}
if err := setupDevice(config, br); err != nil {
t.Fatalf("Bridge creation failed: %v", err)
}
if err := setupBridgeIPv4(config, br); err != nil {
t.Fatalf("Assign IPv4 to bridge failed: %v", err)
}
err := setupFixedCIDRv4(config, br)
if err == nil {
t.Fatal("Setup bridge FixedCIDRv4 should have failed")
}
if _, ok := err.(*FixedCIDRv4Error); !ok {
t.Fatalf("Did not fail with expected error. Actual error: %v", err)
}
}

View File

@@ -0,0 +1,12 @@
package bridge
import log "github.com/Sirupsen/logrus"
func setupFixedCIDRv6(config *NetworkConfiguration, i *bridgeInterface) error {
log.Debugf("Using IPv6 subnet: %v", config.FixedCIDRv6)
if err := ipAllocator.RegisterSubnet(config.FixedCIDRv6, config.FixedCIDRv6); err != nil {
return &FixedCIDRv6Error{net: config.FixedCIDRv6, err: err}
}
return nil
}

View File

@@ -0,0 +1,37 @@
package bridge
import (
"net"
"testing"
"github.com/docker/libnetwork/netutils"
)
func TestSetupFixedCIDRv6(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
config := &NetworkConfiguration{}
br := newInterface(config)
_, config.FixedCIDRv6, _ = net.ParseCIDR("2002:db8::/48")
if err := setupDevice(config, br); err != nil {
t.Fatalf("Bridge creation failed: %v", err)
}
if err := setupBridgeIPv4(config, br); err != nil {
t.Fatalf("Assign IPv4 to bridge failed: %v", err)
}
if err := setupBridgeIPv6(config, br); err != nil {
t.Fatalf("Assign IPv4 to bridge failed: %v", err)
}
if err := setupFixedCIDRv6(config, br); err != nil {
t.Fatalf("Failed to setup bridge FixedCIDRv6: %v", err)
}
if ip, err := ipAllocator.RequestIP(config.FixedCIDRv6, nil); err != nil {
t.Fatalf("Failed to request IP to allocator: %v", err)
} else if expected := "2002:db8::1"; ip.String() != expected {
t.Fatalf("Expected allocated IP %s, got %s", expected, ip)
}
}

View File

@@ -0,0 +1,25 @@
package bridge
import (
"fmt"
"io/ioutil"
)
const (
ipv4ForwardConf = "/proc/sys/net/ipv4/ip_forward"
ipv4ForwardConfPerm = 0644
)
func setupIPForwarding(config *Configuration) error {
// Sanity Check
if config.EnableIPForwarding == false {
return ErrIPFwdCfg
}
// Enable IPv4 forwarding
if err := ioutil.WriteFile(ipv4ForwardConf, []byte{'1', '\n'}, ipv4ForwardConfPerm); err != nil {
return fmt.Errorf("Setup IP forwarding failed: %v", err)
}
return nil
}

View File

@@ -0,0 +1,75 @@
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'})
}
// Create test interface with ip forwarding setting enabled
config := &Configuration{
EnableIPForwarding: true}
// Set IP Forwarding
if err := setupIPForwarding(config); 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.Fatalf("Failed to effectively setup IP forwarding")
}
}
func TestUnexpectedSetupIPForwarding(t *testing.T) {
// Read current setting and ensure the original value gets restored
procSetting := readCurrentIPForwardingSetting(t)
defer reconcileIPForwardingSetting(t, procSetting)
// Create test interface without ip forwarding setting enabled
config := &Configuration{
EnableIPForwarding: false}
// Attempt Set IP Forwarding
err := setupIPForwarding(config)
if err == nil {
t.Fatal("Setup IP forwarding was expected to fail")
}
if err != ErrIPFwdCfg {
t.Fatalf("Setup IP forwarding failed with unexpected error: %v", err)
}
}
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)
}
}

View File

@@ -0,0 +1,173 @@
package bridge
import (
"fmt"
"net"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/netutils"
)
// DockerChain: DOCKER iptable chain name
const (
DockerChain = "DOCKER"
)
func setupIPTables(config *NetworkConfiguration, i *bridgeInterface) error {
// Sanity check.
if config.EnableIPTables == false {
return ipTableCfgError(config.BridgeName)
}
hairpinMode := !config.EnableUserlandProxy
addrv4, _, err := netutils.GetIfaceAddr(config.BridgeName)
if err != nil {
return fmt.Errorf("Failed to setup IP tables, cannot acquire Interface address: %s", err.Error())
}
if err = setupIPTablesInternal(config.BridgeName, addrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil {
return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
}
_, err = iptables.NewChain(DockerChain, config.BridgeName, iptables.Nat, hairpinMode)
if err != nil {
return fmt.Errorf("Failed to create NAT chain: %s", err.Error())
}
chain, err := iptables.NewChain(DockerChain, config.BridgeName, iptables.Filter, hairpinMode)
if err != nil {
return fmt.Errorf("Failed to create FILTER chain: %s", err.Error())
}
portMapper.SetIptablesChain(chain)
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"}}
outRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}}
inRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}
)
// Set NAT.
if ipmasq {
if err := programChainRule(natRule, "NAT", 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.
if err := programChainRule(outRule, "ACCEPT NON_ICC OUTGOING", enable); err != nil {
return err
}
// Set Accept on incoming packets for existing connections.
if err := programChainRule(inRule, "ACCEPT INCOMING", enable); err != nil {
return err
}
return nil
}
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 output, err := iptables.Raw(append(prefix, rule.args...)...); err != nil {
return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
} else if len(output) != 0 {
return &iptables.ChainError{Chain: rule.chain, Output: output}
}
}
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 output, err := iptables.Raw(append([]string{"-A", chain}, dropArgs...)...); err != nil {
return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
} else if len(output) != 0 {
return fmt.Errorf("Error disabling intercontainer communication: %s", output)
}
}
} else {
iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
if !iptables.Exists(table, chain, acceptArgs...) {
if output, err := iptables.Raw(append([]string{"-A", chain}, acceptArgs...)...); err != nil {
return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
} else if len(output) != 0 {
return fmt.Errorf("Error enabling intercontainer communication: %s", output)
}
}
}
} 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
}

View File

@@ -0,0 +1,103 @@
package bridge
import (
"net"
"testing"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/netutils"
)
const (
iptablesTestBridgeIP = "192.168.42.1"
)
func TestProgramIPTable(t *testing.T) {
// Create a test bridge with a basic bridge configuration (name + IPv4).
defer netutils.SetupTestNetNS(t)()
createTestBridge(getBasicTestConfig(), &bridgeInterface{}, 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{"-i", DefaultBridgeName, "!", "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test ACCEPT NON_ICC OUTGOING"},
{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 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 TestSetupIPTables(t *testing.T) {
// Create a test bridge with a basic bridge configuration (name + IPv4).
defer netutils.SetupTestNetNS(t)()
config := getBasicTestConfig()
br := &bridgeInterface{}
createTestBridge(config, br, t)
// Modify iptables params in base configuration and apply them.
config.EnableIPTables = true
assertBridgeConfig(config, br, t)
config.EnableIPMasquerade = true
assertBridgeConfig(config, br, t)
config.EnableICC = true
assertBridgeConfig(config, br, t)
config.EnableIPMasquerade = false
assertBridgeConfig(config, br, 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 pushes chains based on bridge config parameters.
func assertBridgeConfig(config *NetworkConfiguration, br *bridgeInterface, t *testing.T) {
// Attempt programming of ip tables.
err := setupIPTables(config, br)
if err != nil {
t.Fatalf("%v", err)
}
}

View File

@@ -0,0 +1,136 @@
package bridge
import (
"fmt"
"io/ioutil"
"net"
"path/filepath"
log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
var bridgeNetworks []*net.IPNet
func init() {
// Here we don't follow the convention of using the 1st IP of the range for the gateway.
// This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
// In theory this shouldn't matter - in practice there's bound to be a few scripts relying
// on the internal addressing or other stupid things like that.
// They shouldn't, but hey, let's not break them unless we really have to.
for _, addr := range []string{
"172.17.42.1/16", // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23
"10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive
"10.1.42.1/16",
"10.42.42.1/16",
"172.16.42.1/24",
"172.16.43.1/24",
"172.16.44.1/24",
"10.0.42.1/24",
"10.0.43.1/24",
"192.168.42.1/24",
"192.168.43.1/24",
"192.168.44.1/24",
} {
ip, net, err := net.ParseCIDR(addr)
if err != nil {
log.Errorf("Failed to parse address %s", addr)
continue
}
net.IP = ip.To4()
bridgeNetworks = append(bridgeNetworks, net)
}
}
func setupBridgeIPv4(config *NetworkConfiguration, i *bridgeInterface) error {
addrv4, _, err := i.addresses()
if err != nil {
return err
}
// Check if we have an IP address already on the bridge.
if addrv4.IPNet != nil {
// Make sure to store bridge network and default gateway before getting out.
i.bridgeIPv4 = addrv4.IPNet
i.gatewayIPv4 = addrv4.IPNet.IP
return nil
}
// Do not try to configure IPv4 on a non-default bridge unless you are
// specifically asked to do so.
if config.BridgeName != DefaultBridgeName && !config.AllowNonDefaultBridge {
return NonDefaultBridgeExistError(config.BridgeName)
}
bridgeIPv4, err := electBridgeIPv4(config)
if err != nil {
return err
}
log.Debugf("Creating bridge interface %q with network %s", config.BridgeName, bridgeIPv4)
if err := netlink.AddrAdd(i.Link, &netlink.Addr{IPNet: bridgeIPv4}); err != nil {
return &IPv4AddrAddError{ip: bridgeIPv4, err: err}
}
// Store bridge network and default gateway
i.bridgeIPv4 = bridgeIPv4
i.gatewayIPv4 = i.bridgeIPv4.IP
return nil
}
func allocateBridgeIP(config *NetworkConfiguration, i *bridgeInterface) error {
ipAllocator.RequestIP(i.bridgeIPv4, i.bridgeIPv4.IP)
return nil
}
func electBridgeIPv4(config *NetworkConfiguration) (*net.IPNet, error) {
// Use the requested IPv4 CIDR when available.
if config.AddressIPv4 != nil {
return config.AddressIPv4, nil
}
// 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.
nameservers := []string{}
if resolvConf, _ := readResolvConf(); resolvConf != nil {
nameservers = append(nameservers, getNameserversAsCIDR(resolvConf)...)
}
// Try to automatically elect appropriate bridge IPv4 settings.
for _, n := range bridgeNetworks {
if err := netutils.CheckNameserverOverlaps(nameservers, n); err == nil {
if err := netutils.CheckRouteOverlaps(n); err == nil {
return n, nil
}
}
}
return nil, IPv4AddrRangeError(config.BridgeName)
}
func setupGatewayIPv4(config *NetworkConfiguration, i *bridgeInterface) error {
if !i.bridgeIPv4.Contains(config.DefaultGatewayIPv4) {
return ErrInvalidGateway
}
if _, err := ipAllocator.RequestIP(i.bridgeIPv4, config.DefaultGatewayIPv4); err != nil {
return err
}
// Store requested default gateway
i.gatewayIPv4 = config.DefaultGatewayIPv4
return nil
}
func setupLoopbackAdressesRouting(config *NetworkConfiguration, i *bridgeInterface) error {
// Enable loopback adresses routing
sysPath := filepath.Join("/proc/sys/net/ipv4/conf", config.BridgeName, "route_localnet")
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
}

View File

@@ -0,0 +1,100 @@
package bridge
import (
"net"
"testing"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
func setupTestInterface(t *testing.T) (*NetworkConfiguration, *bridgeInterface) {
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName}
br := &bridgeInterface{}
if err := setupDevice(config, br); err != nil {
t.Fatalf("Bridge creation failed: %v", err)
}
return config, br
}
func TestSetupBridgeIPv4Fixed(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
ip, netw, err := net.ParseCIDR("192.168.1.1/24")
if err != nil {
t.Fatalf("Failed to parse bridge IPv4: %v", err)
}
config, br := setupTestInterface(t)
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 := netlink.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 TestSetupBridgeIPv4Auto(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
config, br := setupTestInterface(t)
if err := setupBridgeIPv4(config, br); err != nil {
t.Fatalf("Failed to setup bridge IPv4: %v", err)
}
addrsv4, err := netlink.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 bridgeNetworks[0].String() == addr.IPNet.String() {
found = true
break
}
}
if !found {
t.Fatalf("Bridge device does not have the automatic IPv4 address %v", bridgeNetworks[0].String())
}
}
func TestSetupGatewayIPv4(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
ip, nw, _ := net.ParseCIDR("192.168.0.24/16")
nw.IP = ip
gw := net.ParseIP("192.168.0.254")
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
DefaultGatewayIPv4: gw}
br := &bridgeInterface{bridgeIPv4: nw}
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)
}
}

View File

@@ -0,0 +1,66 @@
package bridge
import (
"fmt"
"io/ioutil"
"net"
"github.com/vishvananda/netlink"
)
var bridgeIPv6 *net.IPNet
const bridgeIPv6Str = "fe80::1/64"
func init() {
// We allow ourselves to panic in this special case because we indicate a
// failure to parse a compile-time define constant.
if ip, netw, err := net.ParseCIDR(bridgeIPv6Str); err == nil {
bridgeIPv6 = &net.IPNet{IP: ip, Mask: netw.Mask}
} else {
panic(fmt.Sprintf("Cannot parse default bridge IPv6 address %q: %v", bridgeIPv6Str, err))
}
}
func setupBridgeIPv6(config *NetworkConfiguration, i *bridgeInterface) error {
// Enable IPv6 on the bridge
procFile := "/proc/sys/net/ipv6/conf/" + config.BridgeName + "/disable_ipv6"
if err := ioutil.WriteFile(procFile, []byte{'0', '\n'}, 0644); err != nil {
return fmt.Errorf("Unable to enable IPv6 addresses on bridge: %v", err)
}
_, addrsv6, err := i.addresses()
if err != nil {
return err
}
// Add the default link local ipv6 address if it doesn't exist
if !findIPv6Address(netlink.Addr{IPNet: bridgeIPv6}, addrsv6) {
if err := netlink.AddrAdd(i.Link, &netlink.Addr{IPNet: bridgeIPv6}); err != nil {
return &IPv6AddrAddError{ip: bridgeIPv6, err: err}
}
}
// Store bridge network and default gateway
i.bridgeIPv6 = bridgeIPv6
i.gatewayIPv6 = i.bridgeIPv6.IP
return nil
}
func setupGatewayIPv6(config *NetworkConfiguration, i *bridgeInterface) error {
if config.FixedCIDRv6 == nil {
return ErrInvalidContainerSubnet
}
if !config.FixedCIDRv6.Contains(config.DefaultGatewayIPv6) {
return ErrInvalidGateway
}
if _, err := ipAllocator.RequestIP(config.FixedCIDRv6, config.DefaultGatewayIPv6); err != nil {
return err
}
// Store requested default gateway
i.gatewayIPv6 = config.DefaultGatewayIPv6
return nil
}

View File

@@ -0,0 +1,70 @@
package bridge
import (
"bytes"
"fmt"
"io/ioutil"
"net"
"testing"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
func TestSetupIPv6(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
config, br := setupTestInterface(t)
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 := netlink.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 netutils.SetupTestNetNS(t)()
_, nw, _ := net.ParseCIDR("2001:db8:ea9:9abc:ffff::/80")
gw := net.ParseIP("2001:db8:ea9:9abc:ffff::254")
config := &NetworkConfiguration{
BridgeName: DefaultBridgeName,
FixedCIDRv6: nw,
DefaultGatewayIPv6: gw}
br := &bridgeInterface{}
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)
}
}

View File

@@ -0,0 +1,44 @@
package bridge
import "github.com/vishvananda/netlink"
func setupVerifyAndReconcile(config *NetworkConfiguration, i *bridgeInterface) error {
// Fetch a single IPv4 and a slice of IPv6 addresses from the bridge.
addrv4, addrsv6, err := i.addresses()
if err != nil {
return err
}
// 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)
}
// By this time we have either configured a new bridge with an IP address
// or made sure an existing bridge's IP matches the configuration
// Now is the time to cache these states in the bridgeInterface.
i.bridgeIPv4 = addrv4.IPNet
i.bridgeIPv6 = bridgeIPv6
return nil
}
func findIPv6Address(addr netlink.Addr, addresses []netlink.Addr) bool {
for _, addrv6 := range addresses {
if addrv6.String() == addr.String() {
return true
}
}
return false
}

View File

@@ -0,0 +1,110 @@
package bridge
import (
"net"
"testing"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
func setupVerifyTest(t *testing.T) *bridgeInterface {
inf := &bridgeInterface{}
br := netlink.Bridge{}
br.LinkAttrs.Name = "default0"
if err := netlink.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 netutils.SetupTestNetNS(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 netutils.SetupTestNetNS(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 netutils.SetupTestNetNS(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 netutils.SetupTestNetNS(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 netutils.SetupTestNetNS(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")
}
}

View File

@@ -0,0 +1,53 @@
package host
import (
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/types"
)
const networkType = "host"
type driver struct{}
// Init registers a new instance of host driver
func Init(dc driverapi.DriverCallback) error {
return dc.RegisterDriver(networkType, &driver{})
}
func (d *driver) Config(option map[string]interface{}) error {
return nil
}
func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
return nil
}
func (d *driver) DeleteNetwork(nid types.UUID) error {
return nil
}
func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
return nil
}
func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
return nil
}
func (d *driver) EndpointOperInfo(nid, eid types.UUID) (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 types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
return (jinfo.SetHostsPath("/etc/hosts"))
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid types.UUID) error {
return nil
}
func (d *driver) Type() string {
return networkType
}

View File

@@ -0,0 +1,53 @@
package null
import (
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/types"
)
const networkType = "null"
type driver struct{}
// Init registers a new instance of null driver
func Init(dc driverapi.DriverCallback) error {
return dc.RegisterDriver(networkType, &driver{})
}
func (d *driver) Config(option map[string]interface{}) error {
return nil
}
func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
return nil
}
func (d *driver) DeleteNetwork(nid types.UUID) error {
return nil
}
func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
return nil
}
func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
return nil
}
func (d *driver) EndpointOperInfo(nid, eid types.UUID) (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 types.UUID, 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 types.UUID) error {
return nil
}
func (d *driver) Type() string {
return networkType
}

View File

@@ -0,0 +1,69 @@
package remote
import (
"errors"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/types"
)
var errNoCallback = errors.New("No Callback handler registered with Driver")
type driver struct {
endpoint *plugins.Client
networkType string
}
// Init does the necessary work to register remote drivers
func Init(dc driverapi.DriverCallback) error {
plugins.Handle(driverapi.NetworkPluginEndpointType, func(name string, client *plugins.Client) {
// TODO : Handhake with the Remote Plugin goes here
newDriver := &driver{networkType: name, endpoint: client}
if err := dc.RegisterDriver(name, newDriver); err != nil {
log.Errorf("Error registering Driver for %s due to %v", name, err)
}
})
return nil
}
func (d *driver) Config(option map[string]interface{}) error {
return driverapi.ErrNotImplemented
}
func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
return driverapi.ErrNotImplemented
}
func (d *driver) DeleteNetwork(nid types.UUID) error {
return driverapi.ErrNotImplemented
}
func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
return driverapi.ErrNotImplemented
}
func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
return driverapi.ErrNotImplemented
}
func (d *driver) EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error) {
return nil, driverapi.ErrNotImplemented
}
// Join method is invoked when a Sandbox is attached to an endpoint.
func (d *driver) Join(nid, eid types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
return driverapi.ErrNotImplemented
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid types.UUID) error {
return driverapi.ErrNotImplemented
}
func (d *driver) Type() string {
return d.networkType
}

View File

@@ -0,0 +1,724 @@
package libnetwork
import (
"bytes"
"io/ioutil"
"os"
"path"
"path/filepath"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/libnetwork/etchosts"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/resolvconf"
"github.com/docker/libnetwork/sandbox"
"github.com/docker/libnetwork/types"
)
// 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 creates a new sandbox for the given container ID and populates the
// network resources allocated for the endpoint and joins the sandbox to
// the endpoint. It returns the sandbox key to the caller
Join(containerID string, options ...EndpointOption) (*ContainerData, error)
// Leave removes the sandbox associated with container ID and detaches
// the network resources populated in the sandbox
Leave(containerID string, options ...EndpointOption) error
// Return certain operational data belonging to this endpoint
Info() EndpointInfo
// Info 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() error
}
// EndpointOption is a option setter function type used to pass varios 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)
// ContainerData is a set of data returned when a container joins an endpoint.
type ContainerData struct {
SandboxKey string
}
// These are the container configs used to customize container /etc/hosts file.
type hostsPathConfig struct {
hostName string
domainName string
hostsPath string
extraHosts []extraHost
parentUpdates []parentUpdate
}
// These are the container configs used to customize container /etc/resolv.conf file.
type resolvConfPathConfig struct {
resolvConfPath string
dnsList []string
dnsSearchList []string
}
type containerConfig struct {
hostsPathConfig
resolvConfPathConfig
generic map[string]interface{}
useDefaultSandBox bool
}
type extraHost struct {
name string
IP string
}
type parentUpdate struct {
eid string
name string
ip string
}
type containerInfo struct {
id string
config containerConfig
data ContainerData
}
type endpoint struct {
name string
id types.UUID
network *network
sandboxInfo *sandbox.Info
iFaces []*endpointInterface
joinInfo *endpointJoinInfo
container *containerInfo
exposedPorts []netutils.TransportPort
generic map[string]interface{}
joinLeaveDone chan struct{}
sync.Mutex
}
const defaultPrefix = "/var/lib/docker/network/files"
func (ep *endpoint) ID() string {
ep.Lock()
defer ep.Unlock()
return string(ep.id)
}
func (ep *endpoint) Name() string {
ep.Lock()
defer ep.Unlock()
return ep.name
}
func (ep *endpoint) Network() string {
ep.Lock()
defer ep.Unlock()
return ep.network.name
}
func (ep *endpoint) processOptions(options ...EndpointOption) {
ep.Lock()
defer ep.Unlock()
for _, opt := range options {
if opt != nil {
opt(ep)
}
}
}
func createBasePath(dir string) error {
err := os.MkdirAll(dir, 0644)
if err != nil && !os.IsExist(err) {
return err
}
return nil
}
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
}
// joinLeaveStart waits to ensure there are no joins or leaves in progress and
// marks this join/leave in progress without race
func (ep *endpoint) joinLeaveStart() {
ep.Lock()
defer ep.Unlock()
for ep.joinLeaveDone != nil {
joinLeaveDone := ep.joinLeaveDone
ep.Unlock()
select {
case <-joinLeaveDone:
}
ep.Lock()
}
ep.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 (ep *endpoint) joinLeaveEnd() {
ep.Lock()
defer ep.Unlock()
if ep.joinLeaveDone != nil {
close(ep.joinLeaveDone)
ep.joinLeaveDone = nil
}
}
func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*ContainerData, error) {
var err error
if containerID == "" {
return nil, InvalidContainerIDError(containerID)
}
ep.joinLeaveStart()
defer ep.joinLeaveEnd()
ep.Lock()
if ep.container != nil {
ep.Unlock()
return nil, ErrInvalidJoin
}
ep.container = &containerInfo{
id: containerID,
config: containerConfig{
hostsPathConfig: hostsPathConfig{
extraHosts: []extraHost{},
parentUpdates: []parentUpdate{},
},
}}
ep.joinInfo = &endpointJoinInfo{}
container := ep.container
network := ep.network
epid := ep.id
joinInfo := ep.joinInfo
ifaces := ep.iFaces
ep.Unlock()
defer func() {
ep.Lock()
if err != nil {
ep.container = nil
}
ep.Unlock()
}()
network.Lock()
driver := network.driver
nid := network.id
ctrlr := network.ctrlr
network.Unlock()
ep.processOptions(options...)
sboxKey := sandbox.GenerateKey(containerID)
if container.config.useDefaultSandBox {
sboxKey = sandbox.GenerateKey("default")
}
err = driver.Join(nid, epid, sboxKey, ep, container.config.generic)
if err != nil {
return nil, err
}
err = ep.buildHostsFiles()
if err != nil {
return nil, err
}
err = ep.updateParentHosts()
if err != nil {
return nil, err
}
err = ep.setupDNS()
if err != nil {
return nil, err
}
sb, err := ctrlr.sandboxAdd(sboxKey, !container.config.useDefaultSandBox)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
ctrlr.sandboxRm(sboxKey)
}
}()
for _, i := range ifaces {
iface := &sandbox.Interface{
SrcName: i.srcName,
DstName: i.dstName,
Address: &i.addr,
}
if i.addrv6.IP.To16() != nil {
iface.AddressIPv6 = &i.addrv6
}
err = sb.AddInterface(iface)
if err != nil {
return nil, err
}
}
err = sb.SetGateway(joinInfo.gw)
if err != nil {
return nil, err
}
err = sb.SetGatewayIPv6(joinInfo.gw6)
if err != nil {
return nil, err
}
container.data.SandboxKey = sb.Key()
cData := container.data
return &cData, nil
}
func (ep *endpoint) Leave(containerID string, options ...EndpointOption) error {
var err error
ep.joinLeaveStart()
defer ep.joinLeaveEnd()
ep.processOptions(options...)
ep.Lock()
container := ep.container
n := ep.network
if container == nil || container.id == "" ||
containerID == "" || container.id != containerID {
if container == nil {
err = ErrNoContainer
} else {
err = InvalidContainerIDError(containerID)
}
ep.Unlock()
return err
}
ep.container = nil
ep.Unlock()
n.Lock()
driver := n.driver
ctrlr := n.ctrlr
n.Unlock()
err = driver.Leave(n.id, ep.id)
sb := ctrlr.sandboxGet(container.data.SandboxKey)
for _, i := range sb.Interfaces() {
err = sb.RemoveInterface(i)
if err != nil {
logrus.Debugf("Remove interface failed: %v", err)
}
}
ctrlr.sandboxRm(container.data.SandboxKey)
return err
}
func (ep *endpoint) Delete() error {
var err error
ep.Lock()
epid := ep.id
name := ep.name
if ep.container != nil {
ep.Unlock()
return &ActiveContainerError{name: name, id: string(epid)}
}
n := ep.network
ep.Unlock()
n.Lock()
_, ok := n.endpoints[epid]
if !ok {
n.Unlock()
return &UnknownEndpointError{name: name, id: string(epid)}
}
nid := n.id
driver := n.driver
delete(n.endpoints, epid)
n.Unlock()
defer func() {
if err != nil {
n.Lock()
n.endpoints[epid] = ep
n.Unlock()
}
}()
err = driver.DeleteEndpoint(nid, epid)
return err
}
func (ep *endpoint) buildHostsFiles() error {
var extraContent []etchosts.Record
ep.Lock()
container := ep.container
joinInfo := ep.joinInfo
ifaces := ep.iFaces
ep.Unlock()
if container == nil {
return ErrNoContainer
}
if container.config.hostsPath == "" {
container.config.hostsPath = defaultPrefix + "/" + container.id + "/hosts"
}
dir, _ := filepath.Split(container.config.hostsPath)
err := createBasePath(dir)
if err != nil {
return err
}
if joinInfo != nil && joinInfo.hostsPath != "" {
content, err := ioutil.ReadFile(joinInfo.hostsPath)
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
return ioutil.WriteFile(container.config.hostsPath, content, 0644)
}
}
name := container.config.hostName
if container.config.domainName != "" {
name = name + "." + container.config.domainName
}
for _, extraHost := range container.config.extraHosts {
extraContent = append(extraContent,
etchosts.Record{Hosts: extraHost.name, IP: extraHost.IP})
}
IP := ""
if len(ifaces) != 0 && ifaces[0] != nil {
IP = ifaces[0].addr.IP.String()
}
return etchosts.Build(container.config.hostsPath, IP, container.config.hostName,
container.config.domainName, extraContent)
}
func (ep *endpoint) updateParentHosts() error {
ep.Lock()
container := ep.container
network := ep.network
ep.Unlock()
if container == nil {
return ErrNoContainer
}
for _, update := range container.config.parentUpdates {
network.Lock()
pep, ok := network.endpoints[types.UUID(update.eid)]
if !ok {
network.Unlock()
continue
}
network.Unlock()
pep.Lock()
pContainer := pep.container
pep.Unlock()
if pContainer != nil {
if err := etchosts.Update(pContainer.config.hostsPath, update.ip, update.name); err != nil {
return err
}
}
}
return nil
}
func (ep *endpoint) updateDNS(resolvConf []byte) error {
ep.Lock()
container := ep.container
network := ep.network
ep.Unlock()
if container == nil {
return ErrNoContainer
}
oldHash := []byte{}
hashFile := container.config.resolvConfPath + ".hash"
resolvBytes, err := ioutil.ReadFile(container.config.resolvConfPath)
if err != nil {
if !os.IsNotExist(err) {
return err
}
} else {
oldHash, err = ioutil.ReadFile(hashFile)
if err != nil {
if !os.IsNotExist(err) {
return err
}
oldHash = []byte{}
}
}
curHash, err := ioutils.HashData(bytes.NewReader(resolvBytes))
if err != nil {
return err
}
if string(oldHash) != "" && curHash != string(oldHash) {
// Seems the user has changed the container resolv.conf since the last time
// we checked so return without doing anything.
return nil
}
// replace any localhost/127.* and remove IPv6 nameservers if IPv6 disabled.
resolvConf, _ = resolvconf.FilterResolvDNS(resolvConf, network.enableIPv6)
newHash, err := ioutils.HashData(bytes.NewReader(resolvConf))
if err != nil {
return err
}
// for atomic updates to these files, use temporary files with os.Rename:
dir := path.Dir(container.config.resolvConfPath)
tmpHashFile, err := ioutil.TempFile(dir, "hash")
if err != nil {
return err
}
tmpResolvFile, err := ioutil.TempFile(dir, "resolv")
if err != nil {
return err
}
// write the updates to the temp files
if err = ioutil.WriteFile(tmpHashFile.Name(), []byte(newHash), 0644); err != nil {
return err
}
if err = ioutil.WriteFile(tmpResolvFile.Name(), resolvConf, 0644); err != nil {
return err
}
// rename the temp files for atomic replace
if err = os.Rename(tmpHashFile.Name(), hashFile); err != nil {
return err
}
return os.Rename(tmpResolvFile.Name(), container.config.resolvConfPath)
}
func (ep *endpoint) setupDNS() error {
ep.Lock()
container := ep.container
ep.Unlock()
if container == nil {
return ErrNoContainer
}
if container.config.resolvConfPath == "" {
container.config.resolvConfPath = defaultPrefix + "/" + container.id + "/resolv.conf"
}
dir, _ := filepath.Split(container.config.resolvConfPath)
err := createBasePath(dir)
if err != nil {
return err
}
resolvConf, err := resolvconf.Get()
if err != nil {
return err
}
if len(container.config.dnsList) > 0 ||
len(container.config.dnsSearchList) > 0 {
var (
dnsList = resolvconf.GetNameservers(resolvConf)
dnsSearchList = resolvconf.GetSearchDomains(resolvConf)
)
if len(container.config.dnsList) > 0 {
dnsList = container.config.dnsList
}
if len(container.config.dnsSearchList) > 0 {
dnsSearchList = container.config.dnsSearchList
}
return resolvconf.Build(container.config.resolvConfPath, dnsList, dnsSearchList)
}
return ep.updateDNS(resolvConf)
}
// 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
}
}
}
// JoinOptionHostname function returns an option setter for hostname option to
// be passed to endpoint Join method.
func JoinOptionHostname(name string) EndpointOption {
return func(ep *endpoint) {
ep.container.config.hostName = name
}
}
// JoinOptionDomainname function returns an option setter for domainname option to
// be passed to endpoint Join method.
func JoinOptionDomainname(name string) EndpointOption {
return func(ep *endpoint) {
ep.container.config.domainName = name
}
}
// JoinOptionHostsPath function returns an option setter for hostspath option to
// be passed to endpoint Join method.
func JoinOptionHostsPath(path string) EndpointOption {
return func(ep *endpoint) {
ep.container.config.hostsPath = path
}
}
// JoinOptionExtraHost function returns an option setter for extra /etc/hosts options
// which is a name and IP as strings.
func JoinOptionExtraHost(name string, IP string) EndpointOption {
return func(ep *endpoint) {
ep.container.config.extraHosts = append(ep.container.config.extraHosts, extraHost{name: name, IP: IP})
}
}
// JoinOptionParentUpdate function returns an option setter for parent container
// which needs to update the IP address for the linked container.
func JoinOptionParentUpdate(eid string, name, ip string) EndpointOption {
return func(ep *endpoint) {
ep.container.config.parentUpdates = append(ep.container.config.parentUpdates, parentUpdate{eid: eid, name: name, ip: ip})
}
}
// JoinOptionResolvConfPath function returns an option setter for resolvconfpath option to
// be passed to endpoint Join method.
func JoinOptionResolvConfPath(path string) EndpointOption {
return func(ep *endpoint) {
ep.container.config.resolvConfPath = path
}
}
// JoinOptionDNS function returns an option setter for dns entry option to
// be passed to endpoint Join method.
func JoinOptionDNS(dns string) EndpointOption {
return func(ep *endpoint) {
ep.container.config.dnsList = append(ep.container.config.dnsList, dns)
}
}
// JoinOptionDNSSearch function returns an option setter for dns search entry option to
// be passed to endpoint Join method.
func JoinOptionDNSSearch(search string) EndpointOption {
return func(ep *endpoint) {
ep.container.config.dnsSearchList = append(ep.container.config.dnsSearchList, search)
}
}
// JoinOptionUseDefaultSandbox function returns an option setter for using default sandbox to
// be passed to endpoint Join method.
func JoinOptionUseDefaultSandbox() EndpointOption {
return func(ep *endpoint) {
ep.container.config.useDefaultSandBox = true
}
}
// CreateOptionExposedPorts function returns an option setter for the container exposed
// ports option to be passed to network.CreateEndpoint() method.
func CreateOptionExposedPorts(exposedPorts []netutils.TransportPort) EndpointOption {
return func(ep *endpoint) {
// Defensive copy
eps := make([]netutils.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 []netutils.PortBinding) EndpointOption {
return func(ep *endpoint) {
// Store a copy of the bindings as generic data to pass to the driver
pbs := make([]netutils.PortBinding, len(portBindings))
copy(pbs, portBindings)
ep.generic[netlabel.PortMap] = pbs
}
}
// JoinOptionGeneric 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
// endpoint join method. Container Labels are a good example.
func JoinOptionGeneric(generic map[string]interface{}) EndpointOption {
return func(ep *endpoint) {
ep.container.config.generic = generic
}
}

View File

@@ -0,0 +1,215 @@
package libnetwork
import (
"net"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netutils"
)
// EndpointInfo provides an interface to retrieve network resources bound to the endpoint.
type EndpointInfo interface {
// InterfaceList returns an interface list which were assigned to the endpoint
// by the driver. This can be used after the endpoint has been created.
InterfaceList() []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
// SandboxKey returns the sanbox key for the container which has joined
// the endpoint. If there is no container joined then this will return an
// empty string.
SandboxKey() string
}
// 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
}
type endpointInterface struct {
id int
mac net.HardwareAddr
addr net.IPNet
addrv6 net.IPNet
srcName string
dstName string
}
type endpointJoinInfo struct {
gw net.IP
gw6 net.IP
hostsPath string
resolvConfPath string
}
func (ep *endpoint) Info() EndpointInfo {
return ep
}
func (ep *endpoint) DriverInfo() (map[string]interface{}, error) {
ep.Lock()
network := ep.network
epid := ep.id
ep.Unlock()
network.Lock()
driver := network.driver
nid := network.id
network.Unlock()
return driver.EndpointOperInfo(nid, epid)
}
func (ep *endpoint) InterfaceList() []InterfaceInfo {
ep.Lock()
defer ep.Unlock()
iList := make([]InterfaceInfo, len(ep.iFaces))
for i, iface := range ep.iFaces {
iList[i] = iface
}
return iList
}
func (ep *endpoint) Interfaces() []driverapi.InterfaceInfo {
ep.Lock()
defer ep.Unlock()
iList := make([]driverapi.InterfaceInfo, len(ep.iFaces))
for i, iface := range ep.iFaces {
iList[i] = iface
}
return iList
}
func (ep *endpoint) AddInterface(id int, mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
ep.Lock()
defer ep.Unlock()
iface := &endpointInterface{
id: id,
addr: *netutils.GetIPNetCopy(&ipv4),
addrv6: *netutils.GetIPNetCopy(&ipv6),
}
iface.mac = netutils.GetMacCopy(mac)
ep.iFaces = append(ep.iFaces, iface)
return nil
}
func (i *endpointInterface) ID() int {
return i.id
}
func (i *endpointInterface) MacAddress() net.HardwareAddr {
return netutils.GetMacCopy(i.mac)
}
func (i *endpointInterface) Address() net.IPNet {
return (*netutils.GetIPNetCopy(&i.addr))
}
func (i *endpointInterface) AddressIPv6() net.IPNet {
return (*netutils.GetIPNetCopy(&i.addrv6))
}
func (i *endpointInterface) SetNames(srcName string, dstName string) error {
i.srcName = srcName
i.dstName = dstName
return nil
}
func (ep *endpoint) InterfaceNames() []driverapi.InterfaceNameInfo {
ep.Lock()
defer ep.Unlock()
iList := make([]driverapi.InterfaceNameInfo, len(ep.iFaces))
for i, iface := range ep.iFaces {
iList[i] = iface
}
return iList
}
func (ep *endpoint) SandboxKey() string {
ep.Lock()
defer ep.Unlock()
if ep.container == nil {
return ""
}
return ep.container.data.SandboxKey
}
func (ep *endpoint) Gateway() net.IP {
ep.Lock()
defer ep.Unlock()
if ep.joinInfo == nil {
return net.IP{}
}
return netutils.GetIPCopy(ep.joinInfo.gw)
}
func (ep *endpoint) GatewayIPv6() net.IP {
ep.Lock()
defer ep.Unlock()
if ep.joinInfo == nil {
return net.IP{}
}
return netutils.GetIPCopy(ep.joinInfo.gw6)
}
func (ep *endpoint) SetGateway(gw net.IP) error {
ep.Lock()
defer ep.Unlock()
ep.joinInfo.gw = netutils.GetIPCopy(gw)
return nil
}
func (ep *endpoint) SetGatewayIPv6(gw6 net.IP) error {
ep.Lock()
defer ep.Unlock()
ep.joinInfo.gw6 = netutils.GetIPCopy(gw6)
return nil
}
func (ep *endpoint) SetHostsPath(path string) error {
ep.Lock()
defer ep.Unlock()
ep.joinInfo.hostsPath = path
return nil
}
func (ep *endpoint) SetResolvConfPath(path string) error {
ep.Lock()
defer ep.Unlock()
ep.joinInfo.resolvConfPath = path
return nil
}

View File

@@ -0,0 +1,98 @@
package libnetwork
import (
"errors"
"fmt"
)
var (
// ErrNoSuchNetwork is returned when a network query finds no result
ErrNoSuchNetwork = errors.New("network not found")
// ErrNoSuchEndpoint is returned when a endpoint query finds no result
ErrNoSuchEndpoint = errors.New("endpoint not found")
// ErrNilNetworkDriver is returned if a nil network driver
// is passed to NewNetwork api.
ErrNilNetworkDriver = errors.New("nil NetworkDriver instance")
// ErrInvalidNetworkDriver is returned if an invalid driver
// instance is passed.
ErrInvalidNetworkDriver = errors.New("invalid driver bound to network")
// ErrInvalidJoin is returned if a join is attempted on an endpoint
// which already has a container joined.
ErrInvalidJoin = errors.New("a container has already joined the endpoint")
// ErrNoContainer is returned when the endpoint has no container
// attached to it.
ErrNoContainer = errors.New("no container attached to the endpoint")
// ErrInvalidID is returned when a query-by-id method is being invoked
// with an empty id parameter
ErrInvalidID = errors.New("invalid ID")
// ErrInvalidName is returned when a query-by-name or resource create method is
// invoked with an empty name parameter
ErrInvalidName = errors.New("invalid Name")
)
// 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))
}
// NetworkNameError is returned when a network with the same name already exists.
type NetworkNameError string
func (name NetworkNameError) Error() string {
return fmt.Sprintf("network with name %s already exists", string(name))
}
// UnknownNetworkError is returned when libnetwork could not find in it's 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)
}
// 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 with name %s id %s has active endpoints", aee.name, aee.id)
}
// UnknownEndpointError is returned when libnetwork could not find in it's 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)
}
// 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)
}
// 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))
}

View File

@@ -0,0 +1,79 @@
package etchosts
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"regexp"
)
// 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
}
// Default hosts config records slice
var 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"},
}
// 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 {
content := bytes.NewBuffer(nil)
if IP != "" {
//set main record
var mainRec Record
mainRec.IP = IP
if domainname != "" {
mainRec.Hosts = fmt.Sprintf("%s.%s %s", hostname, domainname, hostname)
} else {
mainRec.Hosts = hostname
}
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)
}
// 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 {
old, err := ioutil.ReadFile(path)
if err != nil {
return err
}
var re = regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)", regexp.QuoteMeta(hostname)))
return ioutil.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2")), 0644)
}

View File

@@ -0,0 +1,136 @@
package etchosts
import (
"bytes"
"io/ioutil"
"os"
"testing"
_ "github.com/docker/libnetwork/netutils"
)
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 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)
}
}

View File

@@ -0,0 +1,172 @@
// Package ipallocator defines the default IP allocator. It will move out of libnetwork as an external IPAM plugin.
// This has been imported unchanged from Docker, besides additon of registration logic
package ipallocator
import (
"errors"
"math/big"
"net"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/netutils"
)
// allocatedMap is thread-unsafe set of allocated IP
type allocatedMap struct {
p map[string]struct{}
last *big.Int
begin *big.Int
end *big.Int
}
func newAllocatedMap(network *net.IPNet) *allocatedMap {
firstIP, lastIP := netutils.NetworkRange(network)
begin := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1))
end := big.NewInt(0).Sub(ipToBigInt(lastIP), big.NewInt(1))
return &allocatedMap{
p: make(map[string]struct{}),
begin: begin,
end: end,
last: big.NewInt(0).Sub(begin, big.NewInt(1)), // so first allocated will be begin
}
}
type networkSet map[string]*allocatedMap
var (
// ErrNoAvailableIPs preformatted error
ErrNoAvailableIPs = errors.New("no available ip addresses on network")
// ErrIPAlreadyAllocated preformatted error
ErrIPAlreadyAllocated = errors.New("ip already allocated")
// ErrIPOutOfRange preformatted error
ErrIPOutOfRange = errors.New("requested ip is out of range")
// ErrNetworkAlreadyRegistered preformatted error
ErrNetworkAlreadyRegistered = errors.New("network already registered")
// ErrBadSubnet preformatted error
ErrBadSubnet = errors.New("network does not contain specified subnet")
)
// IPAllocator manages the ipam
type IPAllocator struct {
allocatedIPs networkSet
mutex sync.Mutex
}
// New returns a new instance of IPAllocator
func New() *IPAllocator {
return &IPAllocator{networkSet{}, sync.Mutex{}}
}
// RegisterSubnet registers network in global allocator with bounds
// defined by subnet. If you want to use network range you must call
// this method before first RequestIP, otherwise full network range will be used
func (a *IPAllocator) RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error {
a.mutex.Lock()
defer a.mutex.Unlock()
key := network.String()
if _, ok := a.allocatedIPs[key]; ok {
return ErrNetworkAlreadyRegistered
}
// Check that subnet is within network
beginIP, endIP := netutils.NetworkRange(subnet)
if !(network.Contains(beginIP) && network.Contains(endIP)) {
return ErrBadSubnet
}
n := newAllocatedMap(subnet)
a.allocatedIPs[key] = n
return nil
}
// RequestIP requests an available ip from the given network. It
// will return the next available ip if the ip provided is nil. If the
// ip provided is not nil it will validate that the provided ip is available
// for use or return an error
func (a *IPAllocator) RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) {
a.mutex.Lock()
defer a.mutex.Unlock()
key := network.String()
allocated, ok := a.allocatedIPs[key]
if !ok {
allocated = newAllocatedMap(network)
a.allocatedIPs[key] = allocated
}
if ip == nil {
return allocated.getNextIP()
}
return allocated.checkIP(ip)
}
// ReleaseIP adds the provided ip back into the pool of
// available ips to be returned for use.
func (a *IPAllocator) ReleaseIP(network *net.IPNet, ip net.IP) error {
a.mutex.Lock()
defer a.mutex.Unlock()
if allocated, exists := a.allocatedIPs[network.String()]; exists {
delete(allocated.p, ip.String())
}
return nil
}
func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) {
if _, ok := allocated.p[ip.String()]; ok {
return nil, ErrIPAlreadyAllocated
}
pos := ipToBigInt(ip)
// Verify that the IP address is within our network range.
if pos.Cmp(allocated.begin) == -1 || pos.Cmp(allocated.end) == 1 {
return nil, ErrIPOutOfRange
}
// Register the IP.
allocated.p[ip.String()] = struct{}{}
return ip, nil
}
// return an available ip if one is currently available. If not,
// return the next available ip for the network
func (allocated *allocatedMap) getNextIP() (net.IP, error) {
pos := big.NewInt(0).Set(allocated.last)
allRange := big.NewInt(0).Sub(allocated.end, allocated.begin)
for i := big.NewInt(0); i.Cmp(allRange) <= 0; i.Add(i, big.NewInt(1)) {
pos.Add(pos, big.NewInt(1))
if pos.Cmp(allocated.end) == 1 {
pos.Set(allocated.begin)
}
if _, ok := allocated.p[bigIntToIP(pos).String()]; ok {
continue
}
allocated.p[bigIntToIP(pos).String()] = struct{}{}
allocated.last.Set(pos)
return bigIntToIP(pos), nil
}
return nil, ErrNoAvailableIPs
}
// Converts a 4 bytes IP into a 128 bit integer
func ipToBigInt(ip net.IP) *big.Int {
x := big.NewInt(0)
if ip4 := ip.To4(); ip4 != nil {
return x.SetBytes(ip4)
}
if ip6 := ip.To16(); ip6 != nil {
return x.SetBytes(ip6)
}
logrus.Errorf("ipToBigInt: Wrong IP length! %s", ip)
return nil
}
// Converts 128 bit integer into a 4 bytes IP address
func bigIntToIP(v *big.Int) net.IP {
return net.IP(v.Bytes())
}

View File

@@ -0,0 +1,690 @@
package ipallocator
import (
"fmt"
"math/big"
"net"
"testing"
)
func TestConversion(t *testing.T) {
ip := net.ParseIP("127.0.0.1")
i := ipToBigInt(ip)
if i.Cmp(big.NewInt(0x7f000001)) != 0 {
t.Fatal("incorrect conversion")
}
conv := bigIntToIP(i)
if !ip.Equal(conv) {
t.Error(conv.String())
}
}
func TestConversionIPv6(t *testing.T) {
ip := net.ParseIP("2a00:1450::1")
ip2 := net.ParseIP("2a00:1450::2")
ip3 := net.ParseIP("2a00:1450::1:1")
i := ipToBigInt(ip)
val, success := big.NewInt(0).SetString("2a001450000000000000000000000001", 16)
if !success {
t.Fatal("Hex-String to BigInt conversion failed.")
}
if i.Cmp(val) != 0 {
t.Fatal("incorrent conversion")
}
conv := bigIntToIP(i)
conv2 := bigIntToIP(big.NewInt(0).Add(i, big.NewInt(1)))
conv3 := bigIntToIP(big.NewInt(0).Add(i, big.NewInt(0x10000)))
if !ip.Equal(conv) {
t.Error("2a00:1450::1 should be equal to " + conv.String())
}
if !ip2.Equal(conv2) {
t.Error("2a00:1450::2 should be equal to " + conv2.String())
}
if !ip3.Equal(conv3) {
t.Error("2a00:1450::1:1 should be equal to " + conv3.String())
}
}
func TestRequestNewIps(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
var ip net.IP
var err error
for i := 1; i < 10; i++ {
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if expected := fmt.Sprintf("192.168.0.%d", i); ip.String() != expected {
t.Fatalf("Expected ip %s got %s", expected, ip.String())
}
}
value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String()
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if ip.String() != value {
t.Fatalf("Expected to receive the next ip %s got %s", value, ip.String())
}
}
func TestRequestNewIpV6(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
var ip net.IP
var err error
for i := 1; i < 10; i++ {
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if expected := fmt.Sprintf("2a00:1450::%d", i); ip.String() != expected {
t.Fatalf("Expected ip %s got %s", expected, ip.String())
}
}
value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String()
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if ip.String() != value {
t.Fatalf("Expected to receive the next ip %s got %s", value, ip.String())
}
}
func TestReleaseIp(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
}
func TestReleaseIpV6(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
}
func TestGetReleasedIp(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
value := ip.String()
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
for i := 0; i < 253; i++ {
_, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
err = a.ReleaseIP(network, ip)
if err != nil {
t.Fatal(err)
}
}
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if ip.String() != value {
t.Fatalf("Expected to receive same ip %s got %s", value, ip.String())
}
}
func TestGetReleasedIpV6(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0},
}
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
value := ip.String()
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
for i := 0; i < 253; i++ {
_, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
err = a.ReleaseIP(network, ip)
if err != nil {
t.Fatal(err)
}
}
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if ip.String() != value {
t.Fatalf("Expected to receive same ip %s got %s", value, ip.String())
}
}
func TestRequestSpecificIp(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 224},
}
ip := net.ParseIP("192.168.0.5")
// Request a "good" IP.
if _, err := a.RequestIP(network, ip); err != nil {
t.Fatal(err)
}
// Request the same IP again.
if _, err := a.RequestIP(network, ip); err != ErrIPAlreadyAllocated {
t.Fatalf("Got the same IP twice: %#v", err)
}
// Request an out of range IP.
if _, err := a.RequestIP(network, net.ParseIP("192.168.0.42")); err != ErrIPOutOfRange {
t.Fatalf("Got an out of range IP: %#v", err)
}
}
func TestRequestSpecificIpV6(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
ip := net.ParseIP("2a00:1450::5")
// Request a "good" IP.
if _, err := a.RequestIP(network, ip); err != nil {
t.Fatal(err)
}
// Request the same IP again.
if _, err := a.RequestIP(network, ip); err != ErrIPAlreadyAllocated {
t.Fatalf("Got the same IP twice: %#v", err)
}
// Request an out of range IP.
if _, err := a.RequestIP(network, net.ParseIP("2a00:1500::1")); err != ErrIPOutOfRange {
t.Fatalf("Got an out of range IP: %#v", err)
}
}
func TestIPAllocator(t *testing.T) {
a := New()
expectedIPs := []net.IP{
0: net.IPv4(127, 0, 0, 1),
1: net.IPv4(127, 0, 0, 2),
2: net.IPv4(127, 0, 0, 3),
3: net.IPv4(127, 0, 0, 4),
4: net.IPv4(127, 0, 0, 5),
5: net.IPv4(127, 0, 0, 6),
}
gwIP, n, _ := net.ParseCIDR("127.0.0.1/29")
network := &net.IPNet{IP: gwIP, Mask: n.Mask}
// Pool after initialisation (f = free, u = used)
// 1(f) - 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
// ↑
// Check that we get 6 IPs, from 127.0.0.1127.0.0.6, in that
// order.
for i := 0; i < 6; i++ {
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, expectedIPs[i], ip)
}
// Before loop begin
// 1(f) - 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
// ↑
// After i = 0
// 1(u) - 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
// ↑
// After i = 1
// 1(u) - 2(u) - 3(f) - 4(f) - 5(f) - 6(f)
// ↑
// After i = 2
// 1(u) - 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
// ↑
// After i = 3
// 1(u) - 2(u) - 3(u) - 4(u) - 5(f) - 6(f)
// ↑
// After i = 4
// 1(u) - 2(u) - 3(u) - 4(u) - 5(u) - 6(f)
// ↑
// After i = 5
// 1(u) - 2(u) - 3(u) - 4(u) - 5(u) - 6(u)
// ↑
// Check that there are no more IPs
ip, err := a.RequestIP(network, nil)
if err == nil {
t.Fatalf("There shouldn't be any IP addresses at this point, got %s\n", ip)
}
// Release some IPs in non-sequential order
if err := a.ReleaseIP(network, expectedIPs[3]); err != nil {
t.Fatal(err)
}
// 1(u) - 2(u) - 3(u) - 4(f) - 5(u) - 6(u)
// ↑
if err := a.ReleaseIP(network, expectedIPs[2]); err != nil {
t.Fatal(err)
}
// 1(u) - 2(u) - 3(f) - 4(f) - 5(u) - 6(u)
// ↑
if err := a.ReleaseIP(network, expectedIPs[4]); err != nil {
t.Fatal(err)
}
// 1(u) - 2(u) - 3(f) - 4(f) - 5(f) - 6(u)
// ↑
// Make sure that IPs are reused in sequential order, starting
// with the first released IP
newIPs := make([]net.IP, 3)
for i := 0; i < 3; i++ {
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
newIPs[i] = ip
}
assertIPEquals(t, expectedIPs[2], newIPs[0])
assertIPEquals(t, expectedIPs[3], newIPs[1])
assertIPEquals(t, expectedIPs[4], newIPs[2])
_, err = a.RequestIP(network, nil)
if err == nil {
t.Fatal("There shouldn't be any IP addresses at this point")
}
}
func TestAllocateFirstIP(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 0},
Mask: []byte{255, 255, 255, 0},
}
firstIP := network.IP.To4().Mask(network.Mask)
first := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1))
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
allocated := ipToBigInt(ip)
if allocated == first {
t.Fatalf("allocated ip should not equal first ip: %d == %d", first, allocated)
}
}
func TestAllocateAllIps(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
var (
current, first net.IP
err error
isFirst = true
)
for err == nil {
current, err = a.RequestIP(network, nil)
if isFirst {
first = current
isFirst = false
}
}
if err != ErrNoAvailableIPs {
t.Fatal(err)
}
if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs {
t.Fatal(err)
}
if err := a.ReleaseIP(network, first); err != nil {
t.Fatal(err)
}
again, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, first, again)
// ensure that alloc.last == alloc.begin won't result in dead loop
if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs {
t.Fatal(err)
}
// Test by making alloc.last the only free ip and ensure we get it back
// #1. first of the range, (alloc.last == ipToInt(first) already)
if err := a.ReleaseIP(network, first); err != nil {
t.Fatal(err)
}
ret, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, first, ret)
// #2. last of the range, note that current is the last one
last := net.IPv4(192, 168, 0, 254)
setLastTo(t, a, network, last)
ret, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, last, ret)
// #3. middle of the range
mid := net.IPv4(192, 168, 0, 7)
setLastTo(t, a, network, mid)
ret, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, mid, ret)
}
// make sure the pool is full when calling setLastTo.
// we don't cheat here
func setLastTo(t *testing.T, a *IPAllocator, network *net.IPNet, ip net.IP) {
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
ret, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, ip, ret)
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
}
func TestAllocateDifferentSubnets(t *testing.T) {
a := New()
network1 := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
network2 := &net.IPNet{
IP: []byte{127, 0, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
network3 := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
network4 := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x16, 0x32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
expectedIPs := []net.IP{
0: net.IPv4(192, 168, 0, 1),
1: net.IPv4(192, 168, 0, 2),
2: net.IPv4(127, 0, 0, 1),
3: net.IPv4(127, 0, 0, 2),
4: net.ParseIP("2a00:1450::1"),
5: net.ParseIP("2a00:1450::2"),
6: net.ParseIP("2a00:1450::3"),
7: net.ParseIP("2a00:1632::1"),
8: net.ParseIP("2a00:1632::2"),
}
ip11, err := a.RequestIP(network1, nil)
if err != nil {
t.Fatal(err)
}
ip12, err := a.RequestIP(network1, nil)
if err != nil {
t.Fatal(err)
}
ip21, err := a.RequestIP(network2, nil)
if err != nil {
t.Fatal(err)
}
ip22, err := a.RequestIP(network2, nil)
if err != nil {
t.Fatal(err)
}
ip31, err := a.RequestIP(network3, nil)
if err != nil {
t.Fatal(err)
}
ip32, err := a.RequestIP(network3, nil)
if err != nil {
t.Fatal(err)
}
ip33, err := a.RequestIP(network3, nil)
if err != nil {
t.Fatal(err)
}
ip41, err := a.RequestIP(network4, nil)
if err != nil {
t.Fatal(err)
}
ip42, err := a.RequestIP(network4, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, expectedIPs[0], ip11)
assertIPEquals(t, expectedIPs[1], ip12)
assertIPEquals(t, expectedIPs[2], ip21)
assertIPEquals(t, expectedIPs[3], ip22)
assertIPEquals(t, expectedIPs[4], ip31)
assertIPEquals(t, expectedIPs[5], ip32)
assertIPEquals(t, expectedIPs[6], ip33)
assertIPEquals(t, expectedIPs[7], ip41)
assertIPEquals(t, expectedIPs[8], ip42)
}
func TestRegisterBadTwice(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 1, 1},
Mask: []byte{255, 255, 255, 0},
}
subnet := &net.IPNet{
IP: []byte{192, 168, 1, 8},
Mask: []byte{255, 255, 255, 248},
}
if err := a.RegisterSubnet(network, subnet); err != nil {
t.Fatal(err)
}
subnet = &net.IPNet{
IP: []byte{192, 168, 1, 16},
Mask: []byte{255, 255, 255, 248},
}
if err := a.RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered {
t.Fatalf("Expecteded ErrNetworkAlreadyRegistered error, got %v", err)
}
}
func TestRegisterBadRange(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 1, 1},
Mask: []byte{255, 255, 255, 0},
}
subnet := &net.IPNet{
IP: []byte{192, 168, 1, 1},
Mask: []byte{255, 255, 0, 0},
}
if err := a.RegisterSubnet(network, subnet); err != ErrBadSubnet {
t.Fatalf("Expected ErrBadSubnet error, got %v", err)
}
}
func TestAllocateFromRange(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
// 192.168.1.9 - 192.168.1.14
subnet := &net.IPNet{
IP: []byte{192, 168, 0, 8},
Mask: []byte{255, 255, 255, 248},
}
if err := a.RegisterSubnet(network, subnet); err != nil {
t.Fatal(err)
}
expectedIPs := []net.IP{
0: net.IPv4(192, 168, 0, 9),
1: net.IPv4(192, 168, 0, 10),
2: net.IPv4(192, 168, 0, 11),
3: net.IPv4(192, 168, 0, 12),
4: net.IPv4(192, 168, 0, 13),
5: net.IPv4(192, 168, 0, 14),
}
for _, ip := range expectedIPs {
rip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, ip, rip)
}
if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs {
t.Fatalf("Expected ErrNoAvailableIPs error, got %v", err)
}
for _, ip := range expectedIPs {
a.ReleaseIP(network, ip)
rip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, ip, rip)
}
}
func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
if !ip1.Equal(ip2) {
t.Fatalf("Expected IP %s, got %s", ip1, ip2)
}
}
func BenchmarkRequestIP(b *testing.B) {
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
a := New()
for j := 0; j < 253; j++ {
_, err := a.RequestIP(network, nil)
if err != nil {
b.Fatal(err)
}
}
}
}

View File

@@ -0,0 +1,164 @@
package iptables
import (
"fmt"
"strings"
"github.com/Sirupsen/logrus"
"github.com/godbus/dbus"
)
// 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.Object
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)
}
if connection != nil {
go signalHandler()
}
firewalldRunning = checkRunning()
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
}
// Innitialize 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)
logrus.Infof("Firewalld running: %t", err == nil)
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
}

View File

@@ -0,0 +1,83 @@
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 *Chain
fwdChain, err = NewChain("FWD", "lo", Filter, false)
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)
if err != nil {
t.Fatal(err)
} else {
// to be re-called again later
OnReloaded(func() { fwdChain.Link(Append, ip1, ip2, port, proto) })
}
rule1 := []string{
"-i", fwdChain.Bridge,
"-o", fwdChain.Bridge,
"-p", proto,
"-s", ip1.String(),
"-d", ip2.String(),
"--dport", strconv.Itoa(port),
"-j", "ACCEPT"}
if !Exists(fwdChain.Table, fwdChain.Name, rule1...) {
t.Fatalf("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.Fatalf("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.Fatalf("rule1 does not exist")
}
}
}

View File

@@ -0,0 +1,320 @@
package iptables
import (
"errors"
"fmt"
"net"
"os/exec"
"strconv"
"strings"
"sync"
"github.com/Sirupsen/logrus"
)
// Action signifies the iptable action.
type Action 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"
)
var (
iptablesPath string
supportsXlock = false
// 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")
)
// Chain defines the iptables chain.
type Chain struct {
Name string
Bridge string
Table Table
}
// 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 initCheck() error {
if iptablesPath == "" {
path, err := exec.LookPath("iptables")
if err != nil {
return ErrIptablesNotFound
}
iptablesPath = path
supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
}
return nil
}
// NewChain adds a new chain to ip table.
func NewChain(name, bridge string, table Table, hairpinMode bool) (*Chain, error) {
c := &Chain{
Name: name,
Bridge: bridge,
Table: table,
}
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)
}
}
switch table {
case Nat:
preroute := []string{
"-m", "addrtype",
"--dst-type", "LOCAL"}
if !Exists(Nat, "PREROUTING", preroute...) {
if err := c.Prerouting(Append, preroute...); err != nil {
return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
}
}
output := []string{
"-m", "addrtype",
"--dst-type", "LOCAL"}
if !hairpinMode {
output = append(output, "!", "--dst", "127.0.0.0/8")
}
if !Exists(Nat, "OUTPUT", output...) {
if err := c.Output(Append, output...); err != nil {
return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
}
}
case Filter:
link := []string{
"-o", c.Bridge,
"-j", c.Name}
if !Exists(Filter, "FORWARD", link...) {
insert := append([]string{string(Insert), "FORWARD"}, link...)
if output, err := Raw(insert...); err != nil {
return nil, err
} else if len(output) != 0 {
return nil, fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
}
}
}
return c, nil
}
// RemoveExistingChain removes existing chain from the table.
func RemoveExistingChain(name string, table Table) error {
c := &Chain{
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 *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int) 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"
}
if output, err := Raw("-t", string(Nat), string(action), c.Name,
"-p", proto,
"-d", daddr,
"--dport", strconv.Itoa(port),
"-j", "DNAT",
"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil {
return err
} else if len(output) != 0 {
return ChainError{Chain: "FORWARD", Output: output}
}
if output, err := Raw("-t", string(Filter), string(action), c.Name,
"!", "-i", c.Bridge,
"-o", c.Bridge,
"-p", proto,
"-d", destAddr,
"--dport", strconv.Itoa(destPort),
"-j", "ACCEPT"); err != nil {
return err
} else if len(output) != 0 {
return ChainError{Chain: "FORWARD", Output: output}
}
if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING",
"-p", proto,
"-s", destAddr,
"-d", destAddr,
"--dport", strconv.Itoa(destPort),
"-j", "MASQUERADE"); err != nil {
return err
} else if len(output) != 0 {
return ChainError{Chain: "FORWARD", Output: output}
}
return nil
}
// Link adds reciprocal ACCEPT rule for two supplied IP addresses.
// Traffic is allowed from ip1 to ip2 and vice-versa
func (c *Chain) Link(action Action, ip1, ip2 net.IP, port int, proto string) error {
if output, err := Raw("-t", string(Filter), string(action), c.Name,
"-i", c.Bridge, "-o", c.Bridge,
"-p", proto,
"-s", ip1.String(),
"-d", ip2.String(),
"--dport", strconv.Itoa(port),
"-j", "ACCEPT"); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Error iptables forward: %s", output)
}
if output, err := Raw("-t", string(Filter), string(action), c.Name,
"-i", c.Bridge, "-o", c.Bridge,
"-p", proto,
"-s", ip2.String(),
"-d", ip1.String(),
"--sport", strconv.Itoa(port),
"-j", "ACCEPT"); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Error iptables forward: %s", output)
}
return nil
}
// Prerouting adds linking rule to nat/PREROUTING chain.
func (c *Chain) 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(append(a, "-j", c.Name)...); 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 *Chain) 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(append(a, "-j", c.Name)...); err != nil {
return err
} else if len(output) != 0 {
return ChainError{Chain: "OUTPUT", Output: output}
}
return nil
}
// Remove removes the chain.
func (c *Chain) 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")
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8")
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // 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 {
if string(table) == "" {
table = Filter
}
// iptables -C, --check option was added in v.1.4.11
// http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
// try -C
// if exit status is 0 then return true, the rule exists
if _, err := Raw(append([]string{
"-t", string(table), "-C", chain}, rule...)...); err == nil {
return true
}
// parse "iptables -S" for the rule (this checks rules in a specific chain
// in a specific table)
ruleString := strings.Join(rule, " ")
existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
return strings.Contains(string(existingRules), ruleString)
}
// Raw calls 'iptables' system command, passing supplied arguments.
func Raw(args ...string) ([]byte, error) {
if firewalldRunning {
output, err := Passthrough(Iptables, args...)
if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
return output, err
}
}
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)
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)
}
// ignore iptables' message about xtables lock
if strings.Contains(string(output), "waiting for it to exit") {
output = []byte("")
}
return output, err
}

View File

@@ -0,0 +1,239 @@
package iptables
import (
"net"
"os/exec"
"strconv"
"strings"
"sync"
"testing"
_ "github.com/docker/libnetwork/netutils"
)
const chainName = "DOCKER-TEST"
var natChain *Chain
var filterChain *Chain
func TestNewChain(t *testing.T) {
var err error
natChain, err = NewChain(chainName, "lo", Nat, false)
if err != nil {
t.Fatal(err)
}
filterChain, err = NewChain(chainName, "lo", Filter, false)
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"
err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort)
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),
}
if !Exists(natChain.Table, natChain.Name, dnatRule...) {
t.Fatalf("DNAT rule does not exist")
}
filterRule := []string{
"!", "-i", filterChain.Bridge,
"-o", filterChain.Bridge,
"-d", dstAddr,
"-p", proto,
"--dport", strconv.Itoa(dstPort),
"-j", "ACCEPT",
}
if !Exists(filterChain.Table, filterChain.Name, filterRule...) {
t.Fatalf("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.Fatalf("MASQUERADE rule does not exist")
}
}
func TestLink(t *testing.T) {
var err error
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)
if err != nil {
t.Fatal(err)
}
rule1 := []string{
"-i", filterChain.Bridge,
"-o", filterChain.Bridge,
"-p", proto,
"-s", ip1.String(),
"-d", ip2.String(),
"--dport", strconv.Itoa(port),
"-j", "ACCEPT"}
if !Exists(filterChain.Table, filterChain.Name, rule1...) {
t.Fatalf("rule1 does not exist")
}
rule2 := []string{
"-i", filterChain.Bridge,
"-o", filterChain.Bridge,
"-p", proto,
"-s", ip2.String(),
"-d", ip1.String(),
"--sport", strconv.Itoa(port),
"-j", "ACCEPT"}
if !Exists(filterChain.Table, filterChain.Name, rule2...) {
t.Fatalf("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)
}
rule := []string{
"-j", natChain.Name}
rule = append(rule, args...)
if !Exists(natChain.Table, "PREROUTING", rule...) {
t.Fatalf("rule does not exist")
}
delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, rule...)
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)
}
rule := []string{
"-j", natChain.Name}
rule = append(rule, args...)
if !Exists(natChain.Table, "OUTPUT", rule...) {
t.Fatalf("rule does not exist")
}
delRule := append([]string{"-D", "OUTPUT", "-t",
string(natChain.Table)}, rule...)
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)
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", filterChain.Bridge,
"-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)
}
}

View File

@@ -0,0 +1,26 @@
package libnetwork
import (
"testing"
"github.com/docker/libnetwork/driverapi"
)
func TestDriverRegistration(t *testing.T) {
bridgeNetType := "bridge"
c, err := New()
if err != nil {
t.Fatal(err)
}
err = c.(*controller).RegisterDriver(bridgeNetType, nil)
if err == nil {
t.Fatalf("Expecting the RegisterDriver to fail for %s", bridgeNetType)
}
if _, ok := err.(driverapi.ErrActiveRegistration); !ok {
t.Fatalf("Failed for unexpected reason: %v", err)
}
err = c.(*controller).RegisterDriver("test-dummy", nil)
if err != nil {
t.Fatalf("Test failed with an error %v", err)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
package netlabel
const (
// GenericData constant that helps to identify an option as a Generic constant
GenericData = "io.docker.network.generic"
// PortMap constant represents Port Mapping
PortMap = "io.docker.network.endpoint.portmap"
// MacAddress constant represents Mac Address config of a Container
MacAddress = "io.docker.network.endpoint.macaddress"
// ExposedPorts constant represents exposedports of a Container
ExposedPorts = "io.docker.network.endpoint.exposedports"
//EnableIPv6 constant represents enabling IPV6 at network level
EnableIPv6 = "io.docker.network.enable_ipv6"
)

View File

@@ -0,0 +1,41 @@
package netutils
import (
"flag"
"runtime"
"syscall"
"testing"
)
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)
}
// SetupTestNetNS joins a new network namespace, and returns its associated
// teardown function.
//
// Example usage:
//
// defer SetupTestNetNS(t)()
//
func SetupTestNetNS(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")
}
return func() {
if err := syscall.Close(fd); err != nil {
t.Logf("Warning: netns closing failed (%v)", err)
}
runtime.UnlockOSThread()
}
}

View File

@@ -0,0 +1,324 @@
// Network utility functions.
package netutils
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"strings"
"github.com/vishvananda/netlink"
)
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")
networkGetRoutesFct = netlink.RouteList
)
// 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))
}
// TransportPort represent a local Layer 4 endpoint
type TransportPort struct {
Proto Protocol
Port uint16
}
// GetCopy returns a copy of this TransportPort structure instance
func (t *TransportPort) GetCopy() TransportPort {
return TransportPort{Proto: t.Proto, Port: t.Port}
}
// PortBinding represent a port binding between the container an the host
type PortBinding struct {
Proto Protocol
IP net.IP
Port uint16
HostIP net.IP
HostPort 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
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
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,
}
}
// 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 {
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
}
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
)
// Protocol represents a 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"
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
default:
return 0
}
}
// 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
}
// CheckRouteOverlaps checks whether the passed network overlaps with any existing routes
func CheckRouteOverlaps(toCheck *net.IPNet) error {
networks, err := networkGetRoutesFct(nil, netlink.FAMILY_V4)
if err != nil {
return err
}
for _, network := range networks {
if network.Dst != nil && NetworkOverlaps(toCheck, network.Dst) {
return ErrNetworkOverlaps
}
}
return nil
}
// NetworkOverlaps detects overlap between one IPNet and another
func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
// Check if both netX and netY are ipv4 or ipv6
if (netX.IP.To4() != nil && netY.IP.To4() != nil) ||
(netX.IP.To4() == nil && netY.IP.To4() == nil) {
if firstIP, _ := NetworkRange(netX); netY.Contains(firstIP) {
return true
}
if firstIP, _ := NetworkRange(netY); netX.Contains(firstIP) {
return true
}
}
return false
}
// NetworkRange calculates the first and last IP addresses in an IPNet
func NetworkRange(network *net.IPNet) (net.IP, net.IP) {
var netIP net.IP
if network.IP.To4() != nil {
netIP = network.IP.To4()
} else if network.IP.To16() != nil {
netIP = network.IP.To16()
} else {
return nil, nil
}
lastIP := make([]byte, len(netIP), len(netIP))
for i := 0; i < len(netIP); i++ {
lastIP[i] = netIP[i] | ^network.Mask[i]
}
return netIP.Mask(network.Mask), net.IP(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
}
// GenerateRandomMAC returns a new 6-byte(48-bit) hardware address (MAC)
func GenerateRandomMAC() 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.
// 3. As "small" as possible: The veth address has to be "smaller" than the bridge address.
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
// Randomly generate the remaining 4 bytes (2^32)
_, err := rand.Read(hw[2:])
if err != nil {
return nil
}
return hw
}
// 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
}
// GetMacCopy returns a copy of the passed MAC address
func GetMacCopy(from net.HardwareAddr) net.HardwareAddr {
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 {
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}
}
// 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)
}

View File

@@ -0,0 +1,343 @@
package netutils
import (
"bytes"
"net"
"testing"
"github.com/vishvananda/netlink"
)
func TestNonOverlapingNameservers(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 TestOverlapingNameservers(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) {
orig := networkGetRoutesFct
defer func() {
networkGetRoutesFct = orig
}()
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
}
_, 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.Fatalf("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 TestCompareIPNet(t *testing.T) {
if CompareIPNet(nil, nil) == false {
t.Fatalf("Failed to detect two nil net.IPNets are equal")
}
_, net1, _ := net.ParseCIDR("192.168.30.22/24")
if CompareIPNet(net1, net1) == false {
t.Fatalf("Failed to detect same net.IPNet pointers equality")
}
_, net2, _ := net.ParseCIDR("192.168.30.22/24")
if CompareIPNet(net1, net2) == false {
t.Fatalf("Failed to detect same net.IPNet object equality")
}
_, net3, _ := net.ParseCIDR("192.168.30.33/24")
if CompareIPNet(net1, net3) == false {
t.Fatalf("Failed to detect semantically equivalent net.IPNets")
}
_, net3, _ = net.ParseCIDR("192.168.31.33/24")
if CompareIPNet(net2, net3) == true {
t.Fatalf("Failed to detect different net.IPNets")
}
}
func TestIPCopyFunctions(t *testing.T) {
ip := net.ParseIP("172.28.30.134")
cp := GetIPCopy(ip)
if !ip.Equal(cp) {
t.Fatalf("Failed to return a copy of net.IP")
}
if &ip == &cp {
t.Fatalf("Failed to return a true copy of net.IP")
}
}
func TestNetIPCopyFunctions(t *testing.T) {
_, net, _ := net.ParseCIDR("192.168.30.23/24")
cp := GetIPNetCopy(net)
if CompareIPNet(net, cp) == false {
t.Fatalf("Failed to return a copy of net.IPNet")
}
if net == cp {
t.Fatalf("Failed to return a true copy of net.IPNet")
}
}
func TestPortBindingEqual(t *testing.T) {
pb1 := &PortBinding{
Proto: TCP,
IP: net.ParseIP("172.17.0.1"),
Port: 80,
HostIP: net.ParseIP("192.168.100.1"),
HostPort: 8080,
}
pb2 := &PortBinding{
Proto: UDP,
IP: net.ParseIP("172.17.0.1"),
Port: 22,
HostIP: net.ParseIP("192.168.100.1"),
HostPort: 2222,
}
if !pb1.Equal(pb1) {
t.Fatalf("PortBinding.Equal() returned false negative")
}
if pb1.Equal(nil) {
t.Fatalf("PortBinding.Equal() returned false negative")
}
if pb1.Equal(pb2) {
t.Fatalf("PortBinding.Equal() returned false positive")
}
if pb1.Equal(pb2) != pb2.Equal(pb1) {
t.Fatalf("PortBinding.Equal() failed commutative check")
}
}
func TestPortBindingGetCopy(t *testing.T) {
pb := &PortBinding{
Proto: TCP,
IP: net.ParseIP("172.17.0.1"),
Port: 80,
HostIP: net.ParseIP("192.168.100.1"),
HostPort: 8080,
}
cp := pb.GetCopy()
if !pb.Equal(&cp) {
t.Fatalf("Failed to return a copy of PortBinding")
}
if pb == &cp {
t.Fatalf("Failed to return a true copy of PortBinding")
}
}
func TestPortBindingContainerAddr(t *testing.T) {
pb := PortBinding{
Proto: TCP,
IP: net.ParseIP("172.17.0.1"),
Port: 80,
HostIP: net.ParseIP("192.168.100.1"),
HostPort: 8080,
}
container, err := pb.ContainerAddr()
if err != nil {
t.Fatal(err)
}
switch netAddr := container.(type) {
case *net.TCPAddr:
if !pb.IP.Equal(netAddr.IP) {
t.Fatalf("PortBinding.ContainerAddr() Failed to return a ContainerAddr")
}
if int(pb.Port) != netAddr.Port {
t.Fatalf("PortBinding.ContainerAddr() Failed to return a ContainerAddr")
}
case *net.UDPAddr:
t.Fatalf("PortBinding.ContainerAddr() Failed to check correct proto")
}
}

View File

@@ -0,0 +1,207 @@
package libnetwork
import (
"sync"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/options"
"github.com/docker/libnetwork/types"
)
// 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 carry driver specific options.
// Labels support will be added in the near future.
CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error)
// Delete the network.
Delete() 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)
}
// 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
type network struct {
ctrlr *controller
name string
networkType string
id types.UUID
driver driverapi.Driver
enableIPv6 bool
endpoints endpointTable
generic options.Generic
sync.Mutex
}
func (n *network) Name() string {
return n.name
}
func (n *network) ID() string {
return string(n.id)
}
func (n *network) Type() string {
if n.driver == nil {
return ""
}
return n.driver.Type()
}
// NetworkOption is a option setter function type used to pass varios 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) {
n.generic = generic
if _, ok := generic[netlabel.EnableIPv6]; ok {
n.enableIPv6 = generic[netlabel.EnableIPv6].(bool)
}
}
}
func (n *network) processOptions(options ...NetworkOption) {
for _, opt := range options {
if opt != nil {
opt(n)
}
}
}
func (n *network) Delete() error {
var err error
n.ctrlr.Lock()
_, ok := n.ctrlr.networks[n.id]
if !ok {
n.ctrlr.Unlock()
return &UnknownNetworkError{name: n.name, id: string(n.id)}
}
n.Lock()
numEps := len(n.endpoints)
n.Unlock()
if numEps != 0 {
n.ctrlr.Unlock()
return &ActiveEndpointsError{name: n.name, id: string(n.id)}
}
delete(n.ctrlr.networks, n.id)
n.ctrlr.Unlock()
defer func() {
if err != nil {
n.ctrlr.Lock()
n.ctrlr.networks[n.id] = n
n.ctrlr.Unlock()
}
}()
err = n.driver.DeleteNetwork(n.id)
return err
}
func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error) {
if name == "" {
return nil, ErrInvalidName
}
ep := &endpoint{name: name, iFaces: []*endpointInterface{}, generic: make(map[string]interface{})}
ep.id = types.UUID(stringid.GenerateRandomID())
ep.network = n
ep.processOptions(options...)
d := n.driver
err := d.CreateEndpoint(n.id, ep.id, ep, ep.generic)
if err != nil {
return nil, err
}
n.Lock()
n.endpoints[ep.id] = ep
n.Unlock()
return ep, nil
}
func (n *network) Endpoints() []Endpoint {
n.Lock()
defer n.Unlock()
list := make([]Endpoint, 0, len(n.endpoints))
for _, e := range n.endpoints {
list = append(list, e)
}
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
}
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
}
return e, nil
}
func (n *network) EndpointByID(id string) (Endpoint, error) {
if id == "" {
return nil, ErrInvalidID
}
n.Lock()
defer n.Unlock()
if e, ok := n.endpoints[types.UUID(id)]; ok {
return e, nil
}
return nil, ErrNoSuchEndpoint
}

View File

@@ -0,0 +1,73 @@
// 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)
}
// Generic is an 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()}
}
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
}

View File

@@ -0,0 +1,97 @@
package options
import (
"reflect"
"strings"
"testing"
_ "github.com/docker/libnetwork/netutils"
)
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())
}
}

View File

@@ -0,0 +1,212 @@
package portallocator
import (
"bufio"
"errors"
"fmt"
"net"
"os"
"sync"
"github.com/Sirupsen/logrus"
)
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
}
portMap struct {
p map[int]struct{}
begin, end int
last int
}
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 {
logrus.Warn(err)
start, end = DefaultPortRangeStart, DefaultPortRangeEnd
}
return &PortAllocator{
ipMap: ipMapping{},
Begin: start,
End: end,
}
}
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)
}
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
}
// 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 pool and return that port or error if port is already busy.
func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
p.mutex.Lock()
defer p.mutex.Unlock()
if proto != "tcp" && proto != "udp" {
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(),
}
p.ipMap[ipstr] = protomap
}
mapping := protomap[proto]
if port > 0 {
if _, ok := mapping.p[port]; !ok {
mapping.p[port] = struct{}{}
return port, nil
}
return 0, newErrPortAlreadyAllocated(ipstr, port)
}
port, err := mapping.findPort()
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 {
return &portMap{
p: map[int]struct{}{},
begin: p.Begin,
end: p.End,
last: p.End,
}
}
// ReleaseAll releases all ports for all ips.
func (p *PortAllocator) ReleaseAll() error {
p.mutex.Lock()
p.ipMap = ipMapping{}
p.mutex.Unlock()
return nil
}
func (pm *portMap) findPort() (int, error) {
port := pm.last
for i := 0; i <= pm.end-pm.begin; i++ {
port++
if port > pm.end {
port = pm.begin
}
if _, ok := pm.p[port]; !ok {
pm.p[port] = struct{}{}
pm.last = port
return port, nil
}
}
return 0, ErrAllPortsAllocated
}

View File

@@ -0,0 +1,254 @@
package portallocator
import (
"net"
"testing"
_ "github.com/docker/libnetwork/netutils"
)
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)
}
}
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)
}
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 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)
}
}

View File

@@ -0,0 +1,207 @@
package portmapper
import (
"errors"
"fmt"
"net"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/portallocator"
)
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")
)
// PortMapper manages the network address translation
type PortMapper struct {
chain *iptables.Chain
// udp:ip:port
currentMappings map[string]*mapping
lock sync.Mutex
Allocator *portallocator.PortAllocator
}
// New returns a new instance of PortMapper
func New() *PortMapper {
return NewWithPortAllocator(portallocator.Get())
}
// NewWithPortAllocator returns a new instance of PortMapper which will use the specified PortAllocator
func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper {
return &PortMapper{
currentMappings: make(map[string]*mapping),
Allocator: allocator,
}
}
// SetIptablesChain sets the specified chain into portmapper
func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) {
pm.chain = c
}
// 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) {
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.RequestPort(hostIP, proto, hostPort); err != nil {
return nil, err
}
m = &mapping{
proto: proto,
host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
container: container,
}
if useProxy {
m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
}
case *net.UDPAddr:
proto = "udp"
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
return nil, err
}
m = &mapping{
proto: proto,
host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
container: container,
}
if useProxy {
m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
}
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 err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
return nil, err
}
cleanup := func() error {
// need to undo the iptables rules before we return
if m.userlandProxy != nil {
m.userlandProxy.Stop()
}
pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
return err
}
return nil
}
if m.userlandProxy != 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.forward(iptables.Delete, 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)
}
return nil
}
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")
}
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
}
return nil, 0
}
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)
}

View File

@@ -0,0 +1,227 @@
package portmapper
import (
"net"
"os"
"testing"
"time"
"github.com/docker/docker/pkg/reexec"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/netutils"
)
func TestMain(m *testing.M) {
if reexec.Init() {
return
}
os.Exit(m.Run())
}
func TestSetIptablesChain(t *testing.T) {
pm := New()
c := &iptables.Chain{
Name: "TEST",
Bridge: "192.168.1.1",
}
if pm.chain != nil {
t.Fatal("chain should be nil at init")
}
pm.SetIptablesChain(c)
if pm.chain == nil {
t.Fatal("chain should not be nil after set")
}
}
func TestMapTCPPorts(t *testing.T) {
defer netutils.SetupTestNetNS(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) {
defer netutils.SetupTestNetNS(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) {
newProxy = newMockProxyCommand
defer func() {
newProxy = newProxyCommand
}()
defer netutils.SetupTestNetNS(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
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 TestExecProxy(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
args := []string{
userlandProxyCommandName,
"-proto", "tcp",
"-host-ip", "0.0.0.0",
"-host-port", "9999",
"-container-ip", "172.168.1.1",
"-container-port", "8888",
}
os.Args = args
doneChan := make(chan bool)
go func() {
execProxy()
doneChan <- true
}()
select {
case <-doneChan:
t.Fatal("execProxy is not supposed to exit")
case <-time.After(3 * time.Second):
return
}
}

View File

@@ -0,0 +1,18 @@
package portmapper
import "net"
func newMockProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) userlandProxy {
return &mockProxyCommand{}
}
type mockProxyCommand struct {
}
func (p *mockProxyCommand) Start() error {
return nil
}
func (p *mockProxyCommand) Stop() error {
return nil
}

View File

@@ -0,0 +1,161 @@
package portmapper
import (
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/docker/docker/pkg/proxy"
"github.com/docker/docker/pkg/reexec"
)
const userlandProxyCommandName = "docker-proxy"
func init() {
reexec.Register(userlandProxyCommandName, execProxy)
}
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
}
// execProxy is the reexec function that is registered to start the userland proxies
func execProxy() {
f := os.NewFile(3, "signal-parent")
host, container := parseHostContainerAddrs()
p, err := proxy.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 or UDP
// 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}
default:
log.Fatalf("unsupported protocol %s", *proto)
}
return host, container
}
func handleStopSignals(p proxy.Proxy) {
s := make(chan os.Signal, 10)
signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGSTOP)
for _ = range s {
p.Close()
os.Exit(0)
}
}
func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) userlandProxy {
args := []string{
userlandProxyCommandName,
"-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: reexec.Self(),
Args: args,
SysProcAttr: &syscall.SysProcAttr{
Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies
},
},
}
}
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
}

View File

@@ -0,0 +1 @@
Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf

View File

@@ -0,0 +1,17 @@
package dns
import (
"regexp"
)
// IPLocalhost is a regex patter for localhost IP address range.
const IPLocalhost = `((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))`
var localhostIPRegexp = regexp.MustCompile(IPLocalhost)
// 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)
}

View File

@@ -0,0 +1,187 @@
// 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/Sirupsen/logrus"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/libnetwork/resolvconf/dns"
)
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})`
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*$`)
searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
)
var lastModified struct {
sync.Mutex
sha256 string
contents []byte
}
// Get returns the contents of /etc/resolv.conf
func Get() ([]byte, error) {
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return nil, err
}
return resolv, 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() ([]byte, string, 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 resolv, 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() ([]byte, string) {
lastModified.Lock()
defer lastModified.Unlock()
return lastModified.contents, 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
//
// It returns a boolean to notify the caller if changes were made at all
func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) {
changed := false
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)) == 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"))...)
}
if !bytes.Equal(resolvConf, cleanedResolvConf) {
changed = true
}
return cleanedResolvConf, changed
}
// 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) []string {
nameservers := []string{}
for _, line := range getLines(resolvConf, []byte("#")) {
var ns = nsRegexp.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) {
nameservers = append(nameservers, nameserver+"/32")
}
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
}
// Build writes a configuration file to path containing a "nameserver" entry
// for every element in dns, and a "search" entry for every element in
// dnsSearch.
func Build(path string, dns, dnsSearch []string) error {
content := bytes.NewBuffer(nil)
for _, dns := range dns {
if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
return err
}
}
if len(dnsSearch) > 0 {
if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
return err
}
}
}
return ioutil.WriteFile(path, content.Bytes(), 0644)
}

View File

@@ -0,0 +1,240 @@
package resolvconf
import (
"bytes"
"io/ioutil"
"os"
"testing"
_ "github.com/docker/libnetwork/netutils"
)
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) != string(resolvConfSystem) {
t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.")
}
}
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))
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 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"})
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\nsearch search1\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{"."})
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\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) {
t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
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) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
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) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
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) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
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) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
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) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
// 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) {
t.Fatalf("Failed Localhost+IPv6 off: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
// 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) {
t.Fatalf("Failed Localhost+IPv6 on: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
// 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) {
t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
// 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) {
t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
}

View File

@@ -0,0 +1,81 @@
package sandbox
import (
"fmt"
"net"
"os"
"runtime"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)
func configureInterface(iface netlink.Link, settings *Interface) error {
ifaceName := iface.Attrs().Name
ifaceConfigurators := []struct {
Fn func(netlink.Link, *Interface) error
ErrMessage string
}{
{setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, settings.DstName)},
{setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, settings.Address)},
{setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %q", ifaceName, settings.AddressIPv6)},
}
for _, config := range ifaceConfigurators {
if err := config.Fn(iface, settings); err != nil {
return fmt.Errorf("%s: %v", config.ErrMessage, err)
}
}
return nil
}
func programGateway(path string, gw net.IP) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
origns, err := netns.Get()
if err != nil {
return err
}
defer origns.Close()
f, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("failed get network namespace %q: %v", path, err)
}
defer f.Close()
nsFD := f.Fd()
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
return err
}
defer netns.Set(origns)
gwRoutes, err := netlink.RouteGet(gw)
if err != nil {
return fmt.Errorf("route for the gateway could not be found: %v", err)
}
return netlink.RouteAdd(&netlink.Route{
Scope: netlink.SCOPE_UNIVERSE,
LinkIndex: gwRoutes[0].LinkIndex,
Gw: gw,
})
}
func setInterfaceIP(iface netlink.Link, settings *Interface) error {
ipAddr := &netlink.Addr{IPNet: settings.Address, Label: ""}
return netlink.AddrAdd(iface, ipAddr)
}
func setInterfaceIPv6(iface netlink.Link, settings *Interface) error {
if settings.AddressIPv6 == nil {
return nil
}
ipAddr := &netlink.Addr{IPNet: settings.AddressIPv6, Label: ""}
return netlink.AddrAdd(iface, ipAddr)
}
func setInterfaceName(iface netlink.Link, settings *Interface) error {
return netlink.LinkSetName(iface, settings.DstName)
}

View File

@@ -0,0 +1,263 @@
package sandbox
import (
"fmt"
"net"
"os"
"runtime"
"sync"
"syscall"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)
const prefix = "/var/run/docker/netns"
var once sync.Once
// 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
sinfo *Info
}
func createBasePath() {
err := os.MkdirAll(prefix, 0644)
if err != nil && !os.IsExist(err) {
panic("Could not create net namespace path directory")
}
}
// 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 prefix + "/" + 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 bool) (Sandbox, error) {
info, err := createNetworkNamespace(key, osCreate)
if err != nil {
return nil, err
}
return &networkNamespace{path: key, sinfo: info}, nil
}
func createNetworkNamespace(path string, osCreate bool) (*Info, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
origns, err := netns.Get()
if err != nil {
return nil, err
}
defer origns.Close()
if err := createNamespaceFile(path); err != nil {
return nil, err
}
if osCreate {
defer netns.Set(origns)
newns, err := netns.New()
if err != nil {
return nil, err
}
defer newns.Close()
if err := loopbackUp(); err != nil {
return nil, err
}
}
procNet := fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), syscall.Gettid())
if err := syscall.Mount(procNet, path, "bind", syscall.MS_BIND, ""); err != nil {
return nil, err
}
interfaces := []*Interface{}
info := &Info{Interfaces: interfaces}
return info, nil
}
func cleanupNamespaceFile(path string) {
if _, err := os.Stat(path); err == nil {
n := &networkNamespace{path: path}
n.Destroy()
}
}
func createNamespaceFile(path string) (err error) {
var f *os.File
once.Do(createBasePath)
// cleanup namespace file if it already exists because of a previous ungraceful exit.
cleanupNamespaceFile(path)
if f, err = os.Create(path); err == nil {
f.Close()
}
return err
}
func loopbackUp() error {
iface, err := netlink.LinkByName("lo")
if err != nil {
return err
}
return netlink.LinkSetUp(iface)
}
func (n *networkNamespace) RemoveInterface(i *Interface) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
origns, err := netns.Get()
if err != nil {
return err
}
defer origns.Close()
f, err := os.OpenFile(n.path, os.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("failed get network namespace %q: %v", n.path, err)
}
defer f.Close()
nsFD := f.Fd()
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
return err
}
defer netns.Set(origns)
// Find the network inteerface identified by the DstName attribute.
iface, err := netlink.LinkByName(i.DstName)
if err != nil {
return err
}
// Down the interface before configuring
if err := netlink.LinkSetDown(iface); err != nil {
return err
}
err = netlink.LinkSetName(iface, i.SrcName)
if err != nil {
fmt.Println("LinkSetName failed: ", err)
return err
}
// Move the network interface to caller namespace.
if err := netlink.LinkSetNsFd(iface, int(origns)); err != nil {
fmt.Println("LinkSetNsPid failed: ", err)
return err
}
return nil
}
func (n *networkNamespace) AddInterface(i *Interface) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
origns, err := netns.Get()
if err != nil {
return err
}
defer origns.Close()
f, err := os.OpenFile(n.path, os.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("failed get network namespace %q: %v", n.path, err)
}
defer f.Close()
// Find the network interface identified by the SrcName attribute.
iface, err := netlink.LinkByName(i.SrcName)
if err != nil {
return err
}
// Move the network interface to the destination namespace.
nsFD := f.Fd()
if err := netlink.LinkSetNsFd(iface, int(nsFD)); err != nil {
return err
}
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
return err
}
defer netns.Set(origns)
// Down the interface before configuring
if err := netlink.LinkSetDown(iface); err != nil {
return err
}
// Configure the interface now this is moved in the proper namespace.
if err := configureInterface(iface, i); err != nil {
return err
}
// Up the interface.
if err := netlink.LinkSetUp(iface); err != nil {
return err
}
n.sinfo.Interfaces = append(n.sinfo.Interfaces, i)
return nil
}
func (n *networkNamespace) SetGateway(gw net.IP) error {
if len(gw) == 0 {
return nil
}
err := programGateway(n.path, gw)
if err == nil {
n.sinfo.Gateway = gw
}
return err
}
func (n *networkNamespace) SetGatewayIPv6(gw net.IP) error {
if len(gw) == 0 {
return nil
}
err := programGateway(n.path, gw)
if err == nil {
n.sinfo.GatewayIPv6 = gw
}
return err
}
func (n *networkNamespace) Interfaces() []*Interface {
return n.sinfo.Interfaces
}
func (n *networkNamespace) Key() string {
return n.path
}
func (n *networkNamespace) Destroy() error {
// 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
}
return os.Remove(n.path)
}

View File

@@ -0,0 +1,155 @@
package sandbox
import (
"net"
"github.com/docker/libnetwork/netutils"
)
// 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
// The collection of Interface previously added with the AddInterface
// method. Note that this doesn't incude network interfaces added in any
// other way (such as the default loopback interface which are automatically
// created on creation of a sandbox).
Interfaces() []*Interface
// 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.
AddInterface(*Interface) error
// Remove an interface from the sandbox by renamin to original name
// and moving it out of the sandbox.
RemoveInterface(*Interface) 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
// Destroy the sandbox
Destroy() error
}
// Info represents all possible information that
// the driver wants to place in the sandbox which includes
// interfaces, routes and gateway
type Info struct {
Interfaces []*Interface
// IPv4 gateway for the sandbox.
Gateway net.IP
// IPv6 gateway for the sandbox.
GatewayIPv6 net.IP
// TODO: Add routes and 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 struct {
// 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.
DstName string
// IPv4 address for the interface.
Address *net.IPNet
// IPv6 address for the interface.
AddressIPv6 *net.IPNet
}
// GetCopy returns a copy of this Interface structure
func (i *Interface) GetCopy() *Interface {
return &Interface{
SrcName: i.SrcName,
DstName: i.DstName,
Address: netutils.GetIPNetCopy(i.Address),
AddressIPv6: netutils.GetIPNetCopy(i.AddressIPv6),
}
}
// Equal checks if this instance of Interface is equal to the passed one
func (i *Interface) Equal(o *Interface) bool {
if i == o {
return true
}
if o == nil {
return false
}
if i.SrcName != o.SrcName || i.DstName != o.DstName {
return false
}
if !netutils.CompareIPNet(i.Address, o.Address) {
return false
}
if !netutils.CompareIPNet(i.AddressIPv6, o.AddressIPv6) {
return false
}
return true
}
// GetCopy returns a copy of this SandboxInfo structure
func (s *Info) GetCopy() *Info {
list := make([]*Interface, len(s.Interfaces))
for i, iface := range s.Interfaces {
list[i] = iface.GetCopy()
}
gw := netutils.GetIPCopy(s.Gateway)
gw6 := netutils.GetIPCopy(s.GatewayIPv6)
return &Info{Interfaces: list, Gateway: gw, GatewayIPv6: gw6}
}
// Equal checks if this instance of SandboxInfo is equal to the passed one
func (s *Info) Equal(o *Info) bool {
if s == o {
return true
}
if o == nil {
return false
}
if !s.Gateway.Equal(o.Gateway) {
return false
}
if !s.GatewayIPv6.Equal(o.GatewayIPv6) {
return false
}
if (s.Interfaces == nil && o.Interfaces != nil) ||
(s.Interfaces != nil && o.Interfaces == nil) ||
(len(s.Interfaces) != len(o.Interfaces)) {
return false
}
// Note: At the moment, the two lists must be in the same order
for i := 0; i < len(s.Interfaces); i++ {
if !s.Interfaces[i].Equal(o.Interfaces[i]) {
return false
}
}
return true
}

View File

@@ -0,0 +1,105 @@
package sandbox
import (
"net"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)
const (
vethName1 = "wierdlongname1"
vethName2 = "wierdlongname2"
sboxIfaceName = "containername"
)
func newKey(t *testing.T) (string, error) {
name, err := netutils.GenerateRandomName("netns", 12)
if err != nil {
return "", err
}
name = filepath.Join("/tmp", name)
if _, err := os.Create(name); err != nil {
return "", err
}
return name, nil
}
func newInfo(t *testing.T) (*Info, error) {
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: vethName1, TxQLen: 0},
PeerName: vethName2}
err := netlink.LinkAdd(veth)
if err != nil {
return nil, err
}
// Store the sandbox side pipe interface
// This is needed for cleanup on DeleteEndpoint()
intf := &Interface{}
intf.SrcName = vethName2
intf.DstName = sboxIfaceName
ip4, addr, err := net.ParseCIDR("192.168.1.100/24")
if err != nil {
return nil, err
}
intf.Address = addr
intf.Address.IP = ip4
// ip6, addrv6, err := net.ParseCIDR("2001:DB8::ABCD/48")
ip6, addrv6, err := net.ParseCIDR("fe80::2/64")
if err != nil {
return nil, err
}
intf.AddressIPv6 = addrv6
intf.AddressIPv6.IP = ip6
sinfo := &Info{Interfaces: []*Interface{intf}}
sinfo.Gateway = net.ParseIP("192.168.1.1")
// sinfo.GatewayIPv6 = net.ParseIP("2001:DB8::1")
sinfo.GatewayIPv6 = net.ParseIP("fe80::1")
return sinfo, nil
}
func verifySandbox(t *testing.T, s Sandbox) {
_, ok := s.(*networkNamespace)
if !ok {
t.Fatalf("The sandox interface returned is not of type networkNamespace")
}
origns, err := netns.Get()
if err != nil {
t.Fatalf("Could not get the current netns: %v", err)
}
defer origns.Close()
f, err := os.OpenFile(s.Key(), os.O_RDONLY, 0)
if err != nil {
t.Fatalf("Failed top open network namespace path %q: %v", s.Key(), err)
}
defer f.Close()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
nsFD := f.Fd()
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
t.Fatalf("Setting to the namespace pointed to by the sandbox %s failed: %v", s.Key(), err)
}
defer netns.Set(origns)
_, err = netlink.LinkByName(sboxIfaceName)
if err != nil {
t.Fatalf("Could not find the interface %s inside the sandbox: %v", sboxIfaceName,
err)
}
}

View File

@@ -0,0 +1,149 @@
package sandbox
import (
"net"
"testing"
)
func TestSandboxCreate(t *testing.T) {
key, err := newKey(t)
if err != nil {
t.Fatalf("Failed to obtain a key: %v", err)
}
s, err := NewSandbox(key, true)
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)
}
info, err := newInfo(t)
if err != nil {
t.Fatalf("Failed to generate new sandbox info: %v", err)
}
for _, i := range info.Interfaces {
err = s.AddInterface(i)
if err != nil {
t.Fatalf("Failed to add interfaces to sandbox: %v", err)
}
}
err = s.SetGateway(info.Gateway)
if err != nil {
t.Fatalf("Failed to set gateway to sandbox: %v", err)
}
err = s.SetGatewayIPv6(info.GatewayIPv6)
if err != nil {
t.Fatalf("Failed to set ipv6 gateway to sandbox: %v", err)
}
verifySandbox(t, s)
s.Destroy()
}
func TestSandboxCreateTwice(t *testing.T) {
key, err := newKey(t)
if err != nil {
t.Fatalf("Failed to obtain a key: %v", err)
}
_, err = NewSandbox(key, true)
if err != nil {
t.Fatalf("Failed to create a new sandbox: %v", err)
}
// Create another sandbox with the same key to see if we handle it
// gracefully.
s, err := NewSandbox(key, true)
if err != nil {
t.Fatalf("Failed to create a new sandbox: %v", err)
}
s.Destroy()
}
func TestInterfaceEqual(t *testing.T) {
list := getInterfaceList()
if !list[0].Equal(list[0]) {
t.Fatalf("Interface.Equal() returned false negative")
}
if list[0].Equal(list[1]) {
t.Fatalf("Interface.Equal() returned false positive")
}
if list[0].Equal(list[1]) != list[1].Equal(list[0]) {
t.Fatalf("Interface.Equal() failed commutative check")
}
}
func TestSandboxInfoEqual(t *testing.T) {
si1 := &Info{Interfaces: getInterfaceList(), Gateway: net.ParseIP("192.168.1.254"), GatewayIPv6: net.ParseIP("2001:2345::abcd:8889")}
si2 := &Info{Interfaces: getInterfaceList(), Gateway: net.ParseIP("172.18.255.254"), GatewayIPv6: net.ParseIP("2001:2345::abcd:8888")}
if !si1.Equal(si1) {
t.Fatalf("Info.Equal() returned false negative")
}
if si1.Equal(si2) {
t.Fatalf("Info.Equal() returned false positive")
}
if si1.Equal(si2) != si2.Equal(si1) {
t.Fatalf("Info.Equal() failed commutative check")
}
}
func TestInterfaceCopy(t *testing.T) {
for _, iface := range getInterfaceList() {
cp := iface.GetCopy()
if !iface.Equal(cp) {
t.Fatalf("Failed to return a copy of Interface")
}
if iface == cp {
t.Fatalf("Failed to return a true copy of Interface")
}
}
}
func TestSandboxInfoCopy(t *testing.T) {
si := Info{Interfaces: getInterfaceList(), Gateway: net.ParseIP("192.168.1.254"), GatewayIPv6: net.ParseIP("2001:2345::abcd:8889")}
cp := si.GetCopy()
if !si.Equal(cp) {
t.Fatalf("Failed to return a copy of Info")
}
if &si == cp {
t.Fatalf("Failed to return a true copy of Info")
}
}
func getInterfaceList() []*Interface {
_, netv4a, _ := net.ParseCIDR("192.168.30.1/24")
_, netv4b, _ := net.ParseCIDR("172.18.255.2/23")
_, netv6a, _ := net.ParseCIDR("2001:2345::abcd:8888/80")
_, netv6b, _ := net.ParseCIDR("2001:2345::abcd:8889/80")
return []*Interface{
&Interface{
SrcName: "veth1234567",
DstName: "eth0",
Address: netv4a,
AddressIPv6: netv6a,
},
&Interface{
SrcName: "veth7654321",
DstName: "eth1",
Address: netv4b,
AddressIPv6: netv6b,
},
}
}

View File

@@ -0,0 +1,15 @@
// +build !linux
package sandbox
import "errors"
var (
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) (Sandbox, error) {
return nil, ErrNotImplemented
}

View File

@@ -0,0 +1,20 @@
// +build !linux
package sandbox
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
}

View File

@@ -0,0 +1,34 @@
package libnetwork
import (
"fmt"
"runtime"
"syscall"
)
// Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092
//
// We need different setns values for the different platforms and arch
// We are declaring the macro here because the SETNS syscall does not exist in th stdlib
var setNsMap = map[string]uintptr{
"linux/386": 346,
"linux/amd64": 308,
"linux/arm": 374,
"linux/ppc64": 350,
"linux/ppc64le": 350,
"linux/s390x": 339,
}
func setns(fd uintptr, flags uintptr) error {
ns, exists := setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)]
if !exists {
return fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH)
}
_, _, err := syscall.RawSyscall(ns, fd, flags, 0)
if err != 0 {
return err
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More