Merge remote-tracking branch 'origin/master' into latest_release_doc_updates

This commit is contained in:
Bryan Boreham committed 2015-12-17 11:48:23 +00:00
commit 37ae169323
80 files changed
+2607 -730

No files matched your search

+5
View File
@@ -40,25 +40,30 @@ prog/weaveproxy/weaveproxy
prog/sigproxy/sigproxy
prog/weavewait/weavewait
prog/weavewait/weavewait_noop
prog/weavewait/weavewait_nomcast
prog/netcheck/netcheck
prog/docker_tls_args/docker_tls_args
prog/plugin/plugin
testing/cover/cover
testing/runner/runner
releases
.weaver.uptodate
.weavedns.uptodate
.weaveexec.uptodate
.dockerplugin.uptodate
weave.tar.gz
prog/weaveexec/weave
prog/weaveexec/weavehosts
prog/weaveexec/weaveproxy
prog/weaveexec/weavewait
prog/weaveexec/weavewait_noop
prog/weaveexec/weavewait_nomcast
prog/weaveexec/sigproxy
prog/weaveexec/netcheck
prog/weaveexec/docker_tls_args
prog/weaveexec/docker*.tgz
Vagrantfile.local
test/tls/tls
test/tls/*.pem
test/coverage
test/coverage.*
+27
View File
@@ -1,3 +1,30 @@
## Release 1.4.0
**Highlights**
* The Docker Network plugin can now operate without a cluster store, so it is now run by default.
* You can now use the fast datapath over trusted links and Weave encryption over untrusted links, in the same network.
Plus many bug fixes and minor enhancements. More details below and in
the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.4.0).
## Release 1.3.1
**Highlights**
* The minimum Docker version has been increased to 1.6 due to the
[upcoming deprecation of Dockerhub access](https://blog.docker.com/2015/10/docker-hub-deprecation-1-5/)
for old clients. From December 7th onwards previous versions of the
`weave` script will fail to pull down images from the hub; if you
are unable to upgrade to 1.3.1 immediately you can work around this
by running `weave --local setup` in conjunction with a compatible
Docker client installation
* Docker networking plugin now works with older kernels and allows you
to configure the MTU
More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.3.1).
## Release 1.3.0
**Highlights**
+36 -17
View File
@@ -14,25 +14,30 @@ WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
SIGPROXY_EXE=prog/sigproxy/sigproxy
WEAVEWAIT_EXE=prog/weavewait/weavewait
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
NETCHECK_EXE=prog/netcheck/netcheck
DOCKERTLSARGS_EXE=prog/docker_tls_args/docker_tls_args
DOCKERPLUGIN_EXE=prog/plugin/plugin
RUNNER_EXE=tools/runner/runner
TEST_TLS_EXE=test/tls/tls
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE)
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE)
WEAVER_UPTODATE=.weaver.uptodate
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
DOCKERPLUGIN_UPTODATE=.dockerplugin.uptodate
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE)
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(DOCKERPLUGIN_UPTODATE)
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
DOCKERPLUGIN_IMAGE=$(DOCKERHUB_USER)/plugin
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE)
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(DOCKERPLUGIN_IMAGE)
WEAVE_EXPORT=weave.tar.gz
WEAVEEXEC_DOCKER_VERSION=1.3.1
WEAVEEXEC_DOCKER_VERSION=1.6.2
DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
@@ -47,7 +52,7 @@ BUILD_FLAGS=-ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)"
PACKAGE_BASE=$(shell go list -e ./)
all: $(WEAVE_EXPORT) $(RUNNER_EXE)
all: $(WEAVE_EXPORT) $(RUNNER_EXE) $(TEST_TLS_EXE)
travis: $(EXES)
@@ -74,38 +79,52 @@ $(WEAVER_EXE): router/*.go mesh/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/main.go
$(NETCHECK_EXE): prog/netcheck/netcheck.go
# Sigproxy and weavewait need separate rules as they fail the netgo check in
# These next programs need separate rules as they fail the netgo check in
# the main build stanza due to not importing net package
$(SIGPROXY_EXE): prog/sigproxy/main.go
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
$(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go
$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/net/*.go plugin/ipam/*.go plugin/skel/*.go api/*.go common/docker/*.go
$(TEST_TLS_EXE): test/tls/*.go
$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE):
$(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE):
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -o $@ ./$(@D)
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -tags "netgo iface mcast" -o $@ ./$(@D)
$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -tags "netgo iface" -o $@ ./$(@D)
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -tags noop -o $@ ./$(@D)
go build $(BUILD_FLAGS) -o $@ ./$(@D)
$(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
$(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVER_IMAGE) prog/weaver
touch $@
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE)
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE)
cp weave prog/weaveexec/weave
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
cp $(NETCHECK_EXE) prog/weaveexec/netcheck
cp $(DOCKERTLSARGS_EXE) prog/weaveexec/docker_tls_args
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
touch $@
$(DOCKERPLUGIN_UPTODATE): prog/plugin/Dockerfile $(DOCKERPLUGIN_EXE)
$(SUDO) docker build -t $(DOCKERPLUGIN_IMAGE) prog/plugin
touch $@
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
$(SUDO) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
$(DOCKER_DISTRIB):
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
@@ -123,16 +142,16 @@ $(RUNNER_EXE): tools/.git
make -C tools/runner
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
$(SUDO) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
$(SUDO) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
ifneq ($(UPDATE_LATEST),false)
$(SUDO) docker push $(DOCKERHUB_USER)/$*:latest
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
endif
publish: $(PUBLISH)
clean-bin:
-$(SUDO) docker rmi $(IMAGES)
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
go clean -r $(addprefix ./,$(dir $(EXES)))
rm -f $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT)
+1 -1
View File
@@ -41,7 +41,7 @@ capabilities, so these can continue to be used by containers.
## Installation
Ensure you are running Linux (kernel 3.8 or later) and have Docker
(version 1.3.1 or later) installed. Then install weave with
(version 1.6.0 or later) installed. Then install weave with
sudo curl -L git.io/weave -o /usr/local/bin/weave
sudo chmod a+x /usr/local/bin/weave
+18
View File
@@ -0,0 +1,18 @@
package api
import (
"fmt"
"net/url"
)
func (client *Client) RegisterWithDNS(ID string, fqdn string, ip string) error {
data := url.Values{}
data.Add("fqdn", fqdn)
_, err := client.httpVerb("PUT", fmt.Sprintf("/name/%s/%s", ID, ip), data)
return err
}
func (client *Client) DeregisterWithDNS(ID string, ip string) error {
_, err := client.httpVerb("DELETE", fmt.Sprintf("/name/%s/%s", ID, ip), nil)
return err
}
+49
View File
@@ -0,0 +1,49 @@
package api
import (
"fmt"
"net"
)
func (client *Client) ipamOp(ID string, op string) (*net.IPNet, error) {
ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), nil)
if err != nil {
return nil, err
}
return parseIP(ip)
}
// returns an IP for the ID given, allocating a fresh one if necessary
func (client *Client) AllocateIP(ID string) (*net.IPNet, error) {
return client.ipamOp(ID, "POST")
}
// returns an IP for the ID given, or nil if one has not been
// allocated
func (client *Client) LookupIP(ID string) (*net.IPNet, error) {
return client.ipamOp(ID, "GET")
}
// release an IP which is no longer needed
func (client *Client) ReleaseIP(ID string) error {
_, err := client.ipamOp(ID, "DELETE")
return err
}
func (client *Client) DefaultSubnet() (*net.IPNet, error) {
cidr, err := client.httpVerb("GET", fmt.Sprintf("/ipinfo/defaultsubnet"), nil)
if err != nil {
return nil, err
}
_, ipnet, err := net.ParseCIDR(cidr)
return ipnet, err
}
func parseIP(body string) (*net.IPNet, error) {
ip, ipnet, err := net.ParseCIDR(string(body))
if err != nil {
return nil, err
}
ipnet.IP = ip
return ipnet, nil
}
+73
View File
@@ -0,0 +1,73 @@
package api
import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
. "github.com/weaveworks/weave/common"
)
const (
WeaveHttpPort = 6784
)
type Client struct {
baseUrl string
resolve func() (string, error)
}
func (client *Client) httpVerb(verb string, url string, values url.Values) (string, error) {
baseUrl := client.baseUrl
if client.resolve != nil {
addr, err := client.resolve()
if err != nil {
return "", err
}
baseUrl = fmt.Sprintf("http://%s:%d", addr, WeaveHttpPort)
}
url = baseUrl + url
Log.Debugf("weave %s to %s with %v", verb, url, values)
var body io.Reader
if values != nil {
body = strings.NewReader(values.Encode())
}
req, err := http.NewRequest(verb, url, body)
if err != nil {
return "", err
}
if values != nil {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if resp, err := http.DefaultClient.Do(req); err != nil {
return "", err
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent {
return string(body), nil
} else {
return "", errors.New(resp.Status + ": " + string(body))
}
}
}
func NewClient(addr string) *Client {
return &Client{baseUrl: fmt.Sprintf("http://%s:%d", addr, WeaveHttpPort)}
}
func NewClientWithResolver(resolver func() (string, error)) *Client {
return &Client{resolve: resolver}
}
func (client *Client) Connect(remote string) error {
_, err := client.httpVerb("POST", "/connect", url.Values{"peer": {remote}})
return err
}
+3
View File
@@ -29,6 +29,7 @@ random_mac() {
case "$1" in
launch)
shift 1
echo $(date -R) launching
for i in $(seq $START $FINISH); do
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch-router --iface '' --name $(random_mac) --nickname weave$i --port $PORT --no-dns "$@"
done
@@ -36,6 +37,7 @@ case "$1" in
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1)))
PREV="${status#true }"
fi
echo $(date -R) connecting
for i in $(seq $START $FINISH); do
if [ -n "$PREV" ] ; then
echo connecting weave$i to $PREV
@@ -44,6 +46,7 @@ case "$1" in
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$i)
PREV="${status#true }"
done
echo $(date -R) done
;;
stop)
for i in $(seq $START $FINISH); do
+1 -1
View File
@@ -3,7 +3,7 @@ FROM ubuntu
RUN apt-get -y update && apt-get -y install --no-install-recommends ca-certificates apt-transport-https
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
RUN echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.3.1 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.6.2 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests
# When doing a build in a container, "apt-get update" happens twice,
# which can be a very significant overhead for incremental builds.
+58 -8
View File
@@ -1,7 +1,12 @@
package docker
import (
"github.com/fsouza/go-dockerclient"
"errors"
"fmt"
"strings"
docker "github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
@@ -17,32 +22,52 @@ type Client struct {
// NewClient creates a new Docker client and checks we can talk to Docker
func NewClient(apiPath string) (*Client, error) {
if apiPath != "" && !strings.Contains(apiPath, "://") {
apiPath = "tcp://" + apiPath
}
dc, err := docker.NewClient(apiPath)
if err != nil {
return nil, err
}
client := &Client{dc}
return client, client.checkWorking(apiPath)
return client, client.checkWorking()
}
func NewVersionedClient(apiPath string, apiVersionString string) (*Client, error) {
if !strings.Contains(apiPath, "://") {
apiPath = "tcp://" + apiPath
}
dc, err := docker.NewVersionedClient(apiPath, apiVersionString)
if err != nil {
return nil, err
}
client := &Client{dc}
return client, client.checkWorking(apiPath)
return client, client.checkWorking()
}
func (c *Client) checkWorking(apiPath string) error {
env, err := c.Version()
func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) {
dc, err := docker.NewVersionedClientFromEnv(apiVersionString)
if err != nil {
return err
return nil, err
}
client := &Client{dc}
return client, client.checkWorking()
}
func (c *Client) checkWorking() error {
_, err := c.Version()
return err
}
func (c *Client) Info() string {
if env, err := c.Version(); err != nil {
return fmt.Sprintf("Docker API error: %s", err)
} else {
return fmt.Sprintf("Docker API on %s: %v", c.Endpoint(), env)
}
Log.Infof("[docker] Using Docker API on %s: %v", apiPath, env)
return nil
}
// AddObserver adds an observer for docker events
@@ -80,3 +105,28 @@ func (c *Client) IsContainerNotRunning(idStr string) bool {
Log.Errorf("[docker] Could not check container status: %s", err)
return false
}
// This is intended to find an IP address that we can reach the container on;
// if it is on the Docker bridge network then that address; if on the host network
// then localhost
func (c *Client) GetContainerIP(nameOrID string) (string, error) {
Log.Debugf("Getting IP for container %s", nameOrID)
info, err := c.InspectContainer(nameOrID)
if err != nil {
return "", err
}
if info.NetworkSettings.Networks != nil {
Log.Debugln("Networks: ", info.NetworkSettings.Networks)
if bridgeNetwork, ok := info.NetworkSettings.Networks["bridge"]; ok {
return bridgeNetwork.IPAddress, nil
} else if _, ok := info.NetworkSettings.Networks["host"]; ok {
return "127.0.0.1", nil
}
} else if info.HostConfig.NetworkMode == "host" {
return "127.0.0.1", nil
}
if info.NetworkSettings.IPAddress == "" {
return "", errors.New("No IP address found for container " + nameOrID)
}
return info.NetworkSettings.IPAddress, nil
}
+170
View File
@@ -0,0 +1,170 @@
# Overlay Method Selection
Weave automatically selects the best working overlay implementation
when connecting to a new peer. It does this by initiating both
forwarders in parallel, eventually settling on the most preferred
established forwarder at which point the other forwarder is shut down,
assuming it had not failed already. In the event that the remaining
forwarder fails at a later time (e.g. because of a heartbeat timeout)
the control plane TCP connection will recycle allowing the selection
algorithm to run again. By default fast datapath is preferred over
Sleeve.
# Local Bridging
Unlike the weave bridge netdev used by Sleeve, the OVS datapath has no
inherent bridging capability. Consequently fastdp implements an
ethernet bridge in addition to the vxlan overlay, maintaining its own
forwarding database by learning MAC addresses and dispatching
broadcast, unicast & multicast traffic accordingly.
# Short Peer IDs
The router relies on having access to the source and destination peer
when deciding how to forward packets. When the Sleeve overlay is in
use this information is conveyed directly within the encapsulation by
including the names of the peers in question, a solution not
accomodated directly by the vxlan wire format. Fortunately vxlan does
have a twenty four bit segment ID field in the header we can use to
encode this data - the challenge is to identify peers uniquely with a
twelve bit identifier instead of the seventeen bytes used by Sleeve.
In practice this is achieved by peers adopting a random 'short ID' and
resolving ownership collisions of same via the existing gossip
mechanism.
# Heartbeats via vxlan
Heartbeats are implemented by sending an encapsulated ethernet frame
with source and destination MAC addresses of `00:00:00:00:00:00` and a
payload consisting of the connection UID and total ethernet frame
length. The vxlan vport miss handler on the receiving side detects the
all-zero addresses and acknowledges the heartbeat via the TCP control
channel after validating the connection UID and frame length against
their expected values.
# PMTU Discovery
The length of the heartbeat ethernet frame is set to the MTU of the
datapath interface. In the event the vxlan packet is dropped or
truncated, the heartbeat will not be acknowledged; this lack of
acknowledgement will cause the peers to fall back to the Sleeve
overlay, which has a more sophisticated dynamic mechanism for coping
with low path MTUs.
To avoid triggering this fallback in typical deployments, the datapath
interface is statically configured with an MTU of 1410 bytes allowing
it to work with most underlay network provider MTUs, including GCE at
1460 bytes (the fifty byte difference accomodates the vxlan overhead).
This value can be overriden by setting `WEAVE_MTU` at launch if
necessary.
# Virtual Ports
There are three kinds of virtual port associated with the weave
datapath:
* `internal` - one of these is created automatically, named after the
datapath. It corresponds to the network interface of the same name
that appears on the host when the datapath is created; it is the
ingress/egress port for `weave expose`
* `netdev` - one for each application container, corresponding to the
host end of the veth pair
* `vxlan` - one for each UDP port on which we are listening for vxlan
encapsulated packets. Typically there is only one of these, but
there may be more in a network in which peers do not all use the same
port configuration
# Misses and Flow Creation
As mentioned above, the datapath has no inherent behaviour - any
ingressing packet which does not match a flow is passed to a userspace
miss handler. The miss handler is then responsible for a) instructing
the datapath to take actions for the packet that caused the miss (for
example by copying it to one or more ports) and optionally b)
installing flow rules which will allow the datapath to act on similar
packets in future without invoking the miss handler.
At a high level the fast datapath implementation can be viewed as a
set of miss handlers that determine what actions the OVS datapath
should take based on router topology information, together with some
additional machinery that manages the expiry of resulting flows when
that state changes.
When a miss handler is invoked, it has two pieces of context: a byte
array containing the packet that triggered the miss, and a set of
'flow keys' that have been extracted from the packet by the kernel
module. The following flow keys are of interest to weave:
* `InPort` - the identifier of the ingress virtual port
* `Ethernet` - the source and destination MAC addresses of the
ethernet frame
* `Tunnel` - tunnel identifier (see section on short peer IDs above),
and source/dest IPv4 addresses. Only present for ingress via a vxlan
vport
Crucially, the router must use this information alone to determine
which actions to take - this allows the specification of a flow which
matches these keys and instructs the kernel to take action
automatically in future without further userspace involvement.
Two actions are of interest:
* `Output` - output the packet to the specified vport
* `SetTunnel` - update the effective vxlan tunnel parameters. Only
required prior to output from a vxlan vport
It is possible to have multiple actions in a flow, so the router can
for example create a single rule that copies broadcast traffic to all
'local' vports (save the ingress vport obviously) as well as to vxlan
vports for onward peers as dictated by the routing topology.
Every flow specified by the router has the following characteristics:
* An `InPort` key matching the ingress vport
* A `Tunnel` key if the ingress vport is of type vxlan
* An `Ethernet` key matching the source and destination MAC
* A list of `Output` (for internal and netdev vports) and
`SetTunnel`+`Output` (for vxlan vports) actions
Under certain circumstances the miss handler will instruct the
datapath to execute a set of actions for a packet without creating a
corresponding flow:
* When broadcasting packets to discover the path to unknown
destination MACs. This condition is transient in the overwhelming
majority of cases and so the benefit of creating a flow is
outweighed by the need to invalidate it once the MAC is learned
* When the packet also needs to be forwarded to one or more peers via
Sleeve. In this case the router needs to handle all subsequent
matching packets as there is no OVS flow action to do it without
userspace involvement
# Flow Invalidation
Once a flow has been created for a particular combination of keys, the
miss handler will never again be invoked for matching packets. It is
therefore extremely important that we detect events which invalidate
existing flow actions:
* Addition of netdev vports to the datapath
* Route invalidation (topology change)
* Short peer ID collision
The response in each case is the same - delete all flows from the
datapath, allowing them to be recreated taking into account the
updated state.
In addition to these event based invalidations there is an expiry
process that executes every five minutes. This process enumerates all
flows in the datapath, removing any which have not been used since the
last check; this cleans up:
* Flows referring to netdev vports which have been removed
* Flows created by forwarders which have been stopped
* Flows related to MAC addresses which have not communicated recently
Flows keyed on tunnel IPv4 address do not need to be cleared when a
peer appears to change IP address due to NAT; this will cause a miss
resulting in a new flow, and the old flow will expire naturally via
the timer mechanism.
+5
View File
@@ -38,6 +38,11 @@ func (g *allocate) Try(alloc *Allocator) bool {
alloc.establishRing()
if ok, addr := alloc.space.Allocate(g.r); ok {
// If caller hasn't supplied a unique ID, file it under the IP address
// which lets the caller then release the address using DELETE /ip/address
if g.ident == "_" {
g.ident = addr.String()
}
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
alloc.addOwned(g.ident, addr)
g.resultChan <- allocateResult{addr, nil}
+2 -2
View File
@@ -478,8 +478,8 @@ type ipamGossipData struct {
alloc *Allocator
}
func (d *ipamGossipData) Merge(other mesh.GossipData) {
// no-op
func (d *ipamGossipData) Merge(other mesh.GossipData) mesh.GossipData {
return d // no-op
}
func (d *ipamGossipData) Encode() [][]byte {
+4
View File
@@ -57,6 +57,10 @@ func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.Resp
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
router.Methods("GET").Path("/ipinfo/defaultsubnet").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s", defaultSubnet)
})
router.Methods("PUT").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
ident := vars["id"]
-7
View File
@@ -14,7 +14,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/net/address"
wt "github.com/weaveworks/weave/testing"
)
func HTTPPost(t *testing.T, url string) string {
@@ -145,12 +144,6 @@ func TestBadHttp(t *testing.T) {
}
func TestHTTPCancel(t *testing.T) {
wt.RunWithTimeout(t, 2*time.Second, func() {
impTestHTTPCancel(t)
})
}
func impTestHTTPCancel(t *testing.T) {
var (
containerID = "deadbeef"
testCIDR1 = "10.0.3.0/29"
+19
View File
@@ -316,6 +316,25 @@ func (r *Ring) OwnedRanges() (result []address.Range) {
return r.splitRangesOverZero(result)
}
// For printing status
type RangeInfo struct {
Peer mesh.PeerName
address.Range
Version uint32
}
func (r *Ring) AllRangeInfo() (result []RangeInfo) {
for i, entry := range r.Entries {
nextEntry := r.Entries.entry(i + 1)
ranges := []address.Range{{Start: entry.Token, End: nextEntry.Token}}
ranges = r.splitRangesOverZero(ranges)
for _, r := range ranges {
result = append(result, RangeInfo{entry.Peer, r, entry.Version})
}
}
return
}
// ClaimForPeers claims the entire ring for the array of peers passed
// in. Only works for empty rings.
func (r *Ring) ClaimForPeers(peers []mesh.PeerName) {
+19 -6
View File
@@ -1,6 +1,7 @@
package ipam
import (
"fmt"
"github.com/weaveworks/weave/ipam/paxos"
"github.com/weaveworks/weave/net/address"
)
@@ -8,6 +9,7 @@ import (
type Status struct {
Paxos *paxos.Status
Range string
RangeNumIPs int
DefaultSubnet string
Entries []EntryStatus
PendingClaims []ClaimStatus
@@ -15,9 +17,12 @@ type Status struct {
}
type EntryStatus struct {
Token string
Peer string
Version uint32
Token string
Size uint32
Peer string
Nickname string
IsKnownPeer bool
Version uint32
}
type ClaimStatus struct {
@@ -40,6 +45,7 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
resultChan <- &Status{
paxosStatus,
allocator.universe.String(),
int(allocator.universe.Size()),
defaultSubnet.String(),
newEntryStatusSlice(allocator),
newClaimStatusSlice(allocator),
@@ -56,8 +62,15 @@ func newEntryStatusSlice(allocator *Allocator) []EntryStatus {
return slice
}
for _, entry := range allocator.ring.Entries {
slice = append(slice, EntryStatus{entry.Token.String(), entry.Peer.String(), entry.Version})
for _, r := range allocator.ring.AllRangeInfo() {
slice = append(slice, EntryStatus{
Token: r.Start.String(),
Size: uint32(r.Size()),
Peer: r.Peer.String(),
Nickname: allocator.nicknames[r.Peer],
IsKnownPeer: allocator.isKnownPeer(r.Peer),
Version: r.Version,
})
}
return slice
@@ -76,7 +89,7 @@ func newAllocateIdentSlice(allocator *Allocator) []string {
var slice []string
for _, op := range allocator.pendingAllocates {
allocate := op.(*allocate)
slice = append(slice, allocate.ident)
slice = append(slice, fmt.Sprintf("%s %s", allocate.ident, allocate.r.String()))
}
return slice
}
+50 -27
View File
@@ -40,16 +40,19 @@ type RemoteConnection struct {
type LocalConnection struct {
sync.RWMutex
RemoteConnection
TCPConn *net.TCPConn
version byte
tcpSender TCPSender
SessionKey *[32]byte
heartbeatTCP *time.Ticker
Router *Router
uid uint64
actionChan chan<- ConnectionAction
finished <-chan struct{} // closed to signal that actorLoop has finished
OverlayConn OverlayConnection
TCPConn *net.TCPConn
TrustRemote bool // is remote on a trusted subnet?
TrustedByRemote bool // does remote trust us?
version byte
tcpSender TCPSender
SessionKey *[32]byte
heartbeatTCP *time.Ticker
Router *Router
uid uint64
actionChan chan<- ConnectionAction
errorChan chan<- error
finished <-chan struct{} // closed to signal that actorLoop has finished
OverlayConn OverlayConnection
}
type ConnectionAction func() error
@@ -86,17 +89,19 @@ func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, ro
if connRemote.local != router.Ourself.Peer {
log.Fatal("Attempt to create local connection from a peer which is not ourself")
}
// NB, we're taking a copy of connRemote here.
actionChan := make(chan ConnectionAction, ChannelSize)
errorChan := make(chan error, 1)
finished := make(chan struct{})
conn := &LocalConnection{
RemoteConnection: *connRemote,
RemoteConnection: *connRemote, // NB, we're taking a copy of connRemote here.
Router: router,
TCPConn: tcpConn,
TrustRemote: router.Trusts(connRemote),
uid: randUint64(),
actionChan: actionChan,
errorChan: errorChan,
finished: finished}
go conn.run(actionChan, finished, acceptNewPeer)
go conn.run(actionChan, errorChan, finished, acceptNewPeer)
}
func (conn *LocalConnection) BreakTie(dupConn Connection) ConnectionTieBreak {
@@ -116,11 +121,6 @@ func (conn *LocalConnection) Established() bool {
return conn.established
}
// Send directly, not via the Actor. If it goes via the Actor we can
// get a deadlock where LocalConnection is blocked talking to
// LocalPeer and LocalPeer is blocked trying send a ProtocolMsg via
// LocalConnection, and the channels are full in both directions so
// nothing can proceed.
func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
if err := conn.sendProtocolMsg(m); err != nil {
conn.Shutdown(err)
@@ -134,15 +134,17 @@ func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
// do not need locks for reading, and only need write locks for fields
// read by other processes.
// Async
// Non-blocking.
func (conn *LocalConnection) Shutdown(err error) {
// err should always be a real error, even if only io.EOF
if err == nil {
panic("nil error")
}
// Run on its own goroutine in case the channel is backed up
go func() { conn.sendAction(func() error { return err }) }()
select {
case conn.errorChan <- err:
default:
}
}
// Send an actor request to the actorLoop, but don't block if
@@ -157,7 +159,7 @@ func (conn *LocalConnection) sendAction(action ConnectionAction) {
// ACTOR server
func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished chan<- struct{}, acceptNewPeer bool) {
func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, errorChan <-chan error, finished chan<- struct{}, acceptNewPeer bool) {
var err error // important to use this var and not create another one with 'err :='
defer func() { conn.shutdown(err) }()
defer close(finished)
@@ -190,13 +192,19 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch
conn.Log("connection ready; using protocol version", conn.version)
// only use negotiated session key for untrusted connections
var sessionKey *[32]byte
if conn.Untrusted() {
sessionKey = conn.SessionKey
}
params := OverlayConnectionParams{
RemotePeer: conn.remote,
LocalAddr: conn.TCPConn.LocalAddr().(*net.TCPAddr),
RemoteAddr: conn.TCPConn.RemoteAddr().(*net.TCPAddr),
Outbound: conn.outbound,
ConnUID: conn.uid,
SessionKey: conn.SessionKey,
SessionKey: sessionKey,
SendControlMessage: conn.sendOverlayControlMessage,
Features: intro.Features,
}
@@ -243,7 +251,7 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch
// running AddConnection in a separate goroutine, at least not
// without some synchronisation. Which in turn requires the
// launching of the receiveTCP goroutine to precede actorLoop.
err = conn.actorLoop(actionChan)
err = conn.actorLoop(actionChan, errorChan)
}
func (conn *LocalConnection) makeFeatures() map[string]string {
@@ -254,6 +262,7 @@ func (conn *LocalConnection) makeFeatures() map[string]string {
"ShortID": fmt.Sprint(conn.local.ShortID),
"UID": fmt.Sprint(conn.local.UID),
"ConnID": fmt.Sprint(conn.uid),
"Trusted": fmt.Sprint(conn.TrustRemote),
}
conn.Router.Overlay.AddFeaturesTo(features)
return features
@@ -295,13 +304,21 @@ func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
var hasShortID bool
if shortIDStr, present := features["ShortID"]; present {
hasShortID = true
shortID, err = strconv.ParseUint(shortIDStr, 10,
PeerShortIDBits)
shortID, err = strconv.ParseUint(shortIDStr, 10, PeerShortIDBits)
if err != nil {
return nil, err
}
}
var trusted bool
if trustedStr, present := features["Trusted"]; present {
trusted, err = strconv.ParseBool(trustedStr)
if err != nil {
return nil, err
}
}
conn.TrustedByRemote = trusted
uid, err := ParsePeerUID(features.Get("UID"))
if err != nil {
return nil, err
@@ -335,7 +352,7 @@ func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) er
return nil
}
func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err error) {
func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction, errorChan <-chan error) (err error) {
fwdErrorChan := conn.OverlayConn.ErrorChannel()
fwdEstablishedChan := conn.OverlayConn.EstablishedChannel()
@@ -344,6 +361,8 @@ func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err
case action := <-actionChan:
err = action()
case err = <-errorChan:
case <-conn.heartbeatTCP.C:
err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat)
@@ -435,3 +454,7 @@ func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte)
func (conn *LocalConnection) extendReadDeadline() {
conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2))
}
func (conn *LocalConnection) Untrusted() bool {
return !conn.TrustRemote || !conn.TrustedByRemote
}
+11 -6
View File
@@ -81,7 +81,7 @@ func (cm *ConnectionMaker) InitiateConnections(peers []string, replace bool) []e
for peer, addr := range addrs {
cm.directPeers[peer] = addr
// curtail any existing reconnect interval
if target, found := cm.targets[addr.String()]; found {
if target, found := cm.targets[cm.completeAddr(*addr)]; found {
target.nextTryNow()
}
}
@@ -159,6 +159,13 @@ func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) {
}
}
func (cm *ConnectionMaker) completeAddr(addr net.TCPAddr) string {
if addr.Port == 0 {
addr.Port = cm.port
}
return addr.String()
}
func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
var (
validTarget = make(map[string]struct{})
@@ -181,18 +188,16 @@ func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
// Add direct targets that are not connected
for _, addr := range cm.directPeers {
completeAddr := *addr
attempt := true
if completeAddr.Port == 0 {
completeAddr.Port = cm.port
if addr.Port == 0 {
// If a peer was specified w/o a port, then we do not
// attempt to connect to it if we have any inbound
// connections from that IP.
if _, connected := ourInboundIPs[completeAddr.IP.String()]; connected {
if _, connected := ourInboundIPs[addr.IP.String()]; connected {
attempt = false
}
}
address := completeAddr.String()
address := cm.completeAddr(*addr)
directTarget[address] = void
if attempt {
addTarget(address)
+3 -4
View File
@@ -6,7 +6,7 @@ import (
type GossipData interface {
Encode() [][]byte
Merge(GossipData)
Merge(GossipData) GossipData
}
type Gossip interface {
@@ -79,8 +79,7 @@ func (sender *GossipSender) Send(data GossipData) {
// NB: this must not be invoked concurrently
select {
case pending := <-sender.cell:
pending.Merge(data)
sender.cell <- pending
sender.cell <- pending.Merge(data)
default:
sender.cell <- data
}
@@ -142,7 +141,7 @@ func (router *Router) SendAllGossip() {
func (router *Router) SendAllGossipDown(conn Connection) {
for channel := range router.gossipChannelSet() {
if gossip := channel.gossiper.Gossip(); gossip != nil {
channel.SendDown(conn, channel.gossiper.Gossip())
channel.SendDown(conn, gossip)
}
}
}
+66 -72
View File
@@ -97,78 +97,6 @@ func (c *GossipChannel) deliver(srcName PeerName, _ []byte, dec *gob.Decoder) er
return nil
}
func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
// do this outside the lock below so we avoid lock nesting
c.routes.EnsureRecalculated()
selectedConnections := make(ConnectionSet)
for name := range c.routes.RandomNeighbours(srcName) {
if conn, found := c.ourself.ConnectionTo(name); found {
selectedConnections[conn] = void
}
}
if len(selectedConnections) == 0 {
return
}
connections := c.ourself.Connections()
c.Lock()
defer c.Unlock()
c.gcSenders(connections)
for conn := range selectedConnections {
c.sendDown(conn, data)
}
}
func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
connections := c.ourself.Connections()
c.Lock()
defer c.Unlock()
c.gcSenders(connections)
c.sendDown(conn, data)
}
// GC - randomly (courtesy of go's map iterator) pick some existing
// senders and stop&remove them if the associated connection is no
// longer active. We stop as soon as we encounter a valid entry; the
// idea being that when there is little or no garbage then this
// executes close to O(1)[1], whereas when there is lots of garbage we
// remove it quickly.
//
// [1] TODO Unfortunately, due to the desire to avoid nested locks,
// instead of simply invoking LocalPeer.ConnectionTo(name), we pass in
// the result of LocalPeer.Connections(). That is O(n_our_connections)
// at best.
func (c *GossipChannel) gcSenders(connections ConnectionSet) {
for conn, sender := range c.senders {
if _, found := connections[conn]; !found {
delete(c.senders, conn)
sender.Stop()
} else {
break
}
}
}
// We have seen a couple of failures which suggest a >128GB slice was encountered.
// 100MB should be enough for anyone.
const maxFeasibleMessageLen = 100 * 1024 * 1024
func (c *GossipChannel) sendDown(conn Connection, data GossipData) {
sender, found := c.senders[conn]
if !found {
sender = NewGossipSender(func(pending GossipData) {
for _, msg := range pending.Encode() {
if len(msg) > maxFeasibleMessageLen {
panic(fmt.Sprintf("Gossip message too large: len=%d bytes; on channel '%s' from %+v", len(msg), c.name, pending))
}
protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, msg)}
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
}
})
c.senders[conn] = sender
}
sender.Send(data)
}
func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error {
return c.relayUnicast(dstPeerName, GobEncode(c.name, c.ourself.Name, dstPeerName, msg))
}
@@ -236,6 +164,72 @@ func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) {
}
}
func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
c.routes.EnsureRecalculated()
destinations := c.routes.RandomNeighbours(srcName)
if len(destinations) == 0 {
return
}
c.sendDown(c.ourself.ConnectionsTo(destinations), data)
}
func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
c.sendDown([]Connection{conn}, data)
}
func (c *GossipChannel) sendDown(selectedConnections []Connection, data GossipData) {
if len(selectedConnections) == 0 {
return
}
connections := c.ourself.Connections()
c.Lock()
defer c.Unlock()
// GC - randomly (courtesy of go's map iterator) pick some
// existing senders and stop&remove them if the associated
// connection is no longer active. We stop as soon as we
// encounter a valid entry; the idea being that when there is
// little or no garbage then this executes close to O(1)[1],
// whereas when there is lots of garbage we remove it quickly.
//
// [1] TODO Unfortunately, due to the desire to avoid nested
// locks, instead of simply invoking LocalPeer.ConnectionTo(name),
// we operate on LocalPeer.Connections(). That is
// O(n_our_connections) at best.
for conn, sender := range c.senders {
if _, found := connections[conn]; !found {
delete(c.senders, conn)
sender.Stop()
} else {
break
}
}
// start senders, if necessary, and send.
for _, conn := range selectedConnections {
sender, found := c.senders[conn]
if !found {
sender = c.makeSender(conn)
c.senders[conn] = sender
}
sender.Send(data)
}
}
// We have seen a couple of failures which suggest a >128GB slice was encountered.
// 100MB should be enough for anyone.
const maxFeasibleMessageLen = 100 * 1024 * 1024
func (c *GossipChannel) makeSender(conn Connection) *GossipSender {
return NewGossipSender(func(pending GossipData) {
for _, msg := range pending.Encode() {
if len(msg) > maxFeasibleMessageLen {
panic(fmt.Sprintf("Gossip message too large: len=%d bytes; on channel '%s' from %+v", len(msg), c.name, pending))
}
protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, msg)}
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
}
})
}
func (c *GossipChannel) log(args ...interface{}) {
log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...)
}
+50 -79
View File
@@ -4,18 +4,17 @@ import (
"fmt"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
wt "github.com/weaveworks/weave/testing"
)
// TODO test gossip unicast; atm we only test topology gossip and
// surrogates, neither of which employ unicast.
type mockChannelConnection struct {
type mockGossipConnection struct {
RemoteConnection
dest *Router
dest *Router
start chan struct{}
}
func NewTestRouter(name string) *Router {
@@ -25,12 +24,17 @@ func NewTestRouter(name string) *Router {
return router
}
func (conn *mockChannelConnection) SendProtocolMsg(protocolMsg ProtocolMsg) {
func (conn *mockGossipConnection) SendProtocolMsg(protocolMsg ProtocolMsg) {
<-conn.start
if err := conn.dest.handleGossip(protocolMsg.tag, protocolMsg.msg); err != nil {
panic(err)
}
}
func (conn *mockGossipConnection) Start() {
close(conn.start)
}
func sendPendingGossip(routers ...*Router) {
// Loop until all routers report they didn't send anything
for sentSomething := true; sentSomething; {
@@ -41,38 +45,32 @@ func sendPendingGossip(routers ...*Router) {
}
}
func (router *Router) AddTestChannelConnection(r *Router) {
fromPeer := NewPeerFrom(router.Ourself.Peer)
toPeer := NewPeerFrom(r.Ourself.Peer)
func AddTestGossipConnection(r1, r2 *Router) {
c1 := r1.NewTestGossipConnection(r2)
c2 := r2.NewTestGossipConnection(r1)
c1.Start()
c2.Start()
}
r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount
router.Peers.FetchWithDefault(toPeer) //
func (router *Router) NewTestGossipConnection(r *Router) *mockGossipConnection {
to := r.Ourself.Peer
toPeer := NewPeer(to.Name, to.NickName, to.UID, 0, to.ShortID)
toPeer = router.Peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
conn := &mockChannelConnection{RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r}
conn := &mockGossipConnection{
RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r, make(chan struct{})}
router.Ourself.handleAddConnection(conn)
router.Ourself.handleConnectionEstablished(conn)
return conn
}
func (router *Router) DeleteTestChannelConnection(r *Router) {
fromName := router.Ourself.Peer.Name
func (router *Router) DeleteTestGossipConnection(r *Router) {
toName := r.Ourself.Peer.Name
fromPeer := r.Peers.Fetch(fromName)
toPeer := router.Peers.Fetch(toName)
r.Peers.Dereference(fromPeer)
router.Peers.Dereference(toPeer)
conn, _ := router.Ourself.ConnectionTo(toName)
router.Peers.Dereference(conn.Remote())
router.Ourself.handleDeleteConnection(conn)
}
func TestGossipTopology(t *testing.T) {
wt.RunWithTimeout(t, 5*time.Second, func() {
implTestGossipTopology(t)
})
}
// Create a Peer representing the receiver router, with connections to
// the routers supplied as arguments, carrying across all UID and
// version information.
@@ -95,69 +93,46 @@ func checkTopology(t *testing.T, router *Router, wantedPeers ...*Peer) {
router.Peers.RUnlock()
}
func implTestGossipTopology(t *testing.T) {
func flushAndCheckTopology(t *testing.T, routers []*Router, wantedPeers ...*Peer) {
sendPendingGossip(routers...)
for _, r := range routers {
checkTopology(t, r, wantedPeers...)
}
}
func TestGossipTopology(t *testing.T) {
// Create some peers that will talk to each other
r1 := NewTestRouter("01:00:00:01:00:00")
r2 := NewTestRouter("02:00:00:02:00:00")
r3 := NewTestRouter("03:00:00:03:00:00")
routers := []*Router{r1, r2, r3}
// Check state when they have no connections
checkTopology(t, r1, r1.tp())
checkTopology(t, r2, r2.tp())
// Now try adding some connections
r1.AddTestChannelConnection(r2)
sendPendingGossip(r1, r2)
checkTopology(t, r1, r1.tp(r2), r2.tp())
checkTopology(t, r2, r1.tp(r2), r2.tp())
r2.AddTestChannelConnection(r1)
AddTestGossipConnection(r1, r2)
sendPendingGossip(r1, r2)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
// Currently, the connection from 2 to 3 is one-way only
r2.AddTestChannelConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp())
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp())
// When r2 gossiped to r3, 1 was unreachable from r3 so it got removed from the
// list of peers, but remains referenced in the connection from 1 to 3.
checkTopology(t, r3, r2.tp(r1, r3), r3.tp())
AddTestGossipConnection(r2, r3)
flushAndCheckTopology(t, routers, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
// Add a connection from 3 to 1 and now r1 is reachable.
r3.AddTestChannelConnection(r1)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r3, r1.tp(), r2.tp(r1, r3), r3.tp(r1))
r1.AddTestChannelConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
AddTestGossipConnection(r3, r1)
flushAndCheckTopology(t, routers, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2))
// Drop the connection from 2 to 3
r2.DeleteTestChannelConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1))
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
r2.DeleteTestGossipConnection(r3)
flushAndCheckTopology(t, routers, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2))
// Drop the connection from 1 to 3
r1.DeleteTestChannelConnection(r3)
r1.DeleteTestGossipConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
checkTopology(t, r1, r1.tp(r2), r2.tp(r1))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
// r3 still thinks r1 has a connection to it
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
// On a timer, r3 will gossip to r1
r3.SendAllGossip()
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2))
}
func TestGossipSurrogate(t *testing.T) {
@@ -165,14 +140,10 @@ func TestGossipSurrogate(t *testing.T) {
r1 := NewTestRouter("01:00:00:01:00:00")
r2 := NewTestRouter("02:00:00:02:00:00")
r3 := NewTestRouter("03:00:00:03:00:00")
r1.AddTestChannelConnection(r2)
r2.AddTestChannelConnection(r1)
r3.AddTestChannelConnection(r2)
r2.AddTestChannelConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
checkTopology(t, r3, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
routers := []*Router{r1, r2, r3}
AddTestGossipConnection(r1, r2)
AddTestGossipConnection(r3, r2)
flushAndCheckTopology(t, routers, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
// create a gossiper at either end, but not the middle
g1 := newTestGossiper()
@@ -238,10 +209,10 @@ func (g *testGossiper) OnGossip(update []byte) (GossipData, error) {
g.state[v] = void
}
}
if len(delta) > 0 {
return NewSurrogateGossipData(delta), nil
if len(delta) == 0 {
return nil, nil
}
return nil, nil
return NewSurrogateGossipData(delta), nil
}
func (g *testGossiper) checkHas(t *testing.T, vs ...byte) {
+1 -1
View File
@@ -17,7 +17,7 @@ func (peers *Peers) AddTestConnection(p *Peer) {
summary := p.PeerSummary
summary.Version = 0
toPeer := NewPeerFromSummary(summary)
peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
toPeer = peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
conn := newMockConnection(peers.ourself.Peer, toPeer)
peers.ourself.addConnection(conn)
peers.ourself.connectionEstablished(conn)
+10 -1
View File
@@ -26,7 +26,12 @@ func randUint16() (r uint16) {
type PeerUID uint64
func randomPeerUID() PeerUID {
return PeerUID(randUint64())
for {
uid := randUint64()
if uid != 0 { // uid 0 is reserved for peer placeholder
return PeerUID(uid)
}
}
}
func ParsePeerUID(s string) (PeerUID, error) {
@@ -95,6 +100,10 @@ func NewPeer(name PeerName, nickName string, uid PeerUID, version uint64, shortI
})
}
func NewPeerPlaceholder(name PeerName) *Peer {
return NewPeerFromSummary(PeerSummary{NameByte: name.Bin()})
}
func NewPeerFrom(peer *Peer) *Peer {
return NewPeerFromSummary(peer.PeerSummary)
}
+10 -18
View File
@@ -3,7 +3,6 @@ package mesh
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"math/rand"
"sync"
@@ -32,14 +31,6 @@ type ShortIDPeers struct {
others []*Peer
}
type UnknownPeerError struct {
Name PeerName
}
func (upe UnknownPeerError) Error() string {
return fmt.Sprint("Reference to unknown peer ", upe.Name)
}
type PeerNameSet map[PeerName]struct{}
type ConnectionSummary struct {
@@ -334,9 +325,7 @@ func (peers *Peers) ApplyUpdate(update []byte) (PeerNameSet, PeerNameSet, error)
return nil, nil, err
}
// By this point, we know the update doesn't refer to any peers we
// have no knowledge of. We can now apply the update. Start by
// adding in any new peers.
// Add new peers
for name, newPeer := range newPeers {
peers.byName[name] = newPeer
peers.addByShortID(newPeer, &pending)
@@ -446,10 +435,8 @@ func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, de
if _, found := peers.byName[remoteName]; found {
continue
}
// Update refers to a peer which we have no knowledge
// of. Thus we can't apply the update. Abort.
err = UnknownPeerError{remoteName}
return
// Update refers to a peer which we have no knowledge of.
newPeers[remoteName] = NewPeerPlaceholder(remoteName)
}
}
return
@@ -476,16 +463,21 @@ func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]Connecti
newUpdate[name] = void
default: // existing peer
if newPeer.Version < peer.Version ||
(newPeer.Version == peer.Version && newPeer.UID <= peer.UID) {
(newPeer.Version == peer.Version &&
(newPeer.UID < peer.UID ||
(newPeer.UID == peer.UID &&
(!newPeer.HasShortID || peer.HasShortID)))) {
continue
}
peer.Version = newPeer.Version
peer.UID = newPeer.UID
peer.NickName = newPeer.NickName
peer.connections = makeConnsMap(peer, connSummaries, peers.byName)
if newPeer.ShortID != peer.ShortID {
if newPeer.ShortID != peer.ShortID || newPeer.HasShortID != peer.HasShortID {
peers.deleteByShortID(peer, pending)
peer.ShortID = newPeer.ShortID
peer.HasShortID = newPeer.HasShortID
peers.addByShortID(peer, pending)
}
newUpdate[name] = void
+32 -23
View File
@@ -37,6 +37,7 @@ type Config struct {
Password []byte
ConnLimit int
PeerDiscovery bool
TrustedSubnets []*net.IPNet
}
type Router struct {
@@ -122,26 +123,28 @@ type TopologyGossipData struct {
update PeerNameSet
}
func (d *TopologyGossipData) Merge(other GossipData) GossipData {
names := make(PeerNameSet)
for name := range d.update {
names[name] = void
}
for name := range other.(*TopologyGossipData).update {
names[name] = void
}
return &TopologyGossipData{peers: d.peers, update: names}
}
func (d *TopologyGossipData) Encode() [][]byte {
return [][]byte{d.peers.EncodePeers(d.update)}
}
func (router *Router) BroadcastTopologyUpdate(update []*Peer) {
names := make(PeerNameSet)
for _, p := range update {
names[p.Name] = void
}
router.TopologyGossip.GossipBroadcast(&TopologyGossipData{
peers: router.Peers,
update: names,
})
}
func (d *TopologyGossipData) Merge(other GossipData) {
for name := range other.(*TopologyGossipData).update {
d.update[name] = void
}
}
func (d *TopologyGossipData) Encode() [][]byte {
return [][]byte{d.peers.EncodePeers(d.update)}
router.TopologyGossip.GossipBroadcast(
&TopologyGossipData{peers: router.Peers, update: names})
}
func (router *Router) OnGossipUnicast(sender PeerName, msg []byte) error {
@@ -170,14 +173,6 @@ func (router *Router) OnGossip(update []byte) (GossipData, error) {
func (router *Router) applyTopologyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) {
origUpdate, newUpdate, err := router.Peers.ApplyUpdate(update)
if _, ok := err.(UnknownPeerError); err != nil && ok {
// That update contained a reference to a peer which wasn't
// itself included in the update, and we didn't know about
// already. We ignore this; eventually we should receive an
// update containing a complete topology.
log.Println("Topology gossip:", err)
return nil, nil, nil
}
if err != nil {
return nil, nil, err
}
@@ -187,3 +182,17 @@ func (router *Router) applyTopologyUpdate(update []byte) (PeerNameSet, PeerNameS
}
return origUpdate, newUpdate, nil
}
func (router *Router) Trusts(remote *RemoteConnection) bool {
if tcpAddr, err := net.ResolveTCPAddr("tcp4", remote.remoteTCPAddr); err == nil {
for _, trustedSubnet := range router.TrustedSubnets {
if trustedSubnet.Contains(tcpAddr.IP) {
return true
}
}
} else {
// Should not happen as remoteTCPAddr was obtained from TCPConn
log.Errorf("Unable to parse remote TCP addr: %s", err)
}
return false
}
+8 -4
View File
@@ -92,20 +92,24 @@ func (routes *Routes) BroadcastAll(name PeerName) []PeerName {
// returns a higher proportion of neighbours than elsewhere. In
// extremis, on peers with fewer than log2(n_peers) neighbours, all
// neighbours are returned.
func (routes *Routes) RandomNeighbours(except PeerName) PeerNameSet {
res := make(PeerNameSet)
func (routes *Routes) RandomNeighbours(except PeerName) []PeerName {
destinations := make(PeerNameSet)
routes.RLock()
defer routes.RUnlock()
count := int(math.Log2(float64(len(routes.unicastAll))))
// depends on go's random map iteration
for _, dst := range routes.unicastAll {
if dst != UnknownPeerName && dst != except {
res[dst] = void
if len(res) >= count {
destinations[dst] = void
if len(destinations) >= count {
break
}
}
}
res := make([]PeerName, 0, len(destinations))
for dst := range destinations {
res = append(res, dst)
}
return res
}
+19 -1
View File
@@ -2,6 +2,7 @@ package mesh
import (
"fmt"
"net"
)
type Status struct {
@@ -19,6 +20,7 @@ type Status struct {
Connections []LocalConnectionStatus
Targets []string
OverlayDiagnostics interface{}
TrustedSubnets []string
}
type PeerStatus struct {
@@ -69,7 +71,8 @@ func NewStatus(router *Router) *Status {
NewBroadcastRouteStatusSlice(router.Routes),
NewLocalConnectionStatusSlice(router.ConnectionMaker),
NewTargetSlice(router.ConnectionMaker),
router.Overlay.Diagnostics()}
router.Overlay.Diagnostics(),
NewTrustedSubnetsSlice(router.TrustedSubnets)}
}
func NewPeerStatusSlice(peers *Peers) []PeerStatus {
@@ -147,6 +150,13 @@ func NewLocalConnectionStatusSlice(cm *ConnectionMaker) []LocalConnectionStatus
}
lc, _ := conn.(*LocalConnection)
info := fmt.Sprintf("%-6v %v", lc.OverlayConn.DisplayName(), conn.Remote())
if lc.Router.UsingPassword() {
if lc.Untrusted() {
info = fmt.Sprintf("%-11v %v", "encrypted", info)
} else {
info = fmt.Sprintf("%-11v %v", "unencrypted", info)
}
}
slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, info})
}
for address, target := range cm.targets {
@@ -191,3 +201,11 @@ func NewTargetSlice(cm *ConnectionMaker) []string {
}
return <-resultChan
}
func NewTrustedSubnetsSlice(trustedSubnets []*net.IPNet) []string {
trustedSubnetStrs := []string{}
for _, trustedSubnet := range trustedSubnets {
trustedSubnetStrs = append(trustedSubnetStrs, trustedSubnet.String())
}
return trustedSubnetStrs
}
+6 -2
View File
@@ -12,8 +12,12 @@ func (d *SurrogateGossipData) Encode() [][]byte {
return d.messages
}
func (d *SurrogateGossipData) Merge(other GossipData) {
d.messages = append(d.messages, other.(*SurrogateGossipData).messages...)
func (d *SurrogateGossipData) Merge(other GossipData) GossipData {
o := other.(*SurrogateGossipData)
messages := make([][]byte, 0, len(d.messages)+len(o.messages))
messages = append(messages, d.messages...)
messages = append(messages, o.messages...)
return &SurrogateGossipData{messages: messages}
}
// SurrogateGossiper ignores unicasts and relays broadcasts and gossips.
+13 -7
View File
@@ -245,12 +245,14 @@ type GossipData struct {
Entries
}
func (g *GossipData) Merge(o mesh.GossipData) {
func (g *GossipData) Merge(o mesh.GossipData) mesh.GossipData {
other := o.(*GossipData)
g.Entries.merge(other.Entries)
if g.Timestamp < other.Timestamp {
g.Timestamp = other.Timestamp
gossip := g.copy()
gossip.Entries.merge(other.Entries)
if gossip.Timestamp < other.Timestamp {
gossip.Timestamp = other.Timestamp
}
return gossip
}
func (g *GossipData) Decode(msg []byte) error {
@@ -264,9 +266,7 @@ func (g *GossipData) Decode(msg []byte) error {
}
func (g *GossipData) Encode() [][]byte {
// Make a copy so we can sort: all outgoing data is sent in case-sensitive order
g2 := GossipData{Timestamp: g.Timestamp, Entries: make(Entries, len(g.Entries))}
copy(g2.Entries, g.Entries)
g2 := g.copy()
sort.Sort(CaseSensitive(g2.Entries))
buf := &bytes.Buffer{}
if err := gob.NewEncoder(buf).Encode(g2); err != nil {
@@ -274,3 +274,9 @@ func (g *GossipData) Encode() [][]byte {
}
return [][]byte{buf.Bytes()}
}
func (g *GossipData) copy() *GossipData {
g2 := &GossipData{Timestamp: g.Timestamp, Entries: make(Entries, len(g.Entries))}
copy(g2.Entries, g.Entries)
return g2
}
+2 -2
View File
@@ -117,7 +117,7 @@ func TestGossipDataMerge(t *testing.T) {
g1 := GossipData{Entries: makeEntries("AcDf")}
g2 := GossipData{Entries: makeEntries("BEf")}
g1.Merge(&g2)
g3 := g1.Merge(&g2).(*GossipData)
require.Equal(t, GossipData{Entries: makeEntries("ABcDEf")}, g1)
require.Equal(t, GossipData{Entries: makeEntries("ABcDEf")}, *g3)
}
+8 -10
View File
@@ -74,13 +74,13 @@ func (n *Nameserver) Stop() {
}
func (n *Nameserver) broadcastEntries(es ...Entry) error {
if n.gossip != nil {
return n.gossip.GossipBroadcast(&GossipData{
Entries: Entries(es),
Timestamp: now(),
})
if n.gossip == nil || len(es) == 0 {
return nil
}
return nil
return n.gossip.GossipBroadcast(&GossipData{
Entries: Entries(es),
Timestamp: now(),
})
}
func (n *Nameserver) AddEntry(hostname, containerid string, origin mesh.PeerName, addr address.Address) error {
@@ -133,10 +133,8 @@ func (n *Nameserver) ContainerDied(ident string) {
return false
})
n.Unlock()
if len(entries) > 0 {
if err := n.broadcastEntries(entries...); err != nil {
n.errorf("failed to broadcast container %s death: %v", ident, err)
}
if err := n.broadcastEntries(entries...); err != nil {
n.errorf("failed to broadcast container %s death: %v", ident, err)
}
}
-7
View File
@@ -13,7 +13,6 @@ import (
"github.com/weaveworks/weave/mesh"
"github.com/weaveworks/weave/net/address"
wt "github.com/weaveworks/weave/testing"
"github.com/weaveworks/weave/testing/gossip"
)
@@ -76,12 +75,6 @@ func (m mapping) Addrs() []address.Address {
}
func TestNameservers(t *testing.T) {
wt.RunWithTimeout(t, 2*time.Minute, func() {
testNameservers(t)
})
}
func testNameservers(t *testing.T) {
//common.SetLogLevel("debug")
lookupTimeout := 10 // ms
+6 -4
View File
@@ -1,10 +1,11 @@
package nameserver
type Status struct {
Domain string
Address string
TTL uint32
Entries []EntryStatus
Domain string
Upstream []string
Address string
TTL uint32
Entries []EntryStatus
}
type EntryStatus struct {
@@ -37,6 +38,7 @@ func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status {
return &Status{
dnsServer.domain,
dnsServer.upstream.Servers,
dnsServer.address,
dnsServer.ttl,
entryStatusSlice}
+28 -54
View File
@@ -5,28 +5,33 @@ import (
"net"
"syscall"
"github.com/vishvananda/netlink/nl"
"github.com/vishvananda/netlink"
)
// Wait for an interface to come up.
func EnsureInterface(ifaceName string) (*net.Interface, error) {
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK)
if err != nil {
return nil, err
}
defer s.Close()
iface, err := ensureInterface(s, ifaceName)
iface, err := ensureInterface(ifaceName)
if err != nil {
return nil, err
}
return iface, err
}
func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, error) {
func ensureInterface(ifaceName string) (*net.Interface, error) {
if iface, err := findInterface(ifaceName); err == nil {
return iface, nil
}
netlinkReceiveUntil(s, matchIfName(ifaceName))
ch := make(chan netlink.LinkUpdate)
done := make(chan struct{})
defer close(done)
if err := netlink.LinkSubscribe(ch, done); err != nil {
return nil, err
}
for update := range ch {
if ifaceName == update.Link.Attrs().Name && update.IfInfomsg.Flags&syscall.IFF_UP != 0 {
break
}
}
iface, err := findInterface(ifaceName)
return iface, err
}
@@ -35,12 +40,7 @@ func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, err
// This matches the behaviour in 'weave attach', which is the only context in which
// we expect this to be called. If you change one, change the other to match.
func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) {
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK, syscall.RTNLGRP_IPV4_ROUTE)
if err != nil {
return nil, err
}
defer s.Close()
iface, err := ensureInterface(s, ifaceName)
iface, err := ensureInterface(ifaceName)
if err != nil {
return nil, err
}
@@ -48,8 +48,19 @@ func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) {
if CheckRouteExists(ifaceName, dest) {
return iface, err
}
netlinkReceiveUntil(s, matchRoute(ifaceName, dest))
return iface, err
ch := make(chan netlink.RouteUpdate)
done := make(chan struct{})
defer close(done)
if err := netlink.RouteSubscribe(ch, done); err != nil {
return nil, err
}
for update := range ch {
link, _ := netlink.LinkByIndex(update.Route.LinkIndex)
if link.Attrs().Name == ifaceName && update.Route.Dst.IP.Equal(dest) {
break
}
}
return iface, nil
}
func findInterface(ifaceName string) (iface *net.Interface, err error) {
@@ -61,40 +72,3 @@ func findInterface(ifaceName string) (iface *net.Interface, err error) {
}
return
}
func netlinkReceiveUntil(s *nl.NetlinkSocket, f func(syscall.NetlinkMessage) (bool, error)) error {
for {
msgs, err := s.Receive()
if err != nil {
return err
}
for _, m := range msgs {
if done, err := f(m); done {
return err
}
}
}
}
func matchIfName(ifaceName string) func(m syscall.NetlinkMessage) (bool, error) {
return func(m syscall.NetlinkMessage) (bool, error) {
switch m.Header.Type {
case syscall.RTM_NEWLINK: // receive this type for link 'up'
ifmsg := nl.DeserializeIfInfomsg(m.Data)
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
if err != nil {
return true, err
}
name := ""
for _, attr := range attrs {
if attr.Attr.Type == syscall.IFA_LABEL {
name = string(attr.Value[:len(attr.Value)-1])
}
}
if ifaceName == name && ifmsg.Flags&syscall.IFF_UP != 0 {
return true, nil
}
}
return false, nil
}
}
-50
View File
@@ -3,10 +3,8 @@ package net
import (
"fmt"
"net"
"syscall"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
)
// A network is considered free if it does not overlap any existing
@@ -65,51 +63,3 @@ func CheckRouteExists(ifaceName string, dest net.IP) bool {
})
return found
}
func matchRoute(ifaceName string, dest net.IP) func(m syscall.NetlinkMessage) (bool, error) {
return func(m syscall.NetlinkMessage) (bool, error) {
switch m.Header.Type {
case syscall.RTM_NEWROUTE:
route, err := deserializeRoute(m.Data)
if err != nil {
return true, err
}
link, _ := netlink.LinkByIndex(route.LinkIndex)
if link.Attrs().Name == ifaceName && route.Dst.IP.Equal(dest) {
return true, nil
}
}
return false, nil
}
}
// This code proposed for addition to the vishvananda/netlink library
// deserializeRoute decodes a binary netlink message into a Route struct
func deserializeRoute(m []byte) (netlink.Route, error) {
route := netlink.Route{}
msg := nl.DeserializeRtMsg(m)
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
if err != nil {
return route, err
}
route.Scope = netlink.Scope(msg.Scope)
native := nl.NativeEndian()
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.RTA_GATEWAY:
route.Gw = net.IP(attr.Value)
case syscall.RTA_PREFSRC:
route.Src = net.IP(attr.Value)
case syscall.RTA_DST:
route.Dst = &net.IPNet{
IP: attr.Value,
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)),
}
case syscall.RTA_OIF:
routeIndex := int(native.Uint32(attr.Value[0:4]))
route.LinkIndex = routeIndex
}
}
return route, nil
}
+9
View File
@@ -0,0 +1,9 @@
# Weave network driver extension for Docker
A [plugin](http://docs.docker.com/engine/extend/plugin_api/) to
integrate [weave Net](http://weave.works/net/) with Docker.
The plugin runs automatically when you `weave launch`, provided your
Docker daemon is version 1.9 or newer.
More detail [here](http://docs.weave.works/weave/latest_release/plugin.html).
+58
View File
@@ -0,0 +1,58 @@
package ipamplugin
import (
"net"
"github.com/docker/libnetwork/ipamapi"
"github.com/docker/libnetwork/netlabel"
"github.com/weaveworks/weave/api"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
)
const (
WeaveContainer = "weave"
)
type ipam struct {
client *docker.Client
weave *api.Client
}
func NewIpam(client *docker.Client, version string) (ipamapi.Ipam, error) {
resolver := func() (string, error) { return client.GetContainerIP(WeaveContainer) }
return &ipam{client: client, weave: api.NewClientWithResolver(resolver)}, nil
}
func (i *ipam) GetDefaultAddressSpaces() (string, string, error) {
Log.Debugln("GetDefaultAddressSpaces")
return "weavelocal", "weaveglobal", nil
}
func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
Log.Debugln("RequestPool", addressSpace, pool, subPool, options)
cidr, err := i.weave.DefaultSubnet()
Log.Debugln("RequestPool returning ", cidr, err)
// Pass back a fake "gateway address"; we don't actually use it,
// so just give the network address.
data := map[string]string{netlabel.Gateway: cidr.String()}
return "weavepool", cidr, data, err
}
func (i *ipam) ReleasePool(poolID string) error {
Log.Debugln("ReleasePool", poolID)
return nil
}
func (i *ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) {
Log.Debugln("RequestAddress", poolID, address, options)
// Pass magic string to weave IPAM, which then stores the address under its own string
ip, err := i.weave.AllocateIP("_")
Log.Debugln("allocateIP returned", ip, err)
return ip, nil, err
}
func (i *ipam) ReleaseAddress(poolID string, address net.IP) error {
Log.Debugln("ReleaseAddress", poolID, address)
return i.weave.ReleaseIP(address.String())
}
+206
View File
@@ -0,0 +1,206 @@
package plugin
import (
"fmt"
"sync"
"github.com/docker/libnetwork/drivers/remote/api"
"github.com/docker/libnetwork/types"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
"github.com/weaveworks/weave/common/odp"
"github.com/weaveworks/weave/plugin/skel"
"github.com/vishvananda/netlink"
)
const (
WeaveContainer = "weave"
WeaveBridge = "weave"
)
type driver struct {
version string
nameserver string
scope string
noMulticastRoute bool
sync.RWMutex
endpoints map[string]struct{}
}
func New(client *docker.Client, version string, nameserver string, scope string, noMulticastRoute bool) (skel.Driver, error) {
driver := &driver{
nameserver: nameserver,
noMulticastRoute: noMulticastRoute,
version: version,
scope: scope,
endpoints: make(map[string]struct{}),
}
_, err := NewWatcher(client, driver)
if err != nil {
return nil, err
}
return driver, nil
}
func errorf(format string, a ...interface{}) error {
Log.Errorf(format, a...)
return fmt.Errorf(format, a...)
}
// === protocol handlers
func (driver *driver) GetCapabilities() (*api.GetCapabilityResponse, error) {
var caps = &api.GetCapabilityResponse{
Scope: driver.scope,
}
Log.Debugf("Get capabilities: responded with %+v", caps)
return caps, nil
}
func (driver *driver) CreateNetwork(create *api.CreateNetworkRequest) error {
Log.Debugf("Create network request %+v", create)
Log.Infof("Create network %s", create.NetworkID)
return nil
}
func (driver *driver) DeleteNetwork(delete *api.DeleteNetworkRequest) error {
Log.Debugf("Delete network request: %+v", delete)
Log.Infof("Destroy network %s", delete.NetworkID)
return nil
}
func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error) {
Log.Debugf("Create endpoint request %+v", create)
endID := create.EndpointID
if create.Interface == nil {
return nil, fmt.Errorf("Not supported: creating an interface from within CreateEndpoint")
}
driver.Lock()
driver.endpoints[endID] = struct{}{}
driver.Unlock()
resp := &api.CreateEndpointResponse{}
Log.Infof("Create endpoint %s %+v", endID, resp)
return resp, nil
}
func (driver *driver) DeleteEndpoint(deleteReq *api.DeleteEndpointRequest) error {
Log.Debugf("Delete endpoint request: %+v", deleteReq)
Log.Infof("Delete endpoint %s", deleteReq.EndpointID)
driver.Lock()
delete(driver.endpoints, deleteReq.EndpointID)
driver.Unlock()
return nil
}
func (driver *driver) HasEndpoint(endpointID string) bool {
driver.Lock()
_, found := driver.endpoints[endpointID]
driver.Unlock()
return found
}
func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error) {
Log.Debugf("Endpoint info request: %+v", req)
Log.Infof("Endpoint info %s", req.EndpointID)
return &api.EndpointInfoResponse{Value: map[string]interface{}{}}, nil
}
func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error) {
endID := j.EndpointID
maybeBridge, err := netlink.LinkByName(WeaveBridge)
if err != nil {
return nil, errorf(`bridge "%s" not present; did you launch weave?`, WeaveBridge)
}
// create and attach local name to the bridge
local := vethPair(endID[:5])
local.Attrs().MTU = maybeBridge.Attrs().MTU
if err := netlink.LinkAdd(local); err != nil {
return nil, errorf("could not create veth pair: %s", err)
}
switch maybeBridge.(type) {
case *netlink.Bridge:
if err := netlink.LinkSetMasterByIndex(local, maybeBridge.Attrs().Index); err != nil {
return nil, errorf(`unable to set master: %s`, err)
}
case *netlink.GenericLink:
if maybeBridge.Type() != "openvswitch" {
Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge)
return nil, errorf(`device "%s" is of type "%s"`, WeaveBridge, maybeBridge.Type())
}
odp.AddDatapathInterface(WeaveBridge, local.Name)
case *netlink.Device:
Log.Warnf("kernel does not report what kind of device %s is, just %+v", WeaveBridge, maybeBridge)
// Assume it's our openvswitch device, and the kernel has not been updated to report the kind.
odp.AddDatapathInterface(WeaveBridge, local.Name)
default:
Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge)
return nil, errorf(`device "%s" not a bridge`, WeaveBridge)
}
if err := netlink.LinkSetUp(local); err != nil {
return nil, errorf(`unable to bring veth up: %s`, err)
}
ifname := &api.InterfaceName{
SrcName: local.PeerName,
DstPrefix: "ethwe",
}
response := &api.JoinResponse{
InterfaceName: ifname,
}
if driver.nameserver != "" {
routeToDNS := api.StaticRoute{
Destination: driver.nameserver + "/32",
RouteType: types.CONNECTED,
NextHop: "",
}
response.StaticRoutes = []api.StaticRoute{routeToDNS}
}
if !driver.noMulticastRoute {
multicastRoute := api.StaticRoute{
Destination: "224.0.0.0/4",
RouteType: types.CONNECTED,
}
response.StaticRoutes = append(response.StaticRoutes, multicastRoute)
}
Log.Infof("Join endpoint %s:%s to %s", j.NetworkID, j.EndpointID, j.SandboxKey)
return response, nil
}
func (driver *driver) LeaveEndpoint(leave *api.LeaveRequest) error {
Log.Debugf("Leave request: %+v", leave)
local := vethPair(leave.EndpointID[:5])
if err := netlink.LinkDel(local); err != nil {
Log.Warningf("unable to delete veth on leave: %s", err)
}
Log.Infof("Leave %s:%s", leave.NetworkID, leave.EndpointID)
return nil
}
func (driver *driver) DiscoverNew(disco *api.DiscoveryNotification) error {
Log.Debugf("Dicovery new notification: %+v", disco)
return nil
}
func (driver *driver) DiscoverDelete(disco *api.DiscoveryNotification) error {
Log.Debugf("Dicovery delete notification: %+v", disco)
return nil
}
// ===
func vethPair(suffix string) *netlink.Veth {
return &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: "vethwl" + suffix},
PeerName: "vethwg" + suffix,
}
}
+57
View File
@@ -0,0 +1,57 @@
package plugin
import (
"fmt"
weaveapi "github.com/weaveworks/weave/api"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
)
const (
WeaveDomain = "weave.local"
)
type watcher struct {
client *docker.Client
weave *weaveapi.Client
driver *driver
}
type Watcher interface {
}
func NewWatcher(client *docker.Client, driver *driver) (Watcher, error) {
resolver := func() (string, error) { return client.GetContainerIP(WeaveContainer) }
w := &watcher{client: client, driver: driver,
weave: weaveapi.NewClientWithResolver(resolver)}
err := client.AddObserver(w)
if err != nil {
return nil, err
}
return w, nil
}
func (w *watcher) ContainerStarted(id string) {
Log.Debugf("Container started %s", id)
info, err := w.client.InspectContainer(id)
if err != nil {
Log.Warningf("error inspecting container: %s", err)
return
}
// check that it's on our network, via the endpointID
for _, net := range info.NetworkSettings.Networks {
if w.driver.HasEndpoint(net.EndpointID) {
fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname)
if err := w.weave.RegisterWithDNS(id, fqdn, net.IPAddress); err != nil {
Log.Warningf("unable to register with weaveDNS: %s", err)
}
}
}
}
func (w *watcher) ContainerDied(id string) {
// don't need to do this as WeaveDNS removes names on container died anyway
// (note by the time we get this event we can't see the EndpointID)
}
+298
View File
@@ -0,0 +1,298 @@
package skel
import (
"encoding/json"
"fmt"
"net"
"net/http"
"github.com/gorilla/mux"
"github.com/docker/libnetwork/drivers/remote/api"
"github.com/docker/libnetwork/ipamapi"
iapi "github.com/docker/libnetwork/ipams/remote/api"
)
const (
networkReceiver = "NetworkDriver"
ipamReceiver = ipamapi.PluginEndpointType
)
type Driver interface {
GetCapabilities() (*api.GetCapabilityResponse, error)
CreateNetwork(create *api.CreateNetworkRequest) error
DeleteNetwork(delete *api.DeleteNetworkRequest) error
CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error)
DeleteEndpoint(delete *api.DeleteEndpointRequest) error
EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error)
JoinEndpoint(j *api.JoinRequest) (response *api.JoinResponse, error error)
LeaveEndpoint(leave *api.LeaveRequest) error
DiscoverNew(discover *api.DiscoveryNotification) error
DiscoverDelete(delete *api.DiscoveryNotification) error
}
type listener struct {
d Driver
i ipamapi.Ipam
}
func Listen(socket net.Listener, driver Driver, ipamDriver ipamapi.Ipam) error {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFound)
listener := &listener{driver, ipamDriver}
router.Methods("POST").Path("/Plugin.Activate").HandlerFunc(listener.handshake)
handleMethod := func(receiver, method string, h http.HandlerFunc) {
router.Methods("POST").Path(fmt.Sprintf("/%s.%s", receiver, method)).HandlerFunc(h)
}
handleMethod(networkReceiver, "GetCapabilities", listener.getCapabilities)
if driver != nil {
handleMethod(networkReceiver, "CreateNetwork", listener.createNetwork)
handleMethod(networkReceiver, "DeleteNetwork", listener.deleteNetwork)
handleMethod(networkReceiver, "CreateEndpoint", listener.createEndpoint)
handleMethod(networkReceiver, "DeleteEndpoint", listener.deleteEndpoint)
handleMethod(networkReceiver, "EndpointOperInfo", listener.infoEndpoint)
handleMethod(networkReceiver, "Join", listener.joinEndpoint)
handleMethod(networkReceiver, "Leave", listener.leaveEndpoint)
}
if ipamDriver != nil {
handleMethod(ipamReceiver, "GetDefaultAddressSpaces", listener.getDefaultAddressSpaces)
handleMethod(ipamReceiver, "RequestPool", listener.requestPool)
handleMethod(ipamReceiver, "ReleasePool", listener.releasePool)
handleMethod(ipamReceiver, "RequestAddress", listener.requestAddress)
handleMethod(ipamReceiver, "ReleaseAddress", listener.releaseAddress)
}
return http.Serve(socket, router)
}
func decode(w http.ResponseWriter, r *http.Request, v interface{}) error {
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
}
return err
}
// === protocol handlers
type handshakeResp struct {
Implements []string
}
func (listener *listener) handshake(w http.ResponseWriter, r *http.Request) {
var resp handshakeResp
if listener.d != nil {
resp.Implements = append(resp.Implements, "NetworkDriver")
}
if listener.i != nil {
resp.Implements = append(resp.Implements, "IpamDriver")
}
err := json.NewEncoder(w).Encode(&resp)
if err != nil {
sendError(w, "encode error", http.StatusInternalServerError)
return
}
}
func (listener *listener) getCapabilities(w http.ResponseWriter, r *http.Request) {
caps, err := listener.d.GetCapabilities()
objectOrErrorResponse(w, caps, err)
}
func (listener *listener) createNetwork(w http.ResponseWriter, r *http.Request) {
var create api.CreateNetworkRequest
err := json.NewDecoder(r.Body).Decode(&create)
if err != nil {
sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.CreateNetwork(&create))
}
func (listener *listener) deleteNetwork(w http.ResponseWriter, r *http.Request) {
var delete api.DeleteNetworkRequest
if err := json.NewDecoder(r.Body).Decode(&delete); err != nil {
sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.DeleteNetwork(&delete))
}
func (listener *listener) createEndpoint(w http.ResponseWriter, r *http.Request) {
var create api.CreateEndpointRequest
if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
sendError(w, "unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
return
}
res, err := listener.d.CreateEndpoint(&create)
objectOrErrorResponse(w, res, err)
}
func (listener *listener) deleteEndpoint(w http.ResponseWriter, r *http.Request) {
var delete api.DeleteEndpointRequest
if err := json.NewDecoder(r.Body).Decode(&delete); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.DeleteEndpoint(&delete))
}
func (listener *listener) infoEndpoint(w http.ResponseWriter, r *http.Request) {
var req api.EndpointInfoRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
info, err := listener.d.EndpointInfo(&req)
objectOrErrorResponse(w, info, err)
}
func (listener *listener) joinEndpoint(w http.ResponseWriter, r *http.Request) {
var join api.JoinRequest
if err := json.NewDecoder(r.Body).Decode(&join); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
res, err := listener.d.JoinEndpoint(&join)
objectOrErrorResponse(w, res, err)
}
func (listener *listener) leaveEndpoint(w http.ResponseWriter, r *http.Request) {
var l api.LeaveRequest
if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.LeaveEndpoint(&l))
}
func (listener *listener) discoverNew(w http.ResponseWriter, r *http.Request) {
var disco api.DiscoveryNotification
if err := json.NewDecoder(r.Body).Decode(&disco); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.DiscoverNew(&disco))
}
func (listener *listener) discoverDelete(w http.ResponseWriter, r *http.Request) {
var disco api.DiscoveryNotification
if err := json.NewDecoder(r.Body).Decode(&disco); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.DiscoverDelete(&disco))
}
// ===
func (listener *listener) getDefaultAddressSpaces(w http.ResponseWriter, r *http.Request) {
local, global, err := listener.i.GetDefaultAddressSpaces()
response := &iapi.GetAddressSpacesResponse{
LocalDefaultAddressSpace: local,
GlobalDefaultAddressSpace: global,
}
objectOrErrorResponse(w, response, err)
}
func (listener *listener) requestPool(w http.ResponseWriter, r *http.Request) {
var rq iapi.RequestPoolRequest
if err := decode(w, r, &rq); err != nil {
return
}
poolID, pool, data, err := listener.i.RequestPool(rq.AddressSpace, rq.Pool, rq.SubPool, rq.Options, rq.V6)
if err != nil {
errorResponse(w, err.Error())
return
}
response := &iapi.RequestPoolResponse{
PoolID: poolID,
Pool: pool.String(),
Data: data,
}
objectResponse(w, response)
}
func (listener *listener) releasePool(w http.ResponseWriter, r *http.Request) {
var rq iapi.ReleasePoolRequest
if err := decode(w, r, &rq); err != nil {
return
}
err := listener.i.ReleasePool(rq.PoolID)
emptyOrErrorResponse(w, err)
}
func (listener *listener) requestAddress(w http.ResponseWriter, r *http.Request) {
var rq iapi.RequestAddressRequest
if err := decode(w, r, &rq); err != nil {
return
}
address, data, err := listener.i.RequestAddress(rq.PoolID, net.ParseIP(rq.Address), rq.Options)
if err != nil {
errorResponse(w, err.Error())
return
}
response := &iapi.RequestAddressResponse{
Address: address.String(),
Data: data,
}
objectResponse(w, response)
}
func (listener *listener) releaseAddress(w http.ResponseWriter, r *http.Request) {
var rq iapi.ReleaseAddressRequest
if err := decode(w, r, &rq); err != nil {
return
}
err := listener.i.ReleaseAddress(rq.PoolID, net.ParseIP(rq.Address))
emptyOrErrorResponse(w, err)
}
// ===
func notFound(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}
func sendError(w http.ResponseWriter, msg string, code int) {
http.Error(w, msg, code)
}
func errorResponse(w http.ResponseWriter, fmtString string, item ...interface{}) {
json.NewEncoder(w).Encode(map[string]string{
"Err": fmt.Sprintf(fmtString, item...),
})
}
func objectResponse(w http.ResponseWriter, obj interface{}) {
if err := json.NewEncoder(w).Encode(obj); err != nil {
sendError(w, "Could not JSON encode response", http.StatusInternalServerError)
return
}
}
func emptyResponse(w http.ResponseWriter) {
json.NewEncoder(w).Encode(map[string]string{})
}
func objectOrErrorResponse(w http.ResponseWriter, obj interface{}, err error) {
if err != nil {
errorResponse(w, err.Error())
return
}
objectResponse(w, obj)
}
func emptyOrErrorResponse(w http.ResponseWriter, err error) {
if err != nil {
errorResponse(w, err.Error())
return
}
emptyResponse(w)
}
+3
View File
@@ -0,0 +1,3 @@
FROM weaveworks/weaveexec
ADD ./plugin /home/weave/
ENTRYPOINT ["/home/weave/plugin"]
+163
View File
@@ -0,0 +1,163 @@
package main
import (
"flag"
"fmt"
"net"
"os"
"os/signal"
"strings"
"syscall"
"github.com/docker/libnetwork/ipamapi"
go_docker "github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
ipamplugin "github.com/weaveworks/weave/plugin/ipam"
netplugin "github.com/weaveworks/weave/plugin/net"
"github.com/weaveworks/weave/plugin/skel"
)
var version = "(unreleased version)"
func main() {
var (
justVersion bool
address string
nameserver string
meshAddress string
logLevel string
meshNetworkName string
noMulticastRoute bool
removeNetwork bool
)
flag.BoolVar(&justVersion, "version", false, "print version and exit")
flag.StringVar(&logLevel, "log-level", "info", "logging level (debug, info, warning, error)")
flag.StringVar(&address, "socket", "/run/docker/plugins/weave.sock", "socket on which to listen")
flag.StringVar(&nameserver, "nameserver", "", "nameserver to provide to containers")
flag.StringVar(&meshAddress, "meshsocket", "/run/docker/plugins/weavemesh.sock", "socket on which to listen in mesh mode")
flag.StringVar(&meshNetworkName, "mesh-network-name", "weave", "network name to create in mesh mode")
flag.BoolVar(&noMulticastRoute, "no-multicast-route", false, "do not add a multicast route to network endpoints")
flag.BoolVar(&removeNetwork, "remove-network", false, "remove mesh network and exit")
flag.Parse()
if justVersion {
fmt.Printf("weave plugin %s\n", version)
os.Exit(0)
}
SetLogLevel(logLevel)
// API 1.21 is the first version that supports docker network commands
dockerClient, err := docker.NewVersionedClientFromEnv("1.21")
if err != nil {
Log.Fatalf("unable to connect to docker: %s", err)
}
if removeNetwork {
if _, err = dockerClient.Client.NetworkInfo(meshNetworkName); err == nil {
err = dockerClient.Client.RemoveNetwork(meshNetworkName)
if err != nil {
Log.Fatalf("unable to remove network: %s", err)
}
}
os.Exit(0)
}
Log.Println("Weave plugin", version, "Command line options:", os.Args[1:])
Log.Info(dockerClient.Info())
var globalListener, meshListener net.Listener
endChan := make(chan error, 1)
if address != "" {
globalListener, err := listenAndServe(dockerClient, address, nameserver, noMulticastRoute, endChan, "global", false)
if err != nil {
Log.Fatalf("unable to create driver: %s", err)
}
defer globalListener.Close()
}
if meshAddress != "" {
meshListener, err := listenAndServe(dockerClient, meshAddress, nameserver, noMulticastRoute, endChan, "local", true)
if err != nil {
Log.Fatalf("unable to create driver: %s", err)
}
defer meshListener.Close()
}
if meshNetworkName != "" {
createNetwork(dockerClient, meshNetworkName, meshAddress)
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGTERM)
select {
case sig := <-sigChan:
Log.Debugf("Caught signal %s; shutting down", sig)
if err := dockerClient.Client.RemoveNetwork(meshNetworkName); err != nil {
Log.Fatal(err)
}
case err := <-endChan:
if err != nil {
Log.Errorf("Error from listener: %s", err)
globalListener.Close()
meshListener.Close()
os.Exit(1)
}
}
}
func listenAndServe(dockerClient *docker.Client, address, nameserver string, noMulticastRoute bool, endChan chan<- error, scope string, withIpam bool) (net.Listener, error) {
d, err := netplugin.New(dockerClient, version, nameserver, scope, noMulticastRoute)
if err != nil {
return nil, err
}
var i ipamapi.Ipam
if withIpam {
if i, err = ipamplugin.NewIpam(dockerClient, version); err != nil {
return nil, err
}
}
var listener net.Listener
// remove sockets from last invocation
if err := os.Remove(address); err != nil && !os.IsNotExist(err) {
return nil, err
}
listener, err = net.Listen("unix", address)
if err != nil {
return nil, err
}
Log.Printf("Listening on %s for %s scope", address, scope)
go func() {
endChan <- skel.Listen(listener, d, i)
}()
return listener, nil
}
func createNetwork(dockerClient *docker.Client, networkName, address string) {
if _, err := dockerClient.Client.NetworkInfo(networkName); err == nil {
Log.Printf("Docker network '%s' already exists", networkName)
} else if _, ok := err.(*go_docker.NoSuchNetwork); ok {
driverName := strings.TrimSuffix(address, ".sock")
if i := strings.LastIndex(driverName, "/"); i >= 0 {
driverName = driverName[i+1:]
}
options := go_docker.CreateNetworkOptions{
Name: networkName,
CheckDuplicate: true,
Driver: driverName,
IPAM: go_docker.IPAMOptions{Driver: driverName},
}
_, err := dockerClient.Client.CreateNetwork(options)
if err != nil {
Log.Fatalf("Error creating network: %s", err)
}
}
}
+3 -1
View File
@@ -6,6 +6,7 @@ LABEL works.weave.role=system
WORKDIR /home/weave
VOLUME /w
VOLUME /w-noop
VOLUME /w-nomcast
ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"]
RUN apk add --update \
@@ -18,8 +19,9 @@ RUN apk add --update \
bind-tools \
&& rm -rf /var/cache/apk/*
ADD ./weave ./sigproxy ./weaveproxy /home/weave/
ADD ./weave ./sigproxy ./weaveproxy ./symlink /home/weave/
ADD ./netcheck ./docker_tls_args /usr/bin/
ADD ./weavewait /w/w
ADD ./weavewait_noop /w-noop/w
ADD ./weavewait_nomcast /w-nomcast/w
ADD ./docker.tgz /
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# This lets us force-symlink a bunch of stuff all at once, so we can minimize
# running containers.
set -e
[ -z "$WEAVE_DEBUG" ] || set -x
target="$1"
user="$(stat -c '%u:%g' "$target")"
mode="$(stat -c '%a' "$target")"
shift 1
for link in "$@" ; do
ln -sfT "$target" "$link"
done
chown -h "$user" "$@"
chmod "$mode" "$@"
+14 -8
View File
@@ -12,14 +12,15 @@ import (
)
var (
version = "(unreleased version)"
version = "(unreleased version)"
defaultDockerHost = "unix:///var/run/docker.sock"
)
func main() {
var (
justVersion bool
logLevel = "info"
c = proxy.Config{ListenAddrs: []string{}}
c = proxy.Config{Image: "weaveworks/weaveexec"}
)
c.Version = version
@@ -35,11 +36,11 @@ func main() {
mflag.BoolVar(&c.NoRewriteHosts, []string{"-no-rewrite-hosts"}, false, "do not automatically rewrite /etc/hosts. Use if you need the docker IP to remain in /etc/hosts")
mflag.StringVar(&c.TLSConfig.CACert, []string{"#tlscacert", "-tlscacert"}, "", "Trust certs signed only by this CA")
mflag.StringVar(&c.TLSConfig.Cert, []string{"#tlscert", "-tlscert"}, "", "Path to TLS certificate file")
mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tls-verify")
mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tlsverify")
mflag.StringVar(&c.TLSConfig.Key, []string{"#tlskey", "-tlskey"}, "", "Path to TLS key file")
mflag.BoolVar(&c.TLSConfig.Verify, []string{"#tlsverify", "-tlsverify"}, false, "Use TLS and verify the remote")
mflag.BoolVar(&c.WithDNS, []string{"-with-dns", "w"}, false, "instruct created containers to always use weaveDNS as their nameserver")
mflag.BoolVar(&c.WithoutDNS, []string{"-without-dns"}, false, "instruct created containers to never use weaveDNS as their nameserver")
mflag.BoolVar(&c.NoMulticastRoute, []string{"-no-multicast-route"}, false, "do not add a multicast route via the weave interface when attaching containers")
mflag.Parse()
if justVersion {
@@ -47,15 +48,20 @@ func main() {
os.Exit(0)
}
if c.WithDNS && c.WithoutDNS {
Log.Fatalf("Cannot use both '--with-dns' and '--without-dns' flags")
}
SetLogLevel(logLevel)
if image := os.Getenv("EXEC_IMAGE"); image != "" {
c.Image = image
}
Log.Infoln("weave proxy", version)
Log.Infoln("Command line arguments:", strings.Join(os.Args[1:], " "))
c.DockerHost = defaultDockerHost
if dockerHost := os.Getenv("DOCKER_HOST"); dockerHost != "" {
c.DockerHost = dockerHost
}
p, err := proxy.NewProxy(c)
if err != nil {
Log.Fatalf("Could not start proxy: %s", err)
+104 -25
View File
@@ -1,8 +1,13 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"text/template"
"github.com/gorilla/mux"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam"
@@ -10,9 +15,6 @@ import (
"github.com/weaveworks/weave/nameserver"
"github.com/weaveworks/weave/net/address"
weave "github.com/weaveworks/weave/router"
"net/http"
"strings"
"text/template"
)
var rootTemplate = template.New("root").Funcs(map[string]interface{}{
@@ -25,6 +27,66 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{
}
return count
},
"printList": func(list []string) string {
if len(list) == 0 {
return "none"
}
return strings.Join(list, ", ")
},
"printIPAMRanges": func(router weave.NetworkRouterStatus, status ipam.Status) string {
var buffer bytes.Buffer
type stats struct {
ips uint32
nickname string
reachable bool
}
peerStats := make(map[string]*stats)
for _, entry := range status.Entries {
s, found := peerStats[entry.Peer]
if !found {
s = &stats{nickname: entry.Nickname, reachable: entry.IsKnownPeer}
peerStats[entry.Peer] = s
}
s.ips += entry.Size
}
printOwned := func(name string, nickName string, reachable bool, ips uint32) {
reachableStr := ""
if !reachable {
reachableStr = "- unreachable!"
}
percentageRanges := float32(ips) * 100.0 / float32(status.RangeNumIPs)
displayName := name + "(" + nickName + ")"
fmt.Fprintf(&buffer, "%-37v %8d IPs (%04.1f%% of total) %s\n",
displayName, ips, percentageRanges, reachableStr)
}
// print the local info first
if ourStats := peerStats[router.Name]; ourStats != nil {
printOwned(router.Name, ourStats.nickname, true, ourStats.ips)
}
// and then the rest
for peer, stats := range peerStats {
if peer != router.Name {
printOwned(peer, stats.nickname, stats.reachable, stats.ips)
}
}
return buffer.String()
},
"allIPAMOwnersUnreachable": func(status ipam.Status) bool {
for _, entry := range status.Entries {
if entry.Size > 0 && entry.IsKnownPeer {
return false
}
}
return true
},
"printConnectionCounts": func(conns []mesh.LocalConnectionStatus) string {
counts := make(map[string]int)
for _, conn := range conns {
@@ -76,40 +138,48 @@ func defTemplate(name string, text string) *template.Template {
}
var statusTemplate = defTemplate("status", `\
Version: {{.Version}}
Version: {{.Version}}
Service: router
Protocol: {{.Router.Protocol}} \
Service: router
Protocol: {{.Router.Protocol}} \
{{if eq .Router.ProtocolMinVersion .Router.ProtocolMaxVersion}}\
{{.Router.ProtocolMaxVersion}}\
{{else}}\
{{.Router.ProtocolMinVersion}}..{{.Router.ProtocolMaxVersion}}\
{{end}}
Name: {{.Router.Name}}({{.Router.NickName}})
Encryption: {{printState .Router.Encryption}}
PeerDiscovery: {{printState .Router.PeerDiscovery}}
Targets: {{len .Router.Targets}}
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections){{end}}
Name: {{.Router.Name}}({{.Router.NickName}})
Encryption: {{printState .Router.Encryption}}
PeerDiscovery: {{printState .Router.PeerDiscovery}}
Targets: {{len .Router.Targets}}
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections){{end}}
TrustedSubnets: {{printList .Router.TrustedSubnets}}
{{if .IPAM}}\
Service: ipam
Service: ipam
{{if .IPAM.Entries}}\
Consensus: achieved
{{else if .IPAM.Paxos}}\
Consensus: waiting (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
{{if allIPAMOwnersUnreachable .IPAM}}\
Status: all IP ranges owned by unreachable peers - use 'rmpeer' if they are dead
{{else if len .IPAM.PendingAllocates}}\
Status: waiting for IP range grant from peers
{{else}}\
Consensus: deferred
Status: ready
{{end}}\
Range: {{.IPAM.Range}}
DefaultSubnet: {{.IPAM.DefaultSubnet}}
{{else if .IPAM.Paxos}}\
Status: awaiting consensus (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
{{else}}\
Status: idle
{{end}}\
Range: {{.IPAM.Range}}
DefaultSubnet: {{.IPAM.DefaultSubnet}}
{{end}}\
{{if .DNS}}\
Service: dns
Domain: {{.DNS.Domain}}
TTL: {{.DNS.TTL}}
Entries: {{countDNSEntries .DNS.Entries}}
Service: dns
Domain: {{.DNS.Domain}}
Upstream: {{printList .DNS.Upstream}}
TTL: {{.DNS.TTL}}
Entries: {{countDNSEntries .DNS.Entries}}
{{end}}\
`)
@@ -129,7 +199,7 @@ var peersTemplate = defTemplate("peers", `\
{{.Name}}({{.NickName}})
{{range .Connections}}\
{{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} \
{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-32v" $nameNickName}} \
{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-37v" $nameNickName}} \
{{if .Established}}established{{else}}pending{{end}}
{{end}}\
{{end}}\
@@ -145,6 +215,8 @@ var dnsEntriesTemplate = defTemplate("dnsEntries", `\
{{end}}\
`)
var ipamTemplate = defTemplate("ipamTemplate", `{{printIPAMRanges .Router .IPAM}}`)
type WeaveStatus struct {
Version string
Router *weave.NetworkRouterStatus `json:"Router,omitempty"`
@@ -174,7 +246,13 @@ func HandleHTTP(muxRouter *mux.Router, version string, router *weave.NetworkRout
muxRouter.Methods("GET").Path("/report").Queries("format", "{format}").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
formatTemplate, err := template.New("format").Parse(mux.Vars(r)["format"])
funcs := template.FuncMap{
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}
formatTemplate, err := template.New("format").Funcs(funcs).Parse(mux.Vars(r)["format"])
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -200,5 +278,6 @@ func HandleHTTP(muxRouter *mux.Router, version string, router *weave.NetworkRout
defHandler("/status/connections", connectionsTemplate)
defHandler("/status/peers", peersTemplate)
defHandler("/status/dns", dnsEntriesTemplate)
defHandler("/status/ipam", ipamTemplate)
}
+35 -5
View File
@@ -69,8 +69,15 @@ func main() {
dnsEffectiveListenAddress string
iface *net.Interface
datapathName string
trustedSubnetStr string
defaultDockerHost = "unix:///var/run/docker.sock"
)
if val := os.Getenv("DOCKER_HOST"); val != "" {
defaultDockerHost = val
}
mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
mflag.BoolVar(&createDatapath, []string{"-create-datapath"}, false, "create ODP datapath and exit")
mflag.BoolVar(&deleteDatapath, []string{"-delete-datapath"}, false, "delete ODP datapath and exit")
@@ -92,7 +99,7 @@ func main() {
mflag.StringVar(&iprangeCIDR, []string{"#iprange", "#-iprange", "-ipalloc-range"}, "", "IP address range reserved for automatic allocation, in CIDR notation")
mflag.StringVar(&ipsubnetCIDR, []string{"#ipsubnet", "#-ipsubnet", "-ipalloc-default-subnet"}, "", "subnet to allocate within by default, in CIDR notation")
mflag.IntVar(&peerCount, []string{"#initpeercount", "#-initpeercount", "-init-peer-count"}, 0, "number of peers in network (for IP address allocation)")
mflag.StringVar(&dockerAPI, []string{"#api", "#-api", "-docker-api"}, "", "Docker API endpoint, e.g. unix:///var/run/docker.sock")
mflag.StringVar(&dockerAPI, []string{"#api", "#-api", "-docker-api"}, defaultDockerHost, "Docker API endpoint")
mflag.BoolVar(&noDNS, []string{"-no-dns"}, false, "disable DNS server")
mflag.StringVar(&dnsDomain, []string{"-dns-domain"}, nameserver.DefaultDomain, "local domain to server requests for")
mflag.StringVar(&dnsListenAddress, []string{"-dns-listen-address"}, nameserver.DefaultListenAddress, "address to listen on for DNS requests")
@@ -101,6 +108,8 @@ func main() {
mflag.StringVar(&dnsEffectiveListenAddress, []string{"-dns-effective-listen-address"}, "", "address DNS will actually be listening, after Docker port mapping")
mflag.StringVar(&datapathName, []string{"-datapath"}, "", "ODP datapath name")
mflag.StringVar(&trustedSubnetStr, []string{"-trusted-subnets"}, "", "Command separated list of trusted subnets in CIDR notation")
// crude way of detecting that we probably have been started in a
// container, with `weave launch` --> suppress misleading paths in
// mflags error messages.
@@ -193,10 +202,7 @@ func main() {
Log.Println("Communication between peers is unencrypted.")
} else {
config.Password = []byte(password)
Log.Println("Communication between peers is encrypted.")
// fastdp doesn't support encryption
fastDPOverlay = nil
Log.Println("Communication between peers via untrusted networks is encrypted.")
}
overlays := weave.NewOverlaySwitch()
@@ -237,6 +243,10 @@ func main() {
networkConfig.PacketLogging = nopPacketLogging{}
}
if config.TrustedSubnets, err = parseTrustedSubnets(trustedSubnetStr); err != nil {
Log.Fatal("Unable to parse trusted subnets: ", err)
}
router := weave.NewNetworkRouter(config, networkConfig, name, nickName, overlays)
Log.Println("Our name is", router.Ourself)
@@ -245,6 +255,8 @@ func main() {
dc, err := docker.NewClient(dockerAPI)
if err != nil {
Log.Fatal("Unable to start docker client: ", err)
} else {
Log.Info(dc.Info())
}
dockerCli = dc
}
@@ -405,6 +417,24 @@ func determineQuorum(initPeerCountFlag int, peers []string) uint {
return quorum
}
func parseTrustedSubnets(trustedSubnetStr string) ([]*net.IPNet, error) {
trustedSubnets := []*net.IPNet{}
if trustedSubnetStr == "" {
return trustedSubnets, nil
}
for _, subnetStr := range strings.Split(trustedSubnetStr, ",") {
_, subnet, err := net.ParseCIDR(subnetStr)
if err != nil {
return nil, err
}
trustedSubnets = append(trustedSubnets, subnet)
}
return trustedSubnets, nil
}
func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) {
protocol := "tcp"
if strings.HasPrefix(httpAddr, "/") {
+2 -7
View File
@@ -1,12 +1,7 @@
// +build !noop
// +build !iface,!mcast
package main
import (
weavenet "github.com/weaveworks/weave/net"
)
func checkNetwork() error {
_, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe")
return err
return nil
}
+12
View File
@@ -0,0 +1,12 @@
// +build iface,!mcast
package main
import (
weavenet "github.com/weaveworks/weave/net"
)
func checkNetwork() error {
_, err := weavenet.EnsureInterface("ethwe")
return err
}
@@ -0,0 +1,12 @@
// +build iface,mcast
package main
import (
weavenet "github.com/weaveworks/weave/net"
)
func checkNetwork() error {
_, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe")
return err
}
-7
View File
@@ -1,7 +0,0 @@
// +build noop
package main
func checkNetwork() error {
return nil
}
+1
View File
@@ -39,6 +39,7 @@ func callWeave(args ...string) ([]byte, []byte, error) {
}
propagateEnv("DOCKER_BRIDGE")
propagateEnv("DOCKER_HOST")
// Propogage WEAVE_DEBUG, to make debugging easier.
propagateEnv("WEAVE_DEBUG")
+8 -2
View File
@@ -53,8 +53,14 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
Log.Infof("Leaving container alone because %s", err)
} else {
Log.Infof("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " "))
if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil {
return err
if i.proxy.NoMulticastRoute {
if err := addVolume(hostConfig, i.proxy.weaveWaitNomcastVolume, "/w", "ro"); err != nil {
return err
}
} else {
if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil {
return err
}
}
if err := i.setWeaveWaitEntrypoint(container); err != nil {
return err
+134 -22
View File
@@ -1,6 +1,7 @@
package proxy
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
@@ -8,6 +9,7 @@ import (
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
@@ -16,15 +18,15 @@ import (
docker "github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
weavedocker "github.com/weaveworks/weave/common/docker"
"github.com/weaveworks/weave/nameserver"
)
const (
defaultCaFile = "ca.pem"
defaultKeyFile = "key.pem"
defaultCertFile = "cert.pem"
dockerSock = "/var/run/docker.sock"
dockerSockUnix = "unix://" + dockerSock
weaveSock = "/var/run/weave/weave.sock"
weaveSockUnix = "unix://" + weaveSock
)
var (
@@ -46,14 +48,16 @@ type Config struct {
HostnameFromLabel string
HostnameMatch string
HostnameReplacement string
Image string
ListenAddrs []string
RewriteInspect bool
NoDefaultIPAM bool
NoRewriteHosts bool
TLSConfig TLSConfig
Version string
WithDNS bool
WithoutDNS bool
NoMulticastRoute bool
DockerHost string
}
type wait struct {
@@ -65,13 +69,14 @@ type wait struct {
type Proxy struct {
sync.Mutex
Config
client *docker.Client
dockerBridgeIP string
hostnameMatchRegexp *regexp.Regexp
weaveWaitVolume string
weaveWaitNoopVolume string
normalisedAddrs []string
waiters map[*http.Request]*wait
client *docker.Client
dockerBridgeIP string
hostnameMatchRegexp *regexp.Regexp
weaveWaitVolume string
weaveWaitNoopVolume string
weaveWaitNomcastVolume string
normalisedAddrs []string
waiters map[*http.Request]*wait
}
func NewProxy(c Config) (*Proxy, error) {
@@ -81,14 +86,16 @@ func NewProxy(c Config) (*Proxy, error) {
Log.Fatalf("Could not configure tls for proxy: %s", err)
}
// We pin the protocol version to 1.15 (which corresponds to
// Docker 1.3.x; the earliest version supported by weave) in order
// We pin the protocol version to 1.18 (which corresponds to
// Docker 1.6.x; the earliest version supported by weave) in order
// to insulate ourselves from breaking changes to the API, as
// happened in 1.20 (Docker 1.8.0) when the presentation of
// volumes changed in `inspect`.
client, err := weavedocker.NewVersionedClient(dockerSockUnix, "1.15")
client, err := weavedocker.NewVersionedClient(c.DockerHost, "1.18")
if err != nil {
return nil, err
} else {
Log.Info(client.Info())
}
p.client = client.Client
@@ -132,7 +139,16 @@ func (proxy *Proxy) AttachExistingContainers() {
}
func (proxy *Proxy) Dial() (net.Conn, error) {
return net.Dial("unix", dockerSock)
proto := "tcp"
addr := proxy.Config.DockerHost
switch {
case strings.HasPrefix(addr, "unix://"):
proto = "unix"
addr = strings.TrimPrefix(addr, "unix://")
case strings.HasPrefix(addr, "tcp://"):
addr = strings.TrimPrefix(addr, "tcp://")
}
return net.Dial(proto, addr)
}
func (proxy *Proxy) findWeaveWaitVolumes() error {
@@ -140,7 +156,10 @@ func (proxy *Proxy) findWeaveWaitVolumes() error {
if proxy.weaveWaitVolume, err = proxy.findVolume("/w"); err != nil {
return err
}
proxy.weaveWaitNoopVolume, err = proxy.findVolume("/w-noop")
if proxy.weaveWaitNoopVolume, err = proxy.findVolume("/w-noop"); err != nil {
return err
}
proxy.weaveWaitNomcastVolume, err = proxy.findVolume("/w-nomcast")
return err
}
@@ -186,7 +205,12 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (proxy *Proxy) Listen() []net.Listener {
listeners := []net.Listener{}
proxy.normalisedAddrs = []string{}
unixAddrs := []string{}
for _, addr := range proxy.ListenAddrs {
if strings.HasPrefix(addr, "unix://") || strings.HasPrefix(addr, "/") {
unixAddrs = append(unixAddrs, addr)
continue
}
listener, normalisedAddr, err := proxy.listen(addr)
if err != nil {
Log.Fatalf("Cannot listen on %s: %s", addr, err)
@@ -195,6 +219,20 @@ func (proxy *Proxy) Listen() []net.Listener {
proxy.normalisedAddrs = append(proxy.normalisedAddrs, normalisedAddr)
}
if len(unixAddrs) > 0 {
listener, _, err := proxy.listen(weaveSockUnix)
if err != nil {
Log.Fatalf("Cannot listen on %s: %s", weaveSockUnix, err)
}
listeners = append(listeners, listener)
if err := proxy.symlink(unixAddrs); err != nil {
Log.Fatalf("Cannot listen on unix sockets: %s", err)
}
proxy.normalisedAddrs = append(proxy.normalisedAddrs, weaveSockUnix)
}
for _, addr := range proxy.normalisedAddrs {
Log.Infoln("proxy listening on", addr)
}
@@ -291,8 +329,10 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) {
if err != nil {
return nil, "", err
}
if err = copyOwnerAndPermissions(dockerSock, addr); err != nil {
return nil, "", err
if strings.HasPrefix(proxy.Config.DockerHost, "unix://") {
if err = copyOwnerAndPermissions(strings.TrimPrefix(proxy.Config.DockerHost, "unix://"), addr); err != nil {
return nil, "", err
}
}
default:
@@ -405,6 +445,9 @@ func (proxy *Proxy) attach(container *docker.Container, orDie bool) error {
}
}
}
if proxy.NoMulticastRoute {
args = append(args, "--no-multicast-route")
}
if orDie {
args = append(args, "--or-die")
}
@@ -476,10 +519,6 @@ func (proxy *Proxy) getDNSDomain() (domain string) {
return ""
}
if proxy.WithDNS {
domain = nameserver.DefaultDomain
}
weaveContainer, err := proxy.client.InspectContainer("weave")
var weaveIP string
if err == nil && weaveContainer.NetworkSettings != nil {
@@ -527,3 +566,76 @@ func (proxy *Proxy) updateContainerNetworkSettings(container jsonObject) error {
networkSettings["IPPrefixLen"], _ = nets[0].Mask.Size()
return nil
}
func (proxy *Proxy) symlink(unixAddrs []string) (err error) {
var container *docker.Container
binds := []string{"/var/run/weave:/var/run/weave"}
froms := []string{}
for _, addr := range unixAddrs {
if addr == weaveSockUnix {
continue
}
from := strings.TrimPrefix(addr, "unix://")
dir := filepath.Dir(from)
binds = append(binds, dir+":"+filepath.Join("/host", dir))
froms = append(froms, filepath.Join("/host", from))
proxy.normalisedAddrs = append(proxy.normalisedAddrs, addr)
}
if len(froms) == 0 {
return
}
env := []string{
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
}
if val := os.Getenv("WEAVE_DEBUG"); val != "" {
env = append(env, fmt.Sprintf("%s=%s", "WEAVE_DEBUG", val))
}
container, err = proxy.client.CreateContainer(docker.CreateContainerOptions{
Config: &docker.Config{
Image: proxy.Image,
Entrypoint: []string{"/home/weave/symlink", weaveSock},
Cmd: froms,
Env: env,
},
HostConfig: &docker.HostConfig{Binds: binds},
})
if err != nil {
return
}
defer func() {
err2 := proxy.client.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID})
if err == nil {
err = err2
}
}()
err = proxy.client.StartContainer(container.ID, nil)
if err != nil {
return
}
var buf bytes.Buffer
err = proxy.client.AttachToContainer(docker.AttachToContainerOptions{
Container: container.ID,
ErrorStream: &buf,
Logs: true,
Stderr: true,
})
if err != nil {
return
}
var rc int
rc, err = proxy.client.WaitContainer(container.ID)
if err != nil {
return
}
if rc != 0 {
err = errors.New(buf.String())
}
return
}
+8 -2
View File
@@ -41,8 +41,14 @@ func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error {
return err
}
} else {
if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil {
return err
if i.proxy.NoMulticastRoute {
if err := addVolume(hostConfig, i.proxy.weaveWaitNomcastVolume, "/w", "ro"); err != nil {
return err
}
} else {
if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil {
return err
}
}
if dnsDomain := i.proxy.getDNSDomain(); dnsDomain != "" {
if err := i.proxy.setWeaveDNS(hostConfig, container.Config.Hostname, dnsDomain); err != nil {
+32 -6
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"net"
"sync"
"syscall"
"time"
"github.com/weaveworks/go-odp/odp"
@@ -40,6 +41,7 @@ type FastDatapath struct {
dpif *odp.Dpif
dp odp.DatapathHandle
deleteFlowsCount uint64
missCount uint64
missHandlers map[odp.VportID]missHandler
localPeer *mesh.Peer
peers *mesh.Peers
@@ -109,6 +111,10 @@ func NewFastDatapath(config FastDatapathConfig) (*FastDatapath, error) {
forwarders: make(map[mesh.PeerName]*fastDatapathForwarder),
}
// This delete happens asynchronously in the kernel, meaning that
// we can sometimes fail to recreate the vxlan vport with EADDRINUSE -
// consequently we retry a small number of times in
// getVxlanVportIDHarder() to compensate.
if err := fastdp.deleteVxlanVports(); err != nil {
return nil, err
}
@@ -125,7 +131,7 @@ func NewFastDatapath(config FastDatapathConfig) (*FastDatapath, error) {
// numbers to be independent, but working out how to specify
// them on the connecting side. So we can wait to find out if
// anyone wants that.
fastdp.mainVxlanVportID, err = fastdp.getVxlanVportID(config.Port + 1)
fastdp.mainVxlanVportID, err = fastdp.getVxlanVportIDHarder(config.Port+1, 5, time.Millisecond*10)
if err != nil {
return nil, err
}
@@ -216,8 +222,13 @@ func (fastdp fastDatapathBridge) String() string {
return fmt.Sprint(fastdp.dpname, " (via ODP)")
}
func (fastDatapathBridge) Stats() map[string]int {
return nil
func (fastdp fastDatapathBridge) Stats() map[string]int {
lock := fastdp.startLock()
defer lock.unlock()
return map[string]int{
"FlowMisses": int(fastdp.missCount),
}
}
var routerBridgePortID = bridgePortID{router: true}
@@ -400,6 +411,20 @@ func (fastdp fastDatapathOverlay) StartConsumingPackets(localPeer *mesh.Peer, pe
return nil
}
func (fastdp *FastDatapath) getVxlanVportIDHarder(udpPort int, retries int, duration time.Duration) (odp.VportID, error) {
var vxlanVportID odp.VportID
var err error
for try := 0; try < retries; try++ {
vxlanVportID, err = fastdp.getVxlanVportID(udpPort)
if err == nil || err != odp.NetlinkError(syscall.EADDRINUSE) {
return vxlanVportID, err
}
log.Warning("Address already in use creating vxlan vport ", udpPort, " - retrying")
time.Sleep(duration)
}
return 0, err
}
func (fastdp *FastDatapath) getVxlanVportID(udpPort int) (odp.VportID, error) {
fastdp.lock.Lock()
defer fastdp.lock.Unlock()
@@ -509,9 +534,8 @@ type fastDatapathForwarder struct {
func (fastdp fastDatapathOverlay) PrepareConnection(params mesh.OverlayConnectionParams) (mesh.OverlayConnection, error) {
if params.SessionKey != nil {
// No encryption suport in fastdp. The weaver main.go
// is responsible for ensuring this doesn't happen.
log.Fatal("Attempt to use FastDatapath with encryption")
// No encryption support in fastdp
return nil, fmt.Errorf("encryption not supported")
}
vxlanVportID := fastdp.mainVxlanVportID
@@ -937,6 +961,8 @@ func (fastdp *FastDatapath) Miss(packet []byte, fks odp.FlowKeys) error {
lock := fastdp.startLock()
defer lock.unlock()
fastdp.missCount++
handler := fastdp.getMissHandler(ingress)
if handler == nil {
return nil
+10 -2
View File
@@ -204,8 +204,16 @@ func (osw *OverlaySwitch) PrepareConnection(params mesh.OverlayConnectionParams)
subConn, err := overlay.PrepareConnection(params)
if err != nil {
fwd.stopFrom(0)
return nil, err
log.Infof("Unable to use %s for connection to %s(%s): %s",
overlay.name,
params.RemotePeer.Name,
params.RemotePeer.NickName,
err)
// failed to start subforwarder - record overlay name and continue
fwd.forwarders[i] = subForwarder{
overlayName: overlay.name,
}
continue
}
subFwd := subConn.(OverlayForwarder)
+1 -1
View File
@@ -11,7 +11,7 @@ You only need to build weave if you want to work on the weave codebase
Apart from the `weave` shell script, weave is delivered as a set of
container images. There is no distribution-specific packaging, so in
principle it shouldn't matter which Linux distribution you build
under. But naturally, Docker is a prerequisite (version 1.3.1 or
under. But naturally, Docker is a prerequisite (version 1.6.0 or
later). And it is difficult to build under Fedora because [Fedora
does not include static
libraries](http://fedoraproject.org/wiki/Packaging:Guidelines#Packaging_Static_Libraries).
+16 -17
View File
@@ -63,7 +63,9 @@ Weave automatically chooses the fastest available method to transport
data between peers. The most performant of these ('fastdp') offers
near-native throughput and latency but does not support encryption;
consequently supplying a password will cause the router to fall back
to a slower mode ('sleeve') that does.
to a slower mode ('sleeve') that does, for connections that traverse
untrusted networks (see the [security](#security) section for more
details).
Even when encryption is not in use, certain adverse network conditions
will cause this fallback to occur dynamically; in these circumstances,
@@ -91,21 +93,12 @@ re-attached to the weave network by the weave Docker API proxy.
### <a name="plugin"></a>Docker network plugin
Alternatively, you can use weave as a Docker plugin. First you must
configure the Docker daemon to use a cluster store as documented
[here](https://docs.docker.com/engine/userguide/networking/dockernetworks/#an-overlay-network);
you can then launch weave and create a network:
$ weave launch
$ weave launch-plugin
$ docker network create --driver=weave weave
and then start a container:
Alternatively, you can use weave as a Docker plugin. A Docker network
named `weave` is created by `weave launch`, which you can use like this:
$ docker run --net=weave -ti ubuntu
For more details see the
[plugin README](https://github.com/weaveworks/docker-plugin).
For more details see the [plugin documentation](plugin.html).
### <a name="addressing"></a>Address allocation
@@ -321,10 +314,16 @@ way to generate a random password which satsifies this requirement is
< /dev/urandom tr -dc A-Za-z0-9 | head -c9 ; echo
The same password must be specified for all weave peers. Note that
supplying a password will [cause weave to fall back to a slower
method](#fast-data-path) for transporting data between
peers.
The same password must be specified for all weave peers; by default
both control and data plane traffic will then use authenticated
encryption. If some of your peers are colocated in a trusted network
(for example within the boundary of your own datacentre) you can use
the `--trusted-subnets` argument to `weave launch` to selectively
disable data plane encryption as an optimisation. Both peers must
consider the other to be in a trusted subnet for this to take place -
if they do not, weave will [fall back to a slower
method](#fast-data-path) for transporting data between peers as fast
datapath does not support encryption.
Be aware that:
+92
View File
@@ -0,0 +1,92 @@
---
title: Using Weave Net via Docker Networking
layout: default
---
New in Docker version 1.9 is a plugin mechanism to add different
network providers.
To install, download the `weave` script as per the [readme][].
The Weave Net plugin runs automatically when you `weave launch`. You
must still tell the weave peers to connect to one another, either via
`weave launch` or `weave connect`.
Note that the plugin container (`weaveplugin`) is run with
`--restart=always`, so that it is there after a restart or reboot. If
you remove this container (e.g. using `weave reset`) before removing
all endpoints created using `--net=weave`, Docker can
[hang](https://github.com/docker/libnetwork/issues/813).
## Starting a container:
$ docker run --net=weave -ti ubuntu
## Using WeaveDNS for Service Discovery:
You need to pass the additional arguments `--dns` and `-dns-search`,
for which we provide a helper in the weave script:
$ docker run --net=weave -h foo.weave.local $(weave dns-args) -tdi ubuntu
$ docker run --net=weave -h bar.weave.local $(weave dns-args) -ti ubuntu
# ping foo
For more details see the [Weave Service Discovery documentation][service-discovery].
## Under the hood
The Weave Net plugin actually provides *two* network drivers to Docker
- one named `weavemesh` that can operate without a [cluster
store](#cluster-store) (like Docker's overlay driver) and one named
`weave` that can only work with one.
### `weavemesh` driver
* Weave handles all co-ordination between hosts (referred to by Docker as a "local scope" driver)
* Supports only a single network (we create one named `weave` for you automatically)
* Uses Weave's partition tolerant IPAM
### `weave` driver
* This runs in what Docker call "global scope"; requires a cluster store
* Supports multiple networks (these must be created with `docker network create --driver weave ...`)
* Used with Docker's cluster store based IPAM
There's no specific documentation from Docker on using a cluster
store, but the first part of [Getting Started with Docker Multi-host Networking][docker-net]
should point the way.
Note that in the case of multiple docker networks, all containers are
on the same virtual network but Docker allocates their addresses on
different subnets so they cannot talk to each other directly.
## Plugin command-line arguments
If you want to give some arguments to the plugin independently, don't
use `weave launch`; instead run:
$ weave launch-router [other peers]
$ weave launch-plugin [plugin arguments]
The plugin command-line arguments are:
* `--log-level=debug|info|warning|error`, which tells the plugin
how much information to emit for debugging.
* `--mesh-network-name=<name>`: set it to blank to disable creation
of a default network, or a name of your own choice.
* `--no-multicast-route`: stop weave adding a static IP route for
multicast traffic on its interface
## Restarting
We start the plugin with a restart policy, because Docker attempts to
recreate networks on startup. If it isn't started automatically by
Docker with a restart policy, it's not possible to start it manually
because Docker is blocked.
Specifically, we use the policy `on-failure:3`, so Docker will give up
after three attempts if something just isn't working.
[readme]: https://github.com/weaveworks/weave/blob/master/README.md#installation
[service-discovery]: weavedns.html
[docker-net]: https://github.com/docker/docker/blob/master/docs/userguide/networking/get-started-overlay.md
+5 -5
View File
@@ -231,11 +231,11 @@ be able to launch a TLS-enabled proxy with:
host1$ weave launch-proxy
You can also manually configure the proxy's TLS. This is accomplished
by launching the proxy with the same TLS-related command-line flags as
supplied to the docker daemon. For example, if you have generated your
certificates and keys into the docker host's `/tls` directory, we can
launch the proxy with:
To disable auto-detection of TLS configuration, you can either pass
the `--no-detect-tls` flag, or manually configure the proxy's TLS with
the same TLS-related command-line flags as supplied to the docker
daemon. For example, if you have generated your certificates and keys
into the docker host's `/tls` directory, we can launch the proxy with:
host1$ weave launch-proxy --tlsverify --tlscacert=/tls/ca.pem \
--tlscert=/tls/server-cert.pem --tlskey=/tls/server-key.pem
+36 -23
View File
@@ -68,29 +68,33 @@ A status summary can be obtained with `weave status`:
````
$ weave status
Version: 1.1.0
Version: 1.1.0
Service: router
Protocol: weave 1..2
Name: 4a:0f:f6:ec:1c:93(host1)
Encryption: disabled
PeerDiscovery: enabled
Targets: [192.168.48.14 192.168.48.15]
Connections: 5 (1 established, 1 pending, 1 retrying, 1 failed, 1 connecting)
Peers: 3 (with 5 established, 1 pending connections)
Service: router
Protocol: weave 1..2
Name: 4a:0f:f6:ec:1c:93(host1)
Encryption: disabled
PeerDiscovery: enabled
Targets: [192.168.48.14 192.168.48.15]
Connections: 5 (1 established, 1 pending, 1 retrying, 1 failed, 1 connecting)
Peers: 3 (with 5 established, 1 pending connections)
TrustedSubnets: none
Service: ipam
Consensus: achieved
Range: 10.32.0.0-10.47.255.255
DefaultSubnet: 10.32.0.0/12
Service: ipam
Consensus: achieved
Range: 10.32.0.0-10.47.255.255
DefaultSubnet: 10.32.0.0/12
Service: dns
Domain: weave.local.
TTL: 1
Entries: 9
Service: dns
Domain: weave.local.
TTL: 1
Entries: 9
Service: proxy
Address: tcp://127.0.0.1:12375
Service: proxy
Address: tcp://127.0.0.1:12375
Service: plugin
DriverName: weave
````
@@ -129,6 +133,9 @@ state. Further details are available with
number of connections peers have to other peers. Further details are
available with [`weave status peers`](#weave-status-peers).
'TrustedSubnets' shows subnets which the router trusts as specified by
the `--trusted-subnets` option to `weave launch`.
There are further sections for the [IP address
allocator](ipam.html#troubleshooting),
[weaveDNS](weavedns.html#troubleshooting), and [Weave Docker API
@@ -141,8 +148,8 @@ obtained with `weave status connections`:
````
$ weave status connections
<- 192.168.48.12:33866 established fastdp 7e:21:4a:70:2f:45(host2)
<- 192.168.48.13:60773 pending fastdp 7e:ae:cd:d5:23:8d(host3)
<- 192.168.48.12:33866 established unencrypted fastdp 7e:21:4a:70:2f:45(host2)
<- 192.168.48.13:60773 pending encrypted fastdp 7e:ae:cd:d5:23:8d(host3)
-> 192.168.48.14:6783 retrying dial tcp4 192.168.48.14:6783: no route to host
-> 192.168.48.15:6783 failed dial tcp4 192.168.48.15:6783: no route to host, retry: 2015-08-06 18:55:38.246910357 +0000 UTC
-> 192.168.48.16:6783 connecting
@@ -162,8 +169,8 @@ The columns are as follows:
heartbeat
* `established` - TCP connection and corresponding UDP path are up
* Info - the failure reason for failed and retrying connections, or
the data transport method, remote peer name and nickname for
pending and established connections
the encryption mode, data transport method, remote peer name and
nickname for pending and established connections
### <a name="weave-status-peers"></a>List peers
@@ -228,6 +235,12 @@ inspect`:
$ weave report -f {% raw %}'{{.DNS.Domain}}'{% endraw %}
weave.local.
Weave adds a template function, `json`, which can be applied to get
results in JSON format.
$ weave report -f {% raw %}'{{json .DNS}}'{% endraw %}
{% raw %}{"Domain":"weave.local.","Upstream":["8.8.8.8","8.8.4.4"],"Address":"172.17.0.1:53","TTL":1,"Entries":null}{% endraw %}
### <a name="list-attached-containers"></a>List attached containers
weave ps
+2
View File
@@ -259,6 +259,7 @@ DNS:
Service: dns
Domain: weave.local.
Upstream: 8.8.8.8, 8.8.4.4
TTL: 1
Entries: 9
@@ -271,6 +272,7 @@ guide](troubleshooting.html#weave-status) for more detail.
The 'Service: dns' section is pertinent to weaveDNS, and includes:
* The local domain suffix which is being served
* The list of upstream servers used for resolving names not in the local domain
* The response ttl
* The total number of entries
+3
View File
@@ -14,4 +14,7 @@ start_container $HOST1 $C1/24 --name=c1
start_container $HOST2 $C2/24 --name=c2
assert_raises "exec_on $HOST1 c1 $PING $C2"
assert_raises "weave_on $HOST1 status connections | grep encrypted"
assert_raises "weave_on $HOST2 status connections | grep encrypted"
end_suite
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
. ./config.sh
start_suite "Optional encryption via trusted subnets"
# Determine subnet for hosts given either an IP or name. We need to resolve
# these entries on the remote hosts to make sure we get the private
# IP addresses in the Circle/GCE context
HOST1_IP=$($SSH $HOST1 "getent hosts $HOST1" | grep $HOST1 | cut -d ' ' -f 1)
HOST2_IP=$($SSH $HOST2 "getent hosts $HOST2" | grep $HOST2 | cut -d ' ' -f 1)
HOST1_CIDR=$($SSH $HOST1 "ip addr show" | grep -oP $HOST1_IP/[0-9]+)
HOST2_CIDR=$($SSH $HOST2 "ip addr show" | grep -oP $HOST2_IP/[0-9]+)
# Check asymmetric trust - connections should be encrypted
weave_on $HOST1 launch --password wfvAwt7sj --trusted-subnets $HOST2_CIDR
weave_on $HOST2 launch --password wfvAwt7sj $HOST1
assert_raises "weave_on $HOST1 status connections | grep encrypted"
assert_raises "weave_on $HOST2 status connections | grep encrypted"
weave_on $HOST1 stop
weave_on $HOST2 stop
# Check symmetric trust - overlay in plaintext
weave_on $HOST1 launch --password wfvAwt7sj --trusted-subnets $HOST2_CIDR
weave_on $HOST2 launch --password wfvAwt7sj --trusted-subnets $HOST1_CIDR $HOST1
assert_raises "weave_on $HOST1 status connections | grep unencrypted"
assert_raises "weave_on $HOST2 status connections | grep unencrypted"
end_suite
+33
View File
@@ -0,0 +1,33 @@
#! /bin/bash
. ./config.sh
show_multicast_route_on() {
exec_on $1 $2 ip route show 224.0.0.0/4
}
start_suite "--no-multicast-route operation"
# Ensure containers run either way have no multicast route
weave_on $HOST1 launch-router
weave_on $HOST1 launch-proxy --no-multicast-route
weave_on $HOST1 launch-plugin --no-multicast-route
start_container $HOST1 --no-multicast-route --name c1
proxy_start_container $HOST1 --name c2
start_container_local_plugin $HOST1 --name=c3
assert "show_multicast_route_on $HOST1 c1"
assert "show_multicast_route_on $HOST1 c2"
assert "show_multicast_route_on $HOST1 c3"
# Ensure current proxy options are obeyed on container start
docker_on $HOST1 stop c2
weave_on $HOST1 stop-proxy
weave_on $HOST1 launch-proxy
proxy docker_on $HOST1 start c2
assert "show_multicast_route_on $HOST1 c2" "224.0.0.0/4 dev ethwe "
end_suite
-1
View File
@@ -27,6 +27,5 @@ start_container $HOST1 $TARGET_IP/24 --name c2 -h $TARGET
check_dns assert_no_dns_record --without-dns
check_dns assert_dns_record
check_dns assert_dns_record --with-dns
end_suite
+5 -3
View File
@@ -9,18 +9,20 @@ docker_py_test() {
start_suite "Run docker-py test suite against the proxy"
# Work round https://github.com/docker/docker-py/issues/852
docker_on $HOST1 pull busybox:buildroot-2014.02 >/dev/null
# Get a list of the tests for use to shard
docker_on $HOST1 pull joffrey/docker-py >/dev/null
CANDIDATES=$(docker_on $HOST1 run \
joffrey/docker-py \
py.test --collect-only tests/integration_test.py \
| sed -En "s/\s*<UnitTestCase '([[:alpha:]]+)'>/\1/p")
py.test --collect-only tests/integration/ \
| sed -En "s/\s*<Module '([[:print:]]+)'>/\1/p")
i=0
TESTS=
for test in $CANDIDATES; do
if [ $(($i % $TOTAL_SHARDS)) -eq $SHARD ]; then
TESTS="$TESTS tests/integration_test.py::$test"
TESTS="$TESTS $test"
fi
i=$(($i + 1))
done
-5
View File
@@ -23,11 +23,6 @@ start_suite "Proxy registers containers with dns"
bridge_ip=$(weave_on $HOST1 docker-bridge-ip)
# Assert behaviour without weaveDNS running, but dns forced
weave_on $HOST1 launch-proxy --with-dns
do_assert_resolution assert_no_dns_record
weave_on $HOST1 stop-proxy
# Assert behaviour without weaveDNS
weave_on $HOST1 launch-proxy
do_assert_resolution assert_no_dns_record
+6
View File
@@ -28,4 +28,10 @@ weave_on $HOST1 stop
weave_on $HOST1 launch-proxy -H tcp://0.0.0.0:12375 -H unix:///var/run/weave/weave.sock
check
# Check we can use weave env/config with unix -Hs specified
weave_on $HOST1 stop
run_on $HOST1 "COVERAGE=$COVERAGE weave launch-proxy -H unix:///var/run/weave/weave.sock"
assert_raises "run_on $HOST1 'eval \$(weave env) ; docker $CMD'"
assert_raises "run_on $HOST1 'docker \$(weave config) $CMD'"
end_suite
@@ -2,10 +2,10 @@
. ./config.sh
start_suite "Boot the proxy should only listen on client's interface"
start_suite "Various launch-proxy configurations"
# Booting it over unix socket listens on unix socket
run_on $HOST1 COVERAGE=$COVERAGE sudo -E weave launch-proxy
run_on $HOST1 COVERAGE=$COVERAGE weave launch-proxy
assert_raises "run_on $HOST1 sudo docker -H unix:///var/run/weave/weave.sock ps"
assert_raises "proxy docker_on $HOST1 ps" 1
weave_on $HOST1 stop-proxy
@@ -17,11 +17,21 @@ assert_raises "proxy docker_on $HOST1 ps"
weave_on $HOST1 stop-proxy
# Booting it over tcp (no prefix) listens on tcp
DOCKER_CLIENT_ARGS="-H $HOST1:$DOCKER_PORT" $WEAVE launch-proxy
DOCKER_HOST=tcp://$HOST1:$DOCKER_PORT $WEAVE launch-proxy
assert_raises "run_on $HOST1 sudo docker -H unix:///var/run/weave/weave.sock ps" 1
assert_raises "proxy docker_on $HOST1 ps"
weave_on $HOST1 stop-proxy
# Booting it with -H outside /var/run/weave, still works
socket="$($SSH $HOST1 mktemp -d)/weave.sock"
weave_on $HOST1 launch-proxy -H unix://$socket
assert_raises "run_on $HOST1 sudo docker -H unix:///$socket ps" 0
weave_on $HOST1 stop-proxy
# Booting it against non-standard docker unix sock
run_on $HOST1 "DOCKER_HOST=unix:///var/run/alt-docker.sock COVERAGE=$COVERAGE weave launch-proxy -H tcp://0.0.0.0:12375"
assert_raises "proxy docker_on $HOST1 ps"
weave_on $HOST1 stop-proxy
# Booting it over tls errors
assert_raises "DOCKER_CLIENT_ARGS='--tls' weave_on $HOST1 launch-proxy" 1
+21
View File
@@ -0,0 +1,21 @@
#! /bin/bash
. ./config.sh
C1_NAME=c1.weave.local
C2_NAME=seetwo.weave.local
start_suite "Ping and DNS over cross-host weave network (via 'local' plugin)"
DNS_IP=$(weave_on $host docker-bridge-ip)
weave_on $HOST1 launch
weave_on $HOST2 launch $HOST1
start_container_local_plugin $HOST1 --name=c1 --hostname=$C1_NAME --dns=$DNS_IP
start_container_local_plugin $HOST2 --name=c2 --hostname=$C2_NAME --dns=$DNS_IP
assert_raises "exec_on $HOST1 c1 $PING $C2_NAME"
assert_raises "exec_on $HOST2 c2 $PING $C1_NAME"
end_suite
+1 -1
View File
@@ -2,7 +2,7 @@ This directory contains integration tests for weave.
## Requirements
You need two VMs with docker >=1.3.1 installed and listening on TCP
You need two VMs with docker >=1.6.0 installed and listening on TCP
port 2375 (see below). You also need to be able to ssh to these VMs,
preferably without having to input anything.
+13 -2
View File
@@ -151,7 +151,14 @@ start_container() {
start_container_with_dns() {
host=$1
shift 1
weave_on $host run --with-dns "$@" -t $DNS_IMAGE /bin/sh
weave_on $host run "$@" -t $DNS_IMAGE /bin/sh
}
start_container_local_plugin() {
host=$1
shift 1
# using ssh rather than docker -H because CircleCI docker client is older
$SSH $host docker run "$@" -dt --net=weave $SMALL_IMAGE /bin/sh
}
proxy_start_container() {
@@ -217,8 +224,12 @@ assert_dns_ptr_record() {
start_suite() {
for host in $HOSTS; do
[ -z "$DEBUG" ] || echo "Cleaning up on $host: removing all containers and resetting weave"
PLUGIN_ID=$(docker_on $host ps -aq --filter=name=weaveplugin)
PLUGIN_FILTER="cat"
[ -n "$PLUGIN_ID" ] && PLUGIN_FILTER="grep -v $PLUGIN_ID"
rm_containers $host $(docker_on $host ps -aq 2>/dev/null | $PLUGIN_FILTER)
run_on $host "docker network ls | grep -q ' weave ' && docker network rm weave" || true
weave_on $host reset 2>/dev/null
rm_containers $host $(docker_on $host ps -aq 2>/dev/null)
done
whitely echo "$@"
}
+3 -3
View File
@@ -11,7 +11,7 @@ set -e
: ${SSH_KEY_FILE:=$HOME/.ssh/gce_ssh_key}
: ${PROJECT:=positive-cocoa-90213}
: ${IMAGE:=ubuntu-14-04}
: ${TEMPLATE_NAME:=test-template-8}
: ${TEMPLATE_NAME:=test-template-9}
: ${ZONE:=us-central1-a}
: ${NUM_HOSTS:=5}
SUFFIX=""
@@ -73,7 +73,7 @@ curl -sSL https://get.docker.com/ | sh
apt-get update -qq;
apt-get install -q -y --force-yes --no-install-recommends ethtool;
usermod -a -G docker vagrant;
echo 'DOCKER_OPTS="-H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375 -s overlay"' >> /etc/default/docker;
echo 'DOCKER_OPTS="-H unix:///var/run/docker.sock -H unix:///var/run/alt-docker.sock -H tcp://0.0.0.0:2375 -s overlay"' >> /etc/default/docker;
service docker restart
EOF
# It seems we need a short delay for docker to start up, so I put this in
@@ -140,7 +140,7 @@ function hosts {
hosts="$hostname $hosts"
args="--add-host=$hostname:$(internal_ip $json $name) $args"
done
echo export SSH=ssh
echo export SSH=\"ssh -l vagrant\"
echo export HOSTS=\"$hosts\"
echo export ADD_HOST_ARGS=\"$args\"
rm $json
+2 -2
View File
@@ -6,7 +6,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
. ./config.sh
(cd ./tls && go get -tags netgo ./... && go run generate_certs.go $HOSTS)
(cd ./tls && ./tls $HOSTS)
echo "Copying weave images, scripts, and certificates to hosts, and"
echo " prefetch test images"
@@ -19,7 +19,7 @@ setup_host() {
run_on $HOST mkdir -p bin
upload_executable $HOST ../bin/docker-ns
upload_executable $HOST ../weave
rsync -az -e "$SSH" ./tls/ $HOST:~/tls
rsync -az -e "$SSH" --exclude=tls ./tls/ $HOST:~/tls
for IMG in $TEST_IMAGES ; do
docker_on $HOST inspect --format=" " $IMG >/dev/null 2>&1 || docker_on $HOST pull $IMG
done
-17
View File
@@ -6,7 +6,6 @@ import (
"reflect"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/require"
)
@@ -36,22 +35,6 @@ func stackTrace(all bool) string {
return string(buf[:stacklen])
}
// Borrowed from net/http tests:
// goTimeout runs f, failing t if f takes more than d to complete.
func RunWithTimeout(t *testing.T, d time.Duration, f func()) {
ch := make(chan bool, 2)
timer := time.AfterFunc(d, func() {
t.Errorf("Timeout expired after %v: stacks:\n%s", d, stackTrace(true))
ch <- true
})
defer timer.Stop()
go func() {
defer func() { ch <- true }()
f()
}()
<-ch
}
// TrimTestArgs finds the first -- in os.Args and trim all args before that
func TrimTestArgs() {
i, l := 0, len(os.Args)
+1 -1
View File
@@ -45,7 +45,7 @@ end
def tweak_docker_daemon(vm)
vm.provision :shell, :inline => <<SCRIPT
usermod -a -G docker vagrant
sed -i -e's%-H fd://%-H fd:// -H tcp://0.0.0.0:2375 -s overlay%' /lib/systemd/system/docker.service
sed -i -e's%-H fd://%-H fd:// -H unix:///var/run/alt-docker.sock -H tcp://0.0.0.0:2375 -s overlay%' /lib/systemd/system/docker.service
systemctl daemon-reload
systemctl restart docker
systemctl enable docker
+324 -145
View File
@@ -11,19 +11,14 @@ else
fi
IMAGE_VERSION=${WEAVE_VERSION:-$IMAGE_VERSION}
# - Docker versions prior to 1.2.0 cannot parse `--dns-search=.`
# - Docker versions prior to 1.3.0 fail to include the domain when
# setting the hostname, so lookups for non-fq names fail.
# - Docker version 1.3.0 contains the severe bug
# https://github.com/docker/docker/issues/8632
# - The weavexec image embeds a Docker 1.3.1 client. Misleading
# version mismatch errors are produced if the user is running with a
# client *and* server with a lower version.
# - Docker will complain if you run a client against a server older
# than that client.
# - Docker versions prior to 1.6 cannot pull images from DockerHub
# - The weavexec image embeds a Docker 1.6.2 client. Docker will give
# a "client is newer than server error" if the daemon has an older
# API version, which could be confusing if the user's Docker client
# is correctly matched with that older version.
#
# We therefore check that the user's Docker *client* is >= 1.3.1
MIN_DOCKER_VERSION=1.3.1
# We therefore check that the user's Docker *client* is >= 1.6
MIN_DOCKER_VERSION=1.6.0
# These are needed for remote execs, hence we introduce them here
DOCKERHUB_USER=${DOCKERHUB_USER:-weaveworks}
@@ -32,7 +27,6 @@ EXEC_IMAGE=$BASE_EXEC_IMAGE:$IMAGE_VERSION
PROXY_HOST=${PROXY_HOST:-$(echo "${DOCKER_HOST#tcp://}" | cut -s -d: -f1)}
PROXY_HOST=${PROXY_HOST:-127.0.0.1}
DOCKER_CLIENT_HOST=${DOCKER_CLIENT_HOST:-$DOCKER_HOST}
DOCKER_CLIENT_TLS_VERIFY=${DOCKER_CLIENT_TLS_VERIFY:-$DOCKER_TLS_VERIFY}
# Define some regular expressions for matching addresses.
# The regexp here is far from precise, but good enough.
@@ -48,24 +42,27 @@ weave setup
weave version
weave launch [--password <password>] [--nickname <nickname>]
[--ipalloc-range <cidr> [--ipalloc-default-subnet <cidr>]]
[--no-discovery] [--init-peer-count <count>] <peer> ...
[--no-discovery] [--init-peer-count <count>]
[--trusted-subnets <cidr>,...] <peer> ...
weave launch-router [--password <password>] [--nickname <nickname>]
[--ipalloc-range <cidr> [--ipalloc-default-subnet <cidr>]]
[--no-discovery] [--init-peer-count <count>] <peer> ...
weave launch-proxy [-H <endpoint>] [--with-dns | --without-dns]
weave launch-proxy [-H <endpoint>] [--without-dns]
[--no-default-ipalloc] [--no-rewrite-hosts]
[--no-multicast-route]
[--hostname-from-label <labelkey>]
[--hostname-match <regexp>]
[--hostname-replacement <replacement>]
[--rewrite-inspect]
weave launch-plugin
weave launch-plugin [--no-multicast-route]
weave env [--restore]
weave config
weave dns-args
weave connect [--replace] [<peer> ...]
weave forget <peer> ...
weave status [targets | connections | peers | dns]
weave report [-f <format>]
weave run [--with-dns | --without-dns] [--no-rewrite-hosts]
weave run [--without-dns] [--no-rewrite-hosts] [--no-multicast-route]
[<addr> ...] <docker run args> ...
weave start [<addr> ...] <container_id>
weave attach [<addr> ...] <container_id>
@@ -98,9 +95,25 @@ usage() {
exit 1
}
docker_sock() {
if [ -z "$DOCKER_HOST" ]; then
echo "/var/run/docker.sock"
return
fi
if echo "$DOCKER_HOST" | grep -q "^unix://" >/dev/null; then
echo "${DOCKER_HOST#unix://}"
fi
}
docker_sock_volume_mount() {
if [ -n "$(docker_sock)" ]; then
echo "-v $(docker_sock):$(docker_sock)"
fi
}
exec_remote() {
docker $DOCKER_CLIENT_ARGS run --rm --privileged --net=host \
-v /var/run/docker.sock:/var/run/docker.sock \
$(docker_sock_volume_mount) \
-v /proc:/hostproc \
-e PROCFS=/hostproc \
-e DOCKERHUB_USER="$DOCKERHUB_USER" \
@@ -113,9 +126,11 @@ exec_remote() {
-e WEAVE_CONTAINER_NAME \
-e WEAVE_MTU \
-e WEAVE_NO_FASTDP \
-e WEAVE_NO_BRIDGED_FASTDP \
-e WEAVE_NO_PLUGIN \
-e DOCKER_HOST \
-e DOCKER_BRIDGE \
-e DOCKER_CLIENT_HOST="$DOCKER_CLIENT_HOST" \
-e DOCKER_CLIENT_TLS_VERIFY="$DOCKER_CLIENT_TLS_VERIFY" \
-e DOCKER_CLIENT_ARGS \
-e PROXY_HOST="$PROXY_HOST" \
-e WEAVE_CIDR=none \
@@ -123,30 +138,54 @@ exec_remote() {
$WEAVEEXEC_DOCKER_ARGS $EXEC_IMAGE --local "$@"
}
# Given $1 and $2 as semantic version numbers like 3.1.2, return [ $1 < $2 ]
version_lt() {
VERSION_MAJOR=${1%.*.*}
REST=${1%.*} VERSION_MINOR=${REST#*.}
VERSION_PATCH=${1#*.*.}
MIN_VERSION_MAJOR=${2%.*.*}
REST=${2%.*} MIN_VERSION_MINOR=${REST#*.}
MIN_VERSION_PATCH=${2#*.*.}
if [ \( "$VERSION_MAJOR" -lt "$MIN_VERSION_MAJOR" \) -o \
\( "$VERSION_MAJOR" -eq "$MIN_VERSION_MAJOR" -a \
\( "$VERSION_MINOR" -lt "$MIN_VERSION_MINOR" -o \
\( "$VERSION_MINOR" -eq "$MIN_VERSION_MINOR" -a \
\( "$VERSION_PATCH" -lt "$MIN_VERSION_PATCH" \) \) \) \) ] ; then
return 0
fi
return 1
}
check_docker_version() {
if ! DOCKER_VERSION=$(docker -v | sed -n -e 's|^Docker version \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*|\1|p') || [ -z "$DOCKER_VERSION" ] ; then
echo "ERROR: Unable to parse docker version" >&2
exit 1
fi
DOCKER_VERSION_MAJOR=$(echo "$DOCKER_VERSION" | cut -d. -f 1)
DOCKER_VERSION_MINOR=$(echo "$DOCKER_VERSION" | cut -d. -f 2)
DOCKER_VERSION_PATCH=$(echo "$DOCKER_VERSION" | cut -d. -f 3)
MIN_DOCKER_VERSION_MAJOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 1)
MIN_DOCKER_VERSION_MINOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 2)
MIN_DOCKER_VERSION_PATCH=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 3)
if [ \( "$DOCKER_VERSION_MAJOR" -lt "$MIN_DOCKER_VERSION_MAJOR" \) -o \
\( "$DOCKER_VERSION_MAJOR" -eq "$MIN_DOCKER_VERSION_MAJOR" -a \
\( "$DOCKER_VERSION_MINOR" -lt "$MIN_DOCKER_VERSION_MINOR" -o \
\( "$DOCKER_VERSION_MINOR" -eq "$MIN_DOCKER_VERSION_MINOR" -a \
\( "$DOCKER_VERSION_PATCH" -lt "$MIN_DOCKER_VERSION_PATCH" \) \) \) \) ] ; then
if version_lt $DOCKER_VERSION $MIN_DOCKER_VERSION ; then
echo "ERROR: weave requires Docker version $MIN_DOCKER_VERSION or later; you are running $DOCKER_VERSION" >&2
exit 1
fi
}
check_docker_server_api_version() {
# Cope with various versions of `docker version` output format
if ! DOCKER_API_VERSION=$(docker version -f '{{.Server.APIVersion}}' 2> /dev/null ) ; then
if ! DOCKER_API_VERSION=$(docker version -f '{{.Server.ApiVersion}}' 2> /dev/null ) ; then
if ! DOCKER_API_VERSION=$(docker version | sed -n -e 's|^Server API version: *\([0-9][0-9]*\.[0-9][0-9]\).*|\1|p') ; then
echo "ERROR: Unable to determine docker version" >&2
exit 1
fi
fi
fi
if version_lt ${DOCKER_API_VERSION}.0 ${1}.0 ; then
return 1
fi
}
enforce_docker_bridge_addr_assign_type() {
if ! ADDR_ASSIGN_TYPE=$(cat /sys/class/net/$DOCKER_BRIDGE/addr_assign_type 2>/dev/null) ; then
echo "Could not determine address assignment type of $DOCKER_BRIDGE" >&2
@@ -264,20 +303,42 @@ elif [ "$1" = "run" -a -z "$IS_LOCAL" ] ; then
# because we are passing arbitrary arguments on to docker run, and
# we can't rely on our baked-in docker to support those arguments.
shift 1
if [ "$1" != "--without-dns" ] ; then
DNS_ARGS=$(exec_remote dns-args "$@" || true)
fi
shift $(dns_arg_count "$@")
if [ "$1" = "--no-rewrite-hosts" ] ; then
REWRITE_HOSTS=1
NO_MULTICAST_ROUTE=
while [ $# -gt 0 ]; do
case "$1" in
--no-rewrite-hosts)
REWRITE_HOSTS=
;;
--no-multicast-route)
NO_MULTICAST_ROUTE=1
;;
*)
break
;;
esac
shift
else
done
if [ -n "$REWRITE_HOSTS" ] ; then
extra_hosts_args "$@"
DNS_EXTRA_HOSTS_ARGS="--rewrite-hosts $DNS_EXTRA_HOSTS_ARGS"
fi
if [ -n "$NO_MULTICAST_ROUTE" ] ; then
ATTACH_ARGS="--no-multicast-route"
fi
collect_cidr_args "$@"
shift $CIDR_ARG_COUNT
CONTAINER=$(docker $DOCKER_CLIENT_ARGS run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
exec_remote attach $CIDR_ARGS --or-die $DNS_EXTRA_HOSTS_ARGS $CONTAINER >/dev/null
exec_remote attach $CIDR_ARGS --or-die $DNS_EXTRA_HOSTS_ARGS $ATTACH_ARGS $CONTAINER >/dev/null
echo $CONTAINER
exit 0
fi
@@ -298,9 +359,11 @@ PROCFS=${PROCFS:-/proc}
DOCKER_BRIDGE=${DOCKER_BRIDGE:-docker0}
CONTAINER_NAME=${WEAVE_CONTAINER_NAME:-weave}
BRIDGE=weave
# This value is overridden when the datapath is used unbridged
DATAPATH=datapath
BRIDGE_IFNAME=vethwe-bridge
DATAPATH_IFNAME=vethwe-datapath
CONTAINER_IFNAME=ethwe
# ROUTER_HOSTNETNS_IFNAME is only used for fastdp with encryption
ROUTER_HOSTNETNS_IFNAME=veth-weave
PORT=${WEAVE_PORT:-6783}
HTTP_PORT=6784
PROXY_PORT=12375
@@ -402,22 +465,58 @@ random_mac() {
# weave and docker specific helpers
######################################################################
create_bridge() {
# Detect the current bridge/datapath state. When invoked, the values of
# $BRIDGE and $DATAPATH are expected to be distinct. $BRIDGE_TYPE and
# $DATAPATH are set correctly on success; failure indicates that the
# bridge/datapath devices have yet to be configured. If netdevs do exist
# but are in an inconsistent state the script aborts with an error.
detect_bridge_type() {
BRIDGE_TYPE=
if [ ! -d /sys/class/net/$BRIDGE ] ; then
if [ -n "$WEAVE_NO_FASTDP" ] ; then
BRIDGE_TYPE=bridge
elif docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --create-datapath --datapath=$BRIDGE ; then
BRIDGE_TYPE=fastdp
elif [ $? = 17 ] ; then
# Exit status of 17 means the kernel doesn't have ODP
if [ -d /sys/class/net/$DATAPATH ] ; then
# Unfortunately there's no simple way to positively check whether
# $DATAPATH is an ODP netdev so we have to make sure it isn't
# a bridge instead (and that $BRIDGE is).
if [ ! -d /sys/class/net/$DATAPATH/bridge -a -d /sys/class/net/$BRIDGE/bridge ] ; then
BRIDGE_TYPE=bridged_fastdp
else
echo "Inconsistent bridge state detected. Please do 'weave reset' and try again" >&2
exit 1
fi
elif [ -d /sys/class/net/$BRIDGE ] ; then
if [ -d /sys/class/net/$BRIDGE/bridge ] ; then
BRIDGE_TYPE=bridge
else
return 1
BRIDGE_TYPE=fastdp
# The datapath is the bridge when there is no intermediary
DATAPATH="$BRIDGE"
fi
else
# No bridge/datapath devices configured
return 1
fi
}
create_bridge() {
if ! detect_bridge_type ; then
BRIDGE_TYPE=bridge
if [ -z "$WEAVE_NO_FASTDP" ] ; then
BRIDGE_TYPE=bridged_fastdp
if [ -n "$WEAVE_NO_BRIDGED_FASTDP" ] ; then
BRIDGE_TYPE=fastdp
# The datapath is the bridge when there is no intermediary
DATAPATH="$BRIDGE"
fi
if docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --create-datapath --datapath=$DATAPATH ; then
: # ODP datapath created successfully
elif [ $? = 17 ] ; then
# Exit status of 17 means the kernel doesn't have ODP
BRIDGE_TYPE=bridge
else
return 1
fi
fi
init_bridge_$BRIDGE_TYPE
init_$BRIDGE_TYPE
# Drop traffic from Docker bridge to Weave; it can break
# subnet isolation
@@ -426,6 +525,13 @@ create_bridge() {
run_iptables -t filter -I FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP
fi
[ -n "$DOCKER_BRIDGE_IP" ] || docker_bridge_ip
# forbid traffic to the Weave port from other containers
add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p tcp --dst $DOCKER_BRIDGE_IP --dport $PORT -j DROP
add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $PORT -j DROP
add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $(($PORT + 1)) -j DROP
# let DNS traffic to weaveDNS, since otherwise it might get blocked by the likes of UFW
add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p udp --dport 53 -j ACCEPT
add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p tcp --dport 53 -j ACCEPT
@@ -438,14 +544,8 @@ create_bridge() {
run_iptables -t nat -N WEAVE >/dev/null 2>&1 || true
add_iptables_rule nat POSTROUTING -j WEAVE
else
# Detect whether fast datapath is in use on
# $BRIDGE. Unfortunately there's no simple way to positively
# check whether $BRIDGE is a ODP netdev, so we have to check
# whether it is a bridge instead.
if [ -d /sys/class/net/$BRIDGE/bridge ] ; then
BRIDGE_TYPE=bridge
if [ -n "$LAUNCHING_ROUTER" -a -z "$WEAVE_NO_FASTDP" ] ; then
if [ -n "$LAUNCHING_ROUTER" ] ; then
if [ "$BRIDGE_TYPE" = bridge -a -z "$WEAVE_NO_FASTDP" ] ; then
cat <<EOF >&1
WEAVE_NO_FASTDP is not set, but there is already a bridge present of
the wrong type for fast datapath. Please do 'weave reset' to remove
@@ -453,10 +553,7 @@ the bridge first.
EOF
return 1
fi
else
BRIDGE_TYPE=fastdp
if [ -n "$WEAVE_NO_FASTDP" ] ; then
if [ "$BRIDGE_TYPE" != bridge -a -n "$WEAVE_NO_FASTDP" ] ; then
cat <<EOF >&1
WEAVE_NO_FASTDP is set, but there is already a weave fast datapath
bridge present. Please do 'weave reset' to remove the bridge first.
@@ -483,7 +580,7 @@ EOF
configure_arp_cache $BRIDGE
}
init_bridge_fastdp() {
init_fastdp() {
# GCE has the lowest underlay network MTU we're likely to encounter on
# a local network, at 1460 bytes. To get the overlay MTU from that we
# subtract 20 bytes for the outer IPv4 header, 8 bytes for the outer
@@ -492,11 +589,14 @@ init_bridge_fastdp() {
MTU=${WEAVE_MTU:-1410}
# create_bridge already created the datapath netdev
ip link set dev $BRIDGE mtu $MTU
ip link set dev $DATAPATH mtu $MTU
}
init_bridge_bridge() {
MTU=${WEAVE_MTU:-65535}
init_bridge() {
# Observe any MTU that is already set
if [ -z "$MTU" ] ; then
MTU=${WEAVE_MTU:-65535}
fi
ip link add name $BRIDGE type bridge
@@ -514,6 +614,36 @@ init_bridge_bridge() {
ip link del dev v${CONTAINER_IFNAME}du
}
init_bridged_fastdp() {
# Initialise the datapath as normal. NB sets MTU for use below
init_fastdp
# Create linking veth pair. We do this before initialising the bridge
# so that `ip link show` displays the datapath, linking veths and
# the bridge in natural order
ip link del $DATAPATH_IFNAME >/dev/null 2>&1 || true
ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true
ip link add name $BRIDGE_IFNAME mtu $MTU type veth peer name $DATAPATH_IFNAME mtu $MTU || return 1
# Initialise the bridge using fast datapath MTU
init_bridge
# Link intermediary bridge and datapath
if ! ip link set $DATAPATH_IFNAME up ||
! ip link set $BRIDGE_IFNAME up ||
! add_iface_fastdp $DATAPATH_IFNAME || ! ip link set $BRIDGE_IFNAME master $BRIDGE ; then
# Failed to link bridge and datapath - clean up
ip link del $BRIDGE >/dev/null 2>&1 || true
ip link del $DATAPATH_IFNAME >/dev/null 2>&1 || true
ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true
docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --delete-datapath --datapath=$DATAPATH >/dev/null 2>&1 || true
return 1
fi
# Finally, bring the datapath up
ip link set dev $DATAPATH up
}
ethtool_tx_off_fastdp() {
true
}
@@ -522,21 +652,42 @@ ethtool_tx_off_bridge() {
ethtool -K $1 tx off >/dev/null
}
ethtool_tx_off_bridged_fastdp() {
true
}
destroy_bridge() {
if [ -d /sys/class/net/$BRIDGE ] ; then
if [ -d /sys/class/net/$BRIDGE/bridge ] ; then
ip link del dev $BRIDGE
else
docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --delete-datapath --datapath=$BRIDGE
# It's important that detect_bridge_type has not been called so
# we have distinct values for $BRIDGE and $DATAPATH. Make best efforts
# to remove netdevs of any type with those names so `weave reset` can
# recover from inconsistent states.
for NETDEV in $BRIDGE $DATAPATH ; do
if [ -d /sys/class/net/$NETDEV ] ; then
if [ -d /sys/class/net/$NETDEV/bridge ] ; then
ip link del $NETDEV
else
docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --delete-datapath --datapath=$NETDEV
fi
fi
fi
done
# Remove any lingering bridged fastdp veth
ip link del $DATAPATH_IFNAME >/dev/null 2>&1 || true
ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true
if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then
run_iptables -t filter -D FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP 2>/dev/null || true
fi
[ -n "$DOCKER_BRIDGE_IP" ] || docker_bridge_ip
run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p udp --dport 53 -j ACCEPT >/dev/null 2>&1 || true
run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p tcp --dport 53 -j ACCEPT >/dev/null 2>&1 || true
run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p tcp --dst $DOCKER_BRIDGE_IP --dport $PORT -j DROP >/dev/null 2>&1 || true
run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $PORT -j DROP >/dev/null 2>&1 || true
run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $(($PORT + 1)) -j DROP >/dev/null 2>&1 || true
run_iptables -t filter -D FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT 2>/dev/null || true
run_iptables -t nat -F WEAVE >/dev/null 2>&1 || true
run_iptables -t nat -D POSTROUTING -j WEAVE >/dev/null 2>&1 || true
@@ -634,13 +785,17 @@ connect_container_to_bridge() {
}
add_iface_fastdp() {
docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --datapath=$BRIDGE --add-datapath-iface=$1
docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --datapath=$DATAPATH --add-datapath-iface=$1
}
add_iface_bridge() {
ip link set $1 master $BRIDGE
}
add_iface_bridged_fastdp() {
add_iface_bridge "$@"
}
ask_version() {
if ! DOCKERIMAGE=$(docker inspect --format='{{.Image}}' $1 2>/dev/null) ; then
if ! DOCKERIMAGE=$(docker inspect --format='{{.Id}}' $2 2>/dev/null) ; then
@@ -651,47 +806,23 @@ ask_version() {
}
router_opts_fastdp() {
if [ -z "$WEAVE_PASSWORD" ] ; then
echo "--datapath $BRIDGE"
else
# When using encryption, we still do bridging on the ODP
# datapath, because you can 'weave launch' without encryption
# and then later restart the router with encryption, or vice
# versa. Encryption disables the use of the fastdp Overlay,
# but the router could still use the fastdp Bridge to receive
# packets. However, pcap has better performance when sniffing
# every packet. So we pass --iface to use the pcap Bridge.
#
# Why don't we simply pass "--iface $BRIDGE". We could,
# except for the fact that NetworkManager likes to down the
# odp $BRIDGE netdev (at least under ubuntu), and you can only
# use pcap on an interface that is up. We avoid that by use
# pcap via a veth pair (NetworkManager leaves them alone).
# Having a netdev in the host netns called "ethwe" might
# surprise people, so it is called $ROUTER_HOSTNETNS_IFNAME
# instead.
echo "--datapath $BRIDGE --iface $ROUTER_HOSTNETNS_IFNAME"
fi
echo "--datapath $DATAPATH"
}
router_opts_bridge() {
echo "--iface $CONTAINER_IFNAME"
}
router_opts_bridged_fastdp() {
router_opts_fastdp "$@"
}
######################################################################
# functions invoked through with_container_netns
######################################################################
setup_router_iface_fastdp() {
if [ -n "$WEAVE_PASSWORD" ] ; then
# See router_opts_fastdp
# No-op if already attached
if ip link show $LOCAL_IFNAME >/dev/null 2>&1 ; then
return 0
fi
connect_container_to_bridge $ROUTER_HOSTNETNS_IFNAME &&
ip link set $ROUTER_HOSTNETNS_IFNAME up
fi
true
}
setup_router_iface_bridge() {
@@ -708,6 +839,10 @@ setup_router_iface_bridge() {
netnsenter ip link set $CONTAINER_IFNAME up
}
setup_router_iface_bridged_fastdp() {
setup_router_iface_fastdp "$@"
}
attach() {
if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "/proc/self/ns/net" -a "$(readlink $PROCFS/$CONTAINER_PID/ns/net)" = "$(readlink /proc/self/ns/net)" ] ; then
echo "Container is running in the host network namespace, and therefore cannot be" >&2
@@ -743,8 +878,10 @@ attach() {
# multicast packets. Since RFC1122 prohibits sending of ICMP
# errors for packets with multicast destinations, that causes
# packets larger than the PMTU to be dropped silently.
if ! netnsenter ip route show | grep '^224\.0\.0\.0/4' >/dev/null ; then
netnsenter ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME mtu lock $MTU
if [ -z "$NO_MULTICAST_ROUTE" ] ; then
if ! netnsenter ip route show | grep '^224\.0\.0\.0/4' >/dev/null ; then
netnsenter ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME mtu lock $MTU
fi
fi
}
@@ -956,12 +1093,11 @@ dns_args() {
NAME_ARG=""
HOSTNAME_SPECIFIED=
DNS_SEARCH_SPECIFIED=
WITH_DNS=
WITHOUT_DNS=
while [ $# -gt 0 ] ; do
case "$1" in
--with-dns)
WITH_DNS=1
echo "Warning: $1 is deprecated; it is on by default" >&2
;;
--without-dns)
WITHOUT_DNS=1
@@ -1042,7 +1178,7 @@ rewrite_etc_hosts() {
CONTAINERS_PATH=$(dirname $HOSTS)
MNT=/container
MNT_HOSTS=$MNT/$(basename $HOSTS)
CONTENTS="$(etc_hosts_contents $FQDN $@)"
CONTENTS="$(etc_hosts_contents $FQDN "$@")"
# rewrite /etc/hosts, unlinking the file (so Docker does not modify it again) but
# leaving it with valid contents...
docker run --rm --net=none --privileged \
@@ -1184,7 +1320,7 @@ ipam_cidrs() {
CONTAINER_ID="$2"
shift 2
ALL_CIDRS=""
IPAM_CIDRs=""
IPAM_CIDRS=""
# If no addresses passed in, select the default subnet
[ $# -gt 0 ] || set -- net:default
for arg in "$@" ; do
@@ -1240,8 +1376,6 @@ show_addrs() {
######################################################################
docker_client_args() {
CLIENT_TLS_ENABLED=""
[ -z "$DOCKER_CLIENT_TLS_VERIFY" ] || CLIENT_TLS_ENABLED=1
while [ $# -gt 0 ]; do
case "$1" in
-H|--host)
@@ -1251,9 +1385,6 @@ docker_client_args() {
-H=*|--host=*)
DOCKER_CLIENT_HOST="${1#*=}"
;;
-tls|--tls|-tlsverify|--tlsverify)
CLIENT_TLS_ENABLED=1
;;
esac
shift
done
@@ -1266,17 +1397,33 @@ tls_arg() {
PROXY_ARGS="$PROXY_ARGS $1 /home/weave/tls/$3.pem"
}
# TODO: Handle relative paths for args
# TODO: Handle args with spaces
host_arg() {
PROXY_HOST="$1"
if [ "$PROXY_HOST" != "${PROXY_HOST#unix://}" ]; then
host=$(dirname ${PROXY_HOST#unix://})
if [ "$host" = "${host#/}" ]; then
echo "When launching the proxy, unix sockets must be specified as an absolute path." >&2
exit 1
fi
PROXY_VOLUMES="$PROXY_VOLUMES -v /var/run/weave:/var/run/weave"
fi
PROXY_ARGS="$PROXY_ARGS -H $1"
}
proxy_parse_args() {
while [ $# -gt 0 ]; do
case "$1" in
-H)
PROXY_HOST="$2"
PROXY_ARGS="$PROXY_ARGS $1 $2"
host_arg "$2"
shift
;;
-H=*)
PROXY_HOST="${1#*=}"
PROXY_ARGS="$PROXY_ARGS $1"
host_arg "${1#*=}"
;;
-no-detect-tls|--no-detect-tls)
PROXY_TLS_DETECTION_DISABLED=1
;;
-tls|--tls|-tlsverify|--tlsverify)
PROXY_TLS_ENABLED=1
@@ -1315,12 +1462,13 @@ proxy_args() {
PROXY_VOLUMES=""
PROXY_ARGS=""
PROXY_TLS_ENABLED=""
PROXY_TLS_DETECTION_DISABLED=""
PROXY_HOST=""
proxy_parse_args "$@"
if [ -z "$PROXY_HOST" -a -n "$CLIENT_TLS_ENABLED" -a -z "$PROXY_TLS_ENABLED" ] ; then
if ! proxy_parse_args $(docker_tls_args) ; then
echo "When launching the proxy via TLS, -H and/or TLS options are required." >&2
if [ -z "$PROXY_TLS_ENABLED" ] ; then
if [ -z "$PROXY_TLS_DETECTION_DISABLED" ] && ! proxy_parse_args $(docker_tls_args) ; then
echo "Unable to auto-detect TLS configuration, --no-detect-tls or TLS options are required." >&2
exit 1
fi
fi
@@ -1330,10 +1478,10 @@ proxy_args() {
PROXY_HOST="unix:///var/run/weave/weave.sock"
;;
*)
PROXY_HOST="tcp://0.0.0.0:12375"
PROXY_HOST="tcp://0.0.0.0:$PROXY_PORT"
;;
esac
PROXY_ARGS="$PROXY_ARGS -H $PROXY_HOST"
host_arg "$PROXY_HOST"
fi
}
@@ -1467,20 +1615,19 @@ launch_router() {
fi
fi
if [ "$BRIDGE_TYPE" = fastdp ] ; then
if [ "$BRIDGE_TYPE" != bridge ] ; then
NETHOST_OPT="--net=host"
HTTP_IP=127.0.0.1
# In case there is a lingering veth-weave netdev
ip link del $ROUTER_HOSTNETNS_IFNAME >/dev/null 2>&1 || true
fi
# Set WEAVE_DOCKER_ARGS in the environment in order to supply
# additional parameters, such as resource limits, to docker
# when launching the weave container.
ROUTER_CONTAINER=$(docker run --privileged -d --name=$CONTAINER_NAME \
-v /var/run/docker.sock:/var/run/docker.sock \
$(docker_sock_volume_mount) \
-p $PORT:$CONTAINER_PORT/tcp -p $PORT:$CONTAINER_PORT/udp \
${NETHOST_OPT:-$DNS_PORT_MAPPING} \
-e DOCKER_HOST \
-e WEAVE_PASSWORD \
-e WEAVE_CIDR=none \
$WEAVE_DOCKER_ARGS $IMAGE $COVERAGE_ARGS \
@@ -1490,7 +1637,7 @@ launch_router() {
--dns-effective-listen-address $DOCKER_BRIDGE_IP \
${NETHOST_OPT:+$DNS_ROUTER_OPTS} $NO_DNS_OPT \
--http-addr $HTTP_IP:$HTTP_PORT \
--docker-api "unix:///var/run/docker.sock" "$@")
"$@")
with_container_netns_or_die $CONTAINER_NAME setup_router_iface_$BRIDGE_TYPE
attach_router
}
@@ -1524,9 +1671,6 @@ attach_router() {
stop_router() {
stop $CONTAINER_NAME "Weave"
conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true
# remove the veth-weave netdev in a fastdp context
[ -d /sys/class/net/$BRIDGE -a ! -d /sys/class/net/$BRIDGE/bridge ] \
&& ip link del $ROUTER_HOSTNETNS_IFNAME >/dev/null 2>&1 || true
}
launch_proxy() {
@@ -1535,16 +1679,19 @@ launch_proxy() {
# when launching the weaveproxy container.
docker_client_args $DOCKER_CLIENT_ARGS
proxy_args "$@"
mkdir -p /var/run/weave
PROXY_CONTAINER=$(docker run --privileged -d --name=$PROXY_CONTAINER_NAME --net=host \
$PROXY_VOLUMES \
-v /var/run/docker.sock:/var/run/docker.sock \
$(docker_sock_volume_mount) \
-v /var/run/weave:/var/run/weave \
-v /proc:/hostproc \
-e PROCFS=/hostproc \
-e WEAVE_CIDR=none \
-e DOCKER_HOST \
-e DOCKER_BRIDGE \
-e WEAVE_DEBUG \
-e COVERAGE \
-e EXEC_IMAGE=$EXEC_IMAGE \
--entrypoint=/home/weave/weaveproxy \
$WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $COVERAGE_ARGS $PROXY_ARGS)
wait_for_status $PROXY_CONTAINER_NAME http_call_unix $PROXY_CONTAINER_NAME status.sock
@@ -1559,12 +1706,20 @@ launch_plugin() {
PLUGIN_CONTAINER=$(docker run --privileged -d --name=$PLUGIN_CONTAINER_NAME \
--restart=always \
--net=host \
-v /var/run/docker.sock:/var/run/docker.sock \
$(docker_sock_volume_mount) \
-v /run/docker/plugins:/run/docker/plugins \
-e DOCKER_HOST \
$PLUGIN_IMAGE "$@")
}
remove_plugin_network() {
docker run --rm --net=host \
-v /var/run/docker.sock:/var/run/docker.sock \
$PLUGIN_IMAGE --remove-network
}
stop_plugin() {
remove_plugin_network
stop $PLUGIN_CONTAINER_NAME "Plugin"
}
@@ -1616,14 +1771,14 @@ deprecation_warnings() {
case "$1" in
-password|-password=*)
deprecation_warning $1 "--password"
[ "$1" == "-password" ] && shift
[ "$1" = "-password" ] && shift
;;
--password)
shift
;;
-nickname|-nickname=*)
deprecation_warning $1 "--nickname"
[ "$1" == "-nickname" ] && shift
[ "$1" = "-nickname" ] && shift
;;
--nickname)
shift
@@ -1633,21 +1788,21 @@ deprecation_warnings() {
;;
-iprange|--iprange|-iprange=*|--iprange=*)
deprecation_warning $1 "--ipalloc-range"
[ ${1#--} == "iprange" ] && shift
[ ${1#--} = "iprange" ] && shift
;;
--ipalloc-range)
shift
;;
-ipsubnet|--ipsubnet|-ipsubnet=*|--ipsubnet=*)
deprecation_warning $1 "--ipalloc-default-subnet"
[ ${1#--} == "ipsubnet" ] && shift
[ ${1#--} = "ipsubnet" ] && shift
;;
--ipalloc-default-subnet)
shift
;;
-initpeercount|--initpeercount|-initpeercount=*|--initpeercount=*)
deprecation_warning $1 "--init-peer-count"
[ ${1#--} == "initpeercount" ] && shift
[ ${1#--} = "initpeercount" ] && shift
;;
--init-peer-count)
shift
@@ -1690,6 +1845,9 @@ EOF
fi
create_bridge --without-ethtool
;;
bridge-type)
detect_bridge_type && echo $BRIDGE_TYPE
;;
launch)
deprecation_warnings "$@"
check_not_running $CONTAINER_NAME $BASE_IMAGE
@@ -1697,6 +1855,10 @@ EOF
COMMON_ARGS=$(common_launch_args "$@")
launch_router "$@"
launch_proxy $COMMON_ARGS
if [ -z $WEAVE_NO_PLUGIN ] && check_docker_server_api_version 1.21 ; then
check_not_running $PLUGIN_CONTAINER_NAME $BASE_PLUGIN_IMAGE
launch_plugin $COMMON_ARGS
fi
;;
launch-router)
deprecation_warnings "$@"
@@ -1737,7 +1899,7 @@ EOF
;;
connect)
[ $# -gt 0 ] || usage
if [ "$1" == "--replace" ] ; then
if [ "$1" = "--replace" ] ; then
replace="-d replace=true"
shift
fi
@@ -1775,13 +1937,13 @@ EOF
fi
if [ -z "$SUB_STATUS" ] && check_running $PROXY_CONTAINER_NAME 2>/dev/null && PROXY_ADDRS=$(proxy_addrs) ; then
echo
echo " Service: proxy"
echo " Address: $PROXY_ADDRS"
echo " Service: proxy"
echo " Address: $PROXY_ADDRS"
fi
if [ -z "$SUB_STATUS" ] && check_running $PLUGIN_CONTAINER_NAME 2>/dev/null ; then
echo
echo " Service: plugin"
echo " DriverName: weave"
echo " Service: plugin"
echo " DriverName: weave"
fi
[ -n "$SUB_STATUS" ] || echo
[ $res -eq 0 ]
@@ -1798,10 +1960,21 @@ EOF
dns_args "$@"
shift $(dns_arg_count "$@")
REWRITE_HOSTS=1
if [ "$1" = "--no-rewrite-hosts" ] ; then
REWRITE_HOSTS=
NO_MULTICAST_ROUTE=
while [ $# -gt 0 ]; do
case "$1" in
--no-rewrite-hosts)
REWRITE_HOSTS=
;;
--no-multicast-route)
NO_MULTICAST_ROUTE=1
;;
*)
break
;;
esac
shift
fi
done
collect_cidr_args "$@"
shift $CIDR_ARG_COUNT
CONTAINER=$(docker run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
@@ -1838,6 +2011,7 @@ EOF
attach)
DNS_EXTRA_HOSTS=
REWRITE_HOSTS=
NO_MULTICAST_ROUTE=
collect_cidr_args "$@"
shift $CIDR_ARG_COUNT
while [ $# -gt 0 ]; do
@@ -1855,6 +2029,9 @@ EOF
--add-host=*)
DNS_EXTRA_HOSTS="${1#*=} $DNS_EXTRA_HOSTS"
;;
--no-multicast-route)
NO_MULTICAST_ROUTE=1
;;
*)
break
;;
@@ -1958,6 +2135,7 @@ EOF
;;
stop)
[ $# -eq 0 ] || usage
stop_plugin
stop_router
stop_proxy
;;
@@ -1975,9 +2153,10 @@ EOF
;;
reset)
[ $# -eq 0 ] || usage
remove_plugin_network || echo "WARNING: docker may hang in this state; re-launch weave and remove attached containers before shutting down" >&2
warn_if_stopping_proxy_in_env
call_weave DELETE /peer >/dev/null 2>&1 || true
for NAME in $CONTAINER_NAME $PROXY_CONTAINER_NAME ; do
for NAME in $PLUGIN_CONTAINER_NAME $CONTAINER_NAME $PROXY_CONTAINER_NAME ; do
docker stop $NAME >/dev/null 2>&1 || true
docker rm -f $NAME >/dev/null 2>&1 || true
done