diff --git a/.gitignore b/.gitignore index 2cefdfb7..4506fcc5 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,7 @@ releases .weaver.uptodate .weavedns.uptodate .weaveexec.uptodate -weave.tar +weave.tar.gz prog/weaveexec/weave prog/weaveexec/weavehosts prog/weaveexec/weaveproxy diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f442b7d..1136390a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## Release 1.3.0 + +**Highlights** + +This release includes a Docker Plugin, so you have the option to use +Weave Net that way. More details in the +[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.3.0). + +## Release 1.2.1 + +This release contains a number of bug fixes and minor +enhancements. More details in the +[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.2.1). + +The release is fully compatible with 1.2.0 versions, so existing +clusters can be upgraded incrementally. + ## Release 1.2.0 **Highlights** diff --git a/Makefile b/Makefile index a8e6e6f3..b13b3454 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,9 @@ WEAVEWAIT_EXE=prog/weavewait/weavewait WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop NETCHECK_EXE=prog/netcheck/netcheck DOCKERTLSARGS_EXE=prog/docker_tls_args/docker_tls_args -RUNNER_EXE=testing/runner/runner +RUNNER_EXE=tools/runner/runner -EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) $(RUNNER_EXE) +EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) WEAVER_UPTODATE=.weaver.uptodate WEAVEEXEC_UPTODATE=.weaveexec.uptodate @@ -30,7 +30,7 @@ WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) -WEAVE_EXPORT=weave.tar +WEAVE_EXPORT=weave.tar.gz WEAVEEXEC_DOCKER_VERSION=1.3.1 DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz @@ -70,7 +70,7 @@ $(NETCHECK_EXE): common/*.go common/*/*.go net/*.go go build $(BUILD_FLAGS) -o $@ ./$(@D) $(NETGO_CHECK) -$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/weaver/*.go +$(WEAVER_EXE): router/*.go mesh/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/weaver/*.go $(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/main.go $(NETCHECK_EXE): prog/netcheck/netcheck.go @@ -78,10 +78,9 @@ $(NETCHECK_EXE): prog/netcheck/netcheck.go # the main build stanza due to not importing net package $(SIGPROXY_EXE): prog/sigproxy/main.go $(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go -$(RUNNER_EXE): testing/runner/runner.go $(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go -$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(RUNNER_EXE) $(DOCKERTLSARGS_EXE): +$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE): go get -tags netgo ./$(@D) go build $(BUILD_FLAGS) -o $@ ./$(@D) @@ -106,7 +105,7 @@ $(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPR touch $@ $(WEAVE_EXPORT): $(IMAGES_UPTODATE) - $(SUDO) docker save $(addsuffix :latest,$(IMAGES)) > $@ + $(SUDO) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@ $(DOCKER_DISTRIB): curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL) @@ -120,6 +119,9 @@ lint: tools/.git tools/.git: git submodule update --init +$(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) diff --git a/circle.yml b/circle.yml index cd208015..32804c63 100644 --- a/circle.yml +++ b/circle.yml @@ -27,13 +27,10 @@ dependencies: docker save weaveworks/weave-test >$WEAVE_TEST; fi post: - - sudo apt-get update - - sudo apt-get install bc jq - - pip install requests + - sudo apt-get update && sudo apt-get install bc jq && pip install requests - curl https://sdk.cloud.google.com | bash - bin/setup-circleci-secrets "$SECRET_PASSWORD" - - mkdir -p $(dirname $SRCDIR) - - cp -r $(pwd)/ $SRCDIR + - mkdir -p $(dirname $SRCDIR) && cp -r $(pwd)/ $SRCDIR - cd $SRCDIR/test; ./gce.sh make_template: parallel: false @@ -45,9 +42,7 @@ test: parallel: true - docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERAGE=true weaveworks/weave-build: parallel: true - - cd $SRCDIR/test; ./gce.sh setup: - parallel: true - - cd $SRCDIR/test; eval $(./gce.sh hosts); ./setup.sh: + - cd $SRCDIR/test; ./gce.sh setup && eval $(./gce.sh hosts) && ./setup.sh: parallel: true - cd $SRCDIR/test; eval $(./gce.sh hosts); export COVERAGE=true; ./run_all.sh: parallel: true diff --git a/common/logging.go b/common/logging.go index 074eb850..d42ad44a 100644 --- a/common/logging.go +++ b/common/logging.go @@ -52,3 +52,15 @@ func SetLogLevel(levelname string) { } Log.Level = level } + +func CheckFatal(e error) { + if e != nil { + Log.Fatal(e) + } +} + +func CheckWarn(e error) { + if e != nil { + Log.Warnln(e) + } +} diff --git a/router/odp.go b/common/odp/odp.go similarity index 99% rename from router/odp.go rename to common/odp/odp.go index d772d930..53b481ff 100644 --- a/router/odp.go +++ b/common/odp/odp.go @@ -1,4 +1,4 @@ -package router +package odp import ( "fmt" diff --git a/ipam/allocator.go b/ipam/allocator.go index 05fdcda1..888b9433 100644 --- a/ipam/allocator.go +++ b/ipam/allocator.go @@ -11,8 +11,8 @@ import ( "github.com/weaveworks/weave/ipam/paxos" "github.com/weaveworks/weave/ipam/ring" "github.com/weaveworks/weave/ipam/space" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" ) // Kinds of message we can unicast to other peers @@ -43,31 +43,31 @@ type operation interface { // are used around data structures. type Allocator struct { actionChan chan<- func() - ourName router.PeerName + ourName mesh.PeerName universe address.Range // superset of all ranges ring *ring.Ring // information on ranges owned by all peers space space.Space // more detail on ranges owned by us owned map[string][]address.Address // who owns what addresses, indexed by container-ID - nicknames map[router.PeerName]string // so we can map nicknames for rmpeer + nicknames map[mesh.PeerName]string // so we can map nicknames for rmpeer pendingAllocates []operation // held until we get some free space pendingClaims []operation // held until we know who owns the space - gossip router.Gossip // our link to the outside world for sending messages + gossip mesh.Gossip // our link to the outside world for sending messages paxos *paxos.Node paxosTicker *time.Ticker shuttingDown bool // to avoid doing any requests while trying to shut down - isKnownPeer func(router.PeerName) bool + isKnownPeer func(mesh.PeerName) bool now func() time.Time } // NewAllocator creates and initialises a new Allocator -func NewAllocator(ourName router.PeerName, ourUID router.PeerUID, ourNickname string, universe address.Range, quorum uint, isKnownPeer func(name router.PeerName) bool) *Allocator { +func NewAllocator(ourName mesh.PeerName, ourUID mesh.PeerUID, ourNickname string, universe address.Range, quorum uint, isKnownPeer func(name mesh.PeerName) bool) *Allocator { return &Allocator{ ourName: ourName, universe: universe, ring: ring.New(universe.Start, universe.End, ourName), owned: make(map[string][]address.Address), paxos: paxos.NewNode(ourName, ourUID, quorum), - nicknames: map[router.PeerName]string{ourName: ourNickname}, + nicknames: map[mesh.PeerName]string{ourName: ourNickname}, isKnownPeer: isKnownPeer, now: time.Now, } @@ -75,7 +75,7 @@ func NewAllocator(ourName router.PeerName, ourUID router.PeerUID, ourNickname st // Start runs the allocator goroutine func (alloc *Allocator) Start() { - actionChan := make(chan func(), router.ChannelSize) + actionChan := make(chan func(), mesh.ChannelSize) alloc.actionChan = actionChan go alloc.actorLoop(actionChan) } @@ -165,7 +165,7 @@ func (alloc *Allocator) tryPendingOps() { } } -func (alloc *Allocator) spaceRequestDenied(sender router.PeerName, r address.Range) { +func (alloc *Allocator) spaceRequestDenied(sender mesh.PeerName, r address.Range) { for i := 0; i < len(alloc.pendingClaims); { claim := alloc.pendingClaims[i].(*claim) if r.Contains(claim.addr) { @@ -276,27 +276,27 @@ func (alloc *Allocator) Free(ident string, addrToFree address.Address) error { return <-errChan } -func (alloc *Allocator) pickPeerFromNicknames(isValid func(router.PeerName) bool) router.PeerName { +func (alloc *Allocator) pickPeerFromNicknames(isValid func(mesh.PeerName) bool) mesh.PeerName { for name := range alloc.nicknames { if name != alloc.ourName && isValid(name) { return name } } - return router.UnknownPeerName + return mesh.UnknownPeerName } -func (alloc *Allocator) pickPeerForTransfer() router.PeerName { +func (alloc *Allocator) pickPeerForTransfer() mesh.PeerName { // first try alive peers that actively participate in IPAM (i.e. have entries) - if heir := alloc.ring.PickPeerForTransfer(alloc.isKnownPeer); heir != router.UnknownPeerName { + if heir := alloc.ring.PickPeerForTransfer(alloc.isKnownPeer); heir != mesh.UnknownPeerName { return heir } // next try alive peers that have IPAM enabled but have no entries - if heir := alloc.pickPeerFromNicknames(alloc.isKnownPeer); heir != router.UnknownPeerName { + if heir := alloc.pickPeerFromNicknames(alloc.isKnownPeer); heir != mesh.UnknownPeerName { return heir } // next try disappeared peers that still have entries - t := func(router.PeerName) bool { return true } - if heir := alloc.ring.PickPeerForTransfer(t); heir != router.UnknownPeerName { + t := func(mesh.PeerName) bool { return true } + if heir := alloc.ring.PickPeerForTransfer(t); heir != mesh.UnknownPeerName { return heir } // finally, disappeared peers that that passively participated in IPAM @@ -311,7 +311,7 @@ func (alloc *Allocator) Shutdown() { alloc.shuttingDown = true alloc.cancelOps(&alloc.pendingClaims) alloc.cancelOps(&alloc.pendingAllocates) - if heir := alloc.pickPeerForTransfer(); heir != router.UnknownPeerName { + if heir := alloc.pickPeerForTransfer(); heir != mesh.UnknownPeerName { alloc.ring.Transfer(alloc.ourName, heir) alloc.space.Clear() alloc.gossip.GossipBroadcast(alloc.Gossip()) @@ -350,14 +350,14 @@ func (alloc *Allocator) AdminTakeoverRanges(peerNameOrNickname string) error { // call into the router for this because we are interested in peers // that have gone away but are still in the ring, which is why we // maintain our own nicknames map. -func (alloc *Allocator) lookupPeername(name string) (router.PeerName, error) { +func (alloc *Allocator) lookupPeername(name string) (mesh.PeerName, error) { for peername, nickname := range alloc.nicknames { if nickname == name { return peername, nil } } - return router.PeerNameFromString(name) + return mesh.PeerNameFromString(name) } // Restrict the peers in "nicknames" to those in the ring plus peers known to the router @@ -370,7 +370,7 @@ func (alloc *Allocator) pruneNicknames() { } } -func (alloc *Allocator) annotatePeernames(names []router.PeerName) []string { +func (alloc *Allocator) annotatePeernames(names []mesh.PeerName) []string { var res []string for _, name := range names { if nickname, found := alloc.nicknames[name]; found { @@ -388,7 +388,7 @@ func decodeRange(msg []byte) (r address.Range, err error) { } // OnGossipUnicast (Sync) -func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) error { +func (alloc *Allocator) OnGossipUnicast(sender mesh.PeerName, msg []byte) error { alloc.debugln("OnGossipUnicast from", sender, ": ", len(msg), "bytes") resultChan := make(chan error) alloc.actionChan <- func() { @@ -414,7 +414,7 @@ func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) erro } // OnGossipBroadcast (Sync) -func (alloc *Allocator) OnGossipBroadcast(sender router.PeerName, msg []byte) (router.GossipData, error) { +func (alloc *Allocator) OnGossipBroadcast(sender mesh.PeerName, msg []byte) (mesh.GossipData, error) { alloc.debugln("OnGossipBroadcast from", sender, ":", len(msg), "bytes") resultChan := make(chan error) alloc.actionChan <- func() { @@ -427,7 +427,7 @@ type gossipState struct { // We send a timstamp along with the information to be // gossipped in order to detect skewed clocks Now int64 - Nicknames map[router.PeerName]string + Nicknames map[mesh.PeerName]string Paxos paxos.GossipState Ring *ring.Ring @@ -463,11 +463,11 @@ func (alloc *Allocator) Encode() []byte { } // OnGossip (Sync) -func (alloc *Allocator) OnGossip(msg []byte) (router.GossipData, error) { +func (alloc *Allocator) OnGossip(msg []byte) (mesh.GossipData, error) { alloc.debugln("Allocator.OnGossip:", len(msg), "bytes") resultChan := make(chan error) alloc.actionChan <- func() { - resultChan <- alloc.update(router.UnknownPeerName, msg) + resultChan <- alloc.update(mesh.UnknownPeerName, msg) } return nil, <-resultChan // for now, we never propagate updates. TBD } @@ -478,7 +478,7 @@ type ipamGossipData struct { alloc *Allocator } -func (d *ipamGossipData) Merge(other router.GossipData) { +func (d *ipamGossipData) Merge(other mesh.GossipData) { // no-op } @@ -488,12 +488,12 @@ func (d *ipamGossipData) Encode() [][]byte { // Gossip returns a GossipData implementation, which in this case always // returns the latest ring state (and does nothing on merge) -func (alloc *Allocator) Gossip() router.GossipData { +func (alloc *Allocator) Gossip() mesh.GossipData { return &ipamGossipData{alloc} } // SetInterfaces gives the allocator two interfaces for talking to the outside world -func (alloc *Allocator) SetInterfaces(gossip router.Gossip) { +func (alloc *Allocator) SetInterfaces(gossip mesh.Gossip) { alloc.gossip = gossip } @@ -540,7 +540,7 @@ func (alloc *Allocator) establishRing() { } } -func (alloc *Allocator) createRing(peers []router.PeerName) { +func (alloc *Allocator) createRing(peers []mesh.PeerName) { alloc.debugln("Paxos consensus:", peers) alloc.ring.ClaimForPeers(normalizeConsensus(peers)) alloc.gossip.GossipBroadcast(alloc.Gossip()) @@ -563,7 +563,7 @@ func (alloc *Allocator) ringUpdated() { } // For compatibility with sort.Interface -type peerNames []router.PeerName +type peerNames []mesh.PeerName func (a peerNames) Len() int { return len(a) } func (a peerNames) Less(i, j int) bool { return a[i] < a[j] } @@ -572,7 +572,7 @@ func (a peerNames) Swap(i, j int) { a[i], a[j] = a[j], a[i] } // When we get a consensus from Paxos, the peer names are not in a // defined order and may contain duplicates. This function sorts them // and de-dupes. -func normalizeConsensus(consensus []router.PeerName) []router.PeerName { +func normalizeConsensus(consensus []mesh.PeerName) []mesh.PeerName { if len(consensus) == 0 { return nil } @@ -607,22 +607,22 @@ func encodeRange(r address.Range) []byte { return buf.Bytes() } -func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) error { - msg := router.Concat([]byte{msgSpaceRequest}, encodeRange(r)) +func (alloc *Allocator) sendSpaceRequest(dest mesh.PeerName, r address.Range) error { + msg := append([]byte{msgSpaceRequest}, encodeRange(r)...) return alloc.gossip.GossipUnicast(dest, msg) } -func (alloc *Allocator) sendSpaceRequestDenied(dest router.PeerName, r address.Range) error { - msg := router.Concat([]byte{msgSpaceRequestDenied}, encodeRange(r)) +func (alloc *Allocator) sendSpaceRequestDenied(dest mesh.PeerName, r address.Range) error { + msg := append([]byte{msgSpaceRequestDenied}, encodeRange(r)...) return alloc.gossip.GossipUnicast(dest, msg) } -func (alloc *Allocator) sendRingUpdate(dest router.PeerName) { - msg := router.Concat([]byte{msgRingUpdate}, alloc.encode()) +func (alloc *Allocator) sendRingUpdate(dest mesh.PeerName) { + msg := append([]byte{msgRingUpdate}, alloc.encode()...) alloc.gossip.GossipUnicast(dest, msg) } -func (alloc *Allocator) update(sender router.PeerName, msg []byte) error { +func (alloc *Allocator) update(sender mesh.PeerName, msg []byte) error { reader := bytes.NewReader(msg) decoder := gob.NewDecoder(reader) var data gossipState @@ -674,7 +674,7 @@ func (alloc *Allocator) update(sender router.PeerName, msg []byte) error { alloc.createRing(cons.Value) } } - } else if sender != router.UnknownPeerName { + } else if sender != mesh.UnknownPeerName { // Sender is trying to initialize a ring, but we have one // already - send it straight back alloc.sendRingUpdate(sender) @@ -684,7 +684,7 @@ func (alloc *Allocator) update(sender router.PeerName, msg []byte) error { return nil } -func (alloc *Allocator) donateSpace(r address.Range, to router.PeerName) { +func (alloc *Allocator) donateSpace(r address.Range, to mesh.PeerName) { // No matter what we do, we'll send a unicast gossip // of our ring back to tha chap who asked for space. // This serves to both tell him of any space we might diff --git a/ipam/claim.go b/ipam/claim.go index 2515afd2..20ec6519 100644 --- a/ipam/claim.go +++ b/ipam/claim.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" ) type claim struct { @@ -43,7 +43,7 @@ func (c *claim) Try(alloc *Allocator) bool { switch owner := alloc.ring.Owner(c.addr); owner { case alloc.ourName: // success - case router.UnknownPeerName: + case mesh.UnknownPeerName: // If our ring doesn't know, it must be empty. if c.noErrorOnUnknown { alloc.infof("Claim %s for %s: address allocator still initializing; will try later.", c.addr, c.ident) @@ -86,7 +86,7 @@ func (c *claim) Try(alloc *Allocator) bool { return true } -func (c *claim) deniedBy(alloc *Allocator, owner router.PeerName) { +func (c *claim) deniedBy(alloc *Allocator, owner mesh.PeerName) { name, found := alloc.nicknames[owner] if found { name = " (" + name + ")" diff --git a/ipam/paxos/paxos.go b/ipam/paxos/paxos.go index 18d3aeea..3466bb8c 100644 --- a/ipam/paxos/paxos.go +++ b/ipam/paxos/paxos.go @@ -1,7 +1,7 @@ package paxos import ( - "github.com/weaveworks/weave/router" + "github.com/weaveworks/weave/mesh" ) // The node identifier. The use of the UID here is important: Paxos @@ -9,8 +9,8 @@ import ( // node does not restart and lose its Paxos state but claim to have // the same ID. type NodeID struct { - Name router.PeerName - UID router.PeerUID + Name mesh.PeerName + UID mesh.PeerUID } // note all fields exported in structs so we can Gob them @@ -39,7 +39,7 @@ func (a ProposalID) valid() bool { } // For seeding IPAM, the value we want consensus on is a set of peer names -type Value []router.PeerName +type Value []mesh.PeerName // An AcceptedValue is a Value plus the proposal which originated that // Value. The origin is not essential, but makes comparing @@ -72,7 +72,7 @@ type Node struct { knows GossipState } -func NewNode(name router.PeerName, uid router.PeerUID, quorum uint) *Node { +func NewNode(name mesh.PeerName, uid mesh.PeerUID, quorum uint) *Node { return &Node{ id: NodeID{name, uid}, quorum: quorum, @@ -223,7 +223,7 @@ func (node *Node) Think() bool { // about. This is not necessarily all peer names, but it is at least // a quorum, and so good enough for seeding the ring. func (node *Node) pickValue() Value { - val := make([]router.PeerName, len(node.knows)) + val := make([]mesh.PeerName, len(node.knows)) i := 0 for id := range node.knows { val[i] = id.Name diff --git a/ipam/paxos/paxos_test.go b/ipam/paxos/paxos_test.go index c1d5bfe0..c9dd1ff9 100644 --- a/ipam/paxos/paxos_test.go +++ b/ipam/paxos/paxos_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/weaveworks/weave/router" + "github.com/weaveworks/weave/mesh" ) type TestNode struct { @@ -97,8 +97,8 @@ func makeRandomModel(params *TestParams, r *rand.Rand, t *testing.T) *Model { } for i := range m.nodes { - m.nodes[i].Node = NewNode(router.PeerName(i/2+1), - router.PeerUID(r.Int63()), m.quorum) + m.nodes[i].Node = NewNode(mesh.PeerName(i/2+1), + mesh.PeerUID(r.Int63()), m.quorum) m.nodes[i].Propose() } @@ -172,8 +172,8 @@ func (m *Model) isolateNode(node *TestNode) { // Restart a node func (m *Model) restart(node *TestNode) { - node.Node = NewNode(router.PeerName(m.nextID), - router.PeerUID(m.r.Int63()), m.quorum) + node.Node = NewNode(mesh.PeerName(m.nextID), + mesh.PeerUID(m.r.Int63()), m.quorum) m.nextID++ node.Propose() diff --git a/ipam/ring/entry.go b/ipam/ring/entry.go index d1d832ed..872a13ab 100644 --- a/ipam/ring/entry.go +++ b/ipam/ring/entry.go @@ -4,14 +4,14 @@ import ( "sort" "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" ) // Entry represents entries around the ring type entry struct { Token address.Address // The start of this range - Peer router.PeerName // Who owns this range + Peer mesh.PeerName // Who owns this range Version uint32 // Version of this range Free address.Offset // Number of free IPs in this range } @@ -21,7 +21,7 @@ func (e *entry) Equal(e2 *entry) bool { e.Version == e2.Version } -func (e *entry) update(peername router.PeerName, free address.Offset) { +func (e *entry) update(peername mesh.PeerName, free address.Offset) { e.Peer = peername e.Version++ e.Free = free diff --git a/ipam/ring/ring.go b/ipam/ring/ring.go index fee7991e..30f32b8d 100644 --- a/ipam/ring/ring.go +++ b/ipam/ring/ring.go @@ -13,16 +13,16 @@ import ( "time" "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" ) // Ring represents the ring itself type Ring struct { - Start, End address.Address // [min, max) tokens in this ring. Due to wrapping, min == max (effectively) - Peer router.PeerName // name of peer owning this ring instance - Entries entries // list of entries sorted by token - Seeds []router.PeerName // peers with which the ring was seeded + Start, End address.Address // [min, max) tokens in this ring. Due to wrapping, min == max (effectively) + Peer mesh.PeerName // name of peer owning this ring instance + Entries entries // list of entries sorted by token + Seeds []mesh.PeerName // peers with which the ring was seeded } func (r *Ring) assertInvariants() { @@ -84,7 +84,7 @@ func (r *Ring) checkInvariants() error { } // New creates an empty ring belonging to peer. -func New(start, end address.Address, peer router.PeerName) *Ring { +func New(start, end address.Address, peer mesh.PeerName) *Ring { common.Assert(start < end) ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0)} @@ -111,7 +111,7 @@ func (r *Ring) distance(start, end address.Address) address.Offset { // Preconditions: // - start < end // - [start, end) must be owned by the calling peer -func (r *Ring) GrantRangeToHost(start, end address.Address, peer router.PeerName) { +func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName) { //fmt.Printf("%s GrantRangeToHost [%v,%v) -> %s\n", r.Peer, start, end, peer) r.assertInvariants() @@ -210,7 +210,7 @@ func (r *Ring) Merge(gossip Ring) error { addToResult := func(e entry) { result = append(result, &e) } var mine, theirs *entry - var previousOwner *router.PeerName + var previousOwner *mesh.PeerName // i is index into r.Entries; j is index into gossip.Entries var i, j int for i < len(r.Entries) && j < len(gossip.Entries) { @@ -318,7 +318,7 @@ func (r *Ring) OwnedRanges() (result []address.Range) { // ClaimForPeers claims the entire ring for the array of peers passed // in. Only works for empty rings. -func (r *Ring) ClaimForPeers(peers []router.PeerName) { +func (r *Ring) ClaimForPeers(peers []mesh.PeerName) { common.Assert(r.Empty()) defer r.assertInvariants() defer r.updateExportedVariables() @@ -350,7 +350,7 @@ func (r *Ring) ClaimForPeers(peers []router.PeerName) { r.Seeds = peers } -func (r *Ring) FprintWithNicknames(w io.Writer, m map[router.PeerName]string) { +func (r *Ring) FprintWithNicknames(w io.Writer, m map[mesh.PeerName]string) { for _, entry := range r.Entries { nickname, found := m[entry.Peer] if found { @@ -365,7 +365,7 @@ func (r *Ring) FprintWithNicknames(w io.Writer, m map[router.PeerName]string) { func (r *Ring) String() string { var buffer bytes.Buffer fmt.Fprintf(&buffer, "Ring [%s, %s)", r.Start, r.End) - r.FprintWithNicknames(&buffer, make(map[router.PeerName]string)) + r.FprintWithNicknames(&buffer, make(map[mesh.PeerName]string)) return buffer.String() } @@ -415,7 +415,7 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) { type weightedPeer struct { weight float64 - peername router.PeerName + peername mesh.PeerName } type weightedPeers []weightedPeer @@ -426,10 +426,10 @@ func (ws weightedPeers) Swap(i, j int) { ws[i], ws[j] = ws[j], ws[i] } // ChoosePeersToAskForSpace returns all peers we can ask for space in // the range [start, end), in weighted-random order. Assumes start= 0 { peerIndex = rand.Intn(len(peers) - 1) @@ -729,7 +729,7 @@ func TestFuzzRingHard(t *testing.T) { } func (r *Ring) ClaimItAll() { - r.ClaimForPeers([]router.PeerName{r.Peer}) + r.ClaimForPeers([]mesh.PeerName{r.Peer}) } func (es entries) String() string { diff --git a/ipam/testutils_test.go b/ipam/testutils_test.go index 144e313f..3aac1cce 100644 --- a/ipam/testutils_test.go +++ b/ipam/testutils_test.go @@ -8,13 +8,13 @@ import ( "time" "github.com/stretchr/testify/require" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" "github.com/weaveworks/weave/testing/gossip" ) type mockMessage struct { - dst router.PeerName + dst mesh.PeerName msgType byte buf []byte } @@ -48,13 +48,13 @@ func (m *mockGossipComms) String() string { // that the contents of messages are never re-ordered. Which, for instance, // requires they are not based off iterating through a map. -func (m *mockGossipComms) GossipBroadcast(update router.GossipData) error { +func (m *mockGossipComms) GossipBroadcast(update mesh.GossipData) error { m.Lock() defer m.Unlock() buf := []byte{} if len(m.messages) == 0 { require.FailNow(m, fmt.Sprintf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf)) - } else if msg := m.messages[0]; msg.dst != router.UnknownPeerName { + } else if msg := m.messages[0]; msg.dst != mesh.UnknownPeerName { require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst)) } else if msg.buf != nil && !equalByteBuffer(msg.buf, buf) { require.FailNow(m, fmt.Sprintf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf)) @@ -77,12 +77,12 @@ func equalByteBuffer(a, b []byte) bool { return true } -func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte) error { +func (m *mockGossipComms) GossipUnicast(dstPeerName mesh.PeerName, buf []byte) error { m.Lock() defer m.Unlock() if len(m.messages) == 0 { require.FailNow(m, fmt.Sprintf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf)) - } else if msg := m.messages[0]; msg.dst == router.UnknownPeerName { + } else if msg := m.messages[0]; msg.dst == mesh.UnknownPeerName { require.FailNow(m, fmt.Sprintf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName)) } else if msg.dst != dstPeerName { require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName)) @@ -99,7 +99,7 @@ func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte) func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) { m := alloc.gossip.(*mockGossipComms) - dstPeerName, _ := router.PeerNameFromString(dst) + dstPeerName, _ := mesh.PeerNameFromString(dst) m.Lock() m.messages = append(m.messages, mockMessage{dstPeerName, msgType, buf}) m.Unlock() @@ -108,7 +108,7 @@ func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) { func ExpectBroadcastMessage(alloc *Allocator, buf []byte) { m := alloc.gossip.(*mockGossipComms) m.Lock() - m.messages = append(m.messages, mockMessage{router.UnknownPeerName, 0, buf}) + m.messages = append(m.messages, mockMessage{mesh.UnknownPeerName, 0, buf}) m.Unlock() } @@ -124,7 +124,7 @@ func CheckAllExpectedMessagesSent(allocs ...*Allocator) { } func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.Range) { - peername, err := router.PeerNameFromString(name) + peername, err := mesh.PeerNameFromString(name) if err != nil { panic(err) } @@ -134,8 +134,8 @@ func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, addres panic(err) } - alloc := NewAllocator(peername, router.PeerUID(rand.Int63()), - "nick-"+name, cidr.Range(), quorum, func(router.PeerName) bool { return true }) + alloc := NewAllocator(peername, mesh.PeerUID(rand.Int63()), + "nick-"+name, cidr.Range(), quorum, func(mesh.PeerName) bool { return true }) return alloc, cidr.HostRange() } @@ -149,7 +149,7 @@ func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, } func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) { - peers := []router.PeerName{alloc.ourName} + peers := []mesh.PeerName{alloc.ourName} for _, alloc2 := range allocs { peers = append(peers, alloc2.ourName) } diff --git a/router/connection.go b/mesh/connection.go similarity index 81% rename from router/connection.go rename to mesh/connection.go index 9f047159..b19d2c2c 100644 --- a/router/connection.go +++ b/mesh/connection.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "fmt" @@ -40,17 +40,16 @@ type RemoteConnection struct { type LocalConnection struct { sync.RWMutex RemoteConnection - TCPConn *net.TCPConn - version byte - tcpSender TCPSender - remoteUDPAddr *net.UDPAddr - SessionKey *[32]byte - heartbeatTCP *time.Ticker - Router *Router - uid uint64 - actionChan chan<- ConnectionAction - finished <-chan struct{} // closed to signal that actorLoop has finished - forwarder OverlayForwarder + 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 } type ConnectionAction func() error @@ -83,7 +82,7 @@ func (conn *RemoteConnection) ErrorLog(args ...interface{}) { // Does not return anything. If the connection is successful, it will // end up in the local peer's connections map. -func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router, acceptNewPeer bool) { +func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, router *Router, acceptNewPeer bool) { if connRemote.local != router.Ourself.Peer { log.Fatal("Attempt to create local connection from a peer which is not ourself") } @@ -94,7 +93,6 @@ func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, ud RemoteConnection: *connRemote, Router: router, TCPConn: tcpConn, - remoteUDPAddr: udpAddr, uid: randUint64(), actionChan: actionChan, finished: finished} @@ -192,37 +190,37 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch conn.Log("connection ready; using protocol version", conn.version) - params := ForwarderParams{ + params := OverlayConnectionParams{ RemotePeer: conn.remote, - LocalIP: conn.TCPConn.LocalAddr().(*net.TCPAddr).IP, - RemoteAddr: conn.remoteUDPAddr, + LocalAddr: conn.TCPConn.LocalAddr().(*net.TCPAddr), + RemoteAddr: conn.TCPConn.RemoteAddr().(*net.TCPAddr), + Outbound: conn.outbound, ConnUID: conn.uid, - Crypto: conn.forwarderCrypto(), + SessionKey: conn.SessionKey, SendControlMessage: conn.sendOverlayControlMessage, Features: intro.Features, } - if conn.forwarder, err = conn.Router.Overlay.MakeForwarder(params); err != nil { + if conn.OverlayConn, err = conn.Router.Overlay.PrepareConnection(params); err != nil { return } // As soon as we do AddConnection, the new connection becomes // visible to the packet routing logic. So AddConnection must - // come after MakeForwarder + // come after PrepareConnection if err = conn.Router.Ourself.AddConnection(conn); err != nil { return } conn.Router.ConnectionMaker.ConnectionCreated(conn) - // Forwarder confirmation comes after AddConnection, because - // only after that completes do we know the connection is - // valid: in particular that it is not a duplicate connection - // to the same peer. Sending heartbeats on a duplicate - // connection can trip up crypto at the other end, since the - // associated UDP packets may get decoded by the other - // connection. It is also generally wasteful to engage in any - // interaction with the remote on a connection that turns out - // to be invalid. - conn.forwarder.Confirm() + // OverlayConnection confirmation comes after AddConnection, + // because only after that completes do we know the connection is + // valid: in particular that it is not a duplicate connection to + // the same peer. Overlay communication on a duplicate connection + // can cause problems such as tripping up overlay crypto at the + // other end due to data being decoded by the other connection. It + // is also generally wasteful to engage in any interaction with + // the remote on a connection that turns out to be invalid. + conn.OverlayConn.Confirm() // receiveTCP must follow also AddConnection. In the absence // of any indirect connectivity to the remote peer, the first @@ -330,10 +328,6 @@ func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) er } } - if conn.remote.UID != remote.UID { - return fmt.Errorf("Connection appears to be with different version of a peer we already know of") - } - if conn.remote == conn.local { return ErrConnectToSelf } @@ -342,8 +336,8 @@ func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) er } func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err error) { - fwdErrorChan := conn.forwarder.ErrorChannel() - fwdEstablishedChan := conn.forwarder.EstablishedChannel() + fwdErrorChan := conn.OverlayConn.ErrorChannel() + fwdEstablishedChan := conn.OverlayConn.EstablishedChannel() for err == nil { select { @@ -380,28 +374,17 @@ func (conn *LocalConnection) shutdown(err error) { conn.Router.Ourself.DeleteConnection(conn) } - stopTicker(conn.heartbeatTCP) + if conn.heartbeatTCP != nil { + conn.heartbeatTCP.Stop() + } - if conn.forwarder != nil { - conn.forwarder.Stop() + if conn.OverlayConn != nil { + conn.OverlayConn.Stop() } conn.Router.ConnectionMaker.ConnectionTerminated(conn, err) } -func (conn *LocalConnection) forwarderCrypto() *OverlayCrypto { - if !conn.Router.UsingPassword() { - return nil - } - - name := conn.local.NameByte - return &OverlayCrypto{ - Dec: NewNaClDecryptor(conn.SessionKey, conn.outbound), - Enc: NewNaClEncryptor(name, conn.SessionKey, conn.outbound, false), - EncDF: NewNaClEncryptor(name, conn.SessionKey, conn.outbound, true), - } -} - func (conn *LocalConnection) sendOverlayControlMessage(tag byte, msg []byte) error { return conn.sendProtocolMsg(ProtocolMsg{ProtocolTag(tag), msg}) } @@ -413,7 +396,7 @@ func (conn *LocalConnection) sendSimpleProtocolMsg(tag ProtocolTag) error { } func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error { - return conn.tcpSender.Send(Concat([]byte{byte(m.tag)}, m.msg)) + return conn.tcpSender.Send(append([]byte{byte(m.tag)}, m.msg...)) } func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) { @@ -439,8 +422,8 @@ func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) { func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) error { switch tag { case ProtocolHeartbeat: - case ProtocolConnectionEstablished, ProtocolFragmentationReceived, ProtocolPMTUVerified, ProtocolOverlayControlMsg: - conn.forwarder.ControlMessage(byte(tag), payload) + case ProtocolReserved1, ProtocolReserved2, ProtocolReserved3, ProtocolOverlayControlMsg: + conn.OverlayConn.ControlMessage(byte(tag), payload) case ProtocolGossipUnicast, ProtocolGossipBroadcast, ProtocolGossip: return conn.Router.handleGossip(tag, payload) default: @@ -452,20 +435,3 @@ func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) func (conn *LocalConnection) extendReadDeadline() { conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2)) } - -func (conn *LocalConnection) Forward(key ForwardPacketKey) FlowOp { - return conn.forwarder.Forward(key) -} - -func tickerChan(ticker *time.Ticker) <-chan time.Time { - if ticker != nil { - return ticker.C - } - return nil -} - -func stopTicker(ticker *time.Ticker) { - if ticker != nil { - ticker.Stop() - } -} diff --git a/router/connection_maker.go b/mesh/connection_maker.go similarity index 99% rename from router/connection_maker.go rename to mesh/connection_maker.go index 85775fd2..78edaf58 100644 --- a/router/connection_maker.go +++ b/mesh/connection_maker.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "fmt" diff --git a/router/gossip.go b/mesh/gossip.go similarity index 99% rename from router/gossip.go rename to mesh/gossip.go index 45db0e83..abfab8fa 100644 --- a/router/gossip.go +++ b/mesh/gossip.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "fmt" diff --git a/router/gossip_channel.go b/mesh/gossip_channel.go similarity index 97% rename from router/gossip_channel.go rename to mesh/gossip_channel.go index 11e8e8aa..098553e7 100644 --- a/router/gossip_channel.go +++ b/mesh/gossip_channel.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "bytes" @@ -239,3 +239,12 @@ func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) { func (c *GossipChannel) log(args ...interface{}) { log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...) } + +func GobEncode(items ...interface{}) []byte { + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + for _, i := range items { + checkFatal(enc.Encode(i)) + } + return buf.Bytes() +} diff --git a/router/gossip_test.go b/mesh/gossip_test.go similarity index 99% rename from router/gossip_test.go rename to mesh/gossip_test.go index b618561e..5f622826 100644 --- a/router/gossip_test.go +++ b/mesh/gossip_test.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "fmt" @@ -20,7 +20,7 @@ type mockChannelConnection struct { func NewTestRouter(name string) *Router { peerName, _ := PeerNameFromString(name) - router := NewRouter(Config{}, peerName, "nick") + router := NewRouter(Config{}, peerName, "nick", nil) router.Start() return router } diff --git a/router/local_peer.go b/mesh/local_peer.go similarity index 93% rename from router/local_peer.go rename to mesh/local_peer.go index 1d159221..72262e59 100644 --- a/router/local_peer.go +++ b/mesh/local_peer.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "fmt" @@ -19,8 +19,7 @@ type LocalPeerAction func() func NewLocalPeer(name PeerName, nickName string, router *Router) *LocalPeer { actionChan := make(chan LocalPeerAction, ChannelSize) peer := &LocalPeer{ - Peer: NewPeer(name, nickName, randomPeerUID(), 0, - randomPeerShortID()), + Peer: NewPeer(name, nickName, randomPeerUID(), 0, randomPeerShortID()), router: router, actionChan: actionChan, } @@ -63,21 +62,16 @@ func (peer *LocalPeer) CreateConnection(peerAddr string, acceptNewPeer bool) err if err := peer.checkConnectionLimit(); err != nil { return err } - tcpAddr, tcpErr := net.ResolveTCPAddr("tcp4", peerAddr) - udpAddr, udpErr := net.ResolveUDPAddr("udp4", peerAddr) - if tcpErr != nil || udpErr != nil { - // they really should have the same value, but just in case... - if tcpErr == nil { - return udpErr - } - return tcpErr + tcpAddr, err := net.ResolveTCPAddr("tcp4", peerAddr) + if err != nil { + return err } tcpConn, err := net.DialTCP("tcp4", nil, tcpAddr) if err != nil { return err } connRemote := NewRemoteConnection(peer.Peer, nil, tcpConn.RemoteAddr().String(), true, false) - StartLocalConnection(connRemote, tcpConn, udpAddr, peer.router, acceptNewPeer) + StartLocalConnection(connRemote, tcpConn, peer.router, acceptNewPeer) return nil } @@ -255,3 +249,13 @@ func (peer *LocalPeer) setShortID(shortID PeerShortID) { peer.ShortID = shortID peer.Version++ } + +func (peer *LocalPeer) setVersionBeyond(version uint64) bool { + peer.Lock() + defer peer.Unlock() + if version >= peer.Version { + peer.Version = version + 1 + return true + } + return false +} diff --git a/router/mocks_test.go b/mesh/mocks_test.go similarity index 99% rename from router/mocks_test.go rename to mesh/mocks_test.go index 064850df..08ae90aa 100644 --- a/router/mocks_test.go +++ b/mesh/mocks_test.go @@ -3,7 +3,7 @@ // It supplies some mock implementations to other unit tests, and is // named "...test.go" so it is only compiled under `go test`. -package router +package mesh import ( "fmt" diff --git a/mesh/overlay.go b/mesh/overlay.go new file mode 100644 index 00000000..5c48c927 --- /dev/null +++ b/mesh/overlay.go @@ -0,0 +1,116 @@ +package mesh + +import ( + "net" +) + +type Overlay interface { + // Enhance a features map with overlay-related features + AddFeaturesTo(map[string]string) + + // Prepare on overlay connection. The connection should remain + // passive until it has been Confirm()ed. + PrepareConnection(OverlayConnectionParams) (OverlayConnection, error) + + // Obtain diagnostic information specific to the overlay + Diagnostics() interface{} +} + +type OverlayConnectionParams struct { + RemotePeer *Peer + + // The local address of the corresponding TCP connection. Used to + // derive the local IP address for sending. May differ for + // different overlay connections. + LocalAddr *net.TCPAddr + + // The remote address of the corresponding TCP connection. Used to + // determine the address to send to, but only if the TCP + // connection is outbound. Otherwise the Overlay needs to discover + // it (e.g. from incoming datagrams). + RemoteAddr *net.TCPAddr + + // Is the corresponding TCP connection outbound? + Outbound bool + + // Unique identifier for this connection + ConnUID uint64 + + // Session key, if connection is encrypted; nil otherwise. + // + // NB: overlay connections must take care not to use nonces which + // may collide with those of the main connection. These nonces are + // 192 bits, with the top most bit unspecified, the next bit set + // to 1, followed by 126 zero bits, and a message sequence number + // in the lowest 64 bits. + SessionKey *[32]byte + + // Function to send a control message to the counterpart + // overlay connection. + SendControlMessage func(tag byte, msg []byte) error + + // Features passed at connection initiation + Features map[string]string +} + +// All of the machinery to manage overlay connectivity to a particular +// peer +type OverlayConnection interface { + // Confirm that the connection is really wanted, and so the + // Overlay should begin heartbeats etc. to verify the operation of + // the overlay connection. + Confirm() + + // A channel indicating that the overlay connection is + // established, i.e. its operation has been confirmed. + EstablishedChannel() <-chan struct{} + + // A channel indicating an error from the overlay connection. The + // overlay connection is not expected to be operational after the + // first error, so the channel only needs to buffer a single + // error. + ErrorChannel() <-chan error + + Stop() + + // Handle a message from the peer. 'tag' exists for + // compatibility, and should always be + // ProtocolOverlayControlMessage for non-sleeve overlays. + ControlMessage(tag byte, msg []byte) + + // User facing overlay name + DisplayName() string +} + +type NullOverlay struct{} + +func (NullOverlay) AddFeaturesTo(map[string]string) { +} + +func (NullOverlay) PrepareConnection(OverlayConnectionParams) (OverlayConnection, error) { + return NullOverlay{}, nil +} + +func (NullOverlay) Diagnostics() interface{} { + return nil +} +func (NullOverlay) Confirm() { +} + +func (NullOverlay) EstablishedChannel() <-chan struct{} { + return nil +} + +func (NullOverlay) ErrorChannel() <-chan error { + return nil +} + +func (NullOverlay) Stop() { +} + +func (NullOverlay) ControlMessage(byte, []byte) { +} + +func (NullOverlay) DisplayName() string { + return "null" +} diff --git a/router/peer.go b/mesh/peer.go similarity index 88% rename from router/peer.go rename to mesh/peer.go index e73d1592..1846f352 100644 --- a/router/peer.go +++ b/mesh/peer.go @@ -1,11 +1,28 @@ -package router +package mesh import ( + "crypto/rand" + "encoding/binary" "fmt" "sort" "strconv" ) +func randBytes(n int) []byte { + buf := make([]byte, n) + _, err := rand.Read(buf) + checkFatal(err) + return buf +} + +func randUint64() (r uint64) { + return binary.LittleEndian.Uint64(randBytes(8)) +} + +func randUint16() (r uint16) { + return binary.LittleEndian.Uint16(randBytes(2)) +} + type PeerUID uint64 func randomPeerUID() PeerUID { @@ -45,6 +62,18 @@ type Peer struct { connections map[PeerName]Connection } +type ListOfPeers []*Peer + +func (lop ListOfPeers) Len() int { + return len(lop) +} +func (lop ListOfPeers) Swap(i, j int) { + lop[i], lop[j] = lop[j], lop[i] +} +func (lop ListOfPeers) Less(i, j int) bool { + return lop[i].Name < lop[j].Name +} + type ConnectionSet map[Connection]struct{} func NewPeerFromSummary(summary PeerSummary) *Peer { diff --git a/router/peer_name_hash.go b/mesh/peer_name_hash.go similarity index 98% rename from router/peer_name_hash.go rename to mesh/peer_name_hash.go index 9bcd207e..ac76103e 100644 --- a/router/peer_name_hash.go +++ b/mesh/peer_name_hash.go @@ -3,7 +3,7 @@ // Let peer names be sha256 hashes...of anything (as long as it's // unique). -package router +package mesh import ( "crypto/sha256" diff --git a/router/peer_name_mac.go b/mesh/peer_name_mac.go similarity index 85% rename from router/peer_name_mac.go rename to mesh/peer_name_mac.go index 7bf868a0..c2b6cdbe 100644 --- a/router/peer_name_mac.go +++ b/mesh/peer_name_mac.go @@ -15,7 +15,7 @@ // name. In particular it doesn't actually have to be the MAC of, say, // the network interface the peer is sniffing on. -package router +package mesh import ( "net" @@ -56,3 +56,20 @@ func (name PeerName) Bin() []byte { func (name PeerName) String() string { return intmac(uint64(name)).String() } + +func macint(mac net.HardwareAddr) (r uint64) { + for _, b := range mac { + r <<= 8 + r |= uint64(b) + } + return +} + +func intmac(key uint64) (r net.HardwareAddr) { + r = make([]byte, 6) + for i := 5; i >= 0; i-- { + r[i] = byte(key) + key >>= 8 + } + return +} diff --git a/router/peers.go b/mesh/peers.go similarity index 89% rename from router/peers.go rename to mesh/peers.go index f3d238d0..70ec4c8d 100644 --- a/router/peers.go +++ b/mesh/peers.go @@ -1,13 +1,16 @@ -package router +package mesh import ( "bytes" "encoding/gob" + "fmt" "io" "math/rand" "sync" ) +var void = struct{}{} + type Peers struct { sync.RWMutex ourself *LocalPeer @@ -33,8 +36,8 @@ type UnknownPeerError struct { Name PeerName } -type NameCollisionError struct { - Name PeerName +func (upe UnknownPeerError) Error() string { + return fmt.Sprint("Reference to unknown peer ", upe.Name) } type PeerNameSet map[PeerName]struct{} @@ -57,6 +60,9 @@ type PeersPendingNotifications struct { // The local short ID needs reassigning due to a collision reassignLocalShortID bool + + // The local peer was modified + localPeerModified bool } func NewPeers(ourself *LocalPeer) *Peers { @@ -89,7 +95,8 @@ func (peers *Peers) OnInvalidateShortIDs(callback func()) { } func (peers *Peers) unlockAndNotify(pending *PeersPendingNotifications) { - broadcastLocalPeer := pending.reassignLocalShortID && peers.reassignLocalShortID(pending) + broadcastLocalPeer := (pending.reassignLocalShortID && peers.reassignLocalShortID(pending)) || + pending.localPeerModified onGC := peers.onGC onInvalidateShortIDs := peers.onInvalidateShortIDs peers.Unlock() @@ -425,12 +432,8 @@ func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, de newPeer := NewPeerFromSummary(peerSummary) decodedUpdate = append(decodedUpdate, newPeer) decodedConns = append(decodedConns, connSummaries) - existingPeer, found := peers.byName[newPeer.Name] - if !found { + if _, found := peers.byName[newPeer.Name]; !found { newPeers[newPeer.Name] = newPeer - } else if existingPeer.UID != newPeer.UID { - err = NameCollisionError{Name: newPeer.Name} - return } } @@ -458,36 +461,36 @@ func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]Connecti connSummaries := decodedConns[idx] name := newPeer.Name // guaranteed to find peer in the peers.byName - peer := peers.byName[name] - if peer != newPeer && - (peer == peers.ourself.Peer || peer.Version >= newPeer.Version) { - // Nobody but us updates us. And if we know more about a - // peer than what's in the the update, we ignore the - // latter. - continue + switch peer := peers.byName[name]; peer { + case peers.ourself.Peer: + if newPeer.UID != peer.UID { + // The update contains information about an old + // incarnation of ourselves. We increase our version + // number beyond that which we received, so our + // information supersedes the old one when it is + // received by other peers. + pending.localPeerModified = peers.ourself.setVersionBeyond(newPeer.Version) + } + case newPeer: + peer.connections = makeConnsMap(peer, connSummaries, peers.byName) + newUpdate[name] = void + default: // existing peer + if newPeer.Version < peer.Version || + (newPeer.Version == peer.Version && newPeer.UID <= peer.UID) { + continue + } + peer.Version = newPeer.Version + peer.UID = newPeer.UID + peer.connections = makeConnsMap(peer, connSummaries, peers.byName) + + if newPeer.ShortID != peer.ShortID { + peers.deleteByShortID(peer, pending) + peer.ShortID = newPeer.ShortID + peers.addByShortID(peer, pending) + } + newUpdate[name] = void } - // If we're here, either it was a new peer, or the update has - // more info about the peer than we do. Either case, we need - // to set version and conns and include the updated peer in - // the outgoing update. - - // Can peer have been updated by anyone else in the mean time? - // No - we know that peer is not ourself, so the only prospect - // for an update would be someone else calling - // router.Peers.ApplyUpdate. But ApplyUpdate takes the Lock on - // the router.Peers, so there can be no race here. - peer.Version = newPeer.Version - peer.connections = makeConnsMap(peer, connSummaries, peers.byName) - - if newPeer.ShortID != peer.ShortID { - peers.deleteByShortID(peer, pending) - peer.ShortID = newPeer.ShortID - peers.addByShortID(peer, pending) - } - - newUpdate[name] = void } - return newUpdate } diff --git a/router/peers_test.go b/mesh/peers_test.go similarity index 99% rename from router/peers_test.go rename to mesh/peers_test.go index e5f328f9..5c2b0094 100644 --- a/router/peers_test.go +++ b/mesh/peers_test.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "fmt" diff --git a/router/protocol.go b/mesh/protocol.go similarity index 98% rename from router/protocol.go rename to mesh/protocol.go index add17a17..7ac06050 100644 --- a/router/protocol.go +++ b/mesh/protocol.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "bytes" @@ -329,9 +329,9 @@ type ProtocolTag byte const ( ProtocolHeartbeat = iota - ProtocolConnectionEstablished - ProtocolFragmentationReceived - ProtocolPMTUVerified + ProtocolReserved1 // legacy overlay control messages + ProtocolReserved2 // + ProtocolReserved3 // ProtocolGossip ProtocolGossipUnicast ProtocolGossipBroadcast diff --git a/mesh/protocol_crypto.go b/mesh/protocol_crypto.go new file mode 100644 index 00000000..2adb6921 --- /dev/null +++ b/mesh/protocol_crypto.go @@ -0,0 +1,180 @@ +package mesh + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "encoding/gob" + "fmt" + "io" + "sync" + + "golang.org/x/crypto/nacl/box" + "golang.org/x/crypto/nacl/secretbox" +) + +const MaxTCPMsgSize = 10 * 1024 * 1024 + +func GenerateKeyPair() (publicKey, privateKey *[32]byte, err error) { + return box.GenerateKey(rand.Reader) +} + +func FormSessionKey(remotePublicKey, localPrivateKey *[32]byte, secretKey []byte) *[32]byte { + var sharedKey [32]byte + box.Precompute(&sharedKey, remotePublicKey, localPrivateKey) + sharedKeySlice := sharedKey[:] + sharedKeySlice = append(sharedKeySlice, secretKey...) + sessionKey := sha256.Sum256(sharedKeySlice) + return &sessionKey +} + +// TCP Senders/Receivers + +// The lowest 64 bits of the nonce contain the message sequence +// number. The top most bit indicates the connection polarity at the +// sender - '1' for outbound; the next indicates protocol type - '1' +// for TCP. The remaining 126 bits are zero. The polarity is needed so +// that the two ends of a connection do not use the same nonces; the +// protocol type so that the TCP connection nonces are distinct from +// nonces used by overlay connections, if they share the session key. +// This is a requirement of the NaCl Security Model; see +// http://nacl.cr.yp.to/box.html. +type TCPCryptoState struct { + sessionKey *[32]byte + nonce [24]byte + seqNo uint64 +} + +func NewTCPCryptoState(sessionKey *[32]byte, outbound bool) *TCPCryptoState { + s := &TCPCryptoState{sessionKey: sessionKey} + if outbound { + s.nonce[0] |= (1 << 7) + } + s.nonce[0] |= (1 << 6) + return s +} + +func (s *TCPCryptoState) advance() { + s.seqNo++ + binary.BigEndian.PutUint64(s.nonce[16:24], s.seqNo) +} + +type TCPSender interface { + Send([]byte) error +} + +type GobTCPSender struct { + encoder *gob.Encoder +} + +type LengthPrefixTCPSender struct { + writer io.Writer +} + +type EncryptedTCPSender struct { + sync.RWMutex + sender TCPSender + state *TCPCryptoState +} + +func NewGobTCPSender(encoder *gob.Encoder) *GobTCPSender { + return &GobTCPSender{encoder: encoder} +} + +func (sender *GobTCPSender) Send(msg []byte) error { + return sender.encoder.Encode(msg) +} + +func NewLengthPrefixTCPSender(writer io.Writer) *LengthPrefixTCPSender { + return &LengthPrefixTCPSender{writer: writer} +} + +func (sender *LengthPrefixTCPSender) Send(msg []byte) error { + l := len(msg) + if l > MaxTCPMsgSize { + return fmt.Errorf("outgoing message exceeds maximum size: %d > %d", l, MaxTCPMsgSize) + } + // We copy the message so we can send it in a single Write + // operation, thus making this thread-safe without locking. + prefixedMsg := make([]byte, 4+l) + binary.BigEndian.PutUint32(prefixedMsg, uint32(l)) + copy(prefixedMsg[4:], msg) + _, err := sender.writer.Write(prefixedMsg) + return err +} + +func NewEncryptedTCPSender(sender TCPSender, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender { + return &EncryptedTCPSender{sender: sender, state: NewTCPCryptoState(sessionKey, outbound)} +} + +func (sender *EncryptedTCPSender) Send(msg []byte) error { + sender.Lock() + defer sender.Unlock() + encodedMsg := secretbox.Seal(nil, msg, &sender.state.nonce, sender.state.sessionKey) + sender.state.advance() + return sender.sender.Send(encodedMsg) +} + +type TCPReceiver interface { + Receive() ([]byte, error) +} + +type GobTCPReceiver struct { + decoder *gob.Decoder +} + +type LengthPrefixTCPReceiver struct { + reader io.Reader +} + +type EncryptedTCPReceiver struct { + receiver TCPReceiver + state *TCPCryptoState +} + +func NewGobTCPReceiver(decoder *gob.Decoder) *GobTCPReceiver { + return &GobTCPReceiver{decoder: decoder} +} + +func (receiver *GobTCPReceiver) Receive() ([]byte, error) { + var msg []byte + err := receiver.decoder.Decode(&msg) + return msg, err +} + +func NewLengthPrefixTCPReceiver(reader io.Reader) *LengthPrefixTCPReceiver { + return &LengthPrefixTCPReceiver{reader: reader} +} + +func (receiver *LengthPrefixTCPReceiver) Receive() ([]byte, error) { + lenPrefix := make([]byte, 4) + if _, err := io.ReadFull(receiver.reader, lenPrefix); err != nil { + return nil, err + } + l := binary.BigEndian.Uint32(lenPrefix) + if l > MaxTCPMsgSize { + return nil, fmt.Errorf("incoming message exceeds maximum size: %d > %d", l, MaxTCPMsgSize) + } + msg := make([]byte, l) + _, err := io.ReadFull(receiver.reader, msg) + return msg, err +} + +func NewEncryptedTCPReceiver(receiver TCPReceiver, sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver { + return &EncryptedTCPReceiver{receiver: receiver, state: NewTCPCryptoState(sessionKey, !outbound)} +} + +func (receiver *EncryptedTCPReceiver) Receive() ([]byte, error) { + msg, err := receiver.receiver.Receive() + if err != nil { + return nil, err + } + + decodedMsg, success := secretbox.Open(nil, msg, &receiver.state.nonce, receiver.state.sessionKey) + if !success { + return nil, fmt.Errorf("Unable to decrypt TCP msg") + } + + receiver.state.advance() + return decodedMsg, nil +} diff --git a/router/protocol_test.go b/mesh/protocol_test.go similarity index 99% rename from router/protocol_test.go rename to mesh/protocol_test.go index 1a56079a..1ea2ec41 100644 --- a/router/protocol_test.go +++ b/mesh/protocol_test.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "io" diff --git a/mesh/router.go b/mesh/router.go new file mode 100644 index 00000000..916ea5b7 --- /dev/null +++ b/mesh/router.go @@ -0,0 +1,189 @@ +package mesh + +import ( + "fmt" + "math" + "net" + "sync" + "time" + + "github.com/weaveworks/weave/common" +) + +const ( + Port = 6783 + ChannelSize = 16 + TCPHeartbeat = 30 * time.Second + GossipInterval = 30 * time.Second + MaxDuration = time.Duration(math.MaxInt64) + + acceptMaxTokens = 100 // [1] + acceptTokenDelay = 100 * time.Millisecond // [2] +) + +// [1] capacity of token bucket for rate limiting accepts + +// [2] control rate at which new tokens are added to the bucket + +var ( + log = common.Log + checkFatal = common.CheckFatal + checkWarn = common.CheckWarn +) + +type Config struct { + Port int + ProtocolMinVersion byte + Password []byte + ConnLimit int + PeerDiscovery bool +} + +type Router struct { + Config + Overlay Overlay + Ourself *LocalPeer + Peers *Peers + Routes *Routes + ConnectionMaker *ConnectionMaker + gossipLock sync.RWMutex + gossipChannels GossipChannels + TopologyGossip Gossip + acceptLimiter *TokenBucket +} + +func NewRouter(config Config, name PeerName, nickName string, overlay Overlay) *Router { + router := &Router{Config: config, gossipChannels: make(GossipChannels)} + + if overlay == nil { + overlay = NullOverlay{} + } + + router.Overlay = overlay + router.Ourself = NewLocalPeer(name, nickName, router) + router.Peers = NewPeers(router.Ourself) + router.Peers.OnGC(func(peer *Peer) { + log.Println("Removed unreachable peer", peer) + }) + router.Routes = NewRoutes(router.Ourself, router.Peers) + router.ConnectionMaker = NewConnectionMaker(router.Ourself, router.Peers, router.Port, router.PeerDiscovery) + router.TopologyGossip = router.NewGossip("topology", router) + router.acceptLimiter = NewTokenBucket(acceptMaxTokens, acceptTokenDelay) + + return router +} + +// Start listening for TCP connections. This is separate from +// NewRouter so that gossipers can register before we start forming +// connections. +func (router *Router) Start() { + router.listenTCP(router.Port) +} + +func (router *Router) Stop() error { + // TODO: perform graceful shutdown... + return nil +} + +func (router *Router) UsingPassword() bool { + return router.Password != nil +} + +func (router *Router) listenTCP(localPort int) { + localAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprint(":", localPort)) + checkFatal(err) + ln, err := net.ListenTCP("tcp4", localAddr) + checkFatal(err) + go func() { + defer ln.Close() + for { + tcpConn, err := ln.AcceptTCP() + if err != nil { + log.Errorln(err) + continue + } + router.acceptTCP(tcpConn) + router.acceptLimiter.Wait() + } + }() +} + +func (router *Router) acceptTCP(tcpConn *net.TCPConn) { + remoteAddrStr := tcpConn.RemoteAddr().String() + log.Printf("->[%s] connection accepted", remoteAddrStr) + connRemote := NewRemoteConnection(router.Ourself.Peer, nil, remoteAddrStr, false, false) + StartLocalConnection(connRemote, tcpConn, router, true) +} + +// Gossiper methods - the Router is the topology Gossiper + +type TopologyGossipData struct { + peers *Peers + update PeerNameSet +} + +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)} +} + +func (router *Router) OnGossipUnicast(sender PeerName, msg []byte) error { + return fmt.Errorf("unexpected topology gossip unicast: %v", msg) +} + +func (router *Router) OnGossipBroadcast(_ PeerName, update []byte) (GossipData, error) { + origUpdate, _, err := router.applyTopologyUpdate(update) + if err != nil || len(origUpdate) == 0 { + return nil, err + } + return &TopologyGossipData{peers: router.Peers, update: origUpdate}, nil +} + +func (router *Router) Gossip() GossipData { + return &TopologyGossipData{peers: router.Peers, update: router.Peers.Names()} +} + +func (router *Router) OnGossip(update []byte) (GossipData, error) { + _, newUpdate, err := router.applyTopologyUpdate(update) + if err != nil || len(newUpdate) == 0 { + return nil, err + } + return &TopologyGossipData{peers: router.Peers, update: newUpdate}, nil +} + +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 + } + if len(newUpdate) > 0 { + router.ConnectionMaker.Refresh() + router.Routes.Recalculate() + } + return origUpdate, newUpdate, nil +} diff --git a/router/routes.go b/mesh/routes.go similarity index 95% rename from router/routes.go rename to mesh/routes.go index b7ef0f30..d7787af9 100644 --- a/router/routes.go +++ b/mesh/routes.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "math" @@ -12,7 +12,7 @@ type Routes struct { sync.RWMutex ourself *LocalPeer peers *Peers - onChange func() + onChange []func() unicast unicastRoutes unicastAll unicastRoutes // [1] broadcast broadcastRoutes @@ -23,13 +23,12 @@ type Routes struct { // symmetric ones } -func NewRoutes(ourself *LocalPeer, peers *Peers, onChange func()) *Routes { +func NewRoutes(ourself *LocalPeer, peers *Peers) *Routes { recalculate := make(chan *struct{}, 1) wait := make(chan chan struct{}) routes := &Routes{ ourself: ourself, peers: peers, - onChange: onChange, unicast: make(unicastRoutes), unicastAll: make(unicastRoutes), broadcast: make(broadcastRoutes), @@ -44,6 +43,12 @@ func NewRoutes(ourself *LocalPeer, peers *Peers, onChange func()) *Routes { return routes } +func (routes *Routes) OnChange(callback func()) { + routes.Lock() + defer routes.Unlock() + routes.onChange = append(routes.onChange, callback) +} + func (routes *Routes) PeerNames() PeerNameSet { return routes.peers.Names() } @@ -159,10 +164,13 @@ func (routes *Routes) calculate() { routes.unicastAll = unicastAll routes.broadcast = broadcast routes.broadcastAll = broadcastAll + onChange := routes.onChange routes.Unlock() if !unicast.equals(oldUnicast) || !broadcast.equals(oldBroadcast) { - routes.onChange() + for _, callback := range onChange { + callback() + } } } diff --git a/router/status.go b/mesh/status.go similarity index 86% rename from router/status.go rename to mesh/status.go index 469f9329..0dce08bb 100644 --- a/router/status.go +++ b/mesh/status.go @@ -1,8 +1,7 @@ -package router +package mesh import ( "fmt" - "time" ) type Status struct { @@ -14,9 +13,6 @@ type Status struct { Name string NickName string Port int - Interface string - CaptureStats map[string]int - MACs []MACStatus Peers []PeerStatus UnicastRoutes []UnicastRouteStatus BroadcastRoutes []BroadcastRouteStatus @@ -25,13 +21,6 @@ type Status struct { OverlayDiagnostics interface{} } -type MACStatus struct { - Mac string - Name string - NickName string - LastSeen time.Time -} - type PeerStatus struct { Name string NickName string @@ -75,9 +64,6 @@ func NewStatus(router *Router) *Status { router.Ourself.Name.String(), router.Ourself.NickName, router.Port, - router.Bridge.String(), - router.Bridge.Stats(), - NewMACStatusSlice(router.Macs), NewPeerStatusSlice(router.Peers), NewUnicastRouteStatusSlice(router.Routes), NewBroadcastRouteStatusSlice(router.Routes), @@ -86,22 +72,6 @@ func NewStatus(router *Router) *Status { router.Overlay.Diagnostics()} } -func NewMACStatusSlice(cache *MacCache) []MACStatus { - cache.RLock() - defer cache.RUnlock() - - var slice []MACStatus - for key, entry := range cache.table { - slice = append(slice, MACStatus{ - intmac(key).String(), - entry.peer.Name.String(), - entry.peer.NickName, - entry.lastSeen}) - } - - return slice -} - func NewPeerStatusSlice(peers *Peers) []PeerStatus { var slice []PeerStatus @@ -176,7 +146,7 @@ func NewLocalConnectionStatusSlice(cm *ConnectionMaker) []LocalConnectionStatus state = "established" } lc, _ := conn.(*LocalConnection) - info := fmt.Sprintf("%-6v %v", lc.forwarder.DisplayName(), conn.Remote()) + info := fmt.Sprintf("%-6v %v", lc.OverlayConn.DisplayName(), conn.Remote()) slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, info}) } for address, target := range cm.targets { diff --git a/router/surrogate_gossiper.go b/mesh/surrogate_gossiper.go similarity index 98% rename from router/surrogate_gossiper.go rename to mesh/surrogate_gossiper.go index 04059eb7..aef844a8 100644 --- a/router/surrogate_gossiper.go +++ b/mesh/surrogate_gossiper.go @@ -1,4 +1,4 @@ -package router +package mesh type SurrogateGossipData struct { messages [][]byte diff --git a/router/token_bucket.go b/mesh/token_bucket.go similarity index 98% rename from router/token_bucket.go rename to mesh/token_bucket.go index 254de6eb..e6daeb3b 100644 --- a/router/token_bucket.go +++ b/mesh/token_bucket.go @@ -1,4 +1,4 @@ -package router +package mesh import ( "time" diff --git a/nameserver/dns_test.go b/nameserver/dns_test.go index ae67eda4..9712fe91 100644 --- a/nameserver/dns_test.go +++ b/nameserver/dns_test.go @@ -11,14 +11,14 @@ import ( "github.com/miekg/dns" "github.com/stretchr/testify/require" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" ) func startServer(t *testing.T, upstream *dns.ClientConfig) (*DNSServer, *Nameserver, int, int) { - peername, err := router.PeerNameFromString("00:00:00:02:00:00") + peername, err := mesh.PeerNameFromString("00:00:00:02:00:00") require.Nil(t, err) - nameserver := New(peername, nil, "") + nameserver := New(peername, "", func(mesh.PeerName) bool { return true }) dnsserver, err := NewDNSServer(nameserver, "weave.local.", "0.0.0.0:0", "", 30, 5*time.Second) require.Nil(t, err) udpPort := dnsserver.servers[0].PacketConn.LocalAddr().(*net.UDPAddr).Port @@ -39,7 +39,7 @@ func TestTruncation(t *testing.T) { addrs := []address.Address{} for i := address.Address(0); i < 100; i++ { addrs = append(addrs, i) - nameserver.AddEntry("foo.weave.local.", "", router.UnknownPeerName, i) + nameserver.AddEntry("foo.weave.local.", "", mesh.UnknownPeerName, i) } doRequest := func(client *dns.Client, request *dns.Msg, port int, expectedErr error) *dns.Msg { diff --git a/nameserver/entry.go b/nameserver/entry.go index a3310423..524845a3 100644 --- a/nameserver/entry.go +++ b/nameserver/entry.go @@ -8,15 +8,15 @@ import ( "strings" "time" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" ) var now = func() int64 { return time.Now().Unix() } type Entry struct { ContainerID string - Origin router.PeerName + Origin mesh.PeerName Addr address.Address Hostname string // as supplied lHostname string // lowercased (not exported, so not encoded by gob) @@ -128,7 +128,7 @@ func (es *Entries) checkAndPanic() *Entries { return es } -func (es *Entries) add(hostname, containerid string, origin router.PeerName, addr address.Address) Entry { +func (es *Entries) add(hostname, containerid string, origin mesh.PeerName, addr address.Address) Entry { defer es.checkAndPanic().checkAndPanic() entry := Entry{Hostname: hostname, lHostname: strings.ToLower(hostname), @@ -176,7 +176,7 @@ func (es *Entries) merge(incoming Entries) Entries { } // f returning true means keep the entry. -func (es *Entries) tombstone(ourname router.PeerName, f func(*Entry) bool) Entries { +func (es *Entries) tombstone(ourname mesh.PeerName, f func(*Entry) bool) Entries { defer es.checkAndPanic().checkAndPanic() tombstoned := Entries{} @@ -245,7 +245,7 @@ type GossipData struct { Entries } -func (g *GossipData) Merge(o router.GossipData) { +func (g *GossipData) Merge(o mesh.GossipData) { other := o.(*GossipData) g.Entries.merge(other.Entries) if g.Timestamp < other.Timestamp { diff --git a/nameserver/entry_test.go b/nameserver/entry_test.go index 1e350e64..1e794660 100644 --- a/nameserver/entry_test.go +++ b/nameserver/entry_test.go @@ -5,8 +5,8 @@ import ( "github.com/stretchr/testify/require" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" ) func l(es Entries) Entries { @@ -28,21 +28,21 @@ func TestAdd(t *testing.T) { now = func() int64 { return 1234 } entries := Entries{} - entries.add("A", "", router.UnknownPeerName, address.Address(0)) + entries.add("A", "", mesh.UnknownPeerName, address.Address(0)) expected := l(Entries{ - Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0)}, + Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0)}, }) require.Equal(t, entries, expected) - entries.tombstone(router.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "A" }) + entries.tombstone(mesh.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "A" }) expected = l(Entries{ - Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 1, Tombstone: 1234}, + Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0), Version: 1, Tombstone: 1234}, }) require.Equal(t, entries, expected) - entries.add("A", "", router.UnknownPeerName, address.Address(0)) + entries.add("A", "", mesh.UnknownPeerName, address.Address(0)) expected = l(Entries{ - Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 2}, + Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0), Version: 2}, }) require.Equal(t, entries, expected) } @@ -78,7 +78,7 @@ func TestTombstone(t *testing.T) { es := makeEntries("AB") - es.tombstone(router.UnknownPeerName, func(e *Entry) bool { + es.tombstone(mesh.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "B" }) expected := l(Entries{ diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index f85e5482..0d33d184 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -8,8 +8,8 @@ import ( "github.com/miekg/dns" . "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" ) const ( @@ -34,28 +34,24 @@ const ( // - Update is O(n) for now type Nameserver struct { sync.RWMutex - ourName router.PeerName - domain string - gossip router.Gossip - entries Entries - peers *router.Peers - quit chan struct{} + ourName mesh.PeerName + domain string + gossip mesh.Gossip + entries Entries + isKnownPeer func(mesh.PeerName) bool + quit chan struct{} } -func New(ourName router.PeerName, peers *router.Peers, domain string) *Nameserver { - ns := &Nameserver{ - ourName: ourName, - domain: dns.Fqdn(domain), - peers: peers, - quit: make(chan struct{}), +func New(ourName mesh.PeerName, domain string, isKnownPeer func(mesh.PeerName) bool) *Nameserver { + return &Nameserver{ + ourName: ourName, + domain: dns.Fqdn(domain), + isKnownPeer: isKnownPeer, + quit: make(chan struct{}), } - if peers != nil { - peers.OnGC(ns.PeerGone) - } - return ns } -func (n *Nameserver) SetGossip(gossip router.Gossip) { +func (n *Nameserver) SetGossip(gossip mesh.Gossip) { n.gossip = gossip } @@ -87,7 +83,7 @@ func (n *Nameserver) broadcastEntries(es ...Entry) error { return nil } -func (n *Nameserver) AddEntry(hostname, containerid string, origin router.PeerName, addr address.Address) error { +func (n *Nameserver) AddEntry(hostname, containerid string, origin mesh.PeerName, addr address.Address) error { n.infof("adding entry %s -> %s", hostname, addr.String()) n.Lock() entry := n.entries.add(hostname, containerid, origin, addr) @@ -144,12 +140,12 @@ func (n *Nameserver) ContainerDied(ident string) { } } -func (n *Nameserver) PeerGone(peer *router.Peer) { +func (n *Nameserver) PeerGone(peer mesh.PeerName) { n.infof("peer %s gone", peer.String()) n.Lock() defer n.Unlock() n.entries.filter(func(e *Entry) bool { - return e.Origin != peer.Name + return e.Origin != peer }) } @@ -185,7 +181,7 @@ func (n *Nameserver) deleteTombstones() { }) } -func (n *Nameserver) Gossip() router.GossipData { +func (n *Nameserver) Gossip() mesh.GossipData { n.RLock() defer n.RUnlock() gossip := &GossipData{ @@ -196,11 +192,11 @@ func (n *Nameserver) Gossip() router.GossipData { return gossip } -func (n *Nameserver) OnGossipUnicast(sender router.PeerName, msg []byte) error { +func (n *Nameserver) OnGossipUnicast(sender mesh.PeerName, msg []byte) error { return nil } -func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.GossipData, error) { +func (n *Nameserver) receiveGossip(msg []byte) (mesh.GossipData, mesh.GossipData, error) { var gossip GossipData if err := gossip.Decode(msg); err != nil { return nil, nil, err @@ -212,11 +208,9 @@ func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.Gossip n.Lock() defer n.Unlock() - if n.peers != nil { - gossip.Entries.filter(func(e *Entry) bool { - return n.peers.Fetch(e.Origin) != nil - }) - } + gossip.Entries.filter(func(e *Entry) bool { + return n.isKnownPeer(e.Origin) + }) newEntries := n.entries.merge(gossip.Entries) if len(newEntries) > 0 { @@ -227,14 +221,14 @@ func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.Gossip // merge received data into state and return "everything new I've // just learnt", or nil if nothing in the received data was new -func (n *Nameserver) OnGossip(msg []byte) (router.GossipData, error) { +func (n *Nameserver) OnGossip(msg []byte) (mesh.GossipData, error) { newEntries, _, err := n.receiveGossip(msg) return newEntries, err } // merge received data into state and return a representation of // the received data, for further propagation -func (n *Nameserver) OnGossipBroadcast(_ router.PeerName, msg []byte) (router.GossipData, error) { +func (n *Nameserver) OnGossipBroadcast(_ mesh.PeerName, msg []byte) (mesh.GossipData, error) { _, entries, err := n.receiveGossip(msg) return entries, err } diff --git a/nameserver/nameserver_test.go b/nameserver/nameserver_test.go index f396303f..f7b603b5 100644 --- a/nameserver/nameserver_test.go +++ b/nameserver/nameserver_test.go @@ -11,19 +11,23 @@ import ( "github.com/stretchr/testify/require" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - "github.com/weaveworks/weave/router" wt "github.com/weaveworks/weave/testing" "github.com/weaveworks/weave/testing/gossip" ) +func makeNameserver(name mesh.PeerName) *Nameserver { + return New(name, "", func(mesh.PeerName) bool { return true }) +} + func makeNetwork(size int) ([]*Nameserver, *gossip.TestRouter) { gossipRouter := gossip.NewTestRouter(0.0) nameservers := make([]*Nameserver, size) for i := 0; i < size; i++ { - name, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:02:00:00", i)) - nameserver := New(name, nil, "") + name, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:00:00:02:00:00", i)) + nameserver := makeNameserver(name) nameserver.SetGossip(gossipRouter.Connect(nameserver.ourName, nameserver)) nameserver.Start() nameservers[i] = nameserver @@ -40,7 +44,7 @@ func stopNetwork(nameservers []*Nameserver, grouter *gossip.TestRouter) { } type pair struct { - origin router.PeerName + origin mesh.PeerName addr address.Address } @@ -87,7 +91,7 @@ func testNameservers(t *testing.T) { badNameservers := nameservers[25:] // This subset will remain well-connected, and we will deal mainly with them nameservers = nameservers[:25] - nameserversByName := map[router.PeerName]*Nameserver{} + nameserversByName := map[mesh.PeerName]*Nameserver{} for _, n := range nameservers { nameserversByName[n.ourName] = n } @@ -142,7 +146,7 @@ func testNameservers(t *testing.T) { loseConnection := func() { nameserver1 := badNameservers[rand.Intn(len(badNameservers))] nameserver2 := nameservers[rand.Intn(len(nameservers))] - nameserver1.PeerGone(&router.Peer{Name: nameserver2.ourName}) + nameserver1.PeerGone(nameserver2.ourName) } deleteMapping := func() { @@ -222,9 +226,9 @@ func testNameservers(t *testing.T) { } func TestContainerAndPeerDeath(t *testing.T) { - peername, err := router.PeerNameFromString("00:00:00:02:00:00") + peername, err := mesh.PeerNameFromString("00:00:00:02:00:00") require.Nil(t, err) - nameserver := New(peername, nil, "") + nameserver := makeNameserver(peername) err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0)) require.Nil(t, err) @@ -237,7 +241,7 @@ func TestContainerAndPeerDeath(t *testing.T) { require.Nil(t, err) require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname")) - nameserver.PeerGone(&router.Peer{Name: peername}) + nameserver.PeerGone(peername) require.Equal(t, []address.Address{}, nameserver.Lookup("hostname")) } @@ -246,9 +250,9 @@ func TestTombstoneDeletion(t *testing.T) { defer func() { now = oldNow }() now = func() int64 { return 1234 } - peername, err := router.PeerNameFromString("00:00:00:02:00:00") + peername, err := mesh.PeerNameFromString("00:00:00:02:00:00") require.Nil(t, err) - nameserver := New(peername, nil, "") + nameserver := makeNameserver(peername) err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0)) require.Nil(t, err) diff --git a/net/address/address.go b/net/address/address.go index 291a3227..71e00716 100644 --- a/net/address/address.go +++ b/net/address/address.go @@ -19,7 +19,7 @@ func NewRange(start Address, size Offset) Range { return Range{Start: start, End: Add(start, size)} } func (r Range) Size() Offset { return Subtract(r.End, r.Start) } -func (r Range) String() string { return fmt.Sprintf("[%s-%s)", r.Start, r.End) } +func (r Range) String() string { return fmt.Sprintf("%s-%s", r.Start, r.End-1) } func (r Range) Overlaps(or Range) bool { return !(r.Start >= or.End || r.End <= or.Start) } func (r Range) Contains(addr Address) bool { return addr >= r.Start && addr < r.End } diff --git a/prog/weaver/http.go b/prog/weaver/http.go index a53f59c8..cb828f47 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -6,6 +6,7 @@ import ( "github.com/gorilla/mux" . "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/ipam" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/nameserver" "github.com/weaveworks/weave/net/address" weave "github.com/weaveworks/weave/router" @@ -24,14 +25,14 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } return count }, - "printConnectionCounts": func(conns []weave.LocalConnectionStatus) string { + "printConnectionCounts": func(conns []mesh.LocalConnectionStatus) string { counts := make(map[string]int) for _, conn := range conns { counts[conn.State]++ } return printCounts(counts, []string{"established", "pending", "retrying", "failed", "connecting"}) }, - "printPeerConnectionCounts": func(peers []weave.PeerStatus) string { + "printPeerConnectionCounts": func(peers []mesh.PeerStatus) string { counts := make(map[string]int) for _, peer := range peers { for _, conn := range peer.Connections { @@ -146,16 +147,16 @@ var dnsEntriesTemplate = defTemplate("dnsEntries", `\ type WeaveStatus struct { Version string - Router *weave.Status `json:"Router,omitempty"` - IPAM *ipam.Status `json:"IPAM,omitempty"` - DNS *nameserver.Status `json:"DNS,omitempty"` + Router *weave.NetworkRouterStatus `json:"Router,omitempty"` + IPAM *ipam.Status `json:"IPAM,omitempty"` + DNS *nameserver.Status `json:"DNS,omitempty"` } -func HandleHTTP(muxRouter *mux.Router, version string, router *weave.Router, allocator *ipam.Allocator, defaultSubnet address.CIDR, ns *nameserver.Nameserver, dnsserver *nameserver.DNSServer) { +func HandleHTTP(muxRouter *mux.Router, version string, router *weave.NetworkRouter, allocator *ipam.Allocator, defaultSubnet address.CIDR, ns *nameserver.Nameserver, dnsserver *nameserver.DNSServer) { status := func() WeaveStatus { return WeaveStatus{ version, - weave.NewStatus(router), + weave.NewNetworkRouterStatus(router), ipam.NewStatus(allocator, defaultSubnet), nameserver.NewStatus(ns, dnsserver)} } diff --git a/prog/weaver/main.go b/prog/weaver/main.go index 741b0900..d5fb2553 100644 --- a/prog/weaver/main.go +++ b/prog/weaver/main.go @@ -16,7 +16,9 @@ import ( . "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/common/docker" + "github.com/weaveworks/weave/common/odp" "github.com/weaveworks/weave/ipam" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/nameserver" weavenet "github.com/weaveworks/weave/net" "github.com/weaveworks/weave/net/address" @@ -41,7 +43,8 @@ func main() { deleteDatapath bool addDatapathInterface string - config weave.Config + config mesh.Config + networkConfig weave.NetworkConfig protocolMinVersion int ifaceName string routerName string @@ -73,8 +76,8 @@ func main() { mflag.BoolVar(&deleteDatapath, []string{"-delete-datapath"}, false, "delete ODP datapath and exit") mflag.StringVar(&addDatapathInterface, []string{"-add-datapath-iface"}, "", "add a network interface to the ODP datapath and exit") - mflag.IntVar(&config.Port, []string{"#port", "-port"}, weave.Port, "router port") - mflag.IntVar(&protocolMinVersion, []string{"-min-protocol-version"}, weave.ProtocolMinVersion, "minimum weave protocol version") + mflag.IntVar(&config.Port, []string{"#port", "-port"}, mesh.Port, "router port") + mflag.IntVar(&protocolMinVersion, []string{"-min-protocol-version"}, mesh.ProtocolMinVersion, "minimum weave protocol version") mflag.StringVar(&ifaceName, []string{"#iface", "-iface"}, "", "name of interface to capture/inject from (disabled if blank)") mflag.StringVar(&routerName, []string{"#name", "-name"}, "", "name of router (defaults to MAC of interface)") mflag.StringVar(&nickName, []string{"#nickname", "-nickname"}, "", "nickname of peer (defaults to hostname)") @@ -85,7 +88,7 @@ func main() { mflag.IntVar(&config.ConnLimit, []string{"#connlimit", "#-connlimit", "-conn-limit"}, 30, "connection limit (0 for unlimited)") mflag.BoolVar(&noDiscovery, []string{"#nodiscovery", "#-nodiscovery", "-no-discovery"}, false, "disable peer discovery") mflag.IntVar(&bufSzMB, []string{"#bufsz", "-bufsz"}, 8, "capture buffer size in MB") - mflag.StringVar(&httpAddr, []string{"#httpaddr", "#-httpaddr", "-http-addr"}, fmt.Sprintf(":%d", weave.HTTPPort), "address to bind HTTP interface to (disabled if blank, absolute path indicates unix domain socket)") + mflag.StringVar(&httpAddr, []string{"#httpaddr", "#-httpaddr", "-http-addr"}, "", "address to bind HTTP interface to (disabled if blank, absolute path indicates unix domain socket)") 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)") @@ -118,7 +121,7 @@ func main() { os.Exit(0) case createDatapath: - err, odp_supported := weave.CreateDatapath(datapathName) + err, odp_supported := odp.CreateDatapath(datapathName) if !odp_supported { if err != nil { Log.Error(err) @@ -134,23 +137,23 @@ func main() { os.Exit(0) case deleteDatapath: - checkFatal(weave.DeleteDatapath(datapathName)) + checkFatal(odp.DeleteDatapath(datapathName)) os.Exit(0) case addDatapathInterface != "": - checkFatal(weave.AddDatapathInterface(datapathName, addDatapathInterface)) + checkFatal(odp.AddDatapathInterface(datapathName, addDatapathInterface)) os.Exit(0) } Log.Println("Command line options:", options()) Log.Println("Command line peers:", peers) - if protocolMinVersion < weave.ProtocolMinVersion || protocolMinVersion > weave.ProtocolMaxVersion { - Log.Fatalf("--min-protocol-version must be in range [%d,%d]", weave.ProtocolMinVersion, weave.ProtocolMaxVersion) + if protocolMinVersion < mesh.ProtocolMinVersion || protocolMinVersion > mesh.ProtocolMaxVersion { + Log.Fatalf("--min-protocol-version must be in range [%d,%d]", mesh.ProtocolMinVersion, mesh.ProtocolMaxVersion) } config.ProtocolMinVersion = byte(protocolMinVersion) - var fastDPOverlay weave.Overlay + var fastDPOverlay weave.NetworkOverlay if datapathName != "" { // A datapath name implies that "Bridge" and "Overlay" // packet handling use fast datapath, although other @@ -163,7 +166,7 @@ func main() { }) checkFatal(err) - config.Bridge = fastdp.Bridge() + networkConfig.Bridge = fastdp.Bridge() fastDPOverlay = fastdp.Overlay() } @@ -178,7 +181,7 @@ func main() { checkFatal(err) // bufsz flag is in MB - config.Bridge, err = weave.NewPcap(iface, bufSzMB*1024*1024) + networkConfig.Bridge, err = weave.NewPcap(iface, bufSzMB*1024*1024) checkFatal(err) } @@ -203,7 +206,6 @@ func main() { sleeve := weave.NewSleeveOverlay(config.Port) overlays.Add("sleeve", sleeve) overlays.SetCompatOverlay(sleeve) - config.Overlay = overlays if routerName == "" { if iface == nil { @@ -212,7 +214,7 @@ func main() { routerName = iface.HardwareAddr.String() } - name, err := weave.PeerNameFromUserInput(routerName) + name, err := mesh.PeerNameFromUserInput(routerName) checkFatal(err) if nickName == "" { @@ -230,12 +232,12 @@ func main() { config.PeerDiscovery = !noDiscovery if pktdebug { - config.PacketLogging = packetLogging{} + networkConfig.PacketLogging = packetLogging{} } else { - config.PacketLogging = nopPacketLogging{} + networkConfig.PacketLogging = nopPacketLogging{} } - router := weave.NewRouter(config, name, nickName) + router := weave.NewNetworkRouter(config, networkConfig, name, nickName, overlays) Log.Println("Our name is", router.Ourself) var dockerCli *docker.Client @@ -253,10 +255,13 @@ func main() { } } } + isKnownPeer := func(name mesh.PeerName) bool { + return router.Peers.Fetch(name) != nil + } var allocator *ipam.Allocator var defaultSubnet address.CIDR if iprangeCIDR != "" { - allocator, defaultSubnet = createAllocator(router, iprangeCIDR, ipsubnetCIDR, determineQuorum(peerCount, peers)) + allocator, defaultSubnet = createAllocator(router.Router, iprangeCIDR, ipsubnetCIDR, determineQuorum(peerCount, peers), isKnownPeer) observeContainers(allocator) } else if peerCount > 0 { Log.Fatal("--init-peer-count flag specified without --ipalloc-range") @@ -267,7 +272,8 @@ func main() { dnsserver *nameserver.DNSServer ) if !noDNS { - ns = nameserver.New(router.Ourself.Peer.Name, router.Peers, dnsDomain) + ns = nameserver.New(router.Ourself.Peer.Name, dnsDomain, isKnownPeer) + router.Peers.OnGC(func(peer *mesh.Peer) { ns.PeerGone(peer.Name) }) ns.SetGossip(router.NewGossip("nameserver", ns)) observeContainers(ns) ns.Start() @@ -362,7 +368,7 @@ func parseAndCheckCIDR(cidrStr string) address.CIDR { return cidr } -func createAllocator(router *weave.Router, ipRangeStr string, defaultSubnetStr string, quorum uint) (*ipam.Allocator, address.CIDR) { +func createAllocator(router *mesh.Router, ipRangeStr string, defaultSubnetStr string, quorum uint, isKnownPeer func(mesh.PeerName) bool) (*ipam.Allocator, address.CIDR) { ipRange := parseAndCheckCIDR(ipRangeStr) defaultSubnet := ipRange if defaultSubnetStr != "" { @@ -371,7 +377,6 @@ func createAllocator(router *weave.Router, ipRangeStr string, defaultSubnetStr s Log.Fatalf("IP address allocation default subnet %s does not overlap with allocation range %s", defaultSubnet, ipRange) } } - isKnownPeer := func(name weave.PeerName) bool { return router.Peers.Fetch(name) != nil } allocator := ipam.NewAllocator(router.Ourself.Peer.Name, router.Ourself.Peer.UID, router.Ourself.Peer.NickName, ipRange.Range(), quorum, isKnownPeer) allocator.SetInterfaces(router.NewGossip("IPallocation", allocator)) diff --git a/proxy/proxy.go b/proxy/proxy.go index 6618fe51..778742d4 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -17,7 +17,6 @@ import ( . "github.com/weaveworks/weave/common" weavedocker "github.com/weaveworks/weave/common/docker" "github.com/weaveworks/weave/nameserver" - "github.com/weaveworks/weave/router" ) const ( @@ -490,7 +489,7 @@ func (proxy *Proxy) getDNSDomain() (domain string) { weaveIP = "127.0.0.1" } - url := fmt.Sprintf("http://%s:%d/domain", weaveIP, router.HTTPPort) + url := fmt.Sprintf("http://%s:6784/domain", weaveIP) resp, err := http.Get(url) if err != nil || resp.StatusCode != http.StatusOK { return diff --git a/router/consts.go b/router/consts.go deleted file mode 100644 index 6fd11588..00000000 --- a/router/consts.go +++ /dev/null @@ -1,21 +0,0 @@ -package router - -import ( - "math" - "time" -) - -const ( - Port = 6783 - HTTPPort = Port + 1 - MaxUDPPacketSize = 65535 - ChannelSize = 16 - TCPHeartbeat = 30 * time.Second - GossipInterval = 30 * time.Second - MaxDuration = time.Duration(math.MaxInt64) - MaxTCPMsgSize = 10 * 1024 * 1024 - FastHeartbeat = 500 * time.Millisecond - SlowHeartbeat = 10 * time.Second - MaxMissedHeartbeats = 6 - HeartbeatTimeout = MaxMissedHeartbeats * SlowHeartbeat -) diff --git a/router/fastdp.go b/router/fastdp.go index 5e9b6ce0..b4e7eeff 100644 --- a/router/fastdp.go +++ b/router/fastdp.go @@ -9,6 +9,7 @@ import ( "time" "github.com/weaveworks/go-odp/odp" + "github.com/weaveworks/weave/mesh" ) // The virtual bridge accepts packets from ODP vports and the router @@ -40,8 +41,8 @@ type FastDatapath struct { dp odp.DatapathHandle deleteFlowsCount uint64 missHandlers map[odp.VportID]missHandler - localPeer *Peer - peers *Peers + localPeer *mesh.Peer + peers *mesh.Peers overlayConsumer OverlayConsumer // Bridge state: How to send to the given bridge port @@ -62,7 +63,7 @@ type FastDatapath struct { dec *EthernetDecoder // forwarders by remote peer - forwarders map[PeerName]*fastDatapathForwarder + forwarders map[mesh.PeerName]*fastDatapathForwarder } type FastDatapathConfig struct { @@ -105,7 +106,7 @@ func NewFastDatapath(config FastDatapathConfig) (*FastDatapath, error) { sendToMAC: make(map[MAC]bridgeSender), seenMACs: make(map[MAC]struct{}), vxlanVportIDs: make(map[int]odp.VportID), - forwarders: make(map[PeerName]*fastDatapathForwarder), + forwarders: make(map[mesh.PeerName]*fastDatapathForwarder), } if err := fastdp.deleteVxlanVports(); err != nil { @@ -248,8 +249,7 @@ func (fastdp fastDatapathBridge) InjectPacket(key PacketKey) FlowOp { // Ethernet bridge implementation -func (fastdp *FastDatapath) bridge(ingress bridgePortID, - key PacketKey, lock *fastDatapathLock) FlowOp { +func (fastdp *FastDatapath) bridge(ingress bridgePortID, key PacketKey, lock *fastDatapathLock) FlowOp { lock.relock() if fastdp.sendToMAC[key.SrcMAC] == nil { // Learn the source MAC @@ -260,8 +260,7 @@ func (fastdp *FastDatapath) bridge(ingress bridgePortID, // If we know about the destination MAC, deliver it to the // associated port. if sender := fastdp.sendToMAC[key.DstMAC]; sender != nil { - return NewMultiFlowOp(false, odpEthernetFlowKey(key), - sender(key, lock)) + return NewMultiFlowOp(false, odpEthernetFlowKey(key), sender(key, lock)) } // Otherwise, it might be a real broadcast, or it might @@ -299,7 +298,7 @@ type fastDatapathOverlay struct { *FastDatapath } -func (fastdp *FastDatapath) Overlay() Overlay { +func (fastdp *FastDatapath) Overlay() NetworkOverlay { return fastDatapathOverlay{fastdp} } @@ -345,8 +344,7 @@ func (flowStatus *FlowStatus) MarshalJSON() ([]byte, error) { actions = append(actions, fmt.Sprint(action)) } - return json.Marshal(&jsonFlowStatus{flowKeys, actions, - flowStatus.Packets, flowStatus.Bytes, flowStatus.Used}) + return json.Marshal(&jsonFlowStatus{flowKeys, actions, flowStatus.Packets, flowStatus.Bytes, flowStatus.Used}) } type VportStatus odp.Vport @@ -358,8 +356,7 @@ func (vport *VportStatus) MarshalJSON() ([]byte, error) { TypeName string } - return json.Marshal(&jsonVportStatus{vport.ID, vport.Spec.Name(), - vport.Spec.TypeName()}) + return json.Marshal(&jsonVportStatus{vport.ID, vport.Spec.Name(), vport.Spec.TypeName()}) } func (fastdp fastDatapathOverlay) Diagnostics() interface{} { @@ -367,18 +364,14 @@ func (fastdp fastDatapathOverlay) Diagnostics() interface{} { defer lock.unlock() vports, err := fastdp.dp.EnumerateVports() - if err != nil { - log.Warn(err) - } + checkWarn(err) vportStatuses := make([]VportStatus, 0, len(vports)) for _, vport := range vports { vportStatuses = append(vportStatuses, VportStatus(vport)) } flows, err := fastdp.dp.EnumerateFlows() - if err != nil { - log.Warn(err) - } + checkWarn(err) flowStatuses := make([]FlowStatus, 0, len(flows)) for _, flow := range flows { flowStatuses = append(flowStatuses, FlowStatus(flow)) @@ -393,8 +386,7 @@ func (fastdp fastDatapathOverlay) Diagnostics() interface{} { } } -func (fastdp fastDatapathOverlay) StartConsumingPackets(localPeer *Peer, - peers *Peers, consumer OverlayConsumer) error { +func (fastdp fastDatapathOverlay) StartConsumingPackets(localPeer *mesh.Peer, peers *mesh.Peers, consumer OverlayConsumer) error { fastdp.lock.Lock() defer fastdp.lock.Unlock() @@ -417,8 +409,7 @@ func (fastdp *FastDatapath) getVxlanVportID(udpPort int) (odp.VportID, error) { } vxlanVportID, err := fastdp.dp.CreateVport( - odp.NewVxlanVportSpec(fmt.Sprintf("vxlan-%d", udpPort), - uint16(udpPort))) + odp.NewVxlanVportSpec(fmt.Sprintf("vxlan-%d", udpPort), uint16(udpPort))) if err != nil { return 0, err } @@ -464,29 +455,27 @@ func (fastdp *FastDatapath) getVxlanVportID(udpPort int) (odp.VportID, error) { tunnelFlowKey.SetIpv4Src(tunKey.Ipv4Src) tunnelFlowKey.SetIpv4Dst(tunKey.Ipv4Dst) - return NewMultiFlowOp(false, odpFlowKey(tunnelFlowKey), - consumer(key)) + return NewMultiFlowOp(false, odpFlowKey(tunnelFlowKey), consumer(key)) } return vxlanVportID, nil } -func (fastdp *FastDatapath) extractPeers(tunnelID [8]byte) (*Peer, *Peer) { +func (fastdp *FastDatapath) extractPeers(tunnelID [8]byte) (*mesh.Peer, *mesh.Peer) { vni := binary.BigEndian.Uint64(tunnelID[:]) - srcPeer := fastdp.peers.FetchByShortID(PeerShortID(vni & 0xfff)) - dstPeer := fastdp.peers.FetchByShortID(PeerShortID((vni >> 12) & 0xfff)) + srcPeer := fastdp.peers.FetchByShortID(mesh.PeerShortID(vni & 0xfff)) + dstPeer := fastdp.peers.FetchByShortID(mesh.PeerShortID((vni >> 12) & 0xfff)) return srcPeer, dstPeer } type vxlanSpecialPacketFlowOp struct { NonDiscardingFlowOp fastdp *FastDatapath - srcPeer *Peer + srcPeer *mesh.Peer sender *net.UDPAddr } -func (op vxlanSpecialPacketFlowOp) Process(frame []byte, dec *EthernetDecoder, - broadcast bool) { +func (op vxlanSpecialPacketFlowOp) Process(frame []byte, dec *EthernetDecoder, broadcast bool) { op.fastdp.lock.Lock() fwd := op.fastdp.forwarders[op.srcPeer.Name] op.fastdp.lock.Unlock() @@ -498,7 +487,7 @@ func (op vxlanSpecialPacketFlowOp) Process(frame []byte, dec *EthernetDecoder, type fastDatapathForwarder struct { fastdp *FastDatapath - remotePeer *Peer + remotePeer *mesh.Peer localIP [4]byte sendControlMsg func(byte, []byte) error connUID uint64 @@ -518,24 +507,24 @@ type fastDatapathForwarder struct { errorChan chan error } -func (fastdp fastDatapathOverlay) MakeForwarder( - params ForwarderParams) (OverlayForwarder, error) { - if params.Crypto != nil { +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") } vxlanVportID := fastdp.mainVxlanVportID - remoteAddr := params.RemoteAddr - if remoteAddr != nil { + var remoteAddr *net.UDPAddr + + if params.Outbound { + remoteAddr = makeUDPAddr(params.RemoteAddr) // The provided address contains the main weave port // number to connect to. We need to derive the vxlan // port number from that. - vxlanRemoteAddr := *params.RemoteAddr + vxlanRemoteAddr := *remoteAddr vxlanRemoteAddr.Port++ remoteAddr = &vxlanRemoteAddr - var err error vxlanVportID, err = fastdp.getVxlanVportID(remoteAddr.Port) if err != nil { @@ -543,7 +532,7 @@ func (fastdp fastDatapathOverlay) MakeForwarder( } } - localIP, err := ipv4Bytes(params.LocalIP) + localIP, err := ipv4Bytes(params.LocalAddr.IP) if err != nil { return nil, err } @@ -674,15 +663,17 @@ func (fwd *fastDatapathForwarder) sendHeartbeat() { DstPeer: fwd.remotePeer, } fwd.lock.RUnlock() - fwd.Forward(pk).Process(buf, dec, false) + + if fop := fwd.Forward(pk); fop != nil { + fop.Process(buf, dec, false) + } } const ( FastDatapathHeartbeatAck = iota ) -func (fwd *fastDatapathForwarder) handleVxlanSpecialPacket(frame []byte, - sender *net.UDPAddr) { +func (fwd *fastDatapathForwarder) handleVxlanSpecialPacket(frame []byte, sender *net.UDPAddr) { fwd.lock.Lock() defer fwd.lock.Unlock() @@ -706,8 +697,7 @@ func (fwd *fastDatapathForwarder) handleVxlanSpecialPacket(frame []byte, fwd.heartbeatTimer.Reset(0) } } else if !udpAddrsEqual(fwd.remoteAddr, sender) { - log.Info(fwd.logPrefix(), - "Peer IP address changed to ", sender) + log.Info(fwd.logPrefix(), "Peer IP address changed to ", sender) fwd.remoteAddr = sender } @@ -732,8 +722,7 @@ func (fwd *fastDatapathForwarder) ControlMessage(tag byte, msg []byte) { fwd.handleHeartbeatAck() default: - log.Info(fwd.logPrefix(), - "Ignoring unknown control message: ", tag) + log.Info(fwd.logPrefix(), "Ignoring unknown control message: ", tag) } } @@ -809,8 +798,7 @@ func (fwd *fastDatapathForwarder) Stop() { } } -func (fastdp *FastDatapath) addForwarder(peer PeerName, - fwd *fastDatapathForwarder) { +func (fastdp *FastDatapath) addForwarder(peer mesh.PeerName, fwd *fastDatapathForwarder) { fastdp.lock.Lock() defer fastdp.lock.Unlock() @@ -819,8 +807,7 @@ func (fastdp *FastDatapath) addForwarder(peer PeerName, fastdp.forwarders[peer] = fwd } -func (fastdp *FastDatapath) removeForwarder(peer PeerName, - fwd *fastDatapathForwarder) { +func (fastdp *FastDatapath) removeForwarder(peer mesh.PeerName, fwd *fastDatapathForwarder) { fastdp.lock.Lock() defer fastdp.lock.Unlock() if fastdp.forwarders[peer] == fwd { @@ -899,9 +886,7 @@ func (fastdp *FastDatapath) expireFlows() { defer lock.unlock() flows, err := fastdp.dp.EnumerateFlows() - if err != nil { - log.Warn(err) - } + checkWarn(err) for _, flow := range flows { if flow.Used == 0 { @@ -922,8 +907,7 @@ func (fastdp *FastDatapath) expireFlows() { // maintain its MAC->peer table. We do this by querying the router // without an actual packet being involved. Maybe it's // worth devising a more unified approach in the future. -func (fastdp *FastDatapath) touchFlow(fks odp.FlowKeys, - lock *fastDatapathLock) { +func (fastdp *FastDatapath) touchFlow(fks odp.FlowKeys, lock *fastDatapathLock) { // All the flows we create should have an ingress key, but we // check here just in case we encounter one from somewhere // else. @@ -963,8 +947,7 @@ func (fastdp *FastDatapath) Miss(packet []byte, fks odp.FlowKeys) error { // delivery to a local netdev based on the dest MAC), // including the ingress in every flow makes things simpler // in touchFlow. - mfop := NewMultiFlowOp(false, handler(fks, &lock), - odpFlowKey(odp.NewInPortFlowKey(ingress))) + mfop := NewMultiFlowOp(false, handler(fks, &lock), odpFlowKey(odp.NewInPortFlowKey(ingress))) fastdp.send(mfop, packet, &lock) return nil } @@ -1032,8 +1015,7 @@ func (fastdp *FastDatapath) makeBridgeVport(vport odp.Vport) { // Packets coming from the netdev are processed by the bridge fastdp.missHandlers[vportID] = func(flowKeys odp.FlowKeys, lock *fastDatapathLock) FlowOp { - return fastdp.bridge(bridgePortID{vport: vportID}, - flowKeysToPacketKey(flowKeys), lock) + return fastdp.bridge(bridgePortID{vport: vportID}, flowKeysToPacketKey(flowKeys), lock) } } @@ -1044,8 +1026,7 @@ func flowKeysToPacketKey(fks odp.FlowKeys) PacketKey { // The sendToPort map is read-only, so this method does the copy in // order to add an entry. -func (fastdp *FastDatapath) addSendToPort(portID bridgePortID, - sender bridgeSender) { +func (fastdp *FastDatapath) addSendToPort(portID bridgePortID, sender bridgeSender) { sendToPort := map[bridgePortID]bridgeSender{portID: sender} for id, sender := range fastdp.sendToPort { sendToPort[id] = sender @@ -1064,8 +1045,7 @@ func (fastdp *FastDatapath) deleteSendToPort(portID bridgePortID) { } // Send a packet, creating a corresponding ODP flow rule if possible -func (fastdp *FastDatapath) send(fops FlowOp, frame []byte, - lock *fastDatapathLock) { +func (fastdp *FastDatapath) send(fops FlowOp, frame []byte, lock *fastDatapathLock) { // Gather the actions from actionFlowOps, execute any others var dec *EthernetDecoder flow := odp.NewFlowSpec() @@ -1157,8 +1137,7 @@ func (fop odpActionsFlowOp) updateFlowSpec(flow *odp.FlowSpec) { flow.AddActions(fop.actions) } -func (fop odpActionsFlowOp) Process(frame []byte, dec *EthernetDecoder, - broadcast bool) { +func (fop odpActionsFlowOp) Process(frame []byte, dec *EthernetDecoder, broadcast bool) { fastdp := fop.fastdp fastdp.lock.Lock() defer fastdp.lock.Unlock() diff --git a/router/flow.go b/router/flow.go index 7ca1caa2..e523a255 100644 --- a/router/flow.go +++ b/router/flow.go @@ -1,6 +1,10 @@ package router -import "net" +import ( + "net" + + "github.com/weaveworks/weave/mesh" +) // Just enough flow machinery for the weave router @@ -16,8 +20,8 @@ type PacketKey struct { } type ForwardPacketKey struct { - SrcPeer *Peer - DstPeer *Peer + SrcPeer *mesh.Peer + DstPeer *mesh.Peer PacketKey } @@ -64,8 +68,7 @@ func (mfop *MultiFlowOp) Add(op FlowOp) { mfop.ops = append(mfop.ops, op) } -func (mfop *MultiFlowOp) Process(frame []byte, dec *EthernetDecoder, - broadcast bool) { +func (mfop *MultiFlowOp) Process(frame []byte, dec *EthernetDecoder, broadcast bool) { for _, op := range mfop.ops { op.Process(frame, dec, mfop.broadcast) } diff --git a/router/crypto.go b/router/frame_crypto.go similarity index 62% rename from router/crypto.go rename to router/frame_crypto.go index 939157af..71c75257 100644 --- a/router/crypto.go +++ b/router/frame_crypto.go @@ -1,32 +1,13 @@ package router import ( - "crypto/rand" - "crypto/sha256" "encoding/binary" - "encoding/gob" "fmt" - "io" - "sync" "github.com/andybalholm/go-bit" - "golang.org/x/crypto/nacl/box" "golang.org/x/crypto/nacl/secretbox" ) -func GenerateKeyPair() (publicKey, privateKey *[32]byte, err error) { - return box.GenerateKey(rand.Reader) -} - -func FormSessionKey(remotePublicKey, localPrivateKey *[32]byte, secretKey []byte) *[32]byte { - var sharedKey [32]byte - box.Precompute(&sharedKey, remotePublicKey, localPrivateKey) - sharedKeySlice := sharedKey[:] - sharedKeySlice = append(sharedKeySlice, secretKey...) - sessionKey := sha256.Sum256(sharedKeySlice) - return &sessionKey -} - // Frame Encryptors type Encryptor interface { @@ -180,6 +161,10 @@ type PacketDecodingError struct { Desc string } +func (pde PacketDecodingError) Error() string { + return fmt.Sprint("Failed to decode packet: ", pde.Desc) +} + func NewNonDecryptor() *NonDecryptor { return &NonDecryptor{} } @@ -297,154 +282,3 @@ func (di *NaClDecryptorInstance) advanceState(seqNo uint64) (int, *bit.Set) { return offset, di.usedOffsets } } - -// TCP Senders/Receivers - -// The lowest 64 bits of the nonce contain the message sequence -// number. The top most bit indicates the connection polarity at the -// sender - '1' for outbound; the next indicates protocol type - '1' -// for TCP. The remaining 126 bits are zero. The polarity is needed so -// that the two ends of a connection do not use the same nonces; the -// protocol type so that the TCP and UDP sender nonces are disjoint. -// This is a requirement of the NaCl Security Model; see -// http://nacl.cr.yp.to/box.html. -type TCPCryptoState struct { - sessionKey *[32]byte - nonce [24]byte - seqNo uint64 -} - -func NewTCPCryptoState(sessionKey *[32]byte, outbound bool) *TCPCryptoState { - s := &TCPCryptoState{sessionKey: sessionKey} - if outbound { - s.nonce[0] |= (1 << 7) - } - // Ensure that TCP and UDP sequences are disjoint - s.nonce[0] |= (1 << 6) - return s -} - -func (s *TCPCryptoState) advance() { - s.seqNo++ - binary.BigEndian.PutUint64(s.nonce[16:24], s.seqNo) -} - -type TCPSender interface { - Send([]byte) error -} - -type GobTCPSender struct { - encoder *gob.Encoder -} - -type LengthPrefixTCPSender struct { - writer io.Writer -} - -type EncryptedTCPSender struct { - sync.RWMutex - sender TCPSender - state *TCPCryptoState -} - -func NewGobTCPSender(encoder *gob.Encoder) *GobTCPSender { - return &GobTCPSender{encoder: encoder} -} - -func (sender *GobTCPSender) Send(msg []byte) error { - return sender.encoder.Encode(msg) -} - -func NewLengthPrefixTCPSender(writer io.Writer) *LengthPrefixTCPSender { - return &LengthPrefixTCPSender{writer: writer} -} - -func (sender *LengthPrefixTCPSender) Send(msg []byte) error { - l := len(msg) - if l > MaxTCPMsgSize { - return fmt.Errorf("outgoing message exceeds maximum size: %d > %d", l, MaxTCPMsgSize) - } - // We copy the message so we can send it in a single Write - // operation, thus making this thread-safe without locking. - prefixedMsg := make([]byte, 4+l) - binary.BigEndian.PutUint32(prefixedMsg, uint32(l)) - copy(prefixedMsg[4:], msg) - _, err := sender.writer.Write(prefixedMsg) - return err -} - -func NewEncryptedTCPSender(sender TCPSender, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender { - return &EncryptedTCPSender{sender: sender, state: NewTCPCryptoState(sessionKey, outbound)} -} - -func (sender *EncryptedTCPSender) Send(msg []byte) error { - sender.Lock() - defer sender.Unlock() - encodedMsg := secretbox.Seal(nil, msg, &sender.state.nonce, sender.state.sessionKey) - sender.state.advance() - return sender.sender.Send(encodedMsg) -} - -type TCPReceiver interface { - Receive() ([]byte, error) -} - -type GobTCPReceiver struct { - decoder *gob.Decoder -} - -type LengthPrefixTCPReceiver struct { - reader io.Reader -} - -type EncryptedTCPReceiver struct { - receiver TCPReceiver - state *TCPCryptoState -} - -func NewGobTCPReceiver(decoder *gob.Decoder) *GobTCPReceiver { - return &GobTCPReceiver{decoder: decoder} -} - -func (receiver *GobTCPReceiver) Receive() ([]byte, error) { - var msg []byte - err := receiver.decoder.Decode(&msg) - return msg, err -} - -func NewLengthPrefixTCPReceiver(reader io.Reader) *LengthPrefixTCPReceiver { - return &LengthPrefixTCPReceiver{reader: reader} -} - -func (receiver *LengthPrefixTCPReceiver) Receive() ([]byte, error) { - lenPrefix := make([]byte, 4) - if _, err := io.ReadFull(receiver.reader, lenPrefix); err != nil { - return nil, err - } - l := binary.BigEndian.Uint32(lenPrefix) - if l > MaxTCPMsgSize { - return nil, fmt.Errorf("incoming message exceeds maximum size: %d > %d", l, MaxTCPMsgSize) - } - msg := make([]byte, l) - _, err := io.ReadFull(receiver.reader, msg) - return msg, err -} - -func NewEncryptedTCPReceiver(receiver TCPReceiver, sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver { - return &EncryptedTCPReceiver{receiver: receiver, state: NewTCPCryptoState(sessionKey, !outbound)} -} - -func (receiver *EncryptedTCPReceiver) Receive() ([]byte, error) { - msg, err := receiver.receiver.Receive() - if err != nil { - return nil, err - } - - decodedMsg, success := secretbox.Open(nil, msg, &receiver.state.nonce, receiver.state.sessionKey) - if !success { - return nil, fmt.Errorf("Unable to decrypt TCP msg") - } - - receiver.state.advance() - return decodedMsg, nil -} diff --git a/router/http.go b/router/http.go index 7ad552b8..c4057e87 100644 --- a/router/http.go +++ b/router/http.go @@ -2,12 +2,13 @@ package router import ( "fmt" + "net/http" + "github.com/gorilla/mux" "github.com/weaveworks/weave/common" - "net/http" ) -func (router *Router) HandleHTTP(muxRouter *mux.Router) { +func (router *NetworkRouter) HandleHTTP(muxRouter *mux.Router) { muxRouter.Methods("POST").Path("/connect").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { diff --git a/router/mac_cache.go b/router/mac_cache.go index ecc9afb3..0d95d623 100644 --- a/router/mac_cache.go +++ b/router/mac_cache.go @@ -4,11 +4,13 @@ import ( "net" "sync" "time" + + "github.com/weaveworks/weave/mesh" ) type MacCacheEntry struct { lastSeen time.Time - peer *Peer + peer *mesh.Peer } type MacCache struct { @@ -16,10 +18,10 @@ type MacCache struct { table map[uint64]*MacCacheEntry maxAge time.Duration expiryTimer *time.Timer - onExpiry func(net.HardwareAddr, *Peer) + onExpiry func(net.HardwareAddr, *mesh.Peer) } -func NewMacCache(maxAge time.Duration, onExpiry func(net.HardwareAddr, *Peer)) *MacCache { +func NewMacCache(maxAge time.Duration, onExpiry func(net.HardwareAddr, *mesh.Peer)) *MacCache { cache := &MacCache{ table: make(map[uint64]*MacCacheEntry), maxAge: maxAge, @@ -28,7 +30,7 @@ func NewMacCache(maxAge time.Duration, onExpiry func(net.HardwareAddr, *Peer)) * return cache } -func (cache *MacCache) add(mac net.HardwareAddr, peer *Peer, force bool) (bool, *Peer) { +func (cache *MacCache) add(mac net.HardwareAddr, peer *mesh.Peer, force bool) (bool, *mesh.Peer) { key := macint(mac) now := time.Now() @@ -64,15 +66,15 @@ func (cache *MacCache) add(mac net.HardwareAddr, peer *Peer, force bool) (bool, return false, nil } -func (cache *MacCache) Add(mac net.HardwareAddr, peer *Peer) (bool, *Peer) { +func (cache *MacCache) Add(mac net.HardwareAddr, peer *mesh.Peer) (bool, *mesh.Peer) { return cache.add(mac, peer, false) } -func (cache *MacCache) AddForced(mac net.HardwareAddr, peer *Peer) (bool, *Peer) { +func (cache *MacCache) AddForced(mac net.HardwareAddr, peer *mesh.Peer) (bool, *mesh.Peer) { return cache.add(mac, peer, true) } -func (cache *MacCache) Lookup(mac net.HardwareAddr) *Peer { +func (cache *MacCache) Lookup(mac net.HardwareAddr) *mesh.Peer { key := macint(mac) cache.RLock() defer cache.RUnlock() @@ -83,7 +85,7 @@ func (cache *MacCache) Lookup(mac net.HardwareAddr) *Peer { return entry.peer } -func (cache *MacCache) Delete(peer *Peer) bool { +func (cache *MacCache) Delete(peer *mesh.Peer) bool { found := false cache.Lock() defer cache.Unlock() @@ -112,3 +114,20 @@ func (cache *MacCache) expire() { } cache.setExpiryTimer() } + +func macint(mac net.HardwareAddr) (r uint64) { + for _, b := range mac { + r <<= 8 + r |= uint64(b) + } + return +} + +func intmac(key uint64) (r net.HardwareAddr) { + r = make([]byte, 6) + for i := 5; i >= 0; i-- { + r[i] = byte(key) + key >>= 8 + } + return +} diff --git a/router/network_overlay.go b/router/network_overlay.go new file mode 100644 index 00000000..b09b720b --- /dev/null +++ b/router/network_overlay.go @@ -0,0 +1,50 @@ +package router + +import ( + "github.com/weaveworks/weave/mesh" +) + +// Interface to overlay network packet handling +type NetworkOverlay interface { + mesh.Overlay + + // The routes have changed, so any cached information should + // be discarded. + InvalidateRoutes() + + // A mapping of a short id to a peer has changed + InvalidateShortIDs() + + // Start consuming forwarded packets. + StartConsumingPackets(*mesh.Peer, *mesh.Peers, OverlayConsumer) error +} + +// When a consumer is called, the decoder will already have been used +// to decode the frame. +type OverlayConsumer func(ForwardPacketKey) FlowOp + +// All of the machinery to forward packets to a particular peer +type OverlayForwarder interface { + mesh.OverlayConnection + // Forward a packet across the connection. May be called as soon + // as the overlay connection is created, in particular before + // Confirm(). The return value nil means the key could not be + // handled by this forwarder. + Forward(ForwardPacketKey) FlowOp +} + +type NullNetworkOverlay struct{ mesh.NullOverlay } + +func (NullNetworkOverlay) InvalidateRoutes() { +} + +func (NullNetworkOverlay) InvalidateShortIDs() { +} + +func (NullNetworkOverlay) StartConsumingPackets(*mesh.Peer, *mesh.Peers, OverlayConsumer) error { + return nil +} + +func (NullNetworkOverlay) Forward(ForwardPacketKey) FlowOp { + return DiscardingFlowOp{} +} diff --git a/router/network_router.go b/router/network_router.go new file mode 100644 index 00000000..20186de5 --- /dev/null +++ b/router/network_router.go @@ -0,0 +1,209 @@ +package router + +import ( + "math" + "net" + "time" + + "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/mesh" +) + +const ( + ChannelSize = 16 + MaxUDPPacketSize = 65535 + FastHeartbeat = 500 * time.Millisecond + SlowHeartbeat = 10 * time.Second + MaxMissedHeartbeats = 6 + HeartbeatTimeout = MaxMissedHeartbeats * SlowHeartbeat + MaxDuration = time.Duration(math.MaxInt64) + NameSize = mesh.NameSize + // should be greater than typical ARP cache expiries, i.e. > 3/2 * + // /proc/sys/net/ipv4_neigh/*/base_reachable_time_ms on Linux + macMaxAge = 10 * time.Minute +) + +var ( + log = common.Log + checkFatal = common.CheckFatal + checkWarn = common.CheckWarn +) + +type NetworkConfig struct { + BufSz int + PacketLogging PacketLogging + Bridge Bridge +} + +type PacketLogging interface { + LogPacket(string, PacketKey) + LogForwardPacket(string, ForwardPacketKey) +} + +type NetworkRouter struct { + *mesh.Router + NetworkConfig + Macs *MacCache +} + +func NewNetworkRouter(config mesh.Config, networkConfig NetworkConfig, name mesh.PeerName, nickName string, overlay NetworkOverlay) *NetworkRouter { + if overlay == nil { + overlay = NullNetworkOverlay{} + } + if networkConfig.Bridge == nil { + networkConfig.Bridge = NullBridge{} + } + + router := &NetworkRouter{Router: mesh.NewRouter(config, name, nickName, overlay), NetworkConfig: networkConfig} + router.Peers.OnInvalidateShortIDs(overlay.InvalidateShortIDs) + router.Routes.OnChange(overlay.InvalidateRoutes) + router.Macs = NewMacCache(macMaxAge, + func(mac net.HardwareAddr, peer *mesh.Peer) { + log.Println("Expired MAC", mac, "at", peer) + }) + router.Peers.OnGC(func(peer *mesh.Peer) { router.Macs.Delete(peer) }) + return router +} + +// Start listening for TCP connections, locally captured packets, and +// forwarded packets. +func (router *NetworkRouter) Start() { + log.Println("Sniffing traffic on", router.Bridge) + checkFatal(router.Bridge.StartConsumingPackets(router.handleCapturedPacket)) + checkFatal(router.Overlay.(NetworkOverlay).StartConsumingPackets(router.Ourself.Peer, router.Peers, router.handleForwardedPacket)) + router.Router.Start() +} + +func (router *NetworkRouter) handleCapturedPacket(key PacketKey) FlowOp { + router.PacketLogging.LogPacket("Captured", key) + srcMac := net.HardwareAddr(key.SrcMAC[:]) + + switch newSrcMac, conflictPeer := router.Macs.Add(srcMac, router.Ourself.Peer); { + case newSrcMac: + log.Println("Discovered local MAC", srcMac) + + case conflictPeer != nil: + // The MAC cache has an entry for the source MAC + // associated with another peer. This probably means + // we are seeing a frame we injected ourself. That + // shouldn't happen, but discard it just in case. + log.Error("Captured frame from MAC (", srcMac, ") associated with another peer ", conflictPeer) + return DiscardingFlowOp{} + } + + // Discard STP broadcasts + if key.DstMAC == [...]byte{0x01, 0x80, 0xC2, 0x00, 0x00, 0x00} { + return DiscardingFlowOp{} + } + + dstMac := net.HardwareAddr(key.DstMAC[:]) + switch dstPeer := router.Macs.Lookup(dstMac); dstPeer { + case router.Ourself.Peer: + // The packet is destined for a local MAC. The bridge + // won't normally send us such packets, and if it does + // it's likely to be broadcasting the packet to all + // ports. So if it happens, just drop the packet to + // avoid warnings if we try to forward it. + return DiscardingFlowOp{} + case nil: + // If we don't know which peer corresponds to the dest + // MAC, broadcast it. + router.PacketLogging.LogPacket("Broadcasting", key) + return router.relayBroadcast(router.Ourself.Peer, key) + default: + router.PacketLogging.LogPacket("Forwarding", key) + return router.relay(ForwardPacketKey{ + PacketKey: key, + SrcPeer: router.Ourself.Peer, + DstPeer: dstPeer}) + } +} + +func (router *NetworkRouter) handleForwardedPacket(key ForwardPacketKey) FlowOp { + if key.DstPeer != router.Ourself.Peer { + // it's not for us, we're just relaying it + router.PacketLogging.LogForwardPacket("Relaying", key) + return router.relay(key) + } + + // At this point, it's either unicast to us, or a broadcast + // (because the DstPeer on a forwarded broadcast packet is + // always set to the peer being forwarded to) + + srcMac := net.HardwareAddr(key.SrcMAC[:]) + dstMac := net.HardwareAddr(key.DstMAC[:]) + + switch newSrcMac, conflictPeer := router.Macs.AddForced(srcMac, key.SrcPeer); { + case newSrcMac: + log.Print("Discovered remote MAC ", srcMac, " at ", key.SrcPeer) + + case conflictPeer != nil: + log.Print("Discovered remote MAC ", srcMac, " at ", key.SrcPeer, " (was at ", conflictPeer, ")") + + // We need to clear out any flows destined to the MAC + // that forward to the old peer. + router.Overlay.(NetworkOverlay).InvalidateRoutes() + } + + router.PacketLogging.LogForwardPacket("Injecting", key) + injectFop := router.Bridge.InjectPacket(key.PacketKey) + dstPeer := router.Macs.Lookup(dstMac) + if dstPeer == router.Ourself.Peer { + return injectFop + } + + router.PacketLogging.LogForwardPacket("Relaying broadcast", key) + relayFop := router.relayBroadcast(key.SrcPeer, key.PacketKey) + switch { + case injectFop == nil: + return relayFop + + case relayFop == nil: + return injectFop + + default: + mfop := NewMultiFlowOp(false) + mfop.Add(injectFop) + mfop.Add(relayFop) + return mfop + } +} + +// Routing + +func (router *NetworkRouter) relay(key ForwardPacketKey) FlowOp { + relayPeerName, found := router.Routes.Unicast(key.DstPeer.Name) + if !found { + // Not necessarily an error as there could be a race with the + // dst disappearing whilst the frame is in flight + log.Println("Received packet for unknown destination:", key.DstPeer) + return DiscardingFlowOp{} + } + + conn, found := router.Ourself.ConnectionTo(relayPeerName) + if !found { + // Again, could just be a race, not necessarily an error + log.Println("Unable to find connection to relay peer", relayPeerName) + return DiscardingFlowOp{} + } + + return conn.(*mesh.LocalConnection).OverlayConn.(OverlayForwarder).Forward(key) +} + +func (router *NetworkRouter) relayBroadcast(srcPeer *mesh.Peer, key PacketKey) FlowOp { + nextHops := router.Routes.Broadcast(srcPeer.Name) + if len(nextHops) == 0 { + return DiscardingFlowOp{} + } + + op := NewMultiFlowOp(true) + + for _, conn := range router.Ourself.ConnectionsTo(nextHops) { + op.Add(conn.(*mesh.LocalConnection).OverlayConn.(OverlayForwarder).Forward(ForwardPacketKey{ + PacketKey: key, + SrcPeer: srcPeer, + DstPeer: conn.Remote()})) + } + + return op +} diff --git a/router/network_router_status.go b/router/network_router_status.go new file mode 100644 index 00000000..e35267e9 --- /dev/null +++ b/router/network_router_status.go @@ -0,0 +1,45 @@ +package router + +import ( + "time" + + "github.com/weaveworks/weave/mesh" +) + +type NetworkRouterStatus struct { + *mesh.Status + Interface string + CaptureStats map[string]int + MACs []MACStatus +} + +type MACStatus struct { + Mac string + Name string + NickName string + LastSeen time.Time +} + +func NewNetworkRouterStatus(router *NetworkRouter) *NetworkRouterStatus { + return &NetworkRouterStatus{ + mesh.NewStatus(router.Router), + router.Bridge.String(), + router.Bridge.Stats(), + NewMACStatusSlice(router.Macs)} +} + +func NewMACStatusSlice(cache *MacCache) []MACStatus { + cache.RLock() + defer cache.RUnlock() + + var slice []MACStatus + for key, entry := range cache.table { + slice = append(slice, MACStatus{ + intmac(key).String(), + entry.peer.Name.String(), + entry.peer.NickName, + entry.lastSeen}) + } + + return slice +} diff --git a/router/overlay.go b/router/overlay.go deleted file mode 100644 index 3ab9f88d..00000000 --- a/router/overlay.go +++ /dev/null @@ -1,146 +0,0 @@ -package router - -import ( - "net" -) - -// Interface to overlay network packet handling -type Overlay interface { - // Start consuming forwarded packets. - StartConsumingPackets(*Peer, *Peers, OverlayConsumer) error - - // Form a packet-forwarding connection. - MakeForwarder(ForwarderParams) (OverlayForwarder, error) - - // The routes have changed, so any cached information should - // be discarded. - InvalidateRoutes() - - // A mapping of a short id to a peer has changed - InvalidateShortIDs() - - // Enhance a features map with overlay-related features - AddFeaturesTo(map[string]string) - - // Obtain diagnostic information specific to the overlay - Diagnostics() interface{} -} - -type ForwarderParams struct { - RemotePeer *Peer - - // The local IP address to use for sending. Derived from the - // local address of the corresponding TCP socket, so may - // differ for different forwarders. - LocalIP net.IP - - // The remote address to send to. nil if unknown, i.e. an - // incoming connection, in which case the Overlay needs to - // discover it (e.g. from incoming datagrams). - RemoteAddr *net.UDPAddr - - // Unique identifier for this connection - ConnUID uint64 - - // Crypto bits. Nil if not encrypting - Crypto *OverlayCrypto - - // Function to send a control message to the counterpart - // forwarder. - SendControlMessage func(tag byte, msg []byte) error - - // Features passed at connection initiation - Features map[string]string -} - -// When a consumer is called, the decoder will already have been used -// to decode the frame. -type OverlayConsumer func(ForwardPacketKey) FlowOp - -// Crypto settings for a forwarder. -type OverlayCrypto struct { - Dec Decryptor - Enc Encryptor - EncDF Encryptor -} - -// All of the machinery to forward packets to a particular peer -type OverlayForwarder interface { - // Forward a packet across the connection. May be called as - // soon as the forwarder is created, in particular before - // Confirm(). The return value nil means the key could not be - // handled by this forwarder. - Forward(ForwardPacketKey) FlowOp - - // Confirm that the connection is really wanted, and so the - // Overlay should begin heartbeats etc. to verify the - // operation of the forwarder. - Confirm() - - // A channel indicating that the forwarder is established, - // i.e. its operation has been confirmed. - EstablishedChannel() <-chan struct{} - - // A channel indicating an error from the forwarder. The - // forwarder is not expected to be operational after the first - // error, so the channel only needs to buffer a single error. - ErrorChannel() <-chan error - - Stop() - - // Handle a message from the peer. 'tag' exists for - // compatibility, and should always be - // ProtocolOverlayControlMessage for non-sleeve overlays. - ControlMessage(tag byte, msg []byte) - - // User facing overlay name - DisplayName() string -} - -type NullOverlay struct{} - -func (NullOverlay) StartConsumingPackets(*Peer, *Peers, OverlayConsumer) error { - return nil -} - -func (NullOverlay) MakeForwarder(ForwarderParams) (OverlayForwarder, error) { - return NullOverlay{}, nil -} - -func (NullOverlay) InvalidateRoutes() { -} - -func (NullOverlay) InvalidateShortIDs() { -} - -func (NullOverlay) Confirm() { -} - -func (NullOverlay) EstablishedChannel() <-chan struct{} { - return nil -} - -func (NullOverlay) ErrorChannel() <-chan error { - return nil -} - -func (NullOverlay) AddFeaturesTo(map[string]string) { -} - -func (NullOverlay) Forward(ForwardPacketKey) FlowOp { - return DiscardingFlowOp{} -} - -func (NullOverlay) Stop() { -} - -func (NullOverlay) ControlMessage(byte, []byte) { -} - -func (NullOverlay) DisplayName() string { - return "null" -} - -func (NullOverlay) Diagnostics() interface{} { - return nil -} diff --git a/router/overlay_switch.go b/router/overlay_switch.go index 61f4202e..9e6efd00 100644 --- a/router/overlay_switch.go +++ b/router/overlay_switch.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" "sync" + + "github.com/weaveworks/weave/mesh" ) // OverlaySwitch selects which overlay to use, from a set of @@ -13,16 +15,16 @@ import ( // uses the best one that seems to be working. type OverlaySwitch struct { - overlays map[string]Overlay + overlays map[string]NetworkOverlay overlayNames []string - compatOverlay Overlay + compatOverlay NetworkOverlay } func NewOverlaySwitch() *OverlaySwitch { - return &OverlaySwitch{overlays: make(map[string]Overlay)} + return &OverlaySwitch{overlays: make(map[string]NetworkOverlay)} } -func (osw *OverlaySwitch) Add(name string, overlay Overlay) { +func (osw *OverlaySwitch) Add(name string, overlay NetworkOverlay) { // check for repeated names if _, present := osw.overlays[name]; present { log.Fatal("OverlaySwitch: repeated overlay name") @@ -32,7 +34,7 @@ func (osw *OverlaySwitch) Add(name string, overlay Overlay) { osw.overlayNames = append(osw.overlayNames, name) } -func (osw *OverlaySwitch) SetCompatOverlay(overlay Overlay) { +func (osw *OverlaySwitch) SetCompatOverlay(overlay NetworkOverlay) { osw.compatOverlay = overlay } @@ -60,11 +62,9 @@ func (osw *OverlaySwitch) InvalidateShortIDs() { } } -func (osw *OverlaySwitch) StartConsumingPackets(localPeer *Peer, peers *Peers, - consumer OverlayConsumer) error { +func (osw *OverlaySwitch) StartConsumingPackets(localPeer *mesh.Peer, peers *mesh.Peers, consumer OverlayConsumer) error { for _, overlay := range osw.overlays { - if err := overlay.StartConsumingPackets(localPeer, peers, - consumer); err != nil { + if err := overlay.StartConsumingPackets(localPeer, peers, consumer); err != nil { return err } } @@ -72,19 +72,19 @@ func (osw *OverlaySwitch) StartConsumingPackets(localPeer *Peer, peers *Peers, } type namedOverlay struct { - Overlay + NetworkOverlay name string } // Find the common set of overlays supported by both sides, with the // ordering being the same on both sides too. -func (osw *OverlaySwitch) commonOverlays(params ForwarderParams) ([]namedOverlay, error) { +func (osw *OverlaySwitch) commonOverlays(params mesh.OverlayConnectionParams) ([]namedOverlay, error) { var peerOverlays []string if overlaysFeature, present := params.Features["Overlays"]; present { peerOverlays = strings.Split(overlaysFeature, " ") } - common := make(map[string]Overlay) + common := make(map[string]NetworkOverlay) for _, name := range peerOverlays { if overlay := osw.overlays[name]; overlay != nil { common[name] = overlay @@ -120,7 +120,7 @@ func (osw *OverlaySwitch) commonOverlays(params ForwarderParams) ([]namedOverlay } type overlaySwitchForwarder struct { - remotePeer *Peer + remotePeer *mesh.Peer lock sync.Mutex @@ -162,10 +162,9 @@ type subForwarderEvent struct { err error } -func (osw *OverlaySwitch) MakeForwarder( - params ForwarderParams) (OverlayForwarder, error) { +func (osw *OverlaySwitch) PrepareConnection(params mesh.OverlayConnectionParams) (mesh.OverlayConnection, error) { if _, present := params.Features["Overlays"]; !present && osw.compatOverlay != nil { - return osw.compatOverlay.MakeForwarder(params) + return osw.compatOverlay.PrepareConnection(params) } overlays, err := osw.commonOverlays(params) @@ -200,15 +199,15 @@ func (osw *OverlaySwitch) MakeForwarder( xmsg[0] = byte(index) xmsg[1] = tag copy(xmsg[2:], msg) - return origSendControlMessage(ProtocolOverlayControlMsg, - xmsg) + return origSendControlMessage(mesh.ProtocolOverlayControlMsg, xmsg) } - subFwd, err := overlay.MakeForwarder(params) + subConn, err := overlay.PrepareConnection(params) if err != nil { fwd.stopFrom(0) return nil, err } + subFwd := subConn.(OverlayForwarder) subStopChan := make(chan struct{}) go monitorForwarder(i, eventsChan, subStopChan, subFwd) @@ -224,8 +223,7 @@ func (osw *OverlaySwitch) MakeForwarder( return fwd, nil } -func monitorForwarder(index int, eventsChan chan<- subForwarderEvent, - stopChan <-chan struct{}, fwd OverlayForwarder) { +func monitorForwarder(index int, eventsChan chan<- subForwarderEvent, stopChan <-chan struct{}, fwd OverlayForwarder) { establishedChan := fwd.EstablishedChannel() loop: for { @@ -257,8 +255,7 @@ loop: fwd.Stop() } -func (fwd *overlaySwitchForwarder) run(eventsChan <-chan subForwarderEvent, - stopChan <-chan struct{}) { +func (fwd *overlaySwitchForwarder) run(eventsChan <-chan subForwarderEvent, stopChan <-chan struct{}) { loop: for { select { @@ -355,8 +352,7 @@ func (fwd *overlaySwitchForwarder) chooseBest() { if fwd.best != best { fwd.best = best - log.Info(fwd.logPrefix(), - "using ", fwd.forwarders[best].overlayName) + log.Info(fwd.logPrefix(), "using ", fwd.forwarders[best].overlayName) } } diff --git a/router/router.go b/router/router.go deleted file mode 100644 index 5e135a6d..00000000 --- a/router/router.go +++ /dev/null @@ -1,337 +0,0 @@ -package router - -import ( - "fmt" - "net" - "sync" - "time" -) - -const ( - macMaxAge = 10 * time.Minute // [1] - acceptMaxTokens = 100 // [2] - acceptTokenDelay = 100 * time.Millisecond // [3] -) - -// [1] should be greater than typical ARP cache expiries, i.e. > 3/2 * -// /proc/sys/net/ipv4_neigh/*/base_reachable_time_ms on Linux - -// [2] capacity of token bucket for rate limiting accepts - -// [3] control rate at which new tokens are added to the bucket - -type Config struct { - Port int - ProtocolMinVersion byte - Password []byte - ConnLimit int - PeerDiscovery bool - BufSz int - PacketLogging PacketLogging - Bridge Bridge - Overlay Overlay -} - -type PacketLogging interface { - LogPacket(string, PacketKey) - LogForwardPacket(string, ForwardPacketKey) -} - -type Router struct { - Config - Ourself *LocalPeer - Macs *MacCache - Peers *Peers - Routes *Routes - ConnectionMaker *ConnectionMaker - gossipLock sync.RWMutex - gossipChannels GossipChannels - TopologyGossip Gossip - UDPListener *net.UDPConn - acceptLimiter *TokenBucket -} - -func NewRouter(config Config, name PeerName, nickName string) *Router { - router := &Router{Config: config, gossipChannels: make(GossipChannels)} - - if router.Bridge == nil { - router.Bridge = NullBridge{} - } - - if router.Overlay == nil { - router.Overlay = NullOverlay{} - } - - router.Ourself = NewLocalPeer(name, nickName, router) - router.Macs = NewMacCache(macMaxAge, - func(mac net.HardwareAddr, peer *Peer) { - log.Println("Expired MAC", mac, "at", peer) - }) - router.Peers = NewPeers(router.Ourself) - router.Peers.OnGC(func(peer *Peer) { - router.Macs.Delete(peer) - log.Println("Removed unreachable peer", peer) - }) - router.Peers.OnInvalidateShortIDs(router.Overlay.InvalidateShortIDs) - - router.Routes = NewRoutes(router.Ourself, router.Peers, router.Overlay.InvalidateRoutes) - router.ConnectionMaker = NewConnectionMaker(router.Ourself, router.Peers, router.Port, router.PeerDiscovery) - router.TopologyGossip = router.NewGossip("topology", router) - router.acceptLimiter = NewTokenBucket(acceptMaxTokens, acceptTokenDelay) - return router -} - -// Start listening for TCP connections, locally captured packets, and -// forwarded packets. This is separate from NewRouter so -// that gossipers can register before we start forming connections. -func (router *Router) Start() { - log.Println("Sniffing traffic on", router.Bridge) - checkFatal(router.Bridge.StartConsumingPackets(router.handleCapturedPacket)) - checkFatal(router.Overlay.StartConsumingPackets(router.Ourself.Peer, router.Peers, router.handleForwardedPacket)) - router.listenTCP(router.Port) -} - -func (router *Router) Stop() error { - // TODO: perform graceful shutdown... - return nil -} - -func (router *Router) UsingPassword() bool { - return router.Password != nil -} - -func (router *Router) handleCapturedPacket(key PacketKey) FlowOp { - router.PacketLogging.LogPacket("Captured", key) - srcMac := net.HardwareAddr(key.SrcMAC[:]) - - switch newSrcMac, conflictPeer := router.Macs.Add(srcMac, router.Ourself.Peer); { - case newSrcMac: - log.Println("Discovered local MAC", srcMac) - - case conflictPeer != nil: - // The MAC cache has an entry for the source MAC - // associated with another peer. This probably means - // we are seeing a frame we injected ourself. That - // shouldn't happen, but discard it just in case. - log.Error("Captured frame from MAC (", srcMac, ") associated with another peer ", conflictPeer) - return DiscardingFlowOp{} - } - - // Discard STP broadcasts - if key.DstMAC == [...]byte{0x01, 0x80, 0xC2, 0x00, 0x00, 0x00} { - return DiscardingFlowOp{} - } - - dstMac := net.HardwareAddr(key.DstMAC[:]) - switch dstPeer := router.Macs.Lookup(dstMac); dstPeer { - case router.Ourself.Peer: - // The packet is destined for a local MAC. The bridge - // won't normally send us such packets, and if it does - // it's likely to be broadcasting the packet to all - // ports. So if it happens, just drop the packet to - // avoid warnings if we try to forward it. - return DiscardingFlowOp{} - case nil: - // If we don't know which peer corresponds to the dest - // MAC, broadcast it. - router.PacketLogging.LogPacket("Broadcasting", key) - return router.relayBroadcast(router.Ourself.Peer, key) - default: - router.PacketLogging.LogPacket("Forwarding", key) - return router.relay(ForwardPacketKey{ - PacketKey: key, - SrcPeer: router.Ourself.Peer, - DstPeer: dstPeer}) - } -} - -func (router *Router) listenTCP(localPort int) { - localAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprint(":", localPort)) - checkFatal(err) - ln, err := net.ListenTCP("tcp4", localAddr) - checkFatal(err) - go func() { - defer ln.Close() - for { - tcpConn, err := ln.AcceptTCP() - if err != nil { - log.Errorln(err) - continue - } - router.acceptTCP(tcpConn) - router.acceptLimiter.Wait() - } - }() -} - -func (router *Router) acceptTCP(tcpConn *net.TCPConn) { - // someone else is dialing us, so our udp sender is the conn - // on router.Port and we wait for them to send us something on UDP to - // start. - remoteAddrStr := tcpConn.RemoteAddr().String() - log.Printf("->[%s] connection accepted", remoteAddrStr) - connRemote := NewRemoteConnection(router.Ourself.Peer, nil, remoteAddrStr, false, false) - StartLocalConnection(connRemote, tcpConn, nil, router, true) -} - -func (router *Router) handleForwardedPacket(key ForwardPacketKey) FlowOp { - if key.DstPeer != router.Ourself.Peer { - // it's not for us, we're just relaying it - router.PacketLogging.LogForwardPacket("Relaying", key) - return router.relay(key) - } - - // At this point, it's either unicast to us, or a broadcast - // (because the DstPeer on a forwarded broadcast packet is - // always set to the peer being forwarded to) - - srcMac := net.HardwareAddr(key.SrcMAC[:]) - dstMac := net.HardwareAddr(key.DstMAC[:]) - - switch newSrcMac, conflictPeer := router.Macs.AddForced(srcMac, key.SrcPeer); { - case newSrcMac: - log.Print("Discovered remote MAC ", srcMac, " at ", key.SrcPeer) - - case conflictPeer != nil: - log.Print("Discovered remote MAC ", srcMac, " at ", key.SrcPeer, " (was at ", conflictPeer, ")") - - // We need to clear out any flows destined to the MAC - // that forward to the old peer. - router.Overlay.InvalidateRoutes() - } - - router.PacketLogging.LogForwardPacket("Injecting", key) - injectFop := router.Bridge.InjectPacket(key.PacketKey) - dstPeer := router.Macs.Lookup(dstMac) - if dstPeer == router.Ourself.Peer { - return injectFop - } - - router.PacketLogging.LogForwardPacket("Relaying broadcast", key) - relayFop := router.relayBroadcast(key.SrcPeer, key.PacketKey) - switch { - case injectFop == nil: - return relayFop - - case relayFop == nil: - return injectFop - - default: - mfop := NewMultiFlowOp(false) - mfop.Add(injectFop) - mfop.Add(relayFop) - return mfop - } -} - -// Routing - -func (router *Router) relay(key ForwardPacketKey) FlowOp { - relayPeerName, found := router.Routes.Unicast(key.DstPeer.Name) - if !found { - // Not necessarily an error as there could be a race with the - // dst disappearing whilst the frame is in flight - log.Println("Received packet for unknown destination:", key.DstPeer) - return DiscardingFlowOp{} - } - - conn, found := router.Ourself.ConnectionTo(relayPeerName) - if !found { - // Again, could just be a race, not necessarily an error - log.Println("Unable to find connection to relay peer", relayPeerName) - return DiscardingFlowOp{} - } - - return conn.(*LocalConnection).Forward(key) -} - -func (router *Router) relayBroadcast(srcPeer *Peer, key PacketKey) FlowOp { - nextHops := router.Routes.Broadcast(srcPeer.Name) - if len(nextHops) == 0 { - return DiscardingFlowOp{} - } - - op := NewMultiFlowOp(true) - - for _, conn := range router.Ourself.ConnectionsTo(nextHops) { - op.Add(conn.(*LocalConnection).Forward(ForwardPacketKey{ - PacketKey: key, - SrcPeer: srcPeer, - DstPeer: conn.Remote()})) - } - - return op -} - -// Gossiper methods - the Router is the topology Gossiper - -type TopologyGossipData struct { - peers *Peers - update PeerNameSet -} - -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)} -} - -func (router *Router) OnGossipUnicast(sender PeerName, msg []byte) error { - return fmt.Errorf("unexpected topology gossip unicast: %v", msg) -} - -func (router *Router) OnGossipBroadcast(_ PeerName, update []byte) (GossipData, error) { - origUpdate, _, err := router.applyTopologyUpdate(update) - if err != nil || len(origUpdate) == 0 { - return nil, err - } - return &TopologyGossipData{peers: router.Peers, update: origUpdate}, nil -} - -func (router *Router) Gossip() GossipData { - return &TopologyGossipData{peers: router.Peers, update: router.Peers.Names()} -} - -func (router *Router) OnGossip(update []byte) (GossipData, error) { - _, newUpdate, err := router.applyTopologyUpdate(update) - if err != nil || len(newUpdate) == 0 { - return nil, err - } - return &TopologyGossipData{peers: router.Peers, update: newUpdate}, nil -} - -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 - } - if len(newUpdate) > 0 { - router.ConnectionMaker.Refresh() - router.Routes.Recalculate() - } - return origUpdate, newUpdate, nil -} diff --git a/router/sleeve.go b/router/sleeve.go index 676d73c7..0be92572 100644 --- a/router/sleeve.go +++ b/router/sleeve.go @@ -11,12 +11,15 @@ import ( "fmt" "io" "net" + "os" "sync" "syscall" "time" "github.com/google/gopacket" "github.com/google/gopacket/layers" + + "github.com/weaveworks/weave/mesh" ) // This diagram explains the various arithmetic and variables related @@ -53,6 +56,10 @@ const ( FragTestInterval = 5 * time.Minute MTUVerifyAttempts = 8 MTUVerifyTimeout = 10 * time.Millisecond // doubled with each attempt + + ProtocolConnectionEstablished = mesh.ProtocolReserved1 + ProtocolFragmentationReceived = mesh.ProtocolReserved2 + ProtocolPMTUVerified = mesh.ProtocolReserved3 ) type SleeveOverlay struct { @@ -60,24 +67,22 @@ type SleeveOverlay struct { // These fields are set in StartConsumingPackets, and not // subsequently modified - localPeer *Peer + localPeer *mesh.Peer localPeerBin []byte consumer OverlayConsumer - peers *Peers + peers *mesh.Peers conn *net.UDPConn lock sync.Mutex - forwarders map[PeerName]*sleeveForwarder + forwarders map[mesh.PeerName]*sleeveForwarder } -func NewSleeveOverlay(localPort int) Overlay { +func NewSleeveOverlay(localPort int) NetworkOverlay { return &SleeveOverlay{localPort: localPort} } -func (sleeve *SleeveOverlay) StartConsumingPackets(localPeer *Peer, peers *Peers, - consumer OverlayConsumer) error { - localAddr, err := net.ResolveUDPAddr("udp4", - fmt.Sprint(":", sleeve.localPort)) +func (sleeve *SleeveOverlay) StartConsumingPackets(localPeer *mesh.Peer, peers *mesh.Peers, consumer OverlayConsumer) error { + localAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprint(":", sleeve.localPort)) if err != nil { return err } @@ -97,8 +102,7 @@ func (sleeve *SleeveOverlay) StartConsumingPackets(localPeer *Peer, peers *Peers // This makes sure all packets we send out do not have DF set // on them. - err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, - syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DONT) + err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DONT) if err != nil { return err } @@ -116,7 +120,7 @@ func (sleeve *SleeveOverlay) StartConsumingPackets(localPeer *Peer, peers *Peers sleeve.consumer = consumer sleeve.peers = peers sleeve.conn = conn - sleeve.forwarders = make(map[PeerName]*sleeveForwarder) + sleeve.forwarders = make(map[mesh.PeerName]*sleeveForwarder) go sleeve.readUDP() return nil } @@ -137,20 +141,19 @@ func (*SleeveOverlay) Diagnostics() interface{} { return nil } -func (sleeve *SleeveOverlay) lookupForwarder(peer PeerName) *sleeveForwarder { +func (sleeve *SleeveOverlay) lookupForwarder(peer mesh.PeerName) *sleeveForwarder { sleeve.lock.Lock() defer sleeve.lock.Unlock() return sleeve.forwarders[peer] } -func (sleeve *SleeveOverlay) addForwarder(peer PeerName, fwd *sleeveForwarder) { +func (sleeve *SleeveOverlay) addForwarder(peer mesh.PeerName, fwd *sleeveForwarder) { sleeve.lock.Lock() defer sleeve.lock.Unlock() sleeve.forwarders[peer] = fwd } -func (sleeve *SleeveOverlay) removeForwarder(peer PeerName, - fwd *sleeveForwarder) { +func (sleeve *SleeveOverlay) removeForwarder(peer mesh.PeerName, fwd *sleeveForwarder) { sleeve.lock.Lock() defer sleeve.lock.Unlock() if sleeve.forwarders[peer] == fwd { @@ -175,7 +178,7 @@ func (sleeve *SleeveOverlay) readUDP() { continue } - fwdName := PeerNameFromBin(buf[:NameSize]) + fwdName := mesh.PeerNameFromBin(buf[:NameSize]) fwd := sleeve.lookupForwarder(fwdName) if fwd == nil { continue @@ -186,8 +189,7 @@ func (sleeve *SleeveOverlay) readUDP() { err = fwd.crypto.Dec.IterateFrames(packet, func(src []byte, dst []byte, frame []byte) { - sleeve.handleFrame(sender, fwd, - src, dst, frame, dec) + sleeve.handleFrame(sender, fwd, src, dst, frame, dec) }) if err != nil { // Errors during UDP packet decoding / @@ -209,17 +211,15 @@ func (sleeve *SleeveOverlay) readUDP() { } } -func (sleeve *SleeveOverlay) handleFrame(sender *net.UDPAddr, - fwd *sleeveForwarder, src []byte, dst []byte, frame []byte, - dec *EthernetDecoder) { +func (sleeve *SleeveOverlay) handleFrame(sender *net.UDPAddr, fwd *sleeveForwarder, src []byte, dst []byte, frame []byte, dec *EthernetDecoder) { dec.DecodeLayers(frame) decodedLen := len(dec.decoded) if decodedLen == 0 { return } - srcPeer := sleeve.peers.Fetch(PeerNameFromBin(src)) - dstPeer := sleeve.peers.Fetch(PeerNameFromBin(dst)) + srcPeer := sleeve.peers.Fetch(mesh.PeerNameFromBin(src)) + dstPeer := sleeve.peers.Fetch(mesh.PeerNameFromBin(dst)) if srcPeer == nil || dstPeer == nil { return } @@ -246,8 +246,7 @@ func (sleeve *SleeveOverlay) handleFrame(sender *net.UDPAddr, sleeve.sendToConsumer(srcPeer, dstPeer, frame, dec) } -func (sleeve *SleeveOverlay) sendToConsumer(srcPeer, dstPeer *Peer, - frame []byte, dec *EthernetDecoder) { +func (sleeve *SleeveOverlay) sendToConsumer(srcPeer, dstPeer *mesh.Peer, frame []byte, dec *EthernetDecoder) { if sleeve.consumer == nil { return } @@ -280,10 +279,16 @@ func (sleeve *SleeveOverlay) send(msg []byte, raddr *net.UDPAddr) error { return err } +type sleeveCrypto struct { + Dec Decryptor + Enc Encryptor + EncDF Encryptor +} + type sleeveForwarder struct { // Immutable sleeve *SleeveOverlay - remotePeer *Peer + remotePeer *mesh.Peer remotePeerBin []byte sendControlMsg func(byte, []byte) error connUID uint64 @@ -310,7 +315,7 @@ type sleeveForwarder struct { stackFrag bool // State only used within the forwarder goroutine - crypto OverlayCrypto + crypto sleeveCrypto senderDF *udpSenderDF maxPayload int @@ -350,13 +355,17 @@ type controlMessage struct { msg []byte } -func (sleeve *SleeveOverlay) MakeForwarder(params ForwarderParams) (OverlayForwarder, error) { - var crypto OverlayCrypto - if params.Crypto != nil { - crypto = *params.Crypto +func (sleeve *SleeveOverlay) PrepareConnection(params mesh.OverlayConnectionParams) (mesh.OverlayConnection, error) { + name := sleeve.localPeer.NameByte + var crypto sleeveCrypto + if params.SessionKey != nil { + crypto = sleeveCrypto{ + Dec: NewNaClDecryptor(params.SessionKey, params.Outbound), + Enc: NewNaClEncryptor(name, params.SessionKey, params.Outbound, false), + EncDF: NewNaClEncryptor(name, params.SessionKey, params.Outbound, true), + } } else { - name := sleeve.localPeer.NameByte - crypto = OverlayCrypto{ + crypto = sleeveCrypto{ Dec: NewNonDecryptor(), Enc: NewNonEncryptor(name), EncDF: NewNonEncryptor(name), @@ -370,6 +379,11 @@ func (sleeve *SleeveOverlay) MakeForwarder(params ForwarderParams) (OverlayForwa confirmedChan := make(chan struct{}) finishedChan := make(chan struct{}) + var remoteAddr *net.UDPAddr + if params.Outbound { + remoteAddr = makeUDPAddr(params.RemoteAddr) + } + fwd := &sleeveForwarder{ sleeve: sleeve, remotePeer: params.RemotePeer, @@ -384,17 +398,16 @@ func (sleeve *SleeveOverlay) MakeForwarder(params ForwarderParams) (OverlayForwa finishedChan: finishedChan, establishedChan: make(chan struct{}), errorChan: make(chan error, 1), - remoteAddr: params.RemoteAddr, + remoteAddr: remoteAddr, mtu: DefaultMTU, crypto: crypto, maxPayload: DefaultMTU - UDPOverhead, overheadDF: UDPOverhead + crypto.EncDF.PacketOverhead() + crypto.EncDF.FrameOverhead() + EthernetOverhead, - senderDF: newUDPSenderDF(params.LocalIP, sleeve.localPort), + senderDF: newUDPSenderDF(params.LocalAddr.IP, sleeve.localPort), } - go fwd.run(aggChan, aggDFChan, specialChan, controlMsgChan, - confirmedChan, finishedChan) + go fwd.run(aggChan, aggDFChan, specialChan, controlMsgChan, confirmedChan, finishedChan) return fwd, nil } @@ -436,8 +449,7 @@ func (fwd *sleeveForwarder) Forward(key ForwardPacketKey) FlowOp { return curriedForward{fwd: fwd, key: key} } -func (f curriedForward) Process(frame []byte, dec *EthernetDecoder, - broadcast bool) { +func (f curriedForward) Process(frame []byte, dec *EthernetDecoder, broadcast bool) { fwd := f.fwd fwd.lock.RLock() haveContact := (fwd.remoteAddr != nil) @@ -446,8 +458,7 @@ func (f curriedForward) Process(frame []byte, dec *EthernetDecoder, fwd.lock.RUnlock() if !haveContact { - log.Print(fwd.logPrefix(), - "Cannot forward frame yet - awaiting contact") + log.Print(fwd.logPrefix(), "Cannot forward frame yet - awaiting contact") return } @@ -466,8 +477,7 @@ func (f curriedForward) Process(frame []byte, dec *EthernetDecoder, // of our pipeline. if dec.DF() { if !frameTooBig(frame, mtu) { - fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, - frame) + fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, frame) return } @@ -492,8 +502,7 @@ func (f curriedForward) Process(frame []byte, dec *EthernetDecoder, // The frag-needed packet does not have DF set, so the // potential recursion here is bounded. - fwd.sleeve.sendToConsumer(f.key.DstPeer, f.key.SrcPeer, - fragNeededPacket, dec) + fwd.sleeve.sendToConsumer(f.key.DstPeer, f.key.SrcPeer, fragNeededPacket, dec) return } @@ -514,21 +523,18 @@ func (f curriedForward) Process(frame []byte, dec *EthernetDecoder, // fragment it ourself. checkWarn(fragment(dec.Eth, dec.IP, mtu, func(segFrame []byte) { - fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, - segFrame) + fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, segFrame) })) } -func (fwd *sleeveForwarder) aggregate(ch chan<- aggregatorFrame, src []byte, - dst []byte, frame []byte) { +func (fwd *sleeveForwarder) aggregate(ch chan<- aggregatorFrame, src []byte, dst []byte, frame []byte) { select { case ch <- aggregatorFrame{src, dst, frame}: case <-fwd.finishedChan: } } -func fragment(eth layers.Ethernet, ip layers.IPv4, mtu int, - forward func([]byte)) error { +func fragment(eth layers.Ethernet, ip layers.IPv4, mtu int, forward func([]byte)) error { // We are not doing any sort of NAT, so we don't need to worry // about checksums of IP payload (eg UDP checksum). headerSize := int(ip.IHL) * 4 @@ -570,8 +576,7 @@ func fragment(eth layers.Ethernet, ip layers.IPv4, mtu int, ip.FragOffset = uint16((offset + offsetBase) >> 3) buf := gopacket.NewSerializeBuffer() segPayload := gopacket.Payload(segmentPayload) - err := gopacket.SerializeLayers(buf, opts, ð, &ip, - &segPayload) + err := gopacket.SerializeLayers(buf, opts, ð, &ip, &segPayload) if err != nil { return err } @@ -623,13 +628,10 @@ loop: for err == nil { select { case frame := <-aggChan: - err = fwd.aggregateAndSend(frame, aggChan, - fwd.crypto.Enc, fwd.sleeve, - MaxUDPPacketSize-UDPOverhead) + err = fwd.aggregateAndSend(frame, aggChan, fwd.crypto.Enc, fwd.sleeve, MaxUDPPacketSize-UDPOverhead) case frame := <-aggDFChan: - err = fwd.aggregateAndSend(frame, aggDFChan, - fwd.crypto.EncDF, fwd.senderDF, fwd.maxPayload) + err = fwd.aggregateAndSend(frame, aggDFChan, fwd.crypto.EncDF, fwd.senderDF, fwd.maxPayload) case sf := <-specialChan: err = fwd.handleSpecialFrame(sf) @@ -683,8 +685,7 @@ loop: } func (fwd *sleeveForwarder) aggregateAndSend(frame aggregatorFrame, - aggChan <-chan aggregatorFrame, enc Encryptor, sender udpSender, - limit int) error { + aggChan <-chan aggregatorFrame, enc Encryptor, sender udpSender, limit int) error { // Give up after processing N frames, to avoid starving the // other activities of the forwarder goroutine. i := 0 @@ -734,8 +735,7 @@ func fits(frame aggregatorFrame, enc Encryptor, limit int) bool { return enc.TotalLen()+enc.FrameOverhead()+len(frame.frame) <= limit } -func (fwd *sleeveForwarder) flushEncryptor(enc Encryptor, - sender udpSender) error { +func (fwd *sleeveForwarder) flushEncryptor(enc Encryptor, sender udpSender) error { msg, err := enc.Bytes() if err != nil { return err @@ -744,8 +744,7 @@ func (fwd *sleeveForwarder) flushEncryptor(enc Encryptor, return fwd.processSendError(sender.send(msg, fwd.remoteAddr)) } -func (fwd *sleeveForwarder) sendSpecial(enc Encryptor, sender udpSender, - data []byte) error { +func (fwd *sleeveForwarder) sendSpecial(enc Encryptor, sender udpSender, data []byte) error { enc.AppendFrame(fwd.sleeve.localPeerBin, fwd.remotePeerBin, data) return fwd.flushEncryptor(enc, sender) } @@ -776,8 +775,7 @@ func (fwd *sleeveForwarder) handleControlMessage(cm controlMessage) error { return fwd.handleMTUTestAck(cm.msg) default: - log.Print(fwd.logPrefix(), - "Ignoring unknown control message tag: ", cm.tag) + log.Print(fwd.logPrefix(), "Ignoring unknown control message tag: ", cm.tag) return nil } } @@ -836,8 +834,7 @@ func (fwd *sleeveForwarder) handleHeartbeat(special specialFrame) error { } } } else if !udpAddrsEqual(fwd.remoteAddr, special.sender) { - log.Print(fwd.logPrefix(), - "Peer UDP address changed to ", special.sender) + log.Print(fwd.logPrefix(), "Peer UDP address changed to ", special.sender) fwd.setRemoteAddr(special.sender) } @@ -1146,3 +1143,29 @@ func timerChan(timer *time.Timer) <-chan time.Time { } return nil } + +func tickerChan(ticker *time.Ticker) <-chan time.Time { + if ticker != nil { + return ticker.C + } + return nil +} + +func makeUDPAddr(addr *net.TCPAddr) *net.UDPAddr { + return &net.UDPAddr{IP: addr.IP, Port: addr.Port, Zone: addr.Zone} +} + +// Look inside an error produced by the net package to get to the +// syscall.Errno at the root of the problem. +func PosixError(err error) error { + if operr, ok := err.(*net.OpError); ok { + err = operr.Err + } + + // go1.5 wraps an Errno inside a SyscallError inside an OpError + if scerr, ok := err.(*os.SyscallError); ok { + err = scerr.Err + } + + return err +} diff --git a/router/utils.go b/router/utils.go deleted file mode 100644 index 3530abd2..00000000 --- a/router/utils.go +++ /dev/null @@ -1,117 +0,0 @@ -package router - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "encoding/gob" - "fmt" - "net" - "os" - - "github.com/weaveworks/weave/common" -) - -var log = common.Log - -var void = struct{}{} - -func checkFatal(e error) { - if e != nil { - log.Fatal(e) - } -} - -func checkWarn(e error) { - if e != nil { - log.Warnln(e) - } -} - -// Look inside an error produced by the net package to get to the -// syscall.Errno at the root of the problem. -func PosixError(err error) error { - if operr, ok := err.(*net.OpError); ok { - err = operr.Err - } - - // go1.5 wraps an Errno inside a SyscallError inside an OpError - if scerr, ok := err.(*os.SyscallError); ok { - err = scerr.Err - } - - return err -} - -func (upe UnknownPeerError) Error() string { - return fmt.Sprint("Reference to unknown peer ", upe.Name) -} - -func (nce NameCollisionError) Error() string { - return fmt.Sprint("Multiple peers found with same name: ", nce.Name) -} - -func (pde PacketDecodingError) Error() string { - return fmt.Sprint("Failed to decode packet: ", pde.Desc) -} - -func Concat(elems ...[]byte) []byte { - res := []byte{} - for _, e := range elems { - res = append(res, e...) - } - return res -} - -func randBytes(n int) []byte { - buf := make([]byte, n) - _, err := rand.Read(buf) - checkFatal(err) - return buf -} - -func randUint64() (r uint64) { - return binary.LittleEndian.Uint64(randBytes(8)) -} - -func randUint16() (r uint16) { - return binary.LittleEndian.Uint16(randBytes(2)) -} - -func GobEncode(items ...interface{}) []byte { - buf := new(bytes.Buffer) - enc := gob.NewEncoder(buf) - for _, i := range items { - checkFatal(enc.Encode(i)) - } - return buf.Bytes() -} - -func macint(mac net.HardwareAddr) (r uint64) { - for _, b := range mac { - r <<= 8 - r |= uint64(b) - } - return -} - -func intmac(key uint64) (r net.HardwareAddr) { - r = make([]byte, 6) - for i := 5; i >= 0; i-- { - r[i] = byte(key) - key >>= 8 - } - return -} - -type ListOfPeers []*Peer - -func (lop ListOfPeers) Len() int { - return len(lop) -} -func (lop ListOfPeers) Swap(i, j int) { - lop[i], lop[j] = lop[j], lop[i] -} -func (lop ListOfPeers) Less(i, j int) bool { - return lop[i].Name < lop[j].Name -} diff --git a/site/building.md b/site/building.md index 2d7b713f..41b0bfe1 100644 --- a/site/building.md +++ b/site/building.md @@ -54,7 +54,7 @@ $ make This will build the weave components and package them into three Docker images (`weaveworks/weave`, `weaveworks/weavedns`, and -`weaveworks/weaveexec`). These are then exported as `weave.tar`. +`weaveworks/weaveexec`). These are then exported as `weave.tar.gz`. ## Building in a Docker container @@ -167,13 +167,13 @@ you can do so with $ vagrant ssh -c 'make -C src/github.com/weaveworks/weave' ``` -you should then find a `weave.tar` container snapshot tarball in the +you should then find a `weave.tar.gz` container snapshot tarball in the top-level directory. You can use that snapshot with `docker load` against a different host, e.g. ```bash $ export DOCKER_HOST=tcp:// -$ docker load < weave.tar +$ docker load < weave.tar.gz ``` You can provide extra Vagrant configuration by putting a file diff --git a/site/features.md b/site/features.md index f2d4bf44..4dbbb7ab 100644 --- a/site/features.md +++ b/site/features.md @@ -11,6 +11,7 @@ example](https://github.com/weaveworks/weave#example): * [Virtual ethernet switch](#virtual-ethernet-switch) * [Fast data path](#fast-data-path) * [Seamless Docker integration](#docker) + * [Docker network plugin](#plugin) * [Address allocation](#addressing) * [Naming and discovery](#naming-and-discovery) * [Application isolation](#application-isolation) @@ -88,6 +89,24 @@ Containers started in this way that subsequently restart, either by an explicit `docker restart` command or by Docker restart policy, are re-attached to the weave network by the weave Docker API proxy. +### 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: + + $ docker run --net=weave -ti ubuntu + +For more details see the +[plugin README](https://github.com/weaveworks/docker-plugin). + ### Address allocation Containers are automatically allocated an IP address that is unique @@ -219,7 +238,7 @@ NB: By default docker permits communication between containers on the same host, via their docker-assigned IP addresses. For complete isolation between application containers, that feature needs to be disabled by -[setting `--icc=false`](https://docs.docker.com/articles/networking/#between-containers) +[setting `--icc=false`](https://docs.docker.com/engine/userguide/networking/default_network/container-communication/#communication-between-containers) in the docker daemon configuration. Furthermore, containers should be prevented from capturing and injecting raw network packets - this can be accomplished by starting them with the `--cap-drop net_raw` option. @@ -307,6 +326,14 @@ supplying a password will [cause weave to fall back to a slower method](#fast-data-path) for transporting data between peers. +Be aware that: + +* Containers will be able to access the router REST API if you have + disabled fast datapath. You can prevent this by setting + [`--icc=false`](https://docs.docker.com/engine/userguide/networking/default_network/container-communication/#communication-between-containers) +* Containers are able to access the router control and data plane + ports, but you can mitigate this by enabling encryption + ### Host network integration Weave application networks can be integrated with a host's network, diff --git a/site/ipam.md b/site/ipam.md index 35bb66c3..7f19ffc5 100644 --- a/site/ipam.md +++ b/site/ipam.md @@ -214,7 +214,7 @@ reports on the current status of the weave router and IP allocator: Service: ipam Consensus: waiting(quorum: 2, known: 0) - Range: [10.32.0.0-10.48.0.0) + Range: 10.32.0.0-10.47.255.255 DefaultSubnet: 10.32.0.0/12 ... @@ -225,7 +225,3 @@ guide](troubleshooting.html#weave-status) for full details. The 'Service: ipam' section displays the consensus state as well as the total allocation range and default subnet. - -Note that the 'Range' is written using interval notation - '[' means -the start of the range is included and ')' means the end of the range -is not included. diff --git a/site/troubleshooting.md b/site/troubleshooting.md index 9f691c1f..bc5bbb2b 100644 --- a/site/troubleshooting.md +++ b/site/troubleshooting.md @@ -81,7 +81,7 @@ $ weave status Service: ipam Consensus: achieved - Range: [10.32.0.0-10.48.0.0) + Range: 10.32.0.0-10.47.255.255 DefaultSubnet: 10.32.0.0/12 Service: dns diff --git a/test/190_no_fastdp_2_test.sh b/test/190_no_fastdp_2_test.sh index fe793f2b..18d235fc 100755 --- a/test/190_no_fastdp_2_test.sh +++ b/test/190_no_fastdp_2_test.sh @@ -7,8 +7,8 @@ C2=10.2.1.47 start_suite "WEAVE_NO_FASTDP operation" -WEAVE_NO_FASTDP=1 weave_on $HOST1 launch -WEAVE_NO_FASTDP=1 weave_on $HOST2 launch $HOST1 +WEAVE_NO_FASTDP=1 WEAVE_DOCKER_ARGS=$ADD_HOST_ARGS weave_on $HOST1 launch +WEAVE_NO_FASTDP=1 WEAVE_DOCKER_ARGS=$ADD_HOST_ARGS weave_on $HOST2 launch $HOST1 start_container $HOST1 $C1/24 --name=c1 start_container $HOST2 $C2/24 --name=c2 diff --git a/test/210_dns_cross_hosts_3_test.sh b/test/210_dns_cross_hosts_3_test.sh index 813d621f..23a2d42f 100755 --- a/test/210_dns_cross_hosts_3_test.sh +++ b/test/210_dns_cross_hosts_3_test.sh @@ -72,6 +72,5 @@ start_container $HOST2 --name=c4 -h $NAME4 start_container_with_dns $HOST1 --name=c3 C4=$(container_ip $HOST2 c4) assert_dns_record $HOST1 c3 $NAME4 $C4 -assert_raises "exec_on $HOST1 c3 getent hosts 8.8.8.8 | grep google" end_suite diff --git a/test/640_proxy_restart_reattaches_test.sh b/test/640_proxy_restart_reattaches_test.sh index fc1df46c..034d4704 100755 --- a/test/640_proxy_restart_reattaches_test.sh +++ b/test/640_proxy_restart_reattaches_test.sh @@ -7,10 +7,22 @@ C2=10.2.0.34 NAME=seetwo.weave.local check_attached() { - assert_raises "proxy exec_on $HOST1 c2 $CHECK_ETHWE_UP" + assert_raises "exec_on $HOST1 c2 $CHECK_ETHWE_UP" assert_dns_record $HOST1 c1 $NAME $C2 } +wait_for_proxy() { + for i in $(seq 1 120); do + echo "Waiting for proxy to start" + if proxy docker_on $1 info > /dev/null 2>&1 ; then + return + fi + sleep 1 + done + echo "Timed out waiting for proxy to start" >&2 + exit 1 +} + start_suite "Proxy restart reattaches networking to containers" WEAVE_DOCKER_ARGS=--restart=always WEAVEPROXY_DOCKER_ARGS=--restart=always weave_on $HOST1 launch @@ -30,15 +42,16 @@ run_on $HOST1 sudo kill -KILL $(docker_on $HOST1 inspect --format='{{.State.Pid} sleep 1 check_attached +# Disabled because Docker 1.8 often fails to restart weave or weaveproxy # Restart docker itself, using different commands for systemd- and upstart-managed. -run_on $HOST1 sh -c "command -v systemctl >/dev/null && sudo systemctl restart docker || sudo service docker restart" -sleep 10 -check_attached +#run_on $HOST1 sh -c "command -v systemctl >/dev/null && sudo systemctl restart docker || sudo service docker restart" +#wait_for_proxy $HOST1 +#check_attached # Restarting proxy shouldn't kill unattachable containers proxy_start_container $HOST1 -di --name=c3 --restart=always # Use ipam, so it won't be attachable w/o weave weave_on $HOST1 stop weave_on $HOST1 launch-proxy -assert_raises "proxy exec_on $HOST1 c3 $CHECK_ETHWE_UP" +assert_raises "exec_on $HOST1 c3 $CHECK_ETHWE_UP" end_suite diff --git a/test/gce.sh b/test/gce.sh index 55a577de..b1cc7cca 100755 --- a/test/gce.sh +++ b/test/gce.sh @@ -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-6} +: ${TEMPLATE_NAME:=test-template-8} : ${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"' >> /etc/default/docker; +echo 'DOCKER_OPTS="-H unix:///var/run/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 @@ -142,7 +142,7 @@ function hosts { done echo export SSH=ssh echo export HOSTS=\"$hosts\" - echo export WEAVE_DOCKER_ARGS=\"$args\" + echo export ADD_HOST_ARGS=\"$args\" rm $json } diff --git a/test/run_all.sh b/test/run_all.sh index d73c5b1c..78cc7f74 100755 --- a/test/run_all.sh +++ b/test/run_all.sh @@ -23,4 +23,4 @@ if [ -n "$CIRCLECI" -o -n "$PARALLEL" ]; then RUNNER_ARGS="$RUNNER_ARGS -parallel" fi -HOSTS="$HOSTS" "${DIR}/../testing/runner/runner" $RUNNER_ARGS $TESTS +HOSTS="$HOSTS" "${DIR}/../tools/runner/runner" $RUNNER_ARGS $TESTS diff --git a/test/scheduler/.gitignore b/test/scheduler/.gitignore deleted file mode 100644 index a65b4177..00000000 --- a/test/scheduler/.gitignore +++ /dev/null @@ -1 +0,0 @@ -lib diff --git a/test/scheduler/README.md b/test/scheduler/README.md deleted file mode 100644 index 8489d787..00000000 --- a/test/scheduler/README.md +++ /dev/null @@ -1,6 +0,0 @@ -To upload newer version: - -``` -pip install -r requirements.txt -t lib -appcfg.py update . -``` diff --git a/test/scheduler/app.yaml b/test/scheduler/app.yaml deleted file mode 100644 index 8bc59f00..00000000 --- a/test/scheduler/app.yaml +++ /dev/null @@ -1,13 +0,0 @@ -application: positive-cocoa-90213 -version: 1 -runtime: python27 -api_version: 1 -threadsafe: true - -handlers: -- url: .* - script: main.app - -libraries: -- name: webapp2 - version: latest diff --git a/test/scheduler/appengine_config.py b/test/scheduler/appengine_config.py deleted file mode 100644 index f4489ff9..00000000 --- a/test/scheduler/appengine_config.py +++ /dev/null @@ -1,3 +0,0 @@ -from google.appengine.ext import vendor - -vendor.add('lib') diff --git a/test/scheduler/cron.yaml b/test/scheduler/cron.yaml deleted file mode 100644 index 652aed80..00000000 --- a/test/scheduler/cron.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cron: -- description: periodic gc - url: /tasks/gc - schedule: every 5 minutes diff --git a/test/scheduler/main.py b/test/scheduler/main.py deleted file mode 100644 index ed0c78e3..00000000 --- a/test/scheduler/main.py +++ /dev/null @@ -1,112 +0,0 @@ -import collections -import json -import logging -import operator -import re - -import flask -from oauth2client.client import GoogleCredentials -from googleapiclient import discovery - -from google.appengine.api import urlfetch -from google.appengine.ext import ndb - -app = flask.Flask('scheduler') -app.debug = True - -# We use exponential moving average to record -# test run times. Higher alpha discounts historic -# observations faster. -alpha = 0.3 - -PROJECT = 'positive-cocoa-90213' -ZONE = 'us-central1-a' - -class Test(ndb.Model): - total_run_time = ndb.FloatProperty(default=0.) # Not total, but a EWMA - total_runs = ndb.IntegerProperty(default=0) - -class Schedule(ndb.Model): - shards = ndb.JsonProperty() - -@app.route('/record//', methods=['POST']) -@ndb.transactional -def record(test_name, runtime): - test = Test.get_by_id(test_name) - if test is None: - test = Test(id=test_name) - test.total_run_time = (test.total_run_time * (1-alpha)) + (float(runtime) * alpha) - test.total_runs += 1 - test.put() - return ('', 204) - -@app.route('/schedule///', methods=['POST']) -def schedule(test_run, shard_count, shard): - # read tests from body - test_names = flask.request.get_json(force=True)['tests'] - - # first see if we have a scedule already - schedule_id = "%s-%d" % (test_run, shard_count) - schedule = Schedule.get_by_id(schedule_id) - if schedule is not None: - return flask.json.jsonify(tests=schedule.shards[str(shard)]) - - # if not, do simple greedy algorithm - test_times = ndb.get_multi(ndb.Key(Test, test_name) for test_name in test_names) - def avg(test): - if test is not None: - return test.total_run_time - return 1 - test_times = [(test_name, avg(test)) for test_name, test in zip(test_names, test_times)] - test_times_dict = dict(test_times) - test_times.sort(key=operator.itemgetter(1)) - - shards = {i: [] for i in xrange(shard_count)} - while test_times: - test_name, time = test_times.pop() - - # find shortest shard and put it in that - s, _ = min(((i, sum(test_times_dict[t] for t in shards[i])) - for i in xrange(shard_count)), key=operator.itemgetter(1)) - - shards[s].append(test_name) - - # atomically insert or retrieve existing schedule - schedule = Schedule.get_or_insert(schedule_id, shards=shards) - return flask.json.jsonify(tests=schedule.shards[str(shard)]) - -NAME_RE = re.compile(r'^host(?P\d+)-(?P\d+)-(?P\d+)$') - -@app.route('/tasks/gc') -def gc(): - # Get list of running VMs, pick build id out of VM name - credentials = GoogleCredentials.get_application_default() - compute = discovery.build('compute', 'v1', credentials=credentials) - instances = compute.instances().list(project=PROJECT, zone=ZONE).execute() - host_by_build = collections.defaultdict(list) - for instance in instances['items']: - matches = NAME_RE.match(instance['name']) - if matches is None: - continue - host_by_build[int(matches.group('build'))].append(instance['name']) - logging.info("Running VMs by build: %r", host_by_build) - - # Get list of builds, filter down to runnning builds - result = urlfetch.fetch('https://circleci.com/api/v1/project/weaveworks/weave', - headers={'Accept': 'application/json'}) - assert result.status_code == 200 - builds = json.loads(result.content) - running = {build['build_num'] for build in builds if build['status'] == 'running'} - logging.info("Runnings builds: %r", running) - - # Stop VMs for builds that aren't running - stopped = [] - for build, names in host_by_build.iteritems(): - if build in running: - continue - for name in names: - stopped.append(name) - logging.info("Stopping VM %s", name) - compute.instances().delete(project=PROJECT, zone=ZONE, instance=name).execute() - - return (flask.json.jsonify(running=list(running), stopped=stopped), 200) diff --git a/test/scheduler/requirements.txt b/test/scheduler/requirements.txt deleted file mode 100644 index d4d47e6e..00000000 --- a/test/scheduler/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -flask -google-api-python-client diff --git a/test/setup.sh b/test/setup.sh index 11280ce2..9312fc20 100755 --- a/test/setup.sh +++ b/test/setup.sh @@ -13,7 +13,7 @@ echo " prefetch test images" setup_host() { HOST=$1 - docker_on $HOST load -i ../weave.tar + docker_on $HOST load -i ../weave.tar.gz DANGLING_IMAGES="$(docker_on $HOST images -q -f dangling=true)" [ -n "$DANGLING_IMAGES" ] && docker_on $HOST rmi $DANGLING_IMAGES 1>/dev/null 2>&1 || true run_on $HOST mkdir -p bin diff --git a/testing/gossip/mocks.go b/testing/gossip/mocks.go index ae5791f1..1a42b03d 100644 --- a/testing/gossip/mocks.go +++ b/testing/gossip/mocks.go @@ -7,21 +7,21 @@ import ( "time" "github.com/weaveworks/weave/common" - "github.com/weaveworks/weave/router" + "github.com/weaveworks/weave/mesh" ) // Router to convey gossip from one gossiper to another, for testing type unicastMessage struct { - sender router.PeerName + sender mesh.PeerName buf []byte } type broadcastMessage struct { - sender router.PeerName - data router.GossipData + sender mesh.PeerName + data mesh.GossipData } type gossipMessage struct { - sender router.PeerName - data router.GossipData + sender mesh.PeerName + data mesh.GossipData } type exitMessage struct { exitChan chan struct{} @@ -31,12 +31,12 @@ type flushMessage struct { } type TestRouter struct { - gossipChans map[router.PeerName]chan interface{} + gossipChans map[mesh.PeerName]chan interface{} loss float32 // 0.0 means no loss } func NewTestRouter(loss float32) *TestRouter { - return &TestRouter{make(map[router.PeerName]chan interface{}, 100), loss} + return &TestRouter{make(map[mesh.PeerName]chan interface{}, 100), loss} } func (grouter *TestRouter) Stop() { @@ -45,7 +45,7 @@ func (grouter *TestRouter) Stop() { } } -func (grouter *TestRouter) gossipBroadcast(sender router.PeerName, update router.GossipData) error { +func (grouter *TestRouter) gossipBroadcast(sender mesh.PeerName, update mesh.GossipData) error { for _, gossipChan := range grouter.gossipChans { select { case gossipChan <- broadcastMessage{sender: sender, data: update}: @@ -56,7 +56,7 @@ func (grouter *TestRouter) gossipBroadcast(sender router.PeerName, update router return nil } -func (grouter *TestRouter) gossip(sender router.PeerName, update router.GossipData) error { +func (grouter *TestRouter) gossip(sender mesh.PeerName, update mesh.GossipData) error { count := int(math.Log2(float64(len(grouter.gossipChans)))) for dest, gossipChan := range grouter.gossipChans { if dest == sender { @@ -83,7 +83,7 @@ func (grouter *TestRouter) Flush() { } } -func (grouter *TestRouter) RemovePeer(peer router.PeerName) { +func (grouter *TestRouter) RemovePeer(peer mesh.PeerName) { gossipChan := grouter.gossipChans[peer] resultChan := make(chan struct{}) gossipChan <- exitMessage{exitChan: resultChan} @@ -93,10 +93,10 @@ func (grouter *TestRouter) RemovePeer(peer router.PeerName) { type TestRouterClient struct { router *TestRouter - sender router.PeerName + sender mesh.PeerName } -func (grouter *TestRouter) run(sender router.PeerName, gossiper router.Gossiper, gossipChan chan interface{}) { +func (grouter *TestRouter) run(sender mesh.PeerName, gossiper mesh.Gossiper, gossipChan chan interface{}) { gossipTimer := time.Tick(2 * time.Second) for { select { @@ -155,7 +155,7 @@ func (grouter *TestRouter) run(sender router.PeerName, gossiper router.Gossiper, } } -func (grouter *TestRouter) Connect(sender router.PeerName, gossiper router.Gossiper) router.Gossip { +func (grouter *TestRouter) Connect(sender mesh.PeerName, gossiper mesh.Gossiper) mesh.Gossip { gossipChan := make(chan interface{}, 100) go grouter.run(sender, gossiper, gossipChan) @@ -164,7 +164,7 @@ func (grouter *TestRouter) Connect(sender router.PeerName, gossiper router.Gossi return TestRouterClient{grouter, sender} } -func (client TestRouterClient) GossipUnicast(dstPeerName router.PeerName, buf []byte) error { +func (client TestRouterClient) GossipUnicast(dstPeerName mesh.PeerName, buf []byte) error { select { case client.router.gossipChans[dstPeerName] <- unicastMessage{sender: client.sender, buf: buf}: default: // drop the message if we cannot send it @@ -173,6 +173,6 @@ func (client TestRouterClient) GossipUnicast(dstPeerName router.PeerName, buf [] return nil } -func (client TestRouterClient) GossipBroadcast(update router.GossipData) error { +func (client TestRouterClient) GossipBroadcast(update mesh.GossipData) error { return client.router.gossipBroadcast(client.sender, update) } diff --git a/testing/runner/runner.go b/testing/runner/runner.go deleted file mode 100644 index fe4f7efe..00000000 --- a/testing/runner/runner.go +++ /dev/null @@ -1,273 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/url" - "os" - "os/exec" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/docker/docker/pkg/mflag" - "github.com/mgutz/ansi" -) - -const ( - schedulerHost = "positive-cocoa-90213.appspot.com" - JSON = "application/json" -) - -var ( - start = ansi.ColorCode("black+ub") - fail = ansi.ColorCode("red+b") - succ = ansi.ColorCode("green+b") - reset = ansi.ColorCode("reset") - - useScheduler = false - runParallel = false - verbose = false - - consoleLock = sync.Mutex{} -) - -type test struct { - name string - hosts int -} - -type schedule struct { - Tests []string `json:"tests"` -} - -type result struct { - test - errored bool - hosts []string -} - -type tests []test - -func (ts tests) Len() int { return len(ts) } -func (ts tests) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] } -func (ts tests) Less(i, j int) bool { - if ts[i].hosts != ts[j].hosts { - return ts[i].hosts < ts[j].hosts - } - return ts[i].name < ts[j].name -} - -func (ts *tests) pick(availible int) (test, bool) { - // pick the first test that fits in the availible hosts - for i, test := range *ts { - if test.hosts <= availible { - *ts = append((*ts)[:i], (*ts)[i+1:]...) - return test, true - } - } - - return test{}, false -} - -func (t test) run(hosts []string) bool { - consoleLock.Lock() - fmt.Printf("%s>>> Running %s on %s%s\n", start, t.name, hosts, reset) - consoleLock.Unlock() - - var out bytes.Buffer - - cmd := exec.Command(t.name) - cmd.Env = os.Environ() - cmd.Stdout = &out - cmd.Stderr = &out - - // replace HOSTS in env - for i, env := range cmd.Env { - if strings.HasPrefix(env, "HOSTS") { - cmd.Env[i] = fmt.Sprintf("HOSTS=%s", strings.Join(hosts, " ")) - break - } - } - - start := time.Now() - err := cmd.Run() - duration := float64(time.Now().Sub(start)) / float64(time.Second) - - consoleLock.Lock() - if err != nil { - fmt.Printf("%s>>> Test %s finished after %0.1f secs with error: %v%s\n", fail, t.name, duration, err, reset) - } else { - fmt.Printf("%s>>> Test %s finished with success after %0.1f secs%s\n", succ, t.name, duration, reset) - } - if err != nil || verbose { - fmt.Print(out.String()) - fmt.Println() - } - consoleLock.Unlock() - - if err != nil && useScheduler { - updateScheduler(t.name, duration) - } - - return err != nil -} - -func updateScheduler(test string, duration float64) { - req := &http.Request{ - Method: "POST", - Host: schedulerHost, - URL: &url.URL{ - Opaque: fmt.Sprintf("/record/%s/%0.2f", url.QueryEscape(test), duration), - Scheme: "http", - Host: schedulerHost, - }, - Close: true, - } - if resp, err := http.DefaultClient.Do(req); err != nil { - fmt.Printf("Error updating scheduler: %v\n", err) - } else { - resp.Body.Close() - } -} - -func getSchedule(tests []string) ([]string, error) { - var ( - testRun = "integration-" + os.Getenv("CIRCLE_BUILD_NUM") - shardCount = os.Getenv("CIRCLE_NODE_TOTAL") - shardID = os.Getenv("CIRCLE_NODE_INDEX") - requestBody = &bytes.Buffer{} - ) - if err := json.NewEncoder(requestBody).Encode(schedule{tests}); err != nil { - return []string{}, err - } - url := fmt.Sprintf("http://%s/schedule/%s/%s/%s", schedulerHost, testRun, shardCount, shardID) - resp, err := http.Post(url, JSON, requestBody) - if err != nil { - return []string{}, err - } - var sched schedule - if err := json.NewDecoder(resp.Body).Decode(&sched); err != nil { - return []string{}, err - } - return sched.Tests, nil -} - -func getTests(testNames []string) (tests, error) { - var err error - if useScheduler { - testNames, err = getSchedule(testNames) - if err != nil { - return tests{}, err - } - } - tests := tests{} - for _, name := range testNames { - parts := strings.Split(strings.TrimSuffix(name, "_test.sh"), "_") - numHosts, err := strconv.Atoi(parts[len(parts)-1]) - if err != nil { - numHosts = 1 - } - tests = append(tests, test{name, numHosts}) - fmt.Printf("Test %s needs %d hosts\n", name, numHosts) - } - return tests, nil -} - -func summary(tests, failed tests) { - if len(failed) > 0 { - fmt.Printf("%s>>> Ran %d tests, %d failed%s\n", fail, len(tests), len(failed), reset) - for _, test := range failed { - fmt.Printf("%s>>> Fail %s%s\n", fail, test.name, reset) - } - } else { - fmt.Printf("%s>>> Ran %d tests, all succeeded%s\n", succ, len(tests), reset) - } -} - -func parallel(ts tests, hosts []string) bool { - testsCopy := ts - sort.Sort(sort.Reverse(ts)) - resultsChan := make(chan result) - outstanding := 0 - failed := tests{} - for len(ts) > 0 || outstanding > 0 { - // While we have some free hosts, try and schedule - // a test on them - for len(hosts) > 0 { - test, ok := ts.pick(len(hosts)) - if !ok { - break - } - testHosts := hosts[:test.hosts] - hosts = hosts[test.hosts:] - - go func() { - errored := test.run(testHosts) - resultsChan <- result{test, errored, testHosts} - }() - outstanding++ - } - - // Otherwise, wait for the test to finish and return - // the hosts to the pool - result := <-resultsChan - hosts = append(hosts, result.hosts...) - outstanding-- - if result.errored { - failed = append(failed, result.test) - } - } - summary(testsCopy, failed) - return len(failed) > 0 -} - -func sequential(ts tests, hosts []string) bool { - failed := tests{} - for _, test := range ts { - if test.run(hosts) { - failed = append(failed, test) - } - } - summary(ts, failed) - return len(failed) > 0 -} - -func main() { - mflag.BoolVar(&useScheduler, []string{"scheduler"}, false, "Use scheduler to distribute tests across shards") - mflag.BoolVar(&runParallel, []string{"parallel"}, false, "Run tests in parallel on hosts where possible") - mflag.BoolVar(&verbose, []string{"v"}, false, "Print output from all tests (Also enabled via DEBUG=1)") - mflag.Parse() - - if len(os.Getenv("DEBUG")) > 0 { - verbose = true - } - - tests, err := getTests(mflag.Args()) - if err != nil { - fmt.Printf("Error parsing tests: %v\n", err) - os.Exit(1) - } - - hosts := strings.Fields(os.Getenv("HOSTS")) - maxHosts := len(hosts) - if maxHosts == 0 { - fmt.Print("No HOSTS specified.\n") - os.Exit(1) - } - - var errored bool - if runParallel { - errored = parallel(tests, hosts) - } else { - errored = sequential(tests, hosts) - } - - if errored { - os.Exit(1) - } -} diff --git a/tools b/tools index a286ddad..452f2b7d 160000 --- a/tools +++ b/tools @@ -1 +1 @@ -Subproject commit a286ddade02b98e429a8f65d52a825405a9efcf3 +Subproject commit 452f2b7de10dddb1bd8933ebb43bc8d8775e6658 diff --git a/weave b/weave index 77c28d91..248d9df5 100755 --- a/weave +++ b/weave @@ -58,6 +58,7 @@ weave launch-proxy [-H ] [--with-dns | --without-dns] [--hostname-match ] [--hostname-replacement ] [--rewrite-inspect] +weave launch-plugin weave env [--restore] weave config weave connect [--replace] [ ...] @@ -80,6 +81,7 @@ weave ps [ ...] weave stop weave stop-router weave stop-proxy +weave stop-plugin weave reset weave rmpeer @@ -288,6 +290,10 @@ fi BASE_IMAGE=$DOCKERHUB_USER/weave IMAGE=$BASE_IMAGE:$IMAGE_VERSION +BASE_PLUGIN_IMAGE=$DOCKERHUB_USER/plugin +PLUGIN_IMAGE=$BASE_PLUGIN_IMAGE:$IMAGE_VERSION +PLUGIN_CONTAINER_NAME=weaveplugin + PROCFS=${PROCFS:-/proc} DOCKER_BRIDGE=${DOCKER_BRIDGE:-docker0} CONTAINER_NAME=${WEAVE_CONTAINER_NAME:-weave} @@ -703,7 +709,7 @@ setup_router_iface_bridge() { } attach() { - if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "/proc/$$/ns/net" -a "$(readlink $PROCFS/$CONTAINER_PID/ns/net)" = "$(readlink /proc/$$/ns/net)" ] ; then + 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 echo "connected to weave. Perhaps the container was started with --net=host." >&2 return 1 @@ -1463,6 +1469,7 @@ launch_router() { if [ "$BRIDGE_TYPE" = fastdp ] ; 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 @@ -1482,6 +1489,7 @@ launch_router() { --ipalloc-range "$IPRANGE" \ --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 @@ -1527,14 +1535,8 @@ launch_proxy() { # when launching the weaveproxy container. docker_client_args $DOCKER_CLIENT_ARGS proxy_args "$@" - HOSTS_PATH=$(docker run --rm --net=none \ - -v /var/run/docker.sock:/var/run/docker.sock \ - --entrypoint=/bin/sh \ - $EXEC_IMAGE -c 'docker inspect -f {{.HostsPath}} $(hostname)') - CONTAINERS_PATH="$(dirname $(dirname "$HOSTS_PATH"))" PROXY_CONTAINER=$(docker run --privileged -d --name=$PROXY_CONTAINER_NAME --net=host \ $PROXY_VOLUMES \ - -v "$CONTAINERS_PATH":"$CONTAINERS_PATH" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /var/run/weave:/var/run/weave \ -v /proc:/hostproc \ @@ -1553,6 +1555,19 @@ stop_proxy() { stop $PROXY_CONTAINER_NAME "Proxy" } +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 \ + -v /run/docker/plugins:/run/docker/plugins \ + $PLUGIN_IMAGE "$@") +} + +stop_plugin() { + stop $PLUGIN_CONTAINER_NAME "Plugin" +} + ########################################################################################## [ $(id -u) = 0 ] || { @@ -1651,7 +1666,7 @@ shift 1 case "$COMMAND" in setup) - for img in $IMAGE $EXEC_IMAGE ; do + for img in $IMAGE $EXEC_IMAGE $PLUGIN_IMAGE ; do docker pull $img done ;; @@ -1703,6 +1718,11 @@ EOF launch_proxy "$@" echo $PROXY_CONTAINER ;; + launch-plugin) + check_not_running $PLUGIN_CONTAINER_NAME $BASE_PLUGIN_IMAGE + launch_plugin "$@" + echo $PLUGIN_CONTAINER + ;; env|proxy-env) [ "$COMMAND" = "env" ] || deprecation_warning "$COMMAND" "'weave env'" if PROXY_ADDR=$(proxy_addr) ; then @@ -1758,6 +1778,11 @@ EOF 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" + fi [ -n "$SUB_STATUS" ] || echo [ $res -eq 0 ] ;; @@ -1944,6 +1969,10 @@ EOF [ $# -eq 0 ] || usage stop_proxy ;; + stop-plugin) + [ $# -eq 0 ] || usage + stop_plugin + ;; reset) [ $# -eq 0 ] || usage warn_if_stopping_proxy_in_env