Compare commits

...
632 Commits
Author SHA1 Message Date
Bryan Boreham ba266ef8d3 Add release v1.4.1 2015-12-22 17:25:47 +00:00
Bryan Boreham f434fad3be Merge pull request #1822 from weaveworks/1820-unix-only
Only pass $DOCKER_HOST if it is a Unix socket - fixes #1820
2015-12-22 17:16:40 +00:00
Bryan Boreham bab758ebf9 Revert "Launch the proxy via unix socket in last test"
- since we won't now be copying $DOCKER_HOST

This reverts commit d8cfbc4e08.
2015-12-22 17:01:04 +00:00
Bryan Boreham 800f4663b6 Only pass through $DOCKER_HOST in exec_remote() if it is a Unix socket;
a TCP socket may be secured by TLS, in which case we can't use it.
2015-12-22 17:01:04 +00:00
Matthias Radestock b1b62afa90 Merge pull request #1812 from weaveworks/1810-warn-with-dns
Issue a warning on --with-dns option to proxy, because it was removed

Fixes #1810
2015-12-22 15:12:27 +00:00
Bryan Boreham 6b90ef7078 Clarify message 2015-12-22 14:56:40 +00:00
Matthias Radestock 2ed4b45c24 Merge pull request #1817 from weaveworks/1815-docker-version
Check we have Docker 1.9 or newer before trying to remove a network

Fixes #1815.
2015-12-22 14:01:55 +00:00
Matthias Radestock 47ba9748b4 Merge pull request #1814 from weaveworks/1778-check-running
Handle weave containers in 'restarting' state

Fixes #1778.
2015-12-22 13:49:19 +00:00
Bryan Boreham 077ad4208f Merge pull request #1813 from weaveworks/1797-plugin-version
Show plugin version; Fixes #1797
2015-12-22 13:12:38 +00:00
Bryan Boreham c19edec364 Add 'weave stop-xxx' instructions for 'already running' case 2015-12-22 13:04:06 +00:00
Bryan Boreham 0c2aa494ae Reduce duplication 2015-12-22 13:03:04 +00:00
Bryan Boreham 73e0a6bb0a Inform user about weave containers in state 'restarting' 2015-12-22 13:01:00 +00:00
Bryan Boreham abf1db0045 Merge pull request #1819 from weaveworks/1818-dont-launch-when-plugin-running
don't launch anything when plugin is running; fixes #1818 . LGTM.
2015-12-22 12:55:57 +00:00
Matthias Radestock b40b78ece0 refactor docker_sock_volume_mount() 2015-12-22 12:47:36 +00:00
Matthias Radestock 3ade796e8d don't launch anything when plugin is running
...just as we don't launch anything if either the router or proxy are
running.

Fixes #1818.
2015-12-22 12:30:53 +00:00
Bryan Boreham 7c2e808924 Check we have Docker 1.9 or newer before trying to remove a network 2015-12-22 12:10:48 +00:00
Bryan Boreham 7d2227c5a4 Add plugin version to 'weave version' 2015-12-22 10:50:02 +00:00
Bryan Boreham 8a517d5c7c Issue a warning on --with-dns option to proxy, because it was removed 2015-12-22 10:00:06 +00:00
Bryan Boreham 48658f011f Merge pull request #1805 from weaveworks/script-indentation
Fix indentation of weave script
2015-12-18 09:53:12 +00:00
Bryan Boreham 4ebb13fa94 Fix indentation of netcheck if-statement 2015-12-17 16:23:42 +00:00
Adam Harrison ff9a6b91a4 Merge branch 'latest_release_doc_updates' into 1.4 2015-12-17 13:07:48 +00:00
Adam Harrison 86fc37a968 Merge pull request #1799 from weaveworks/plugin-docs
Extend plugin docs
2015-12-17 12:24:12 +00:00
Bryan Boreham 37ae169323 Merge remote-tracking branch 'origin/master' into latest_release_doc_updates 2015-12-17 11:48:23 +00:00
Bryan Boreham 1ce0df4e94 Explain multiple networks 2015-12-17 11:42:41 +00:00
Bryan Boreham db5ba61915 Improve formatting 2015-12-17 11:41:44 +00:00
Bryan Boreham db120fb9be Add release v1.4.0 2015-12-17 10:30:06 +00:00
Adam Harrison ea6e171a7f Merge pull request #1794 from weaveworks/warn-stop-plugin
Warn user if weave network is in use on stop or reset of plugin
2015-12-17 10:27:58 +00:00
Bryan Boreham 246cb76e50 Add note to docs about not removing the plugin 2015-12-17 10:26:08 +00:00
Bryan Boreham ecf4a82b99 Warn user if weave network is in use on stop or reset 2015-12-17 10:05:36 +00:00
Bryan Boreham 357f67255e Move logging of Docker API info to callers 2015-12-17 08:25:51 +00:00
Adam Harrison 7015ab8717 Merge pull request #1780 from weaveworks/1748-no-with-dns
Remove WITH_DNS variable from weave script
2015-12-16 15:59:24 +00:00
Bryan Boreham 67195bc9f3 Remove WITH_DNS variable and deprecate --with-dns option to 'weave run' 2015-12-16 14:55:35 +00:00
Adam Harrison ce18509aa7 Merge pull request #1781 from weaveworks/1754-no-with-dns
Remove --with-dns option from proxy
2015-12-16 14:02:30 +00:00
Bryan Boreham cbaa92dcb2 Revert "don't broadcast-gossip other peer when adding a connection"
while we consider how to fix it properly.

This reverts commit cbb2ad018d.
2015-12-16 13:46:37 +00:00
Bryan Boreham f09d87c112 Merge pull request #1796 from weaveworks/gossip-test-connection-mistake
fix gossip test connection peer initialisation and connection deletion mistakes
2015-12-16 13:36:50 +00:00
Adam Harrison 7a8bba4d95 Merge pull request #1776 from weaveworks/1752-plugin-multicast
Plugin optional multicast route
2015-12-16 13:28:03 +00:00
Bryan Boreham e6e414a017 Merge pull request #1795 from weaveworks/issues/1772-fix-fastdp-address-already-in-use
Introduce limited retry on vxlan vport creation. LGTM; fixes #1772.
2015-12-16 13:22:50 +00:00
Matthias Radestock 81df06b3f4 fix DeleteTestGossipConnection
This is a one-sided operation, so we shouldn't be touching the
remote's Peers.

Also fix the associated test... When both 1 and 2 have lost their
connection to 3, they no longer have a route to 3 and hence gc it.

And because they no longer have a route, gossip from them won't get
through to 3.
2015-12-16 13:18:05 +00:00
Matthias Radestock 5401037743 refactor: inline mockGossipConnection.Connect
Also change Start() order in AddTestGossipConnection to be more
pleasing to the eye; the order isn't actually important.
2015-12-16 12:45:12 +00:00
Matthias Radestock 9d9768ee6f fix incorrect peer initialisation on test gossip connections
We were creating peers with NewPeerFrom, which copies the Version (but
not connections). That's not what happens for a real connection, where
remote peers are always created with an initial version of 0.

The mistake results in brokenness: when we subsequently receive a
topology update for that peer with the same version then we ignore it,
even though the update contains connection information which is new to
us.

Related, it is conceptually wrong to call FetchWithDefault on the
remote router. That is not what happens for a real connection - the
FetchWithDefault occurs in the symmetric counterpart.
2015-12-16 12:33:07 +00:00
Bryan Boreham 3043217b2c Extend --no-multicast-route test to plugin 2015-12-16 12:17:25 +00:00
Bryan Boreham afcb84d42c Set up static route for multicast, with option to turn off 2015-12-16 12:17:25 +00:00
Adam Harrison d5aad8f8d2 Introduce limited retry on vxlan vport creation
The kernel processes vport deletion asynchronously, meaning that we can
sometimes fail to recreate vxlan vports on startup with an EADDRINUSE
error. We compensate by retrying a small number of times with a short
sleep on encountering this error, failing as before on a different error
or exhaustion of retries.
2015-12-16 11:51:28 +00:00
Bryan Boreham c46febd7df Fix Makefile so weavewait* are statically linked
- the `-tags netgo` was being ignored by an interaction with the newer tags
2015-12-16 11:45:10 +00:00
Bryan Boreham f880da4827 Merge pull request #1722 from weaveworks/fdp-high-level-overview
Add fast datapath high level overview
2015-12-16 10:49:31 +00:00
Bryan Boreham 85c9cf43f7 Merge pull request #1689 from weaveworks/1586-alternate-docker-sock
Support weave script running against non-standard DOCKER_HOST.
2015-12-16 10:35:46 +00:00
Adam Harrison ce90d2389b Merge pull request #1790 from weaveworks/1783-veth-name
Prefix interface names with 'veth' so NetworkManager ignores them
2015-12-16 10:34:32 +00:00
Bryan Boreham d8cfbc4e08 Launch the proxy via unix socket in last test
- proxy copies permissions from $DOCKER_HOST socket if available, so in
   this case it was not accessible because we were booting via a TCP
   socket
2015-12-16 10:14:44 +00:00
Paul Bellamy 88fad251aa add test for proxy against docker on non-standard unix socket
Means we need to rebuild the gce images

(also small improvement re tmpdir)
2015-12-16 10:14:15 +00:00
Bryan Boreham 1a1d8b7c74 Prefix interface names with 'veth' so NetworkManager ignores them 2015-12-16 10:01:57 +00:00
Paul Bellamy a4ef96690e support alternate DOCKER_HOST settings when launching the plugin 2015-12-15 18:12:31 +00:00
Paul Bellamy f28ad350bd make weave and the proxy work with tcp-only dockers via DOCKER_HOST 2015-12-15 18:02:59 +00:00
Paul Bellamy 0afc3db383 First pass at passing DOCKER_HOST=unix://* through to the weave and proxy 2015-12-15 18:02:27 +00:00
Paul Bellamy ca1cce3232 Makefile should use DOCKER_HOST env var 2015-12-15 18:02:27 +00:00
Adam Harrison f9228eeb46 Merge pull request #1738 from weaveworks/1727-local-plugin
"mesh mode" plugin
2015-12-15 17:53:26 +00:00
Adam Harrison 0c077b8bb1 Merge pull request #1782 from weaveworks/1709-json-func
Add a 'json' function for 'weave report -f'
2015-12-15 17:09:02 +00:00
Bryan Boreham 0e9991f7d4 Make plugin create 'weave' network by default 2015-12-15 16:52:37 +00:00
Bryan Boreham 41482aff01 Update plugin README and documentation 2015-12-15 16:52:37 +00:00
Bryan Boreham 791fc9d6b1 Return fake 'gateway address' to avoid requiring consensus during initialization 2015-12-15 16:52:37 +00:00
Bryan Boreham 776ccac00a Pin docker API version to 1.21 2015-12-15 16:52:30 +00:00
Bryan Boreham 1539009362 Simple smoke-test for 'local' plugin network 2015-12-15 16:47:33 +00:00
Bryan Boreham 9542b77a34 Extend smoke-test framework for weaveplugin 2015-12-15 16:47:33 +00:00
Bryan Boreham b709cdeab5 Create local and global network from the same driver and create local-scope weave network by default on weave launch 2015-12-15 16:47:18 +00:00
Bryan Boreham e2f936fde3 Rework version check
- extract function so we can use the check elsewhere
- use shell variable expansion rather than exec-ing 'cut'
2015-12-15 16:40:43 +00:00
Bryan Boreham a4ca389f2e Make the plugin scope (local or global) an option 2015-12-15 16:31:07 +00:00
Bryan Boreham c973808b20 Merge pull request #1758 from weaveworks/issues/82-optional-encryption
Make encryption optional; fixes #82, #1788. LGTM.
2015-12-15 15:23:58 +00:00
Adam Harrison e12dbb4605 Disable pcap optimisation of encrypted sleeve
Prior to optional encryption support, enabling encryption disabled the
fast datapath overlay (because it doesn't support encryption) but left
the OVS datapath netdev in place for bridging (so that users could
switch encryption on and off without resetting the bridge). In this
situation the ODP miss handler is guaranteed to be invoked for every
packet, so as an optimisation the weave script configured the router to
use pcap to capture packets from the bridge as that is slightly more
efficient. The introduction of optional encryption means the guarantee
no longer holds, and so the optimisation must be removed.
2015-12-15 14:51:21 +00:00
Bryan Boreham c341399498 Document 'json' function 2015-12-15 13:52:28 +00:00
Bryan Boreham d1ba4ab7f4 Merge pull request #1755 from weaveworks/issues/1741-weave-no-multicast-route
Add options to disable multicast route - LGTM; fixes #1741
2015-12-15 10:17:09 +00:00
Adam Harrison 85d4b1f137 Smoke test trusted subnets 2015-12-14 12:13:16 +00:00
Adam Harrison b9792f8f96 Document trusted subnets
Update features and troubleshooting markdown.
2015-12-14 12:13:16 +00:00
Adam Harrison e0cb9cf24d Restore leading whitespace to weave status 2015-12-14 12:13:16 +00:00
Adam Harrison 4e41d4d451 Add trusted subnets to weave status
`weave status` now displays the list of specified trusted subnets, and
`weave staus connections` shows the encryption state of individual
connections.
2015-12-14 12:13:16 +00:00
Adam Harrison 059cc65b55 Fallback to encrypted sleeve on a per-connection basis
If overlay encryption is required between peers, the fast datapath
forwarder returns an error rather than aborting the process, allowing
the overlay switch to fall back gracefully to an encrypted sleeve
overlay connection.
2015-12-14 12:13:11 +00:00
Adam Harrison d434b50dfc Include trust level in features exchange
Add a 'Trusted' feature to the protocol exchange which indicates whether
the remote considers us to be reachable via a secure network; overlay
encryption will be disabled in the event that both peers agree. For
backwards compatibility purposes a default value of untrusted is assumed
if the feature is missing.
2015-12-14 12:12:25 +00:00
Adam Harrison 96f69583e7 Smoke test --no-multicast-route 2015-12-14 11:53:06 +00:00
Adam Harrison ce25f58639 weave launch-proxy accepts --no-multicast-route 2015-12-14 11:53:06 +00:00
Adam Harrison 367b73e6e3 Introduce nomcast weavewait 2015-12-14 11:53:06 +00:00
Adam Harrison 98a7d3fc99 weave run and weave attach accept --no-multicast-route 2015-12-14 11:53:06 +00:00
Bryan Boreham 6fd0f121a0 Add a 'json' function for 'weave report -f' 2015-12-12 14:44:54 +00:00
Bryan Boreham 68d6c262d1 Remove --with-dns option from proxy 2015-12-12 14:12:39 +00:00
Bryan Boreham 1d9ef76080 Remove mention of --with-dns option; see #1748 and #1754 2015-12-12 13:58:14 +00:00
Bryan Boreham b51b1adfcd Merge pull request #1767 from weaveworks/1765-test-gossip-surrogate-failure
better emulation of connections in gossip tests. Fixes #1765.
2015-12-11 17:20:23 +00:00
Matthias Radestock ab902b45aa refactor: introduce a helper to reduce duplication 2015-12-10 10:56:18 +00:00
Matthias Radestock ed14561afd refactor: rename 2015-12-10 10:54:20 +00:00
Matthias Radestock 86d2db5cc2 better emulation of connections in gossip tests
real Connections do not process gossip until after AddConnection.
mockChannelConnection wasn't behaving in that way, which resulted in
some sporadic test failures since it would cause some gossip to get
processed too early, which in turn would trigger gc of some of the
just-received peers, which we would then find missing in the test
assertions.

To fix this we introduce a latch on mockChannelConnection which closes
when the remote end is ready to receive gossip. Effectively this
emulates the entering of LocalConnection.receiveTCP after the call to
AddConnection completes.

Unfortunately an open latch will cause sendPendingGossip to block. We
get around that by abandoning checks in "half-connected" connection
states.

Fixes #1765.
2015-12-10 10:54:10 +00:00
Bryan Boreham 700851cdd5 Add a note about connecting up peers 2015-12-10 10:40:48 +00:00
Adam Harrison c3e1f5a6c6 .gitignore tls binary 2015-12-10 10:08:04 +00:00
Bryan Boreham 9bf18b1761 Merge pull request #1769 from weaveworks/ditch-run-with-timeout
Wins the prize for "best add-to-delete ratio in a PR this year"
2015-12-09 13:56:54 +00:00
Matthias Radestock 1374e4c4de ditch RunWithTimeout test helper
It breaks stack traces on failure.

We can just rely on running `go test -timeout ...` instead, which we
already do in tools/test.
2015-12-09 13:20:57 +00:00
Matthias Radestock 0ef57a102f cosmetic: remove unnecessary comment 2015-12-09 12:37:53 +00:00
Adam Harrison 1c7954a43d Merge pull request #1728 from weaveworks/1676-ipam-plugin
Docker IPAM plugin
2015-12-09 11:07:46 +00:00
Matthias Radestock be1dc8ef96 Merge branch '1.3' 2015-12-09 10:34:52 +00:00
Matthias Radestock 18fc6ccc03 remove redundant code in test 2015-12-09 10:32:14 +00:00
Matthias Radestock 7ec46983aa Merge branch '1.3' 2015-12-08 23:08:35 +00:00
Matthias Radestock eddcf93942 oops 2015-12-08 23:08:19 +00:00
Matthias Radestock 42807f7b8a Merge branch '1.3' 2015-12-08 23:07:06 +00:00
Matthias Radestock 0b521272ae prevent peer duplication in tests
and correct the test assertions accordingly - they make more sense
now.

Fixes #1766.
2015-12-08 23:06:02 +00:00
Bryan Boreham ecd93bbfe4 Resolve weave IP every time we need it, in case it changed 2015-12-08 16:59:34 +00:00
Bryan Boreham a6f07fbaac Move baseUrl usage up to httpVerb 2015-12-08 16:59:34 +00:00
Bryan Boreham f87876c1c3 Fetch the default subnet from weave to use as the ipam driver 'pool' 2015-12-08 16:59:34 +00:00
Bryan Boreham ec3de1a072 Introduce ipam plugin 2015-12-08 16:59:32 +00:00
Bryan Boreham c1d2c159f0 Expand listener skeleton to support IPAM driver 2015-12-08 16:58:55 +00:00
Bryan Boreham 9f86e2ba43 Moved net plugin to its own directory 2015-12-08 16:58:53 +00:00
Adam Harrison d0be36dc9b Merge branch '1.3'
Conflicts:
	Makefile
2015-12-08 16:53:47 +00:00
Adam Harrison af604a1477 Merge pull request #1760 from weaveworks/make-test-tls
Move test/tls program into Makefile so it builds inside the build container
2015-12-08 16:41:59 +00:00
Bryan Boreham c981cff450 Move build of test/tls program into Makefile so it builds in the build container 2015-12-08 16:14:53 +00:00
Bryan Boreham ab1033c26c Merge pull request #1736 from weaveworks/remove-unknown-peer-error
remove UnknownPeerError for quicker convergence and efficiency. LGTM.
2015-12-04 11:00:05 +00:00
Adam Harrison 01706df7c8 Merge pull request #1753 from weaveworks/1751-remove-unused-vars
remove CLIENT_TLS_ENABLED and DOCKER_CLIENT_TLS_VERIFY, as they're unused
2015-12-04 10:19:09 +00:00
Adam Harrison 827a7c1cf1 Merge branch '1.3' 2015-12-04 10:09:15 +00:00
Adam Harrison d14d9933b4 Merge pull request #1750 from weaveworks/add-some-quotes
quote $@
2015-12-04 10:08:34 +00:00
Adam Harrison af946893f0 Merge pull request #1749 from weaveworks/remove-eqeq-non-posixism
'==' is not POSIX
2015-12-04 10:08:26 +00:00
Paul Bellamy 245ae5e987 remove CLIENT_TLS_ENABLED and DOCKER_CLIENT_TLS_VERIFY, as they're unused 2015-12-02 17:17:04 +00:00
Matthias Radestock 29f3d711c6 Merge branch '1.3' 2015-12-02 15:34:07 +00:00
Matthias Radestock 3909936cc8 cosmetic(ish) use PROXY_PORT 2015-12-02 15:33:38 +00:00
Matthias Radestock 0602c91c54 quote $@ 2015-12-02 15:28:41 +00:00
Matthias Radestock a7bdb02a2b Merge remote-tracking branch 'upstream/master' 2015-12-02 14:33:28 +00:00
Matthias Radestock 2cfaf5a119 Merge branch '1.3' 2015-12-02 14:23:28 +00:00
Matthias Radestock 20a48f05b3 fix (harmless) typo 2015-12-02 14:22:23 +00:00
Matthias Radestock 4e70fb7f9d '==' is not POSIX 2015-12-02 14:20:29 +00:00
Adam Harrison 1f438dbf71 Merge pull request #1740 from weaveworks/1694-symlink-into-var-run-2
Symlink sockets to /var/run/weave/weave.sock from inside the proxy
2015-12-02 14:12:02 +00:00
Adam Harrison 619d0e2bdd Merge pull request #1730 from weaveworks/1725-plugin-dns
Fix DNS integration in Weave docker plugin
2015-12-02 09:49:21 +00:00
Bryan Boreham ec420c66d4 Review feedback 2015-12-01 16:47:47 +00:00
Paul Bellamy ac1cf633c7 clarify some variable shadowing 2015-12-01 16:16:39 +00:00
Paul Bellamy 6f25d2170d [proxy] have symlinking respond to WEAVE_DEBUG not DEBUG 2015-12-01 15:51:16 +00:00
Paul Bellamy 295a2ced32 [proxy] treat addrs prefix with / as unix addrs 2015-12-01 15:45:32 +00:00
Matthias Radestock cbb2ad018d don't broadcast-gossip other peer when adding a connection
This is more efficient since broadcasting about other peers is
strictly redundant. Any change to the peers will be broadcast by
those peers anyway. And SendAllGossipDown takes care of informing new
peers about others on first connect, and periodic gossip take care of
missed broadcasts.

The only reason we did broadcast about the remote peer is to reduce
the liklihood of UnknownPeerError and the associated throwing away of
gossip. This was always a bit of kludge, and is no longer necessary
since UnknownPeerError is gone.

Now peers only ever broadcast information about themselves. Which,
unlike information about other peers, is always authorative and most
up-to-date. Which is nice.
2015-12-01 13:10:06 +00:00
Matthias Radestock 7e15eabe65 remove UnknownPeerError
when encountering an unknown peer in received gossip, rather than
ignoring the entire gossip we create a placeholder Peer. This has
version 0 and uid 0, which means it will be replaced by any complete
peer information subsequently received via gossip.
2015-11-30 16:17:16 +00:00
Bryan Boreham eb9b5b535f Merge branch '1.3' 2015-11-30 16:16:09 +00:00
Bryan Boreham a13351a9eb Merge pull request #1712 from weaveworks/issues/1577-interpose-bridge-netdev
Optional intermediary bridge netdev. Fixes #1577.
2015-11-30 15:54:14 +00:00
Bryan Boreham 74fdf6beec Merge pull request #1732 from weaveworks/1731-merge-peer-has-short-id
update Peer.HasShortID as part of topology merge
Fixes #1731
2015-11-30 15:53:23 +00:00
Paul Bellamy cd7c88e5a5 [proxy] Symlink sockets to /var/run/weave/weave.sock from inside the proxy
We copy docker.sock owner to symlinks. Permissions are not copied,
because the permissions will inherit from the target.

use versioned exec image to create symlinks
2015-11-30 15:45:30 +00:00
Bryan Boreham a22b88ed89 Track endpoints so we can register the right address with DNS 2015-11-30 14:27:33 +00:00
Bryan Boreham 5eb99fe5ef Get container IP for various networks 2015-11-30 14:27:33 +00:00
Bryan Boreham 85e465cfa4 Eliminate 'dockerer' 2015-11-30 14:27:33 +00:00
Bryan Boreham e101dde62e Extract 'api' package 2015-11-30 14:27:33 +00:00
Adam Harrison 1980ffea30 Merge pull request #1702 from weaveworks/issues/1639-ipam-diagnostics
Show some IPAM stats with `weave status ipam`
2015-11-30 14:20:13 +00:00
Adam Harrison 0935e0a13d Merge branch '1.3' 2015-11-30 14:09:44 +00:00
Adam Harrison b2b68119fc Merge pull request #1719 from weaveworks/issues/1653-make-dir
Ensure /var/run/weave directory exists before telling Docker to use it
2015-11-30 13:57:37 +00:00
Adam Harrison 4420057d4f Merge pull request #1716 from weaveworks/issues/1714-complete-addr-1.3
Ensure `weave connect` attempts connection immediately
2015-11-30 13:52:13 +00:00
Bryan Boreham bd2b8df7c5 Refactor to use Weave's docker client and watcher 2015-11-30 13:25:20 +00:00
Matthias Radestock 068bbe0837 Merge branch '1.3' 2015-11-30 13:19:21 +00:00
Bryan Boreham 7e0e1fbaad Adjust formatting after review feedback; hostnames up to 14 chars will now align 2015-11-30 12:07:59 +00:00
Bryan Boreham 9723476392 Merge pull request #1708 from weaveworks/gce-connect-fix
Connect to gce boxes as vagrant so we can access docker unix socket
2015-11-30 11:45:30 +00:00
Matthias Radestock 9dee2f2230 update Peer.HasShortID as part of topology merge
This was missing.

There are two parts to this change:

1. updating the Peers.byName mapping when HasShortID changed (in
addition to the existing update on ShortID change). We need to be
careful about the sequencing here - both ShortID and HasShortID must
be set between the calls to deleteByShortID and addByShortID, since
the former is meant to delete the existing entry (hence use the old
values) and the latter add the new entry (hence use the new values).

2. update a peer when the received info has the same version and UID
but the current record has HasShortID=false, and the received info has
HasShortID=true. This caters for the case where we receive an update
about a weave 1.2+ peer first via a pre-1.2 peer (which does not
transmit ShortIDs, and always sets HasShortID=false) and subsequently
via a 1.2+ peer. The former update will force us to communicate with
that peer over the 'sleeve' overlay. We want to accept the latter
update, even though the version is the same, in order to update the
ShortID and HasShortID, so we can switch to fastdp.

Fixes #1731.
2015-11-29 16:32:50 +00:00
Matthias Radestock 175f38414e cosmetic 2015-11-28 20:15:57 +00:00
Bryan Boreham 8bbf8231d2 Show which range is being allocated in weave report 2015-11-26 17:24:41 +00:00
Bryan Boreham 2fbbb802bd Don't assert invariants when printing status
- if it's broken you might appreciate being able to print it out
2015-11-26 17:24:41 +00:00
Bryan Boreham cb65a80dce Reworded IPAM service description in weave status
following review in #1702
2015-11-26 17:24:41 +00:00
Bryan Boreham 3a9bcbcf6b Remove specialised knowledge of IPAM from weave status formatter
and unnecessary info from `weave report` output
2015-11-26 17:24:40 +00:00
Bryan Boreham e38018ab1e Add warning to main ipam status if all IPs owned by unreachable peers 2015-11-26 17:23:49 +00:00
Bryan Boreham 044c6474b2 Remove counting of ring tokens, since it isn't useful to end-user.
Previously the count of tokens was claimed to be a count of addresses in use,
which is incorrect.
Also rename `appendAddresses` to `appendOwned` since it doesn't involve addresses.
2015-11-26 17:23:49 +00:00
Bryan Boreham 5f42a4d95e Hold stats by peer identity not nickname
so we can print the peer name for each ownership.
Also avoid crashing if we don't own anything
2015-11-26 17:23:49 +00:00
Bryan Boreham 6d3fd6da07 Simplify ipam status format
Don't use the word "universe" in user-visible output; it is an internal term.
Removed everything but owned ranges from 'weave status ipam' results, for consistency with similar functions
2015-11-26 17:23:49 +00:00
Alvaro Saurin 34e7996f2f Show some IPAM status at /status/ipam 2015-11-26 17:23:47 +00:00
Bryan Boreham f2d7a28258 Add docker plugin README 2015-11-26 17:18:59 +00:00
Adam Harrison ba6f8da1e4 Merge pull request #1670 from weaveworks/issues/1634-block-6783-6784
Forbid traffic to the Weave port from containers
2015-11-26 16:52:34 +00:00
Adam Harrison 0a5e79891d Merge pull request #1724 from weaveworks/1674-move-plugin
Move Weave docker plugin into this repo
2015-11-26 15:34:15 +00:00
Bryan Boreham 901bf63cb8 Fix printf error caught by lint 2015-11-26 15:19:57 +00:00
Bryan Boreham 350b8efb0b Move Weave docker plugin into this repo 2015-11-26 14:40:28 +00:00
Adam Harrison 2f69b4d190 Mention WEAVE_MTU 2015-11-26 10:39:41 +00:00
Adam Harrison 57a15a4e96 Add fast datapath high level overview 2015-11-26 10:36:52 +00:00
Adam Harrison 13042002d7 Merge pull request #1663 from weaveworks/issues/1592-fdp-miss-counter
Add FDP miss counter to reports
2015-11-25 17:21:03 +00:00
Adam Harrison 8ac745b4e5 Lock read of miss counter 2015-11-25 17:05:19 +00:00
Adam Harrison 8a4f038ec4 Merge pull request #1662 from weaveworks/issues/1571-upstream-dns-report
DNS upstream servers report
2015-11-25 16:47:14 +00:00
Adam Harrison 890522f6f4 Add hidden weave bridge-type command 2015-11-25 16:31:32 +00:00
Adam Harrison c8aa032051 Eliminate function name stutter 2015-11-25 16:31:32 +00:00
Adam Harrison 9830c146d0 The bridge netdev is always named weave 2015-11-25 16:31:32 +00:00
Adam Harrison 00c9164fdb Pass WEAVE_NO_BRIDGED_FASTDP to remote for testing 2015-11-25 16:31:32 +00:00
Adam Harrison a31e7118b4 Optional intermediary bridge netdev 2015-11-25 16:31:32 +00:00
Matthias Radestock 2058ce3f3a refactor: change Routes.RandomNeighbours return for consistency
PeerNameSet -> []PeerName, which is what the other functions
return. This is also easier to deal with at the call site.
2015-11-25 14:46:41 +00:00
Matthias Radestock 61e3bf4a20 cosmetic(ish) 2015-11-25 14:32:34 +00:00
Bryan Boreham ea5bae711b Ensure /var/run/weave directory exists before telling Docker to use it 2015-11-25 13:56:09 +00:00
Bryan Boreham 44a2c5fdff Merge pull request #1718 from weaveworks/constant-spacetime-connection-shutdown
LGTM. prevent unbounded number of goroutines for connection shutdown
2015-11-25 11:22:08 +00:00
Matthias Radestock 3b43960779 prevent unbounded number of goroutines for connection shutdown
We need Connection.Shutdown to be non-blocked, and the way we achieved
this is by spawning a goroutine. However, there can be a potentially
unbounded number of Connection.Shutdown invocations. For example, if
the TCP connection has gone bad then any Connection.SendProtocolMsg
invocation will end up invoking Connection.Shutdown. There can be many
such invocations until the Connection has been removed from the
various data structures (e.g. LocalPeer.connections and
GossipSender.senders). Hence we can end up with with an unbounded
number of spawned goroutines.

To fix this, we introduce a one-element error channel on the
connection. Connection.Shutdown does a non-blocking send on that, and
the Connection.actorLoop reads from it and terminates.

This will throw away all but the first error encountered, but that is
fine and not much different from what was happening before (where we'd
throw away all but one of the errors encountered).
2015-11-25 11:06:20 +00:00
Bryan Boreham 89f27ec99a Set default port number on address before checking in InitiateConnections, to fix #1714 2015-11-24 14:55:21 +00:00
Adam Harrison 82cf4358ce Add release v1.3.1 2015-11-23 12:06:10 +00:00
Paul Bellamy 0f9b117744 Connect to gce boxes as vagrant so we can access docker unix socket 2015-11-20 16:54:41 +00:00
Adam Harrison c420c21219 Merge pull request #1707 from weaveworks/revert-1696-1694-symlink-into-var-run
Revert "Automatically symlink /var/run/*.sock sockets to /var/run/weave/*.sock"
2015-11-20 16:27:04 +00:00
Paul Bellamy 3bae4b0d93 Revert "Automatically symlink /var/run/*.sock sockets to /var/run/weave/*.sock" 2015-11-20 16:23:40 +00:00
Adam Harrison 1432bd7a81 Merge pull request #1696 from weaveworks/1694-symlink-into-var-run
Automatically symlink /var/run/*.sock sockets to /var/run/weave/*.sock
2015-11-20 16:23:12 +00:00
Adam Harrison bdcf930e25 Merge branch '1.3' 2015-11-20 11:08:49 +00:00
Adam Harrison 2e42d74c59 Merge pull request #1706 from weaveworks/1683-update-docker-1.3
Raise minimum Docker version to 1.6
2015-11-20 11:06:00 +00:00
Bryan Boreham bbf26fc3e8 Raise minimum Docker version to 1.6 to address #1683 2015-11-20 10:44:45 +00:00
Bryan Boreham 6dcc42cdeb Merge branch '1.3' 2015-11-20 10:33:38 +00:00
Bryan Boreham 385b0aa07f Fix typo in -tls info string 2015-11-20 10:33:20 +00:00
Bryan Boreham f2b1ee3398 Merge branch 'latest_release_doc_updates' into 1.3 2015-11-20 10:30:28 +00:00
Bryan Boreham 2a2c60201f Merge pull request #1699 from weaveworks/issues/1693-fix-proxy-docs-tls-args
Fix erroneously hyphenated Docker TLS arguments; Fixes #1693.
LGTM
2015-11-20 10:27:34 +00:00
Matthias Radestock 53749156a7 Merge branch '1.3' 2015-11-19 20:58:14 +00:00
Matthias Radestock ee12b34334 eliminate a gossip inefficiency and dormant bug
Fixes #1704
2015-11-19 20:57:27 +00:00
Matthias Radestock b5a4aefe8b never broadcast empty nameserver gossip 2015-11-19 18:28:37 +00:00
Bryan Boreham c2d5cab1b8 Merge remote-tracking branch 'origin/1.3' 2015-11-19 15:30:20 +00:00
Bryan Boreham f25af8f7d6 Merge pull request #1698 from weaveworks/1697-copy-on-write-gossip-data
make GossipData immutable; Fixes #1697. Fixes #1450. Fixes #1452.
2015-11-19 14:58:32 +00:00
Paul Bellamy 33648b083c SSH into test vms on gce as 'vagrant' user 2015-11-19 14:10:52 +00:00
Alvaro Saurin eb79711e0a Added flow misses count 2015-11-19 15:05:16 +01:00
Alvaro Saurin 03ce6052cc Updated docs 2015-11-19 14:54:37 +01:00
Alvaro Saurin 7992bf4177 DNS upstream servers report 2015-11-19 14:54:23 +01:00
Alvaro Saurin ddb390d5ec Forbid traffic to the Weave port from other containers
Make sure we have the Docker bridge IP before adding/removing iptables rules
2015-11-19 14:46:28 +01:00
Matthias Radestock 1500b1dc00 refactor: extract nameserver GossipData.copy
and remove a self-evident comment
2015-11-18 11:30:40 +00:00
Adam Harrison 9263d3e82c Fix erroneously hyphenated Docker TLS arguments 2015-11-18 10:24:46 +00:00
Adam Harrison 02b7fd53ed Merge tag 'v1.3.0' into latest_release_doc_updates
Resetting doc update branch for 1.3.0 release.
2015-11-18 10:14:10 +00:00
Matthias Radestock 3cd199e852 make GossipData immutable
The same piece of GossipData can be shared and subject to different
Merge operations. We either need to copy the data when we share it, or
create copies on modification, i.e. copy-on-write.

We choose the latter, since it is more efficient and there is just one
mutating operation.

Fixes #1697.
2015-11-17 20:13:46 +00:00
Paul Bellamy 77bc22baf5 [proxy] Auto-symlink /var/run/*.sock in from /var/run/weave/*.sock
Avoids the obscure warning for users
2015-11-17 17:25:21 +00:00
Paul Bellamy f57988761c Revert "Revert "Merge pull request #1580 from weaveworks/1579-launch-proxy-mount-h""
This reverts commit 7987d09438.
2015-11-17 17:15:28 +00:00
Adam Harrison 7987d09438 Revert "Merge pull request #1580 from weaveworks/1579-launch-proxy-mount-h"
This reverts commit 262352f8bb, reversing
changes made to 312691376a due to a bug
whereby `weave env` returns a unix domain socket with an incorrect path.

This change will be superceeded by #1694.
2015-11-17 16:28:31 +00:00
Adam Harrison 262352f8bb Merge pull request #1580 from weaveworks/1579-launch-proxy-mount-h
[proxy] dynamically mount -H dirs into the proxy when launching
2015-11-17 14:52:17 +00:00
Paul Bellamy e8c56a8e8e Review Feedback 2015-11-17 14:49:50 +00:00
Matthias Radestock 312691376a Merge remote-tracking branch 'upstream/master' 2015-11-17 13:57:08 +00:00
Matthias Radestock a9611c782b cosmetic: move comment 2015-11-17 13:56:18 +00:00
Adam Harrison bcc70b36fd Merge pull request #1679 from weaveworks/1539-no-detect-tls-flag
[proxy] add --no-detect-tls flag, and proxy always tries to detect TLS.
2015-11-17 13:55:43 +00:00
Matthias Radestock 9ee3b1bfbd add some timestamps to multiweave 2015-11-17 10:28:28 +00:00
Paul Bellamy e80a83b30b adding work around to the proxy /var/run warning 2015-11-17 09:40:34 +00:00
Paul Bellamy d80e72dfcd [proxy] dynamically mount -H dirs into the proxy when launching
This lets you use -H unix://*.sock outside /var/run/weave. Using a
socket directly in /var/run (i.e. /var/run/foo.sock), will reintroduce
the bug with namespaces not being GCed, so that is a warning.
2015-11-17 09:40:34 +00:00
Matthias Radestock d01c26e618 cosmetic: remove outdated comment
The deadlock described for SendProtocolMsg (if it was going via the
actor) cannot occur since all sending of broadcasts and gossip by
LocalPeer is mediated by GossipSenders, the interaction with which is
non-blocking.
2015-11-17 09:27:49 +00:00
Matthias Radestock cab2e11ca9 refactor: simplify per-connection GossipSender gc and creation
by putting it in one place and inlining some helper functions
2015-11-16 23:21:33 +00:00
Matthias Radestock b48994b1b3 cosmetic: move some functions 2015-11-16 23:01:20 +00:00
Matthias Radestock fb48acddb2 cosmetic: update comment 2015-11-16 19:27:31 +00:00
Paul Bellamy 7d627f7af4 Merge pull request #1673 from weaveworks/1440-cleanup
Move some netlink code to upstream library
2015-11-16 14:24:10 +00:00
Paul Bellamy f458491d88 [proxy] add --no-detect-tls flag, and proxy always tries to detect TLS. 2015-11-13 15:27:27 +00:00
Bryan Boreham de802f9d76 Work round docker-py assuming busybox is present (https://github.com/docker/docker-py/issues/852) 2015-11-13 11:44:08 +00:00
Bryan Boreham 804e6ba586 Cope with changes in docker-py structure.
Tests are now in a different directory,
and get sharded by module rather than individually.
2015-11-13 11:08:57 +00:00
Adam Harrison af3fc82b77 Add release v1.3.0 2015-11-12 16:49:33 +00:00
Adam Harrison 3bb7036cfd Runner depends on tools 2015-11-12 16:48:56 +00:00
Bryan Boreham d802eb485b Merge pull request #1664 from weaveworks/weave-launch-plugin
Add plugin support to weave script. LGTM.
2015-11-12 16:04:41 +00:00
Adam Harrison b4dd9c2490 Add plugin to features doc 2015-11-12 16:01:01 +00:00
Adam Harrison 04ab198e95 Set restart policy to 'always'
Docker attempts to recreate networks on startup which means the plugin
container has to be started. If it isn't started automatically by Docker
with a restart policy, it's not possible to start it manually because
Docker is blocked.
2015-11-12 14:58:54 +00:00
Adam Harrison c345700963 Add plugin to weave status 2015-11-12 14:58:54 +00:00
Adam Harrison b21afcb78f Implement weave launch-plugin and weave stop-plugin 2015-11-12 14:56:13 +00:00
Adam Harrison 7f65530b26 weave setup fetches plugin 2015-11-12 14:56:13 +00:00
Bryan Boreham e230eeb486 Use upstream netlink code 2015-11-12 13:08:56 +00:00
Bryan Boreham b1b03f3a97 Revert "Remove code coverage as temporary work-around to Docker 1.9 issue"
This reverts commit 65fe753a58.
2015-11-12 11:27:48 +00:00
Bryan Boreham 36ef6d8da2 Merge remote-tracking branch 'origin/1.2' 2015-11-12 11:27:01 +00:00
Bryan Boreham 28bb0dd3b0 Merge pull request #1671 from weaveworks/1666-remove-cont-dir
Remove containers directory bind-mount from proxy; fixes #1647 and #1666
2015-11-12 11:25:34 +00:00
Bryan Boreham 79f8a8f909 Merge remote-tracking branch 'origin/1.2' 2015-11-12 10:43:01 +00:00
Bryan Boreham bfe2513540 Merge pull request #1669 from dpw/fdp-fix-npd
Handle the case where Forward returns nil. Fixes #1661.
2015-11-12 10:38:29 +00:00
Bryan Boreham 2536aef737 Proxy doesn't need to mount the containers dir 2015-11-12 10:28:58 +00:00
Adam Harrison 0661ccdea9 Revert "Revert "Disable restart of docker""
This reverts commit 76db732f1b.
2015-11-11 10:10:17 +00:00
Adam Harrison 29f7015e58 Merge pull request #1665 from weaveworks/move-odp-helpers
Move ODP helper functions out to their own package
2015-11-11 09:56:24 +00:00
David Wragg 7dc1b50d3b Handle the case where Forward returns nil
Forward can return nil if the remote peer has HasShortID false.  That
can happen if we learn about a 1.2 peer from a 1.1  peer and attempt to
connect to it.

This change just fixes the panic.  HasShortID will never be set, so we'll
fall back to sleeve.  But if that occurs, if can be fixed by a restart of
the affected routers.
2015-11-10 23:17:01 +00:00
Bryan Boreham 682133a56a Move ODP helper functions out to their own package 2015-11-10 17:09:17 +00:00
Adam Harrison 76db732f1b Revert "Disable restart of docker"
This reverts commit a400a3dd09.
2015-11-10 10:24:58 +00:00
Matthias Radestock 64aa2b5cc3 refactor: remove weave overlay-isms from mesh 2015-11-09 20:56:04 +00:00
Matthias Radestock 1eed5fd271 cosmetic: don't mention UDP in mesh 2015-11-09 19:47:27 +00:00
Matthias Radestock dd7ffa4501 code docs: warn against nonce re-use by overlay connection 2015-11-09 19:46:53 +00:00
Adam Harrison 5e79da0c9c Merge pull request #1658 from weaveworks/tweak-circle
Upgrade CI to Docker 1.9 and work round issues
2015-11-09 17:55:27 +00:00
Bryan Boreham 65fe753a58 Remove code coverage as temporary work-around to Docker 1.9 issue 2015-11-09 17:18:59 +00:00
Bryan Boreham 17c835e672 Compress weave.tar to save upload time 2015-11-09 17:18:58 +00:00
Bryan Boreham fa5bcd0457 Lump some commands together to reduce CircleCI waiting overhead 2015-11-09 17:18:58 +00:00
Bryan Boreham 499d6312b7 Don't need --add-host except in non-FDP case because we put the
mappings in the outer /etc/hosts
2015-11-09 17:18:58 +00:00
Bryan Boreham 20d5903696 Update test template name to pull in new Docker version 2015-11-09 17:18:57 +00:00
Adam Harrison f039221ec9 Merge remote-tracking branch 'origin/create-mesh'
Closes #1659.
2015-11-09 17:16:35 +00:00
Matthias Radestock dc9f8762ca move generic communication substrate into its own 'mesh' package
This is mostly a matter of moving some files, changing package names,
changing some imports, and changing some type references.

We duplicate the odd constant & global var, and the intmac/macint
functions, since neither 'router' or 'mesh' nor, really,' common' are
a natural home for them.
2015-11-09 16:37:39 +00:00
Adam Harrison e5c69801a7 Merge remote-tracking branch 'origin/separate-overlay'
Closes #1648.
2015-11-09 16:17:41 +00:00
Matthias Radestock ab458368de refactor: move NetworkRouterStatus into its own file 2015-11-09 15:51:11 +00:00
Matthias Radestock 8eb680fcc9 refactor: separate out NetworkRouter-specific status 2015-11-09 15:51:11 +00:00
Matthias Radestock 71b05972f4 refactor: move NetworkRouter into its own file 2015-11-09 15:50:55 +00:00
Matthias Radestock 7deeb8dba5 refactor: separate NetworkRouter from Router 2015-11-09 15:47:08 +00:00
Matthias Radestock daad190188 refactor: split overlay.go to extract network overlay stuff 2015-11-09 15:47:08 +00:00
Matthias Radestock 7cc0052451 refactor: separate NullNetworkOverlay from NullOverlay 2015-11-09 15:47:07 +00:00
Matthias Radestock 18f8e29af8 refactor: separate NetworkOverlay from Overlay
...since connections only need to know about the latter.
2015-11-09 15:47:07 +00:00
Matthias Radestock d78f6614cc refactor: separate forwarder from overlay connection
...since the main connections only need to know about the former.

We make LocalConnection.Overlay (formerly '.forwarder') public since
the router needs access to it, and it shouldn't be doing that via a
private field.
2015-11-09 15:45:06 +00:00
Bryan Boreham 53ac43202a Merge pull request #1641 from weaveworks/1554-converge-on-peer-uid-change
better convergence when peers restart; fixes #1554.
2015-11-09 15:09:59 +00:00
Matthias Radestock bfb050c9cc better convergence when peers restart
Previously when a peer restarted, information about the new
incarnation (i.e. with a different UID) was not accepted by other
peers (and connections would be dropped) unless all knowledge of the
previous incarnation had been purged. This could result in a lot of
connection churn and hence connectivity disruption, and, in some
pathological cases, very slow convergence and hence acceptance of the
new incarnation into the network.

We now no longer drop connections when encountering different
incarnations of a peer. There are two situations when that can happen:

1) on connection establishment

we simply proceed

2) on receipt of gossip

to ensure convergence we

a) treat the UID as an additional discriminator when deciding whether
we should update our information about a peer with that which was
gossiped. Specifically, we update the information we hold when a) the
gossiped version is greater, or b) is the same and the UID is
greater.

b) include the UID in the information we update

c) move our own version number beyond any we receive for ourselves, if
the received UID differs from ours.

With (a) we establishes a total order of peer information across
several incarnations of the same peer. i.e. we consider information to
be fresher if it has a higher version, or the same version and higher
UID. This may seem somewhat counter intutive, since it will generally
treat information about new incarnations as older than old
incarnations, since incarnations always start life with version 1. But
to do better we'd need to establish a total order of incarnations that
matches their temporal occurrence. Which requires some sort of durable
state.

So instead we have (c). Through that we learn the highest version
number of any old incarnation of ourselves that other peers still
hold, and then make sure that our version is greater than
that. Essentially we continue where the old incarnations left
off. It's as if instead of restarting we had simply changed UIDs. And
due to (a) and (b) the information about the new incarnation of
ourselves, now with a higher version, will supersede that of the old
incarnations.

Fixes #1554.
2015-11-09 14:58:33 +00:00
Matthias Radestock 97622b961c remove cruft 2015-11-08 13:06:29 +00:00
Matthias Radestock b7e7c11a57 refactor: make Overlay setting more explicit 2015-11-08 12:29:36 +00:00
Matthias Radestock 59e11e4e8a refactor: more flexible Routes API 2015-11-08 11:15:31 +00:00
Matthias Radestock 39b0357517 Merge branch '1.2' 2015-11-08 11:04:48 +00:00
Matthias Radestock 878a12002c set http listen address solely through the weave script
Previously it was set by the script only in fastdp mode; and in the
router's main.go otherwise. Yet every interaction of the script with
weave assumes the port is the script's HTTP_PORT=6784. By always
starting the router with options to listen on that port we ensure that
the router is in fact listening where the script expects it to.

In doing this get rid of router.HTTPPort. We do need to hard-code the
port in the proxy instead, but relying on router.HTTPPort was flawed
anyway, for the above reasons.
2015-11-07 18:01:12 +00:00
Matthias Radestock d3d2863d52 refactor: get rid of const.go 2015-11-07 16:32:23 +00:00
Matthias Radestock f3f23b43b5 refactor: crypto doesn't need to be a generic overlay feature 2015-11-07 16:14:31 +00:00
Matthias Radestock 006262ed8f refactor: Connections don't need to know about overlay crypto 2015-11-07 16:10:00 +00:00
Matthias Radestock 519aef86fa refactor: split crypto code 2015-11-07 15:59:15 +00:00
Matthias Radestock 8bd77c0d47 refactor: get rid of router/utils.go
most functions end up in other files in the router package, but a few
land in common.
2015-11-07 15:45:29 +00:00
Matthias Radestock 7a5c910cd5 tiny refactor: inline a tiny single-use fun and move another 2015-11-07 12:44:13 +00:00
Matthias Radestock 4e34f2001c cosmetic 2015-11-07 11:14:11 +00:00
Matthias Radestock 54b7e2c874 tiny refactor: use checkWarn in a few more places 2015-11-07 11:08:44 +00:00
Matthias Radestock 19f89f7122 remove duplicate test
this is already covered in dns_fallback_test
2015-11-07 07:30:53 +00:00
Matthias Radestock 9f25d72a4a cosmetic 2015-11-07 05:43:20 +00:00
Adam Harrison 3e99b71e0f Merge pull request #1643 from weaveworks/refactor-forwarder-remote-address
Refactor: remove knowledge of overlay connectivity details from Local{Peer,Connection}
2015-11-06 16:02:10 +00:00
Matthias Radestock fb3b690c5f refactor: reduce coupling between LocalConnection and Forwarder 2015-11-05 21:27:14 +00:00
Matthias Radestock 18fe553d23 LocalConnection shouldn't have to know about remote UDP address
...and generally know less about how overlays establish connectivity.
2015-11-05 20:43:52 +00:00
Matthias Radestock e7ff6a2191 LocalPeer shouldn't have to know about remote UDP address
...and LocalConnection shouldn't have to remember it
2015-11-05 19:32:49 +00:00
Matthias Radestock ad15e5a234 cosmetic 2015-11-05 19:01:34 +00:00
Matthias Radestock 094c7aa438 cosmetic 2015-11-05 17:24:38 +00:00
Adam Harrison 472ed5a7d3 Merge pull request #1640 from weaveworks/1588-disable-docker-restart
Disable restart of docker in test 640
2015-11-05 15:40:01 +00:00
Adam Harrison f284300870 Merge pull request #1636 from weaveworks/1635-notation
Drop use of interval notation when printing ranges
2015-11-05 15:38:40 +00:00
Adam Harrison ab5f4410b6 Add release v1.2.1 2015-11-05 14:34:13 +00:00
Bryan Boreham a400a3dd09 Disable restart of docker 2015-11-05 14:20:38 +00:00
Bryan Boreham c4855e8123 Merge branch '1.2' 2015-11-05 12:41:39 +00:00
Adam Harrison 3abb657777 Document port security 2015-11-05 12:23:17 +00:00
Adam Harrison 4dd63d8f72 Bind the HTTP listener to localhost when fastdp enabled
Prevent containers from accessing the weave router control port when
fast datapath is in use.
2015-11-05 12:23:17 +00:00
Bryan Boreham 6c0bdec2c9 Merge pull request #1637 from weaveworks/issues/1632-http-localhost-bind
Bind router HTTP listener to localhost when fastdp enabled
LGTM.  Fixes #1632
2015-11-05 12:20:20 +00:00
Adam Harrison d6dd1f269a Document port security 2015-11-05 12:16:57 +00:00
Bryan Boreham 2bd23f3709 Merge branch '1.2' to pull in latest_release_doc_updates 2015-11-05 12:05:25 +00:00
Bryan Boreham 79dc464b83 Merge branch 'latest_release_doc_updates' into 1.2 2015-11-05 12:04:54 +00:00
Bryan Boreham 3dbd5b11d8 Segregate discussion of range and subnet, and explain subnet a bit 2015-11-05 11:32:06 +00:00
Bryan Boreham 549fd5fe29 Merge branch '1.2' 2015-11-05 11:30:53 +00:00
Bryan Boreham 0ab5c65185 Merge pull request #1630 from weaveworks/issues/1588-improve-proxy-restart-reattach-test
Improve proxy restart reattach test reliability
2015-11-05 11:25:31 +00:00
Bryan Boreham 5f4753f881 Segregate discussion of range and subnet, and explain subnet a bit 2015-11-05 10:13:06 +00:00
Bryan Boreham e03a45f8c5 Update docs to match loss of interval notation 2015-11-05 10:11:43 +00:00
Adam Harrison 67c2d53e59 Bind the HTTP listener to localhost when fastdp enabled
Prevent containers from accessing the weave router control port when
fast datapath is in use.
2015-11-04 17:42:24 +00:00
Bryan Boreham 7b01dd9794 Drop use of interval notation 2015-11-04 16:45:16 +00:00
Bryan Boreham 04e50ff02c Merge branch '1.2' 2015-11-04 16:40:30 +00:00
Bryan Boreham e57ccdc858 Merge branch 'latest_release_doc_updates' into 1.2 2015-11-04 16:39:43 +00:00
Bryan Boreham a73a65b48a Removed obsolete text referring to mixed auto-manual allocation, and clarify Range printout 2015-11-04 16:36:44 +00:00
Adam Harrison 781c805e10 Merge branch '1.2' 2015-11-04 15:32:17 +00:00
Adam Harrison 7c4af88911 Merge pull request #1606 from weaveworks/issues/1578_ufw_ubuntu
Add an iptables rule for letting weaveDNS requests in
2015-11-04 15:30:34 +00:00
Alvaro Saurin 0aca173e96 Add an iptables rule for letting weaveDNS requests in 2015-11-04 14:00:26 +01:00
Matthias Radestock 60db57a41c refactor: remove duplicate isKnownPeer function 2015-11-03 18:11:53 +00:00
Matthias Radestock df68f7dcb9 Merge branch '1.2' 2015-11-03 17:56:36 +00:00
Matthias Radestock e95e3cc29d Merge pull request #1624 from weaveworks/1593-more-peers
Better choice of peers for IPAM to transfer ownership to on `weave reset`

Fixes #1593.
2015-11-03 17:55:40 +00:00
Bryan Boreham 67e9310cc1 Review feedback 2015-11-03 17:01:27 +00:00
Adam Harrison 94984df047 Merge branch '1.2' 2015-11-03 16:57:15 +00:00
Adam Harrison 861bf953bf Merge pull request #1629 from weaveworks/1626-expose-names
Always add 'weave expose -h' argument to DNS
2015-11-03 16:32:13 +00:00
Adam Harrison 06eaac8690 Don't assert ethwe carrier via the proxy
The test script runs commands inside containers via docker exec to
ensure the ethwe carrier is up. We must not run these commands via the
proxy because it will interpose weavewait, which will block forever if
attachment has failed (e.g. due to #1556).
2015-11-03 15:42:37 +00:00
Adam Harrison 6b9e8816d1 Wait for proxy to become available after docker is restarted 2015-11-03 15:33:37 +00:00
Bryan Boreham cf9f715a28 Merge branch '1.2' 2015-11-03 15:31:51 +00:00
Bryan Boreham 08e043fea7 Always add 'weave expose -h' argument to DNS, and add smoke-test for this also 2015-11-03 15:23:28 +00:00
Bryan Boreham 54ba1009d3 Merge pull request #1608 from weaveworks/1600-start-networkmode
LGTM.  Fixes #1600
2015-11-03 15:14:15 +00:00
Adam Harrison 4a6a46fd7b Merge pull request #1627 from tomwilkie/mv-scheduler
Move scheduler to build-tools.
2015-11-03 14:44:52 +00:00
Adam Harrison 0eb7414f72 Merge pull request #1583 from weaveworks/move-runner-to-tools
Remove test runner; it now lives in build-tools.git
2015-11-03 14:44:46 +00:00
Tom Wilkie c02691a70c Move scheduler to build-tools. 2015-11-03 14:38:42 +00:00
Bryan Boreham 8742957f75 Tweak naming to be closer to code changes in nameserver 2015-11-03 14:15:49 +00:00
Bryan Boreham ace372cfc6 More extensive choice of transferee 2015-11-03 12:03:28 +00:00
Bryan Boreham 27c5f5f49f Fix title of test 2015-11-03 12:03:00 +00:00
Paul Bellamy ec5a1a3977 When container is started in host (or container) networkMode, we mount a noop weavewait, so it can continue
review feedback
2015-11-03 10:18:05 +00:00
Adam Harrison abc653c87d Fix merge breakage 2015-11-03 10:13:43 +00:00
Matthias Radestock 30a128c00b Merge branch '1.2' 2015-11-02 20:58:15 +00:00
Matthias Radestock 77c7963bde Merge pull request #1615 from weaveworks/1603-resort-in-encode
Hold DNS entries sorted case-insensitively until just before sending

Fixes #1603 and #1610.
2015-11-02 20:57:40 +00:00
Bryan Boreham b80c1343c3 Take steps to avoid the test timing out 2015-11-02 20:40:17 +00:00
Bryan Boreham e5c5fbcea3 Hold on to the lower-case version of a hostname, rather than re-computing it every time 2015-11-02 20:40:05 +00:00
Bryan Boreham fc7d04ce73 Condense Entries unit tests with helper function 2015-11-02 20:03:10 +00:00
Bryan Boreham 35f57b3ed2 Move decoding code next to encoding code 2015-11-02 20:01:40 +00:00
Bryan Boreham ed9ade05ea Improve merge asserts 2015-11-02 20:01:40 +00:00
Bryan Boreham f485b729bc Hold all entries sorted case-insensitively until just before sending, when we re-sort case-sensitively in Encode() 2015-11-02 20:01:40 +00:00
Bryan Boreham 64f7953423 Add unit test to check outbound mixed-case merging 2015-11-02 20:01:40 +00:00
Bryan Boreham fc1d837501 Extend unit test to cover mixed-case hostnames and daisy-chained gossip;
reduce mock gossip timer to 2 seconds to trigger more often during tests.
2015-11-02 20:01:40 +00:00
Matthias Radestock 6a238818da Merge branch '1.2' 2015-11-02 19:58:37 +00:00
Matthias Radestock 9800c43bfa refactor(ish): don't use reference in method that doesn't modify 2015-11-02 19:57:17 +00:00
Bryan Boreham b3cda39b0b Let IPAM pick any peer we know about to transfer ownership to, when shutting down 2015-11-02 18:34:58 +00:00
Adam Harrison 7e1b7a2fcd Merge branch '1.2' 2015-11-02 17:28:31 +00:00
Adam Harrison ccae8d42fd Merge remote-tracking branch 'dpw/fdp-probe-vxlan' into 1.2
Fixes #1599
2015-11-02 17:24:52 +00:00
David Wragg 38f78286c7 Probe whether the kernel has ODP vxlan support
It's possible to build that kernel with openvswitch, and vxlan, but
without the vport_vxlan module that glues them together.  And at
least one distro has actually done this.
2015-11-02 15:53:16 +00:00
Adam Harrison c3a53a6ec5 Merge remote-tracking branch 'upstream/1.2' 2015-11-02 15:21:06 +00:00
Bryan Boreham 2bbe6a2ad8 Merge pull request #1622 from weaveworks/fix-miekg-dns-truncation-test
LGTM
2015-11-02 15:09:57 +00:00
Adam Harrison f64f63f675 Handle truncated responses from upstream servers 2015-11-02 15:00:02 +00:00
Adam Harrison f0fb44f6d4 Update unit test for miekg DNS API change 2015-11-02 13:24:04 +00:00
Paul Bellamy fa972615e6 Merge pull request #1616 from weaveworks/1549-inspect-wait
Make inspect-rewrite wait for start
2015-11-02 11:00:23 +00:00
Bryan Boreham f7cb2ef25d have waitForStartByIdent() return an error 2015-10-30 13:46:30 +00:00
Bryan Boreham 949f8bb2e7 Move updateContainerNetworkSettings() into proxy 2015-10-30 13:34:16 +00:00
Bryan Boreham 5eaff802d4 Make inspect-rewrite wait if the start operation is still in progress 2015-10-30 13:34:16 +00:00
Matthias Radestock b37d428726 Merge branch '1.2' 2015-10-30 01:31:16 +00:00
Matthias Radestock a6336149f8 guard against chef not running
Provisioning on ubuntu/wily64 was tripping over this.
2015-10-30 01:29:21 +00:00
Matthias Radestock 12f5ec40b5 refactor: reduce coupling of nameserver to router
The nameserver no longer knows anything about router.Peers and
router.Peer. That only leaves PeerName, Gossip and GossipData.
2015-10-29 23:21:07 +00:00
Matthias Radestock 6a672cf8bc tiny refactor: more idiomatic referal to process' /proc 2015-10-28 18:23:15 +00:00
Matthias Radestock 036b7d9f66 Merge branch '1.2' 2015-10-28 09:40:50 +00:00
Matthias Radestock ca6bce3820 Merge branch 'latest_release_doc_updates' into 1.2 2015-10-28 09:20:46 +00:00
Matthias Radestock 9b4f2297b0 drop quotes in eval "$(weave env)"
They are unnecessary and unwieldy.

Fixes #1596
2015-10-28 09:19:10 +00:00
Matthias Radestock 355c4bc58e Merge branch '1.2' 2015-10-28 08:52:01 +00:00
Matthias Radestock a31b2876d0 Merge pull request #1598 from weaveworks/issues/1584-improved-proxy-router-restart
Improved detection of router container restart

Fixes #1584
2015-10-28 08:51:06 +00:00
Matthias Radestock b697b6e57b Merge pull request #1597 from weaveworks/issues/1516-weavedns-ignore-foreign-domains
Check weaveDNS subdomain on REST PUT

Fixes #1516
2015-10-28 08:50:25 +00:00
Adam Harrison 4792fe6391 Improved detection of router container restart
Use the container name in addition to the entrypoint to avoid triggering
reattachment unnecessarily.
2015-10-27 14:41:26 +00:00
Adam Harrison 7408f72de2 Check weaveDNS subdomain on REST PUT 2015-10-27 14:37:57 +00:00
Tom Wilkie 203a3cfdd1 Remove test runner; it now live in build-tools.git 2015-10-23 13:02:00 +00:00
Ilya Dmitrichenko 3a029686e3 Add Pardot script 2015-10-22 15:07:52 -07:00
Matthias Radestock 9fa7b3956d Merge remote-tracking branch 'upstream/master' 2015-10-22 16:17:55 +01:00
Matthias Radestock b888ace508 remove superfluous code in ApplyUpdate
If the local peer's short ID was updated, we broadcastPeerUpdate at
the end (in unlockAndNotify), so there is no need to include the local
peer in the current update.

Also, reading peers.ourself.Version is bad here - we don't take a lock
and it's written by other goroutines. Doesn't actually break anything
though since it's a uint64 and hence atomic.
2015-10-22 16:12:37 +01:00
Bryan Boreham 2e54efe34d Stop the proxy last, so we can stop the router
before being unable to communicate with docker
when $DOCKER_HOST is pointing at the proxy
2015-10-22 15:45:54 +01:00
Bryan Boreham 94d7f33a03 Merge pull request #1541 from weaveworks/weave-1.2-readme-updates
Update README for 1.2 release
2015-10-22 13:22:54 +01:00
Bryan Boreham 11598295cf Add release v1.2.0 2015-10-22 12:53:57 +01:00
Bryan Boreham 383c3769aa Try en even bigger sleep 2015-10-22 12:19:57 +01:00
Adam Harrison caccba18e9 Mention fast datapath port 2015-10-22 11:58:14 +01:00
Paul Bellamy 95ce00e551 moving boot2docker instructions into the readme 2015-10-22 11:58:14 +01:00
David Wragg 85dad58cfe Pass --privileged for the temporary container in rewrite_etc_hosts
Otherwise we run into selinux issues on fedora23 (and probably other
selinux distros).
2015-10-22 11:44:17 +01:00
Bryan Boreham acdc70f72a Merge pull request #1560 from weaveworks/1542-proxy-bug-weave-not-running
LGTM. Fixes #1542
2015-10-22 10:19:32 +01:00
Paul Bellamy bc9db72212 [proxy] Add notifyWaiters to fix anticipated race condition.
* User makes /start request, and blocks on the waiter
* ContainerStarted fires with ID
* Container goes away
* We try to inspect the container to get the entrypoint, which fails
* Previously, this wouldn't call notifyWaiters and the /start would hang (I expect)
2015-10-22 09:50:12 +01:00
Adam Harrison 7905f81d7e Merge pull request #1570 from dpw/mac-invalidate-routes
Do InvalidateRoutes when a forwarded packet updates a MAC cache entry
2015-10-21 23:54:56 +01:00
Matthias Radestock 2ef9cc6a1c start temporary containers without networking when possible
This is more efficient and TRTTD

Closes #1572.
2015-10-21 16:52:19 +01:00
Matthias Radestock 2357b69a5f nicer interface name
and more likely to be ignored by NetworkManager

Closes #1567.
2015-10-21 16:22:16 +01:00
Matthias Radestock 98b47f866b Merge pull request #1569 from weaveworks/issues/1565-docker-1.9.0-workaround
Reorder inspect template to work around docker/docker#17026

Fixes #1565.
2015-10-21 15:29:59 +01:00
David Wragg d2dfb0d15d Do InvalidateRoutes when a forwarded packet updates a MAC cache entry
When we get a forwarded packet, we put its source MAC into the MAC cache,
overriding any existing entry.  But we could have a flow which says
to forward packets destined to that MAC based on the old entry.  Such
a flow could be prolonged indefinitely by attempts to send to the MAC.
This is obviously bad, so do InvalidateRoutes in such cases.
2015-10-21 13:46:18 +01:00
Paul Bellamy 63339c026d [proxy] return errors when attaching new containers
I had implemented this with a promise, but was informed that as
waitForStart is only called once, we are safe to just put the error into
the channel.
2015-10-21 13:05:05 +01:00
Adam Harrison 40922b88f7 Reorder inspect template to work around docker/docker#17026 2015-10-21 12:39:55 +01:00
Bryan Boreham 674c617c16 Merge branch '1.1' 2015-10-21 11:52:49 +01:00
Bryan Boreham 2884b6deca Add release v1.1.2 2015-10-20 17:06:14 +01:00
Adam Harrison 720862dafb Merge pull request #1562 from dpw/fdp-create-bridge
'weave --local create-bridge' is not compatible with fastdp
2015-10-20 16:37:20 +01:00
David Wragg 5120b4b468 'weave --local create-bridge' is not compatible with fastdp 2015-10-20 16:34:50 +01:00
Adam Harrison c5f5d4543e Merge pull request #1518 from weaveworks/401-reattach-weave2
Get proxy to re-attach weave router if it restarts
2015-10-20 15:31:13 +01:00
Paul Bellamy baf92da9f2 Merge pull request #1534 from weaveworks/1531-check-hostconfig
Take more care over HostConfig on start request in proxy
2015-10-20 12:18:49 +01:00
Paul Bellamy 526c9c3d73 Add test for {"HostConfig":{}} overriding previous settings, but still connecting to weave 2015-10-20 11:26:29 +01:00
Bryan Boreham 119d87b795 Extract repeated test into function 2015-10-20 11:26:29 +01:00
Bryan Boreham 17b030cca1 Cope with client sending a HostConfig named HostConfig 2015-10-20 11:26:29 +01:00
Bryan Boreham 81c9b3f327 Check that HostConfig parameters supplied on /start are passed to Docker,
also what happens when you supply a HostConfig named HostConfig
2015-10-20 11:26:29 +01:00
Bryan Boreham 50198a2159 Don't create a HostConfig accidentally if none was passed in 2015-10-20 11:26:29 +01:00
Bryan Boreham 20c8b1eaf6 Reverse the sense of a test, to simplify the next change
and remove unused struct
2015-10-20 11:26:21 +01:00
Bryan Boreham faf94b033d Extend test to check we don't overwrite HostConfig 2015-10-20 11:25:06 +01:00
Adam Harrison 342b3d3d08 Merge pull request #1557 from weaveworks/issues/1497
Add container FQDN to /etc/hosts
2015-10-20 11:22:22 +01:00
Bryan Boreham 26d43f193e Move docker restart test above the place where we stop part of weave 2015-10-20 07:07:04 +00:00
Alvaro Saurin f04ae91c82 Add container FQDN to /etc/hosts 2015-10-19 21:51:38 +02:00
Bryan Boreham bb5515113f Make attach-router work in fastdp+encryption case 2015-10-19 15:49:07 +00:00
Bryan Boreham c09d84efc6 Move setup_router_iface call so we don't kill an already-running router on attach-router 2015-10-19 15:49:07 +00:00
Bryan Boreham 2a99a2a597 Don't fail if the arg we're looking for isn't there 2015-10-19 15:47:44 +00:00
Bryan Boreham 08f6dcf08d Missing format 2015-10-19 15:47:44 +00:00
Bryan Boreham 284d78c6b2 Update fetch_router_args in line with changes elsewhere 2015-10-19 15:47:44 +00:00
Bryan Boreham b8b5fb9eae Review feedback 2015-10-19 15:47:43 +00:00
Bryan Boreham 697ee62d9a Use entrypoint to detect weave router 2015-10-19 15:47:43 +00:00
Bryan Boreham 20ac0edda8 Try a bigger sleep to get Docker restarted 2015-10-19 15:46:48 +00:00
Bryan Boreham a1bbb06e2f Don't need depreciation warnings on internal-only command 2015-10-19 15:46:47 +00:00
Bryan Boreham a87cc5c3b1 Get the proxy to re-attach the weave router if it sees it restart 2015-10-19 15:46:47 +00:00
Matthias Radestock 5ff740cddc cosmetic 2015-10-19 15:43:49 +01:00
Matthias Radestock db9229f485 Merge pull request #1551 from weaveworks/1547-proxy-restart-no-kill-containers
Proxy restart shouldn't kill containers

Fixes #1547.
2015-10-19 13:55:48 +01:00
Paul Bellamy ace6c01b00 Review feedback 2015-10-19 13:53:20 +01:00
Bryan Boreham 2f4d460c04 Merge pull request #1517 from weaveworks/1385-non-container-dns-add
LGTM. Fixes #1385
2015-10-19 12:52:09 +01:00
Matthias Radestock d5401ae3cb Merge pull request #1550 from dpw/weave2-eexist
Don't fail in launch-router if weave2 already exists

Fixes #1548.
2015-10-16 21:36:45 +01:00
David Wragg 3482a7f1f1 Don't fail in launch-router if weave2 already exists
And delete weave2 on stop-router.
2015-10-16 20:59:06 +01:00
David Wragg 23762e0639 Move repeated 'stop' code into functions 2015-10-16 20:55:44 +01:00
Bryan Boreham 50cc15af3b Merge pull request #1477 from weaveworks/weave-1374
LGTM.  Fixes #1374
2015-10-16 18:02:16 +01:00
Paul Bellamy a0f0367e60 [proxy] don't kill containers when starting the proxy 2015-10-16 15:33:43 +01:00
Adam Harrison 3f08ddefe9 Correct DisplayName implementation 2015-10-16 12:24:38 +01:00
Adam Harrison debe656f9f Merge pull request #1522 from dpw/fdp-compat
Fast datapath compatibility with 1.1
2015-10-16 11:26:20 +01:00
Alvaro Saurin 4350b6402f Updated docs 2015-10-16 11:47:07 +02:00
Alvaro Saurin 5d2892f6fc Test we respect --add-host when rewriting /etc/hosts
Simplify some tests
2015-10-16 11:47:07 +02:00
Alvaro Saurin 91f27ddd66 Removed weavehosts 2015-10-16 11:47:07 +02:00
Alvaro Saurin 6a674d0d67 Add hosts when attaching 2015-10-16 11:47:07 +02:00
Alvaro Saurin 0d165487b8 Rewrite /etc/hosts in the containers 2015-10-16 11:47:07 +02:00
Matthias Radestock bacc5836ea log listeners 2015-10-16 09:34:20 +01:00
Matthias Radestock 230931a2c9 prevent accidental var shadowing
Fixes #1544
2015-10-16 01:05:04 +01:00
Matthias Radestock 61d289f414 Merge branch '1.1' 2015-10-15 18:14:20 +01:00
Matthias Radestock 44255862a9 prevent spurious errors from showing up
silly mistake
2015-10-15 18:12:56 +01:00
Matthias Radestock ed9577150b suppress spurious errors 2015-10-15 18:01:06 +01:00
Matthias Radestock 80319e7087 Merge branch '1.1' 2015-10-15 17:41:49 +01:00
Matthias Radestock 379048d8fc make test/setup.sh work under go 1.5
Fixes #1543.
2015-10-15 17:40:51 +01:00
Matthias Radestock 9ac655d774 Merge pull request #1526 from weaveworks/fdp-docs
Document fast data path feature

Fixes #1526.
2015-10-15 17:29:29 +01:00
Adam Harrison 9b17470129 Document fast data path feature 2015-10-15 17:19:13 +01:00
Matthias Radestock 5c114fc6fe Merge pull request #1536 from weaveworks/1515-print-json
Print request and response json when debugging weaveproxy

Fixes #1515
2015-10-15 15:59:35 +01:00
Bryan Boreham efb12f41b2 Also print intercepted responses 2015-10-15 15:07:02 +01:00
Adam Harrison 813edcfe0a Merge branch '1.1' 2015-10-15 14:59:29 +01:00
Adam Harrison dea1405d71 Remove hardcoded docker bridge IP from proxy test 2015-10-15 14:39:56 +01:00
Ilya Dmitrichenko e7f91b7529 Update screencast preview image 2015-10-15 14:26:39 +01:00
Matthias Radestock 5e8a64508e Merge remote-tracking branch 'upstream/master' 2015-10-15 14:19:50 +01:00
Matthias Radestock 21ebb7a585 Merge branch '1.1'
Conflicts:
	Makefile
2015-10-15 14:19:14 +01:00
David Wragg 6edd430538 Use consistent options for all "go get"/"go build" invocations
Without this, building with go-1.5 fails with

    go get ./prog/weavewait
    go install net: open /usr/local/go/pkg/linux_amd64/net.a: permission denied

This makes the options passed too "go get"/"go build" consistent for
all executables. So it should be safe with go-1.4.

Conflicts:
	Makefile
2015-10-15 14:16:33 +01:00
Ilya Dmitrichenko 34606e0f9f Link to the new screencast (close #1402) 2015-10-15 14:07:54 +01:00
Matthias Radestock 5f62f9edc7 Merge branch '1.1' 2015-10-15 13:05:35 +01:00
Matthias Radestock 0d2ff55c07 Merge pull request #1535 from weaveworks/1523-rewrite-inspect-failure
[proxy] only attempt to rewrite successful inspects

Fixes #1523.
2015-10-15 13:04:34 +01:00
Matthias Radestock 207ee8667b Merge pull request #1538 from dpw/detect-no-fastdp-mismatch
signal mismatches between WEAVE_NO_FASTDP and the bridge type

Fixes #1533.
2015-10-14 17:17:35 +01:00
David Wragg 5984ed921e signal mismatches between WEAVE_NO_FASTDP and the bridge type 2015-10-14 16:33:23 +01:00
Matthias Radestock aac5fb8c24 Merge pull request #1537 from weaveworks/1502-flakey-test
Wait until container exits in test to remove race condition

Fixes #1502
2015-10-14 16:19:20 +01:00
Matthias Radestock 8cfc8b3132 Merge pull request #1528 from weaveworks/weave-env-bug
[proxy] Fix bug in weave env/config when passing multiple -H flags

Fixes #1527
2015-10-14 15:50:42 +01:00
Paul Bellamy 0b5ebd7b1d Wait until container exits in test to remove race condition 2015-10-14 14:53:23 +01:00
David Wragg 35141a4aef Forward a packet with sleeve if the source or dest peer lacks a short ID 2015-10-14 13:53:59 +01:00
David Wragg e702234fdc Don't use a nil FlowOp to mean "discard the packet" 2015-10-14 13:53:34 +01:00
Bryan Boreham 05dbe23563 Print request and response json when debugging 2015-10-14 13:45:51 +01:00
Paul Bellamy fd07f59d16 [proxy] Fix bug in weave env/config when passing multiple -H flags
The echo in proxy_addr was not preserving newlines, so the multiple
addrs were all getting returned
2015-10-14 11:58:10 +01:00
Paul Bellamy 36de840c91 [proxy] only attempt to rewrite successful inspects 2015-10-14 11:28:43 +01:00
David Wragg b36ffe1c9d Support peers that don't have short IDs 2015-10-14 11:11:34 +01:00
David Wragg 1f1451e666 Fall back to sleeve when "Overlays" not present in features 2015-10-14 11:11:34 +01:00
David Wragg 1000839309 Merge issues/1505-fast-datapath-weave-status' (PR #1525)
Fast datapath weave status
2015-10-14 11:08:09 +01:00
Adam Harrison ed4ed5fdcd Include short peer ID in report JSON 2015-10-13 17:04:56 +01:00
Adam Harrison eaf1f7cc13 Include overlay diagnostics in report JSON 2015-10-13 17:04:56 +01:00
Adam Harrison ddfc8c2fa4 Handle JSON report marshalling error 2015-10-13 17:04:56 +01:00
Adam Harrison 91afa7f7f4 Include selected overlay type in connection status 2015-10-13 17:04:56 +01:00
Paul Bellamy da49e71eb8 Merge pull request #1529 from weaveworks/expose-proxy-tls
Expose some tls functions from the proxy for use elsewhere.
2015-10-13 16:09:27 +01:00
Tom Wilkie 5b8ce8ebae Expose some tls functions from the proxy for use elsewhere. 2015-10-13 14:56:11 +00:00
Adam Harrison 827bcd898e Merge pull request #1438 from weaveworks/fdp
Fast datapath
2015-10-13 10:34:49 +01:00
Matthias Radestock 54a4add40f cosmetic 2015-10-13 09:02:03 +01:00
David Wragg 0093eb1220 A smoke test for WEAVE_NO_FASTDP
Partly for the side effect of restoring coverage of
router/ethernet_decoder.go
2015-10-12 21:27:11 +01:00
David Wragg b56ff7678c Make code coverage work through the proxy
Previously, the proxy would run the weave script in ways that did
not run the weaver executable.  Now they can.  This is a problem because
of the contortions involved in using golang's code coverage support
in the integration tests, which require the weaver command line args to
be slightly mangled.
2015-10-12 21:27:11 +01:00
David Wragg 431197042d Weave script changes to enable fast datapath
This means:

* The 'weave' netdev is an OVS datapath

* The weave router container runs with --net=host in order to access
  the datapath.

* The MTU is set to a smaller value, to allow vxlan encapsulation.

Setting the WEAVE_NO_FASTDP environment variable disables these
things, effectively running the router without fast datapath.  It is
an environment variable rather than an option to 'weave launch'
because it takes effect in create_bridge, which is called from many
subcommands, and it doesn't seem practical to add option processing to
all of them.
2015-10-12 21:27:11 +01:00
David Wragg 295876e1d1 Fast datapath 2015-10-12 21:27:11 +01:00
David Wragg e8f975e44f Introduce checkFatal in prog/weaver/main.go 2015-10-12 21:27:11 +01:00
David Wragg bc026ab49a Show the overlay name in sleeve log messsages 2015-10-12 21:27:11 +01:00
David Wragg f56457271d OverlaySwitch 2015-10-12 21:27:11 +01:00
David Wragg bc5250c899 Replace OverlayForwarderListener with channels
It's more robust to signal the conditions asynchronously via channels
rather than callbacks.
2015-10-12 21:27:11 +01:00
David Wragg 971511e1c5 Introduce PeerShortIDs
These are needed to support vxlan encapsulation.  In a vxlan packet,
the only place we can straightforwardly hold the source and
destination peer identifiers used by the weave router is in the
24-bit VNI (vxlan network identifier) field.  This gives us 12 bits to
indicate a peer. The resulting limit of 4096 peers on a weave network
does not seem like a problem in the near future.  But it does mean
that decentralized allocation makes collisions fairly likely even in
weave networks of sizes that are common today.  Fortunately, the
problem is much more tractable than IPAM because we can detect when
collisions occur and take action to resolve them.
2015-10-12 21:27:11 +01:00
David Wragg 51317dca70 Introduce PeersPendingNotifications 2015-10-12 21:27:11 +01:00
David Wragg 91dda08165 Rename Peers "table" field to "byName" 2015-10-12 21:27:11 +01:00
David Wragg 67bfa5bc90 applyUpdate can return a PeerNameSet 2015-10-12 21:27:11 +01:00
David Wragg 0841f4a27d Tidy broadcastPeerUpdate 2015-10-12 21:27:11 +01:00
David Wragg c6edf8421f Handle the not-found case implicitly in Broadcast and BroadcastAll 2015-10-12 21:27:11 +01:00
David Wragg 97fb90c37a Always include local peer in Peers 2015-10-12 21:27:11 +01:00
Matthias Radestock 1d00bdd2a4 a better way to restrict coverage
this better fixes #1524.
2015-10-12 18:04:07 +01:00
Matthias Radestock 0ff01871f3 Merge remote-tracking branch 'upstream/master' 2015-10-12 17:43:41 +01:00
Matthias Radestock e00a675e6e update tools to latest version 2015-10-12 17:43:06 +01:00
Paul Bellamy d5e63b875b Merge pull request #1395 from weaveworks/1371-proxy-no-dockerclient
[proxy] all request modification is done through map[string]interface{}
2015-10-12 16:42:44 +01:00
Matthias Radestock a31ccef99b only produce coverage for packages under github.com/weaveworks/weave
Fixes #1524.
2015-10-12 16:06:26 +01:00
Paul Bellamy 375fd9140b review feedback
* Opted to remove json path traversal, as it added more code than it saved.
2015-10-12 15:53:07 +01:00
Paul Bellamy fbb2c213a9 [proxy] all request modification is done through map[string]interface{}
That way we are less likely fall victim to go-dockerclient, or the API changing.
2015-10-12 15:53:07 +01:00
Bryan Boreham d323de3c21 Merge pull request #1520 from weaveworks/1519-rebind-status-sock
LGTM. Fixes #1519
2015-10-09 17:58:33 +01:00
Paul Bellamy 18872cc8ae [proxy] remove status.sock before binding status.sock 2015-10-09 17:44:16 +01:00
Tom Wilkie 33085e3798 Merge pull request #1499 from weaveworks/1463-ipam-seed-mismatch
detect when IPAM has been seeded by different peers
2015-10-09 14:46:55 +01:00
Matthias Radestock e42c811797 docs 2015-10-08 22:43:47 +01:00
Matthias Radestock 6b321e4d02 associate non-container dns entries with 'weave:extern'
...instead of the weave container. This is cleaner.

'weave:extern' because typically these entries will refer to external
(non-weave) IP addresses. And because it's the same length as
'weave:expose', which is desirable for 'weave status dns' output.
2015-10-08 22:21:38 +01:00
Matthias Radestock e6b17c701d dns-{add,remove} can add/remove non-container DNS records
...by omitting the container name/id

Fixes #1385.
2015-10-08 17:37:35 +01:00
Bryan Boreham 60bda7ca25 Merge pull request #1512 from weaveworks/1459-ipam-early-container-death
LGTM. Fixes #1459
2015-10-07 16:01:45 +01:00
Adam Harrison b37d2ec6d1 Merge pull request #1494 from weaveworks/1468-template-on-demand
Build GCE template on demand
2015-10-07 15:42:39 +01:00
Matthias Radestock 751ccd02f6 don't stall in ipam on early container death
When a container died before we got into the allocation code (inside
the allocator run loop) we would not cancel the allocation since the
container death event will have been processed by the allocator before
it had any knowledge of the container.

The fix is to move the container-aliveness check inside the allocation
code. That way we still detect the death even if the event has already
flown past.

We only need to perform the check once, since any subsequent death
will result in an event that is processed by the allocator when it has
recorded the container in its state.

Fixes #1459.
2015-10-07 15:20:22 +01:00
Matthias Radestock 431d04bd40 refactor: move all dataplane code out of LocalPeer 2015-10-06 19:50:41 +01:00
Matthias Radestock e756f07cad Merge branch '1.1' 2015-10-06 16:25:45 +01:00
Matthias Radestock 0ef548b9c9 prevent dropping of large multicast packets
Fixes #1507.
2015-10-06 16:25:02 +01:00
Ilya Dmitrichenko b9e025b50e Merge branch '1.1' 2015-10-06 15:28:54 +01:00
Ilya Dmitrichenko 7743f0e05f Merge branch 'latest_release_doc_updates' into 1.1 2015-10-06 15:26:50 +01:00
Ilya Dmitrichenko a1581ed5b5 Remove stale/invalid flag 2015-10-06 15:26:14 +01:00
Ilya Dmitrichenko 7a83a71a02 Merge branch '1.1' 2015-10-06 15:24:25 +01:00
Ilya Dmitrichenko a6c30088ab Merge branch 'latest_release_doc_updates' into 1.1 2015-10-06 15:24:08 +01:00
Ilya Dmitrichenko fdbe950483 Fix #1509 2015-10-06 15:21:24 +01:00
Matthias Radestock 2f86326405 Merge tag 'v1.1.1' into latest_release_doc_updates
Release v1.1.1
2015-10-06 15:13:50 +01:00
Matthias Radestock 8aaf4cfaf0 include nicknames in error message, if we have them 2015-10-06 14:41:41 +01:00
Ilya Dmitrichenko 8a62d13cee Merge pull request #1500 from weaveworks/1443-dont-assume-docker
allow router exe to be run w/o access to Docker
2015-10-06 14:28:26 +01:00
Matthias Radestock 5a45471c68 more readable errors on ring.Merge
There are three significant changes here:

- rewrite ring.ErrDifferentSeeds errors
- remove the pre-checking of ranges and instead rewrite
  ring.ErrDifferentRange errors
- don't pruneNicknames() and signal ringUpdated() when there was an
  error

We don't rewrite other errors since our current belief is that only
ErrDifferentSeeds and ErrDifferentRange can arise from user error.
2015-10-06 14:20:42 +01:00
Matthias Radestock 6dc21bf433 Merge pull request #1503 from weaveworks/860-detach-hide-no-ipalloc
don't allocate IPs on 'detach' and 'hide'

Fixes #860.
2015-10-06 12:53:51 +01:00
Matthias Radestock 1da24c68ca don't allocate IPs on 'detach' and 'hide'
Instead of POSTing to IPAM in order to obtain IPs, we GET existing
IPs. We ignore 404s, so detaching/hiding remains idempotent.

Fixes #860
2015-10-06 12:35:55 +01:00
Matthias Radestock 3d3b7a5672 Merge branch '1.1'
Conflicts:
	router/ethernet_decoder.go
	router/local_peer.go
2015-10-05 20:02:32 +01:00
Matthias Radestock 6c4ea7a957 unswap src and dst ip in "too big DF broadcast" warning
and some whitespace cosmetics.

Fixes #1508.
2015-10-05 19:56:59 +01:00
Matthias Radestock f221a6d953 Merge branch '1.1' 2015-10-05 12:08:54 +01:00
David Wragg bcf71c0fee Merge branch '1.1' 2015-10-04 20:25:31 +01:00
Matthias Radestock a689317696 make 'no Docker API access' the default in the exe
...and instead enable it in the script, via the (better named) option.
2015-10-03 17:52:57 +01:00
Matthias Radestock 14a5c12404 allow router to be run w/o access to Docker
Fixes #1443.
2015-10-03 17:52:57 +01:00
Matthias Radestock 35b2968b60 Merge branch '1.1' 2015-10-03 17:20:21 +01:00
Matthias Radestock 7e0ae6112c detect when IPAM has been seeded by different peers
This is an early detector for a multitude of other symptoms, some of
which cause panics.

When creating the ring we record the paxos-supplied normalised list of
peers used for seeding. This subsequently gets gossipped around as
part of the Ring data structure, and checked in ring.Merge that it is
the same for the recipient.

There are some subtleties due to the desire to maintain backward
compatibility and deal with empty rings

- The gossip gob decoder will ignore the Ring.Seeds field when an old
  weave receives a Ring from a new weave. So everything on the old
  weave will just continue to work as before.

- The gossip gob decoder will set the Ring.Seeds field to nil when a
  new weave receives a Ring from an old weave. We therefore omit the
  seed equivalence check when the received Seeds slice is empty.

- Our ring may not have any seeds - either because it is empty or was
  created from a ring received from an old weave. We therefore a) omit
  the seed equivalence check in that situation, and b) set our seeds
  to those received. Note that this allows us to learn about the seeds
  well after ring creation - a situation which may arise when
  receiving gossip from a mixture of old and new weaves.

Fixes #1463.
2015-10-02 16:33:28 +01:00
Bryan Boreham 9ad1ccf19f Don't listen to SIGUSR1, since we stopped handling it a few months back 2015-10-02 13:52:23 +01:00
Bryan Boreham 5b7599e1b0 Merge pull request #1496 from weaveworks/953-backoff-late-connection-failures
LGTM.  The timing choices are all very arbitrary, so we should wait for more experience in the wild before trying to tune them further.

Fixes #953
2015-10-02 12:44:52 +01:00
Matthias Radestock 19c44b83d2 refactor: better 'try' method names 2015-10-02 11:31:11 +01:00
Matthias Radestock 41691957d2 refactor: simplify target status reporting 2015-10-01 20:05:12 +01:00
Matthias Radestock e3182624fd refactor: more obviously handle all target states in reporting 2015-10-01 20:05:12 +01:00
Matthias Radestock b8b3e6f9cd refactor: better representation of 3-valued target state 2015-10-01 20:05:12 +01:00
Matthias Radestock 520c7286c4 backoff retries for late-failing connections
If a connection gets through all the handshake and subsequent checks -
i.e. it ends up getting added to the LocalPeer's connection list - but
gets terminated within one minute (of the start of the successful
connection attempt), then we apply the existing backoff logic to
schedule a retry, instead of retrying immediately.

In order to do this we retain the 'Target' data structure for such
connections, marking it as 'connected' instead of deleting it (as we
did previously).

Fixes #953.
2015-10-01 20:05:12 +01:00
Matthias Radestock d547829c5f Merge pull request #1495 from weaveworks/1492-proxy-remove-var-run-mount
Remove /var/run mount from exec_remote. Proxy uses /var/run/weave/weave.sock

Fixes #1492.
2015-10-01 16:58:37 +01:00
Matthias Radestock 49459cfc9b Merge pull request #1462 from weaveworks/1461-dns-case-sensitive
DNS lookups should not be case sensitive.

Fixes #1461.
2015-10-01 16:52:00 +01:00
Paul Bellamy 827ba446ab Remove /var/run mount from exec_remote. Proxy uses /var/run/weave/weave.sock
It was left in for backwards compatibility, in 1.1.
2015-10-01 15:52:29 +01:00
Tom Wilkie e5316221bb Review feedback 2015-10-01 14:14:15 +00:00
Tom Wilkie 357174e351 DNS lookups should not be case sensitive.
- On-the-wire gossip messages are sorted case sensitive, but in memory they are sorted case-insensitive.
- Add test for case insensitive match on multiple names.
2015-10-01 12:19:49 +00:00
Tom Wilkie 5fc2f3236e Build GCE template on demand 2015-10-01 11:49:31 +00:00
Matthias Radestock a3484cce43 Merge pull request #1488 from weaveworks/1481-proxy-null-hostconfig
[proxy] Handle null HostConfigs gracefully in start

Fixes #1481.
2015-10-01 12:06:51 +01:00
Matthias Radestock cdd107b06b Merge branch '1.1'
Conflicts:
	weave
2015-10-01 12:04:44 +01:00
Bryan Boreham 6619b584bc Merge pull request #1483 from weaveworks/1478-cm-termination-race
LGTM.  Fixes #1478
2015-10-01 12:01:55 +01:00
Alfonso Acosta c24dc6609d [proxy] Handle null HostConfigs gracefully in start 2015-10-01 11:40:50 +01:00
Adam Harrison b5e7e9fc5c Merge pull request #1451 from dpw/sleeve-compat
Restore sleeve compatibility with 1.1
2015-10-01 11:24:31 +01:00
Tom Wilkie 58dec7334b Merge branch '1.1' 2015-10-01 08:23:15 +00:00
Matthias Radestock 725f99ea4c Merge branch '1.1' 2015-10-01 08:23:52 +01:00
Matthias Radestock 4b43cac093 Merge branch '1.1' 2015-09-30 23:34:57 +01:00
Matthias Radestock 0159e26631 store connections as a set instead of map-of-remote-addresses
...since it is theoretically possible that the lifecycle events from
two consecutive inbound connections from the same remote address
overlap.
2015-09-30 17:29:13 +01:00
Tom Wilkie 19cb1ae8e1 Merge branch '1.1' 2015-09-30 15:06:26 +00:00
David Wragg f8e06f9dbc Merge 1286-tool (PR #1470): Use test script from build-tools.git 2015-09-30 14:15:20 +01:00
Matthias Radestock e68ca761a9 we no longer need to Refresh() in CM status
since CM now gets an event - ConnectionCreated - for new connections.
2015-09-30 14:00:32 +01:00
Matthias Radestock fc6f3993bd refactor(ish): obtain local connection status from CM alone 2015-09-30 14:00:32 +01:00
Matthias Radestock f8b074f696 a better way to clean up after established connections
...than 3db4e86
2015-09-30 14:00:32 +01:00
Matthias Radestock 66f14099ed prevent ConnectionMaker.ConnectionTerminated race
Get the ConnectionMaker to maintain its own knowledge of the local
connections, based on events emitted in the connection lifecycle.

Fixes #1478.
2015-09-30 14:00:32 +01:00
Tom Wilkie f3dc681f61 Only clean weave packages. 2015-09-30 10:15:50 +00:00
Tom Wilkie 62f62e1fd5 tools.git is now called build-tools.git 2015-09-30 08:21:19 +00:00
Tom Wilkie f3360e61e1 Remove cover tool, this lives in tools.git now. 2015-09-30 08:21:12 +00:00
Tom Wilkie 5208dcd67e Review feedback
- Use git submodule for tools.
- Update Makefile to use submodules.
- Put submodule at tools/
- Tidy up some obsolete references to tools in .gitignore.
2015-09-30 08:21:08 +00:00
Tom Wilkie 6f7d4a7a01 Use lint from tools.git
- Do lint first, on circle.
2015-09-30 08:20:56 +00:00
Tom Wilkie 958a3bbd99 Use test script from tools.git 2015-09-30 07:42:43 +00:00
Matthias Radestock d5c85a3d53 cosmetic: fix typo 2015-09-29 19:40:54 +01:00
Tom Wilkie 5e7b527af2 Make publish from the right place. 2015-09-29 11:02:46 +00:00
Paul Bellamy ddbd33a8e3 Merge pull request #1471 from weaveworks/1295-publish
Publish latest images from CI on successful builds.
2015-09-29 11:37:44 +01:00
Matthias Radestock a7dc368191 Merge pull request #1469 from weaveworks/1204-test-container
Run tests in a separate container.

Fixes #1204.
2015-09-29 11:37:25 +01:00
Matthias Radestock 5b1d0c55a0 Merge pull request #1474 from weaveworks/1472-weave-env-debug
use a container-local unix socket to get the address, instead of logs

Fixes #1453. Fixes #1472.
2015-09-29 11:04:41 +01:00
Matthias Radestock 58515ca36e make weave(proxy) startup death message more generic
...in order to account for `docker logs` not working due to custom
logging drivers.

Partially undoes #958. Partially fixes #1453.
2015-09-29 11:02:05 +01:00
Matthias Radestock 2d753ebf1a cosmetic: reduce confusion w.r.t. meaning of 'connection' 2015-09-29 09:21:39 +01:00
Tom Wilkie 50cd716220 Review feedback. 2015-09-29 07:46:00 +00:00
Matthias Radestock 47c97e8191 refactor: remove some code duplication 2015-09-28 16:10:43 +01:00
Paul Bellamy d704ba175f [proxy] use a container-local unix socket to get the address, instead of logs 2015-09-28 16:08:25 +01:00
Matthias Radestock c1036c5e4f Merge remote-tracking branch 'upstream/master' 2015-09-28 14:53:12 +01:00
Matthias Radestock 356d06915b refactor weave script http calls
- inline http_call into call_weave
- rename http_call_ip to http_call
- make http_call take the IP & port as one single "address" argument
  instead of separate args
2015-09-28 12:43:09 +01:00
Tom Wilkie 2b4fd738da Review feedback 2015-09-28 09:05:10 +00:00
Tom Wilkie f9de6df426 Publish latest images from CI on successful builds.
This time rebuild without insturmentation first.
2015-09-28 08:27:21 +00:00
Tom Wilkie 8ee409d9bd Run tests in a separate container. 2015-09-25 06:19:00 +00:00
Matthias Radestock 0cf3de4541 Merge pull request #1446 from weaveworks/smoke-subdomains
Add smoke-test for subdomains
2015-09-24 15:55:57 +01:00
Bryan Boreham 3a79be7426 Add smoke-test for subdomains 2015-09-24 14:48:39 +01:00
David Wragg 30589bb580 Mention the mailing list 2015-09-24 14:40:20 +01:00
Matthias Radestock e07b18bb18 Merge branch '1.1' 2015-09-24 12:08:13 +01:00
Tom Wilkie b31eb3db82 Merge remote-tracking branch 'upstream/1.1' 2015-09-24 03:54:09 +00:00
Bryan Boreham da2f98fa8f Remove workaround for upstream, no longer required 2015-09-23 10:15:06 +01:00
Tom Wilkie 7ac532a1d0 Merge pull request #1454 from weaveworks/1433-docker-1.8
New test image using docker 1.8
2015-09-23 10:16:06 +08:00
Matthias Radestock b98ab70517 Merge branch '1.1' 2015-09-22 21:18:32 +01:00
Matthias Radestock 0bdc84d42a Merge branch '1.1' 2015-09-22 16:28:05 +01:00
Matthias Radestock 7d7c5ce63e Merge pull request #1449 from weaveworks/1368-start-hostconfig
Override sneaky /start HostConfig params

Fixes #1368.
2015-09-22 15:11:36 +01:00
Paul Bellamy 547c9e7f5a Removing confusing variable from getDNSDomain, just return "" when disabled 2015-09-22 15:09:32 +01:00
Paul Bellamy 3e4d8a3ffe review feedback 2015-09-22 15:09:32 +01:00
Paul Bellamy 74c3b0d079 [proxy] Override sneaky /start HostConfig params 2015-09-22 15:09:32 +01:00
Paul Bellamy 846aa98a9c renaming test so it runs 2015-09-22 14:52:28 +01:00
Matthias Radestock 17b7789531 Merge pull request #1456 from weaveworks/fix-race-condition
[proxy] fix race condition when waiting for containers to start
2015-09-22 14:51:43 +01:00
Paul Bellamy b86d9bf1ab [proxy] fix race condition when waiting for containers to start
If ContainerStarted fired before the response handler, then the "waiter"
chan would be nil, and the response handler would hang.
2015-09-22 12:50:36 +01:00
Tom Wilkie 6e06ce2af1 New test image using docker 1.8 2015-09-22 05:31:26 +00:00
David Wragg dc275d7009 Restore sleeve compatibility with 1.1
Addresses #1396
2015-09-21 08:33:11 +01:00
David Wragg a67b9f566a Use consistent options for all "go get"/"go build" invocations
Without this, building with go-1.5 fails with

    go get ./prog/weavewait
    go install net: open /usr/local/go/pkg/linux_amd64/net.a: permission denied

This makes the options passed too "go get"/"go build" consistent for
all executables. So it should be safe with go-1.4.
2015-09-20 19:58:04 +01:00
Michael Bridgen 9344289262 Merge pull request #1445 from weaveworks/docs-ecs-amis
Document ECS integration
2015-09-17 14:43:38 +01:00
Alfonso Acosta 6713282a43 Link to ECS AMIs from README 2015-09-17 13:27:32 +00:00
Matthias Radestock f834626f00 Merge pull request #1414 from weaveworks/1285-autoconfig-boot2docker-tls
Proxy TLS autoconfiguration

Closes #1285.
2015-09-16 15:49:20 +01:00
Paul Bellamy 0a475bd4cc docker_tls_args should look for '-d' or 'daemon' to handle docker 1.8 change 2015-09-16 13:25:29 +01:00
Paul Bellamy 0820c484d6 [proxy] Documenting TLS autoconfig 2015-09-16 10:44:18 +01:00
Paul Bellamy dbf39403d3 [proxy] Autoconfigure TLS based on daemon settings
With this on OSX you can:
```sh
$ boot2docker init
$ boot2docker up
$ eval $(boot2docker shellinit)
$ weave launch
$ eval $(weave env)
```
2015-09-16 10:44:17 +01:00
Matthias Radestock 14dde9ec32 Merge branch '1.1' 2015-09-15 16:47:05 +01:00
Matthias Radestock 3c266dce8c Merge branch '1.1' 2015-09-15 15:51:36 +01:00
Matthias Radestock f7be34f5bb Merge branch '1.1' 2015-09-15 15:44:53 +01:00
Matthias Radestock 64b47ca3c7 Merge branch '1.1' 2015-09-15 13:17:06 +01:00
Paul Bellamy e39974eb07 Merge pull request #1210 from weaveworks/401-proxy-start-events
make proxy attach network on docker start event
2015-09-15 10:18:48 +01:00
Bryan Boreham 76895c76a1 Use Mutex instead of RWMutex, for simplicity 2015-09-14 17:11:56 +01:00
Bryan Boreham 98e8ef4e11 Merge remote-tracking branch 'upstream/1.1' 2015-09-14 16:33:12 +01:00
Adam Harrison 2ffe9b53f7 Merge branch '1.1' 2015-09-14 14:21:36 +01:00
Paul Bellamy 405c2889f0 Merge pull request #1415 from weaveworks/1341_report_snapshot_versions
correctly report snapshot versions
2015-09-14 10:45:29 +01:00
Matthias Radestock 40cf392504 cosmetic: vertical alignment in version output 2015-09-11 15:56:17 +01:00
Matthias Radestock 42baa03985 report the proxy version instead of weaveexec version
That way we no longer rely on the weavexec image version tag, which
may not always be present. Since the proxy is embedded in the
weaveexec image, this effectively versions the image.

Also report the *local* script version instead of the embedded script
version. There is no point in reporting the latter since it will
always match the weaveexec version.

Closes #1341.
2015-09-11 15:52:50 +01:00
Matthias Radestock 016f20b2d4 Merge branch '1.1' 2015-09-11 14:07:59 +01:00
Matthias Radestock d5faf31d43 Merge remote-tracking branch 'upstream/master' 2015-09-11 13:36:59 +01:00
David Wragg 12324e3550 Revert "Make weave build under go1.5; use our build flags for go get, and force stuff to be static."
This reverts commit 5860492a38.

Although the build works under 1.4, this change had an unfortunate
impact (see #1412).
2015-09-11 13:31:15 +01:00
Matthias Radestock ff67499562 cosmetic 2015-09-11 13:30:22 +01:00
Matthias Radestock 2ba5abc2bb rename 'VERSION' to 'WEAVE_VERSION'
to avoid potential accidental clashes with user's environment.

Closes #1389.
2015-09-11 11:02:51 +01:00
Adam Harrison 3d4a8aaad1 Merge branch '1.1' 2015-09-10 17:24:26 +01:00
Matthias Radestock 4ded1f6193 Merge branch '1.1' 2015-09-09 20:06:34 +01:00
Matthias Radestock 940a0a67a5 Merge branch '1.1' 2015-09-09 19:54:46 +01:00
Adam Harrison be304365a0 Merge branch '1.1' 2015-09-09 14:26:34 +01:00
Adam Harrison fc00767cf8 Remove reference to 1.0 from README 2015-09-09 13:56:25 +01:00
David Wragg 2163b39268 Merge weaveworks/go-1.5 (PR #1386)
Make Weave build & work with Go 1.5
2015-09-09 12:23:57 +01:00
Matthias Radestock aa61cbd574 Merge pull request #1388 from weaveworks/alpine
gliderlabs/alpine is now just alpine
2015-09-08 14:01:40 +01:00
Adam Harrison 2a89d53d7d Merge pull request #991 from dpw/fdp-prep
Fast datapath preparations
2015-09-08 13:19:03 +01:00
David Wragg f7ce6a1fdb Injected packets shouldn't happen in handleCapturedPacket
Check for them just in case, but don't take an extra lock to do it.
2015-09-08 10:46:04 +01:00
David Wragg 20b35f3139 Add NewPeerFromSummary
To reduce the number of places that are explicit about which values
go into a new Peer.
2015-09-08 10:46:02 +01:00
David Wragg 46d454730f Notify the Overlay when routes change 2015-09-08 10:44:57 +01:00
David Wragg c2ad4c5633 Flows
For fast datapath, the router core will only be invoked upon datapath
misses.  And it needs to yield routing decisions in a form that can be
used to configure the in-kernel datapath.  So here we make the router
core yield FlowOps, which represent operations which actually do
something with a packet.  The router core obtains these in turn from
the Bridge or the Overlay, depending on what it wants to do with
a packet.

For fast datapath, it's critical that the router makes decisions based
only on the required packet header information (as the same header
information will be used when configuring the ODP flow rules).  So we
pass only that information into the router core, in the form of
PacketKeys.

The overall effect is to curry the router core: Rather than taking a
packet and doing something with it, it takes packet header information
and returns a function that does something with a packet.
2015-09-08 10:44:52 +01:00
David Wragg 6e0795253e Move ICMP frag-needed handling into sleeveForwarder 2015-09-08 10:44:52 +01:00
David Wragg 9956d422f2 Introduce Overlay / sleeve
This introduces interfaces to represent the packet forwarding
mechanism, and brings all the scattered UDP-related code together into
a single implementation.

This is not a pure refactoring, although much of the code in sleeve.go
has been copied from pre-existing code, and the UDP protocol remains
the same in spirit.  The biggest change is the organization of the
forwarder goroutines (there is now one instead of two, and that
goroutine does a lot of auxiliary work as well as forwarding).
Performance testing between VMs on my laptop suggests that packet
throughput has not gone down, and may even have improved slightly.
2015-09-08 10:44:44 +01:00
David Wragg 57e78e2bec Introduce Bridge 2015-09-08 10:44:37 +01:00
David Wragg 5aa58e6043 go 1.5 broke PMTU detection 2015-09-07 17:31:18 +01:00
Tom Wilkie 5860492a38 Make weave build under go1.5; use our build flags for go get, and force stuff to be static. 2015-09-07 14:45:17 +00:00
Paul Bellamy e74b6cbcaf gliderlabs/alpine is now just alpine 2015-09-04 14:33:18 +01:00
Bryan Boreham 1e2344341d Wait for container to be attached before returning from {re}start request 2015-09-01 17:48:22 +01:00
Bryan Boreham b712ba1e04 Extend test to cover containers restarting automatically 2015-09-01 17:48:22 +01:00
Bryan Boreham 9ae6435317 Attach existing weave containers at startup 2015-09-01 17:48:22 +01:00
Bryan Boreham 81e8ba223f Do network attach on docker event rather than intercepting start and restart commands.
This means it also works when the docker daemon restarts a container.
2015-09-01 17:48:12 +01:00
171 changed files with 10845 additions and 4977 deletions

No files matched your search

+10 -3
View File
@@ -39,24 +39,31 @@ prog/weavehosts/weavehosts
prog/weaveproxy/weaveproxy
prog/sigproxy/sigproxy
prog/weavewait/weavewait
prog/weavewait/weavewait_noop
prog/weavewait/weavewait_nomcast
prog/netcheck/netcheck
prog/docker_tls_args/docker_tls_args
prog/plugin/plugin
testing/cover/cover
testing/runner/runner
tools/bin
tools/build
releases
.weaver.uptodate
.weavedns.uptodate
.weaveexec.uptodate
weave.tar
.dockerplugin.uptodate
weave.tar.gz
prog/weaveexec/weave
prog/weaveexec/weavehosts
prog/weaveexec/weaveproxy
prog/weaveexec/weavewait
prog/weaveexec/weavewait_noop
prog/weaveexec/weavewait_nomcast
prog/weaveexec/sigproxy
prog/weaveexec/netcheck
prog/weaveexec/docker_tls_args
prog/weaveexec/docker*.tgz
Vagrantfile.local
test/tls/tls
test/tls/*.pem
test/coverage
test/coverage.*
+3
View File
@@ -0,0 +1,3 @@
[submodule "tools"]
path = tools
url = https://github.com/weaveworks/build-tools
-1
View File
@@ -21,7 +21,6 @@ install:
- go get ./...
script:
- bin/lint .
- make tests
after_success:
+89
View File
@@ -1,3 +1,92 @@
## Release 1.4.1
This is a bug-fix release to cover a few issues that came up since the
release of 1.4.0.
* Weave would fail to launch when `$DOCKER_HOST` was set to a TCP socket secured with TLS #1820/#1822
* Weave would fail to stop when run against Docker pre-1.9 #1815/#1817
* Issue a warning instead of failing on `--with-dns` option to proxy, which was removed #1810/#1812
* Make `weave version` show the plugin version #1797/#1813
* Make `weave launch` show when the a container is restarting #1778/#1814
* Make `weave launch` fail if the plugin is running, for consistency with router and proxy. #1818/#1819
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.4.1).
## Release 1.4.0
**Highlights**
* The Docker Network plugin can now operate without a cluster store, so it is now run by default.
* You can now use the fast datapath over trusted links and Weave encryption over untrusted links, in the same network.
Plus many bug fixes and minor enhancements. More details below and in
the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.4.0).
## Release 1.3.1
**Highlights**
* The minimum Docker version has been increased to 1.6 due to the
[upcoming deprecation of Dockerhub access](https://blog.docker.com/2015/10/docker-hub-deprecation-1-5/)
for old clients. From December 7th onwards previous versions of the
`weave` script will fail to pull down images from the hub; if you
are unable to upgrade to 1.3.1 immediately you can work around this
by running `weave --local setup` in conjunction with a compatible
Docker client installation
* Docker networking plugin now works with older kernels and allows you
to configure the MTU
More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.3.1).
## Release 1.3.0
**Highlights**
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**
This release introduces the
[Fast Data Path](http://docs.weave.works/weave/master/head/features.html#fast-data-path),
which allows Weave networks to operate at near wire-level speeds. This
new feature is enabled by default.
Other highlights:
- auto-configuration of TLS for the Weave Docker API proxy, making it
easier to run Weave on Macs and in conjunction with tools like
Docker Swarm
- support for restart policies on application containers and weave
infrastructure containers
- better compatibility with recent and future Docker versions
More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.2.0).
## Release 1.1.2
This release contains a small number of bug fixes. More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.1.2).
The release is fully compatible with 1.1.0 versions, so existing
clusters can be upgraded incrementally. When upgrading from 1.0.x,
note the compatibility information in the
[Installation & Upgrading instructions for Weave 1.1.0](https://github.com/weaveworks/weave/releases/tag/v1.1.0).
## Release 1.1.1
This release contains a number of bug fixes and minor
+68 -33
View File
@@ -1,7 +1,7 @@
PUBLISH=publish_weave publish_weaveexec
.DEFAULT: all
.PHONY: all update tests publish $(PUBLISH) clean prerequisites build travis run-smoketests
.PHONY: all update tests lint publish $(PUBLISH) clean clean-bin prerequisites build travis run-smoketests
# If you can use docker without being root, you can do "make SUDO="
SUDO=sudo
@@ -13,26 +13,31 @@ WEAVER_EXE=prog/weaver/weaver
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
SIGPROXY_EXE=prog/sigproxy/sigproxy
WEAVEWAIT_EXE=prog/weavewait/weavewait
WEAVEHOSTS_EXE=prog/weavehosts/weavehosts
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
NETCHECK_EXE=prog/netcheck/netcheck
COVER_EXE=testing/cover/cover
RUNNER_EXE=testing/runner/runner
DOCKERTLSARGS_EXE=prog/docker_tls_args/docker_tls_args
DOCKERPLUGIN_EXE=prog/plugin/plugin
RUNNER_EXE=tools/runner/runner
TEST_TLS_EXE=test/tls/tls
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE) $(COVER_EXE) $(RUNNER_EXE)
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE)
WEAVER_UPTODATE=.weaver.uptodate
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
DOCKERPLUGIN_UPTODATE=.dockerplugin.uptodate
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE)
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(DOCKERPLUGIN_UPTODATE)
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
DOCKERPLUGIN_IMAGE=$(DOCKERHUB_USER)/plugin
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE)
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(DOCKERPLUGIN_IMAGE)
WEAVE_EXPORT=weave.tar
WEAVE_EXPORT=weave.tar.gz
WEAVEEXEC_DOCKER_VERSION=1.3.1
WEAVEEXEC_DOCKER_VERSION=1.6.2
DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
@@ -45,7 +50,9 @@ NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
}
BUILD_FLAGS=-ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo
all: $(WEAVE_EXPORT) $(COVER_EXE) $(RUNNER_EXE)
PACKAGE_BASE=$(shell go list -e ./)
all: $(WEAVE_EXPORT) $(RUNNER_EXE) $(TEST_TLS_EXE)
travis: $(EXES)
@@ -54,7 +61,7 @@ update:
$(WEAVER_EXE) $(WEAVEPROXY_EXE): common/*.go common/*/*.go net/*.go
ifeq ($(COVERAGE),true)
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "weaveworks") | paste -s -d,))
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "^$(PACKAGE_BASE)/") | paste -s -d,))
go get -t -tags netgo ./$(@D)
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
else
@@ -68,59 +75,87 @@ $(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
# Sigproxy and weavewait need separate rules as they fail the netgo check in
# These next programs need separate rules as they fail the netgo check in
# the main build stanza due to not importing net package
$(SIGPROXY_EXE): prog/sigproxy/main.go
$(WEAVEWAIT_EXE): prog/weavewait/main.go net/*.go
$(WEAVEHOSTS_EXE): prog/weavehosts/weavehosts.go
$(COVER_EXE): testing/cover/cover.go
$(RUNNER_EXE): testing/runner/runner.go
$(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go
$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/net/*.go plugin/ipam/*.go plugin/skel/*.go api/*.go common/docker/*.go
$(TEST_TLS_EXE): test/tls/*.go
$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(WEAVEHOSTS_EXE) $(COVER_EXE) $(RUNNER_EXE):
go get ./$(@D)
go build -o $@ ./$(@D)
$(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE):
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -o $@ ./$(@D)
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -tags "netgo iface mcast" -o $@ ./$(@D)
$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -tags "netgo iface" -o $@ ./$(@D)
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -o $@ ./$(@D)
$(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
$(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVER_IMAGE) prog/weaver
touch $@
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE)
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE)
cp weave prog/weaveexec/weave
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
cp $(WEAVEHOSTS_EXE) prog/weaveexec/weavehosts
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
cp $(NETCHECK_EXE) prog/weaveexec/netcheck
cp $(DOCKERTLSARGS_EXE) prog/weaveexec/docker_tls_args
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
touch $@
$(DOCKERPLUGIN_UPTODATE): prog/plugin/Dockerfile $(DOCKERPLUGIN_EXE)
$(SUDO) docker build -t $(DOCKERPLUGIN_IMAGE) prog/plugin
touch $@
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
$(SUDO) docker save $(addsuffix :latest,$(IMAGES)) > $@
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
$(DOCKER_DISTRIB):
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
tests: $(COVER_EXE)
@test/units.sh
tests: tools/.git
tools/test
lint: tools/.git
tools/lint -nocomment -notestpackage .
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)
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
ifneq ($(UPDATE_LATEST),false)
$(SUDO) docker push $(DOCKERHUB_USER)/$*:latest
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
endif
publish: $(PUBLISH)
clean:
-$(SUDO) docker rmi $(IMAGES)
go clean -r ./...
clean-bin:
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
go clean -r $(addprefix ./,$(dir $(EXES)))
rm -f $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT)
clean: clean-bin
rm -rf test/tls/*.pem test/coverage.* test/coverage
build:
+26 -15
View File
@@ -7,11 +7,9 @@
[Weaveworks](http://weave.works) is the company that develops Weave -
the most productive way for developers to connect, observe and control
Docker containers. To learn about our products, including getting
started tutorials, visit our [website](http://weave.works), read about
the
[1.0 release of Weave](http://blog.weave.works/2015/06/18/weave-1-0-is-out-and-it-is-good/),
visit our [documentation](http://docs.weave.works) or continue to read
about some of the more technical aspects of Weave in this readme.
started tutorials, visit our [website](http://weave.works) and
[documentation](http://docs.weave.works) or continue to read about
some of the more technical aspects of Weave in this readme.
# Weave
@@ -43,19 +41,29 @@ capabilities, so these can continue to be used by containers.
## Installation
Ensure you are running Linux (kernel 3.8 or later) and have Docker
(version 1.3.1 or later) installed. Then install weave with
(version 1.6.0 or later) installed. Then install weave with
sudo curl -L git.io/weave -o /usr/local/bin/weave
sudo chmod a+x /usr/local/bin/weave
For usage on OSX (with Docker Machine) you first need to make sure
that a VM is running and configured, as shown in [the Docker Machine
documentation](https://docs.docker.com/installation/mac/#from-your-shell).
Then you can launch weave directly from the OSX host.
CoreOS users see [here](https://github.com/fintanr/weave-gs/blob/master/coreos-simple/user-data) for an example of installing weave using cloud-config.
Amazon ECS users see
[here](https://github.com/weaveworks/guides/blob/master/aws-ecs/LATESTAMIs.md)
for the latest Weave AMIs and
[here](http://weave.works/guides/service-discovery-with-weave-aws-ecs.html) to get started with Weave on ECS.
Weave respects the environment variable `DOCKER_HOST`, so you can run
it locally to control a weave network on a remote host.
## Quick Start Screencast
<a href="https://youtu.be/BF9YJSPQUcY" alt="Click to watch the screencast" target="_blank">
<a href="https://youtu.be/kihQCCT1ykE" alt="Click to watch the screencast" target="_blank">
<img src="/docs/hello-screencast.png" />
</a>
@@ -99,9 +107,8 @@ The only difference, apart from the name of the application container,
is that we tell our weave that it should peer with the weave on
`$HOST1` (specified as the IP address or hostname, and optional
`:port`, by which `$HOST2` can reach it). NB: if there is a firewall
between `$HOST1` and `$HOST2`, you must open the weave port (6783 by
default; this can be overriden by setting `WEAVE_PORT`) for TCP and
UDP.
between `$HOST1` and `$HOST2`, you must permit traffic to the weave
control port (TCP 6783) and data ports (UDP 6783/6784).
Note that we could instead have told the weave on `$HOST1` to connect to
`$HOST2`, or told both about each other. Order does not matter here;
@@ -148,12 +155,16 @@ to each other.
## Contact Us
Found a bug, want to suggest a feature, or have a question?
[File an issue](https://github.com/weaveworks/weave/issues), or email
help@weave.works. When reporting a bug, please include which version of
weave you are running, as shown by `weave version`.
Found a bug, want to suggest a feature, or have a question? You can
[File an issue](https://github.com/weaveworks/weave/issues). And
there is a [Weave Users Google
Group](https://groups.google.com/a/weave.works/forum/#!forum/weave-users),
which you can post to by email at weave-users@weave.works. Or you can
contact Weaveworks directly at help@weave.works. When reporting a
bug, please include which version of weave you are running, as shown
by `weave version`.
Follow weave on Twitter:
Follow on Twitter:
[@weaveworks](https://twitter.com/weaveworks).
Read the Weave blog:
+18
View File
@@ -0,0 +1,18 @@
package api
import (
"fmt"
"net/url"
)
func (client *Client) RegisterWithDNS(ID string, fqdn string, ip string) error {
data := url.Values{}
data.Add("fqdn", fqdn)
_, err := client.httpVerb("PUT", fmt.Sprintf("/name/%s/%s", ID, ip), data)
return err
}
func (client *Client) DeregisterWithDNS(ID string, ip string) error {
_, err := client.httpVerb("DELETE", fmt.Sprintf("/name/%s/%s", ID, ip), nil)
return err
}
+49
View File
@@ -0,0 +1,49 @@
package api
import (
"fmt"
"net"
)
func (client *Client) ipamOp(ID string, op string) (*net.IPNet, error) {
ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), nil)
if err != nil {
return nil, err
}
return parseIP(ip)
}
// returns an IP for the ID given, allocating a fresh one if necessary
func (client *Client) AllocateIP(ID string) (*net.IPNet, error) {
return client.ipamOp(ID, "POST")
}
// returns an IP for the ID given, or nil if one has not been
// allocated
func (client *Client) LookupIP(ID string) (*net.IPNet, error) {
return client.ipamOp(ID, "GET")
}
// release an IP which is no longer needed
func (client *Client) ReleaseIP(ID string) error {
_, err := client.ipamOp(ID, "DELETE")
return err
}
func (client *Client) DefaultSubnet() (*net.IPNet, error) {
cidr, err := client.httpVerb("GET", fmt.Sprintf("/ipinfo/defaultsubnet"), nil)
if err != nil {
return nil, err
}
_, ipnet, err := net.ParseCIDR(cidr)
return ipnet, err
}
func parseIP(body string) (*net.IPNet, error) {
ip, ipnet, err := net.ParseCIDR(string(body))
if err != nil {
return nil, err
}
ipnet.IP = ip
return ipnet, nil
}
+73
View File
@@ -0,0 +1,73 @@
package api
import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
. "github.com/weaveworks/weave/common"
)
const (
WeaveHttpPort = 6784
)
type Client struct {
baseUrl string
resolve func() (string, error)
}
func (client *Client) httpVerb(verb string, url string, values url.Values) (string, error) {
baseUrl := client.baseUrl
if client.resolve != nil {
addr, err := client.resolve()
if err != nil {
return "", err
}
baseUrl = fmt.Sprintf("http://%s:%d", addr, WeaveHttpPort)
}
url = baseUrl + url
Log.Debugf("weave %s to %s with %v", verb, url, values)
var body io.Reader
if values != nil {
body = strings.NewReader(values.Encode())
}
req, err := http.NewRequest(verb, url, body)
if err != nil {
return "", err
}
if values != nil {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if resp, err := http.DefaultClient.Do(req); err != nil {
return "", err
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent {
return string(body), nil
} else {
return "", errors.New(resp.Status + ": " + string(body))
}
}
}
func NewClient(addr string) *Client {
return &Client{baseUrl: fmt.Sprintf("http://%s:%d", addr, WeaveHttpPort)}
}
func NewClientWithResolver(resolver func() (string, error)) *Client {
return &Client{resolve: resolver}
}
func (client *Client) Connect(remote string) error {
_, err := client.httpVerb("POST", "/connect", url.Values{"peer": {remote}})
return err
}
-86
View File
@@ -1,86 +0,0 @@
#!/bin/bash
# This scipt lints go files for common errors.
#
# Its runs gofmt and go vet, and optionally golint and
# gocyclo, if they are installed.
#
# With no arguments, it lints the current files staged
# for git commit. Or you can pass it explicit filenames
# (or directories) and it will lint them.
#
# To use this script automatically, run:
# ln -s ../../bin/lint .git/hooks/pre-commit
set -eu
function lint_go {
filename="$1"
result=0
if [ -n "$(gofmt -l "${filename}")" ]; then
result=1
echo "${filename}: run gofmt -w ${filename}!"
fi
go tool vet "${filename}" || result=$?
# golint is completely optional. If you don't like it
# don't have it installed.
if type golint >/dev/null 2>&1; then
# golint doesn't set an exit code it seems
lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports')
if [ "$lintoutput" != "" ]; then
result=1
echo "$lintoutput"
fi
fi
# gocyclo is completely optional. If you don't like it
# don't have it installed. Also never blocks a commit,
# it just warns.
if type gocyclo >/dev/null 2>&1; then
gocyclo -over 25 "${filename}" | while read line; do
echo "${filename}": higher than 25 cyclomatic complexity - "${line}"
done
fi
return $result
}
function lint {
filename="$1"
ext="${filename##*\.}"
# Don't lint this script
if [ "${filename}" = "bin/lint" ]; then
return
fi
# Don't lint files that don't exist
if ! [ -a "${filename}" ]; then
return
fi
case "$ext" in
go) lint_go "${filename}"
;;
esac
}
function lint_files {
lint_result=0
while read filename; do
lint "${filename}" || lint_result=1
done
exit $lint_result
}
function list_files {
if [ $# -gt 0 ]; then
find "$@" -type f
else
git diff --cached --name-only
fi
}
list_files "$@" | lint_files
+3
View File
@@ -29,6 +29,7 @@ random_mac() {
case "$1" in
launch)
shift 1
echo $(date -R) launching
for i in $(seq $START $FINISH); do
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch-router --iface '' --name $(random_mac) --nickname weave$i --port $PORT --no-dns "$@"
done
@@ -36,6 +37,7 @@ case "$1" in
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1)))
PREV="${status#true }"
fi
echo $(date -R) connecting
for i in $(seq $START $FINISH); do
if [ -n "$PREV" ] ; then
echo connecting weave$i to $PREV
@@ -44,6 +46,7 @@ case "$1" in
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$i)
PREV="${status#true }"
done
echo $(date -R) done
;;
stop)
for i in $(seq $START $FINISH); do
+1 -1
View File
@@ -3,7 +3,7 @@ FROM ubuntu
RUN apt-get -y update && apt-get -y install --no-install-recommends ca-certificates apt-transport-https
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
RUN echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.3.1 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.6.2 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests
# When doing a build in a container, "apt-get update" happens twice,
# which can be a very significant overhead for incremental builds.
+1 -5
View File
@@ -36,11 +36,7 @@ EOF
echo "weave:*:::::::" >>/etc/shadow
echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
if [ "$1" = "tests" ] ; then
su weave -c "PATH=$PATH make -C $WEAVE_SRC tests"
else
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
fi
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
else
# There are arguments to pass to git-clone
mkdir -p ${WEAVE_SRC%/*}
+26 -14
View File
@@ -12,41 +12,45 @@ machine:
SRCDIR: /home/ubuntu/src/github.com/weaveworks/weave
PATH: $PATH:$HOME/.local/bin:$HOME/google-cloud-sdk/bin/
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
WEAVE_BUILD: $HOME/docker/weave-build.tar
WEAVE_TEST: $HOME/docker/weave-test.tar
dependencies:
cache_directories:
- "~/docker"
override:
- if [[ -e "$WEAVE_BUILD" ]]; then
docker load -i $WEAVE_BUILD;
- if [[ -e "$WEAVE_TEST" ]]; then
docker load -i $WEAVE_TEST;
else
docker build -t weaveworks/weave-build build;
mkdir -p $(dirname "$WEAVE_BUILD");
docker save weaveworks/weave-build >$WEAVE_BUILD;
docker build -t weaveworks/weave-test test/image;
mkdir -p $(dirname "$WEAVE_TEST");
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
test:
override:
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERDIR=test/coverage -e SLOW=true -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX weaveworks/weave-build tests:
- cd $SRCDIR; make lint:
parallel: true
- docker run -v /home/ubuntu:/home/go -e COVERDIR=test/coverage -e SLOW=true -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX weaveworks/weave-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
timeout: 300
- cd $SRCDIR; make clean-bin:
parallel: true
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go weaveworks/weave-build:
parallel: true
post:
- cd $SRCDIR/test; ./gce.sh destroy:
parallel: true
@@ -59,3 +63,11 @@ teardown:
parallel: true
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS):
parallel: true
deployment:
hub:
branch: master
owner: weaveworks
commands:
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
- cd $SRCDIR; make publish
+72 -4
View File
@@ -1,12 +1,18 @@
package docker
import (
"github.com/fsouza/go-dockerclient"
"errors"
"fmt"
"strings"
docker "github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
// An observer for container events
type ContainerObserver interface {
ContainerStarted(ident string)
ContainerDied(ident string)
}
@@ -16,18 +22,52 @@ type Client struct {
// NewClient creates a new Docker client and checks we can talk to Docker
func NewClient(apiPath string) (*Client, error) {
if apiPath != "" && !strings.Contains(apiPath, "://") {
apiPath = "tcp://" + apiPath
}
dc, err := docker.NewClient(apiPath)
if err != nil {
return nil, err
}
client := &Client{dc}
env, err := client.Version()
return client, client.checkWorking()
}
func NewVersionedClient(apiPath string, apiVersionString string) (*Client, error) {
if !strings.Contains(apiPath, "://") {
apiPath = "tcp://" + apiPath
}
dc, err := docker.NewVersionedClient(apiPath, apiVersionString)
if err != nil {
return nil, err
}
Log.Infof("[docker] Using Docker API on %s: %v", apiPath, env)
return client, nil
client := &Client{dc}
return client, client.checkWorking()
}
func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) {
dc, err := docker.NewVersionedClientFromEnv(apiVersionString)
if err != nil {
return nil, err
}
client := &Client{dc}
return client, client.checkWorking()
}
func (c *Client) checkWorking() error {
_, err := c.Version()
return err
}
func (c *Client) Info() string {
if env, err := c.Version(); err != nil {
return fmt.Sprintf("Docker API error: %s", err)
} else {
return fmt.Sprintf("Docker API on %s: %v", c.Endpoint(), env)
}
}
// AddObserver adds an observer for docker events
@@ -41,6 +81,9 @@ func (c *Client) AddObserver(ob ContainerObserver) error {
go func() {
for event := range events {
switch event.Status {
case "start":
id := event.ID
ob.ContainerStarted(id)
case "die":
id := event.ID
ob.ContainerDied(id)
@@ -62,3 +105,28 @@ func (c *Client) IsContainerNotRunning(idStr string) bool {
Log.Errorf("[docker] Could not check container status: %s", err)
return false
}
// This is intended to find an IP address that we can reach the container on;
// if it is on the Docker bridge network then that address; if on the host network
// then localhost
func (c *Client) GetContainerIP(nameOrID string) (string, error) {
Log.Debugf("Getting IP for container %s", nameOrID)
info, err := c.InspectContainer(nameOrID)
if err != nil {
return "", err
}
if info.NetworkSettings.Networks != nil {
Log.Debugln("Networks: ", info.NetworkSettings.Networks)
if bridgeNetwork, ok := info.NetworkSettings.Networks["bridge"]; ok {
return bridgeNetwork.IPAddress, nil
} else if _, ok := info.NetworkSettings.Networks["host"]; ok {
return "127.0.0.1", nil
}
} else if info.HostConfig.NetworkMode == "host" {
return "127.0.0.1", nil
}
if info.NetworkSettings.IPAddress == "" {
return "", errors.New("No IP address found for container " + nameOrID)
}
return info.NetworkSettings.IPAddress, nil
}
+12
View File
@@ -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)
}
}
+93
View File
@@ -0,0 +1,93 @@
package odp
import (
"fmt"
"net"
"syscall"
"github.com/weaveworks/go-odp/odp"
)
// ODP admin functionality
func CreateDatapath(dpname string) (err error, supported bool) {
dpif, err := odp.NewDpif()
if err != nil {
if odp.IsKernelLacksODPError(err) {
return nil, false
}
return err, true
}
defer dpif.Close()
dp, err := dpif.CreateDatapath(dpname)
if err != nil && !odp.IsDatapathNameAlreadyExistsError(err) {
return err, true
}
// Pick an ephemeral port number to use in probing for vxlan
// support.
udpconn, err := net.ListenUDP("udp4", nil)
if err != nil {
return err, true
}
// we leave the UDP socket open, so creating a vxlan vport on
// the same port number should fail. But that's fine: It's
// still sufficient to probe for support.
portno := uint16(udpconn.LocalAddr().(*net.UDPAddr).Port)
vpid, err := dp.CreateVport(odp.NewVxlanVportSpec(
fmt.Sprintf("vxlan-%d", portno), portno))
if nlerr, ok := err.(odp.NetlinkError); ok {
if syscall.Errno(nlerr) == syscall.EAFNOSUPPORT {
dp.Delete()
return fmt.Errorf("kernel does not have Open vSwitch VXLAN support"), false
}
}
if err == nil {
dp.DeleteVport(vpid)
}
udpconn.Close()
return nil, true
}
func DeleteDatapath(dpname string) error {
dpif, err := odp.NewDpif()
if err != nil {
return err
}
defer dpif.Close()
dp, err := dpif.LookupDatapath(dpname)
if err != nil {
if odp.IsNoSuchDatapathError(err) {
return nil
}
return err
}
return dp.Delete()
}
func AddDatapathInterface(dpname string, ifname string) error {
dpif, err := odp.NewDpif()
if err != nil {
return err
}
defer dpif.Close()
dp, err := dpif.LookupDatapath(dpname)
if err != nil {
return err
}
_, err = dp.CreateVport(odp.NewNetdevVportSpec(ifname))
return err
}
+1 -1
View File
@@ -14,7 +14,7 @@ type SignalReceiver interface {
func SignalHandlerLoop(ss ...SignalReceiver) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGTERM)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
buf := make([]byte, 1<<20)
for {
switch <-sigs {
+13 -13
View File
@@ -66,23 +66,23 @@ Router:
simply responds to traffic received from the remote peer via TCP
5. We register this connection with the local peer
6a. If we initiated this connection then we now start sending fast
heartbeats to the remote peer so that the remote peer can determine what
address/port it should use to send UDP back to us. To do this, we
spawn off two further threads which are the forwarder
loops. These receive frames which are to be sent to the remote
peer. One of them sends frames without the DF flag set. To do this
it just sends the packets out of the UDP Listener socket. The
other needs to send frames with the DF flag set and needs its own
socket so that it can do PMTU discovery easily. To do this, it
uses a Raw IP socket (IP has no ports, so there's no collision
issue with the UDP Listener socket) and so it must add UDP headers
itself.
6b. If we did not initiate this connection then the UDP Listener
heartbeats to the remote peer so that the remote peer can
determine what address/port it should use to send UDP back to
us. To do this, we spawn off a "forwarder" thread to send
heartbeats, monitor incoming heartbeats, and some other auxiliary
duties. It also consumes frames to be encapsulated and send via
UDP from two channels, for DF and non-DF cases. In the non-DF
case, it can just send the packets out of the UDP Listener
socket. In the DF case, it needs its own socket so that it can do
PMTU discovery easily. To do this, it uses a Raw IP socket (IP has
no ports, so there's no collision issue with the UDP Listener
socket) and so it must add UDP headers itself.
6b. If we did not initiate this connection then the UDP Listener
should start receiving fast heartbeats from the remote peer. From
those it should be able to identify the local connection via the
local peer. It will tell the local connection (communicating to
the actor thread) about the UDP address of the remote peer. The
local connection will then start its forwarder threads as
local connection will then start its forwarder thread as
described in 6a, and start sending fast heartbeats. We send to the
remote peer via TCP a ConnectionEstablished message. The remote
peer receives this (on the TCP receiver process), tells the
+170
View File
@@ -0,0 +1,170 @@
# Overlay Method Selection
Weave automatically selects the best working overlay implementation
when connecting to a new peer. It does this by initiating both
forwarders in parallel, eventually settling on the most preferred
established forwarder at which point the other forwarder is shut down,
assuming it had not failed already. In the event that the remaining
forwarder fails at a later time (e.g. because of a heartbeat timeout)
the control plane TCP connection will recycle allowing the selection
algorithm to run again. By default fast datapath is preferred over
Sleeve.
# Local Bridging
Unlike the weave bridge netdev used by Sleeve, the OVS datapath has no
inherent bridging capability. Consequently fastdp implements an
ethernet bridge in addition to the vxlan overlay, maintaining its own
forwarding database by learning MAC addresses and dispatching
broadcast, unicast & multicast traffic accordingly.
# Short Peer IDs
The router relies on having access to the source and destination peer
when deciding how to forward packets. When the Sleeve overlay is in
use this information is conveyed directly within the encapsulation by
including the names of the peers in question, a solution not
accomodated directly by the vxlan wire format. Fortunately vxlan does
have a twenty four bit segment ID field in the header we can use to
encode this data - the challenge is to identify peers uniquely with a
twelve bit identifier instead of the seventeen bytes used by Sleeve.
In practice this is achieved by peers adopting a random 'short ID' and
resolving ownership collisions of same via the existing gossip
mechanism.
# Heartbeats via vxlan
Heartbeats are implemented by sending an encapsulated ethernet frame
with source and destination MAC addresses of `00:00:00:00:00:00` and a
payload consisting of the connection UID and total ethernet frame
length. The vxlan vport miss handler on the receiving side detects the
all-zero addresses and acknowledges the heartbeat via the TCP control
channel after validating the connection UID and frame length against
their expected values.
# PMTU Discovery
The length of the heartbeat ethernet frame is set to the MTU of the
datapath interface. In the event the vxlan packet is dropped or
truncated, the heartbeat will not be acknowledged; this lack of
acknowledgement will cause the peers to fall back to the Sleeve
overlay, which has a more sophisticated dynamic mechanism for coping
with low path MTUs.
To avoid triggering this fallback in typical deployments, the datapath
interface is statically configured with an MTU of 1410 bytes allowing
it to work with most underlay network provider MTUs, including GCE at
1460 bytes (the fifty byte difference accomodates the vxlan overhead).
This value can be overriden by setting `WEAVE_MTU` at launch if
necessary.
# Virtual Ports
There are three kinds of virtual port associated with the weave
datapath:
* `internal` - one of these is created automatically, named after the
datapath. It corresponds to the network interface of the same name
that appears on the host when the datapath is created; it is the
ingress/egress port for `weave expose`
* `netdev` - one for each application container, corresponding to the
host end of the veth pair
* `vxlan` - one for each UDP port on which we are listening for vxlan
encapsulated packets. Typically there is only one of these, but
there may be more in a network in which peers do not all use the same
port configuration
# Misses and Flow Creation
As mentioned above, the datapath has no inherent behaviour - any
ingressing packet which does not match a flow is passed to a userspace
miss handler. The miss handler is then responsible for a) instructing
the datapath to take actions for the packet that caused the miss (for
example by copying it to one or more ports) and optionally b)
installing flow rules which will allow the datapath to act on similar
packets in future without invoking the miss handler.
At a high level the fast datapath implementation can be viewed as a
set of miss handlers that determine what actions the OVS datapath
should take based on router topology information, together with some
additional machinery that manages the expiry of resulting flows when
that state changes.
When a miss handler is invoked, it has two pieces of context: a byte
array containing the packet that triggered the miss, and a set of
'flow keys' that have been extracted from the packet by the kernel
module. The following flow keys are of interest to weave:
* `InPort` - the identifier of the ingress virtual port
* `Ethernet` - the source and destination MAC addresses of the
ethernet frame
* `Tunnel` - tunnel identifier (see section on short peer IDs above),
and source/dest IPv4 addresses. Only present for ingress via a vxlan
vport
Crucially, the router must use this information alone to determine
which actions to take - this allows the specification of a flow which
matches these keys and instructs the kernel to take action
automatically in future without further userspace involvement.
Two actions are of interest:
* `Output` - output the packet to the specified vport
* `SetTunnel` - update the effective vxlan tunnel parameters. Only
required prior to output from a vxlan vport
It is possible to have multiple actions in a flow, so the router can
for example create a single rule that copies broadcast traffic to all
'local' vports (save the ingress vport obviously) as well as to vxlan
vports for onward peers as dictated by the routing topology.
Every flow specified by the router has the following characteristics:
* An `InPort` key matching the ingress vport
* A `Tunnel` key if the ingress vport is of type vxlan
* An `Ethernet` key matching the source and destination MAC
* A list of `Output` (for internal and netdev vports) and
`SetTunnel`+`Output` (for vxlan vports) actions
Under certain circumstances the miss handler will instruct the
datapath to execute a set of actions for a packet without creating a
corresponding flow:
* When broadcasting packets to discover the path to unknown
destination MACs. This condition is transient in the overwhelming
majority of cases and so the benefit of creating a flow is
outweighed by the need to invalidate it once the MAC is learned
* When the packet also needs to be forwarded to one or more peers via
Sleeve. In this case the router needs to handle all subsequent
matching packets as there is no OVS flow action to do it without
userspace involvement
# Flow Invalidation
Once a flow has been created for a particular combination of keys, the
miss handler will never again be invoked for matching packets. It is
therefore extremely important that we detect events which invalidate
existing flow actions:
* Addition of netdev vports to the datapath
* Route invalidation (topology change)
* Short peer ID collision
The response in each case is the same - delete all flows from the
datapath, allowing them to be recreated taking into account the
updated state.
In addition to these event based invalidations there is an expiry
process that executes every five minutes. This process enumerates all
flows in the datapath, removing any which have not been used since the
last check; this cleans up:
* Flows referring to netdev vports which have been removed
* Flows created by forwarders which have been stopped
* Flows related to MAC addresses which have not communicated recently
Flows keyed on tunnel IPv4 address do not need to be cleared when a
peer appears to change IP address due to NAT; this will cause a miss
resulting in a new flow, and the old flow will expire naturally via
the timer mechanism.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 43 KiB

+5
View File
@@ -38,6 +38,11 @@ func (g *allocate) Try(alloc *Allocator) bool {
alloc.establishRing()
if ok, addr := alloc.space.Allocate(g.r); ok {
// If caller hasn't supplied a unique ID, file it under the IP address
// which lets the caller then release the address using DELETE /ip/address
if g.ident == "_" {
g.ident = addr.String()
}
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
alloc.addOwned(g.ident, addr)
g.resultChan <- allocateResult{addr, nil}
+98 -64
View File
@@ -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,37 +43,39 @@ 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(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) *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},
now: time.Now,
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[mesh.PeerName]string{ourName: ourNickname},
isKnownPeer: isKnownPeer,
now: time.Now,
}
}
// 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)
}
@@ -163,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) {
@@ -175,17 +177,6 @@ func (alloc *Allocator) spaceRequestDenied(sender router.PeerName, r address.Ran
}
}
func hasBeenCancelled(cancelChan <-chan bool) func() bool {
return func() bool {
select {
case <-cancelChan:
return true
default:
return false
}
}
}
type errorCancelled struct {
kind string
ident string
@@ -199,10 +190,9 @@ func (e *errorCancelled) Error() string {
// Allocate (Sync) - get new IP address for container with given name in range
// if there isn't any space in that range we block indefinitely
func (alloc *Allocator) Allocate(ident string, r address.Range, cancelChan <-chan bool) (address.Address, error) {
func (alloc *Allocator) Allocate(ident string, r address.Range, hasBeenCancelled func() bool) (address.Address, error) {
resultChan := make(chan allocateResult)
op := &allocate{resultChan: resultChan, ident: ident, r: r,
hasBeenCancelled: hasBeenCancelled(cancelChan)}
op := &allocate{resultChan: resultChan, ident: ident, r: r, hasBeenCancelled: hasBeenCancelled}
alloc.doOperation(op, &alloc.pendingAllocates)
result := <-resultChan
return result.addr, result.err
@@ -237,6 +227,8 @@ func (alloc *Allocator) ContainerDied(ident string) {
}
}
func (alloc *Allocator) ContainerStarted(ident string) {}
// Delete (Sync) - release all IP addresses for container with given name
func (alloc *Allocator) Delete(ident string) error {
errChan := make(chan error)
@@ -284,6 +276,33 @@ func (alloc *Allocator) Free(ident string, addrToFree address.Address) error {
return <-errChan
}
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 mesh.UnknownPeerName
}
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 != mesh.UnknownPeerName {
return heir
}
// next try alive peers that have IPAM enabled but have no entries
if heir := alloc.pickPeerFromNicknames(alloc.isKnownPeer); heir != mesh.UnknownPeerName {
return heir
}
// next try disappeared peers that still have entries
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
return alloc.pickPeerFromNicknames(t)
}
// Shutdown (Sync)
func (alloc *Allocator) Shutdown() {
alloc.infof("Shutdown")
@@ -292,7 +311,7 @@ func (alloc *Allocator) Shutdown() {
alloc.shuttingDown = true
alloc.cancelOps(&alloc.pendingClaims)
alloc.cancelOps(&alloc.pendingAllocates)
if heir := alloc.ring.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())
@@ -320,7 +339,6 @@ func (alloc *Allocator) AdminTakeoverRanges(peerNameOrNickname string) error {
return
}
delete(alloc.nicknames, peername)
newRanges, err := alloc.ring.Transfer(peername, alloc.ourName)
alloc.space.AddRanges(newRanges)
resultChan <- err
@@ -332,33 +350,45 @@ 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 and our own
// Restrict the peers in "nicknames" to those in the ring plus peers known to the router
func (alloc *Allocator) pruneNicknames() {
ringPeers := alloc.ring.PeerNames()
for name := range alloc.nicknames {
if _, ok := ringPeers[name]; !ok && name != alloc.ourName {
if _, ok := ringPeers[name]; !ok && !alloc.isKnownPeer(name) {
delete(alloc.nicknames, name)
}
}
}
func (alloc *Allocator) annotatePeernames(names []mesh.PeerName) []string {
var res []string
for _, name := range names {
if nickname, found := alloc.nicknames[name]; found {
res = append(res, fmt.Sprint(name, "(", nickname, ")"))
} else {
res = append(res, name.String())
}
}
return res
}
func decodeRange(msg []byte) (r address.Range, err error) {
decoder := gob.NewDecoder(bytes.NewReader(msg))
return r, decoder.Decode(&r)
}
// 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() {
@@ -384,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() {
@@ -397,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
@@ -433,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
}
@@ -448,8 +478,8 @@ type ipamGossipData struct {
alloc *Allocator
}
func (d *ipamGossipData) Merge(other router.GossipData) {
// no-op
func (d *ipamGossipData) Merge(other mesh.GossipData) mesh.GossipData {
return d // no-op
}
func (d *ipamGossipData) Encode() [][]byte {
@@ -458,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
}
@@ -510,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())
@@ -533,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] }
@@ -542,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
}
@@ -577,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
@@ -616,16 +646,20 @@ func (alloc *Allocator) update(sender router.PeerName, msg []byte) error {
// shouldn't get updates for a empty Ring. But tolerate
// them just in case.
if data.Ring != nil {
if data.Ring.Range() != alloc.universe {
return fmt.Errorf("Incompatible IP allocation range %s; ours is %s",
data.Ring.Range().AsCIDRString(), alloc.universe.AsCIDRString())
switch err = alloc.ring.Merge(*data.Ring); err {
case ring.ErrDifferentSeeds:
return fmt.Errorf("IP allocation was seeded by different peers (received: %v, ours: %v)",
alloc.annotatePeernames(data.Ring.Seeds), alloc.annotatePeernames(alloc.ring.Seeds))
case ring.ErrDifferentRange:
return fmt.Errorf("Incompatible IP allocation ranges (received: %s, ours: %s)",
data.Ring.Range().AsCIDRString(), alloc.ring.Range().AsCIDRString())
default:
if err == nil && !alloc.ring.Empty() {
alloc.pruneNicknames()
alloc.ringUpdated()
}
return err
}
err = alloc.ring.Merge(*data.Ring)
if !alloc.ring.Empty() {
alloc.pruneNicknames()
alloc.ringUpdated()
}
return err
}
if data.Paxos != nil {
@@ -640,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)
@@ -650,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
+34 -24
View File
@@ -20,6 +20,8 @@ const (
testStart3 = "10.0.3.0"
)
func returnFalse() bool { return false }
func TestAllocFree(t *testing.T) {
const (
container1 = "abcdef"
@@ -39,31 +41,31 @@ func TestAllocFree(t *testing.T) {
_, cidr2, _ := address.ParseCIDR(subnet2)
alloc.claimRingForTesting()
addr1, err := alloc.Allocate(container1, cidr1.HostRange(), nil)
addr1, err := alloc.Allocate(container1, cidr1.HostRange(), returnFalse)
require.NoError(t, err)
require.Equal(t, testAddr1, addr1.String(), "address")
addr2, err := alloc.Allocate(container1, cidr2.HostRange(), nil)
addr2, err := alloc.Allocate(container1, cidr2.HostRange(), returnFalse)
require.NoError(t, err)
require.Equal(t, testAddr2, addr2.String(), "address")
// Ask for another address for a different container and check it's different
addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), nil)
addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), returnFalse)
if addr1b.String() == testAddr1 {
t.Fatalf("Expected different address but got %s", addr1b.String())
}
// Ask for the first container again and we should get the same addresses again
addr1a, _ := alloc.Allocate(container1, cidr1.HostRange(), nil)
addr1a, _ := alloc.Allocate(container1, cidr1.HostRange(), returnFalse)
require.Equal(t, testAddr1, addr1a.String(), "address")
addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), nil)
addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), returnFalse)
require.Equal(t, testAddr2, addr2a.String(), "address")
// Now delete the first container, and we should get its addresses back
require.NoError(t, alloc.Delete(container1))
addr3, _ := alloc.Allocate(container3, cidr1.HostRange(), nil)
addr3, _ := alloc.Allocate(container3, cidr1.HostRange(), returnFalse)
require.Equal(t, testAddr1, addr3.String(), "address")
addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), nil)
addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), returnFalse)
require.Equal(t, testAddr2, addr4.String(), "address")
alloc.ContainerDied(container2)
@@ -93,7 +95,7 @@ func TestBootstrap(t *testing.T) {
ExpectBroadcastMessage(alloc1, nil) // alloc1 will try to form consensus
done := make(chan bool)
go func() {
alloc1.Allocate("somecontainer", subnet, nil)
alloc1.Allocate("somecontainer", subnet, returnFalse)
done <- true
}()
time.Sleep(100 * time.Millisecond)
@@ -147,15 +149,15 @@ func TestAllocatorClaim(t *testing.T) {
require.NoError(t, err)
// Do one allocate to ensure paxos is all done
alloc.Allocate("unused", subnet, nil)
alloc.Allocate("unused", subnet, returnFalse)
// Do an allocate on the other peer, which we will try to claim later
addrx, err := allocs[0].Allocate(container1, subnet, nil)
addrx, err := allocs[0].Allocate(container1, subnet, returnFalse)
// Now try the claim again
err = alloc.Claim(container3, addr1, true)
require.NoError(t, err)
// Check we get this address back if we try an allocate
addr3, _ := alloc.Allocate(container3, subnet, nil)
addr3, _ := alloc.Allocate(container3, subnet, returnFalse)
require.Equal(t, testAddr1, addr3.String(), "address")
// one more claim should still work
err = alloc.Claim(container3, addr1, true)
@@ -206,9 +208,9 @@ func TestCancel(t *testing.T) {
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
// Get some IPs, so each allocator has some space
res1, _ := alloc1.Allocate("foo", subnet, nil)
res1, _ := alloc1.Allocate("foo", subnet, returnFalse)
common.Log.Debugf("res1 = %s", res1.String())
res2, _ := alloc2.Allocate("bar", subnet, nil)
res2, _ := alloc2.Allocate("bar", subnet, returnFalse)
common.Log.Debugf("res2 = %s", res2.String())
if res1 == res2 {
require.FailNow(t, "Error: got same ips!")
@@ -220,12 +222,20 @@ func TestCancel(t *testing.T) {
// Use up all the IPs that alloc1 owns, so the allocation after this will prompt a request to alloc2
for i := 0; alloc1.NumFreeAddresses(subnet) > 0; i++ {
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, nil)
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, returnFalse)
}
cancelChan := make(chan bool, 1)
doneChan := make(chan bool)
go func() {
_, ok := alloc1.Allocate("baz", subnet, cancelChan)
_, ok := alloc1.Allocate("baz", subnet,
func() bool {
select {
case <-cancelChan:
return true
default:
return false
}
})
doneChan <- ok == nil
}()
@@ -252,7 +262,7 @@ func TestCancelOnDied(t *testing.T) {
doneChan := make(chan bool)
f := func() {
_, ok := alloc1.Allocate(container1, subnet, nil)
_, ok := alloc1.Allocate(container1, subnet, returnFalse)
doneChan <- ok == nil
}
@@ -283,11 +293,11 @@ func TestGossipShutdown(t *testing.T) {
defer alloc.Stop()
alloc.claimRingForTesting()
alloc.Allocate(container1, subnet, nil)
alloc.Allocate(container1, subnet, returnFalse)
alloc.Shutdown()
_, err := alloc.Allocate(container2, subnet, nil) // trying to allocate after shutdown should fail
_, err := alloc.Allocate(container2, subnet, returnFalse) // trying to allocate after shutdown should fail
require.False(t, err == nil, "no address")
CheckAllExpectedMessagesSent(alloc)
@@ -302,10 +312,10 @@ func TestTransfer(t *testing.T) {
alloc2 := allocs[1]
alloc3 := allocs[2] // This will be 'master' and get the first range
_, err := alloc2.Allocate("foo", subnet, nil)
_, err := alloc2.Allocate("foo", subnet, returnFalse)
require.True(t, err == nil, "Failed to get address")
_, err = alloc3.Allocate("bar", subnet, nil)
_, err = alloc3.Allocate("bar", subnet, returnFalse)
require.True(t, err == nil, "Failed to get address")
alloc2.gossip.GossipBroadcast(alloc2.Gossip())
@@ -323,7 +333,7 @@ func TestTransfer(t *testing.T) {
require.Equal(t, address.Offset(1022), alloc1.NumFreeAddresses(subnet))
_, err = alloc1.Allocate("foo", subnet, nil)
_, err = alloc1.Allocate("foo", subnet, returnFalse)
require.True(t, err == nil, "Failed to get address")
alloc1.Stop()
}
@@ -338,7 +348,7 @@ func TestFakeRouterSimple(t *testing.T) {
alloc1 := allocs[0]
//alloc2 := allocs[1]
_, err := alloc1.Allocate("foo", subnet, nil)
_, err := alloc1.Allocate("foo", subnet, returnFalse)
require.NoError(t, err, "Failed to get address")
}
@@ -419,7 +429,7 @@ func TestAllocatorFuzz(t *testing.T) {
allocIndex := rand.Int31n(nodes)
alloc := allocs[allocIndex]
//common.Log.Infof("Allocate: asking allocator %d", allocIndex)
addr, err := alloc.Allocate(name, subnet, nil)
addr, err := alloc.Allocate(name, subnet, returnFalse)
if err != nil {
panic(fmt.Sprintf("Could not allocate addr"))
@@ -476,7 +486,7 @@ func TestAllocatorFuzz(t *testing.T) {
//common.Log.Infof("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc)
newAddr, _ := alloc.Allocate(res.name, subnet, nil)
newAddr, _ := alloc.Allocate(res.name, subnet, returnFalse)
oldAddr, _ := address.ParseIP(addr)
if newAddr != oldAddr {
panic(fmt.Sprintf("Got different address for repeat request for %s: %s != %s", res.name, newAddr, oldAddr))
+3 -3
View File
@@ -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 + ")"
+15 -7
View File
@@ -31,7 +31,17 @@ func parseCIDR(w http.ResponseWriter, cidrStr string) (address.CIDR, bool) {
func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.ResponseWriter, ident string, checkAlive bool, subnet address.CIDR) {
closedChan := w.(http.CloseNotifier).CloseNotify()
addr, err := alloc.Allocate(ident, subnet.HostRange(), closedChan)
addr, err := alloc.Allocate(ident, subnet.HostRange(),
func() bool {
select {
case <-closedChan:
return true
default:
res := checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident)
checkAlive = false // we check only once; if the container dies later we learn about that through events
return res
}
})
if err != nil {
if _, ok := err.(*errorCancelled); ok { // cancellation is not really an error
common.Log.Infoln("[allocator]:", err.Error())
@@ -41,18 +51,16 @@ func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.Resp
badRequest(w, err)
return
}
if checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident) {
common.Log.Infof("[allocator] '%s' is not running: freeing %s", ident, addr)
alloc.Free(ident, addr)
fmt.Fprint(w, "cancelled")
return
}
fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen)
}
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
router.Methods("GET").Path("/ipinfo/defaultsubnet").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s", defaultSubnet)
})
router.Methods("PUT").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
ident := vars["id"]
-7
View File
@@ -14,7 +14,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/net/address"
wt "github.com/weaveworks/weave/testing"
)
func HTTPPost(t *testing.T, url string) string {
@@ -145,12 +144,6 @@ func TestBadHttp(t *testing.T) {
}
func TestHTTPCancel(t *testing.T) {
wt.RunWithTimeout(t, 2*time.Second, func() {
impTestHTTPCancel(t)
})
}
func impTestHTTPCancel(t *testing.T) {
var (
containerID = "deadbeef"
testCIDR1 = "10.0.3.0/29"
+6 -6
View File
@@ -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
+5 -5
View File
@@ -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()
+3 -3
View File
@@ -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
+57 -20
View File
@@ -13,15 +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
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() {
@@ -36,6 +37,7 @@ var (
ErrNotSorted = errors.New("Ring not sorted")
ErrTokenRepeated = errors.New("Token appears twice in ring")
ErrTokenOutOfRange = errors.New("Token is out of range")
ErrDifferentSeeds = errors.New("Received ring was seeded differently from ours!")
ErrDifferentRange = errors.New("Received range differs from ours!")
ErrNewerVersion = errors.New("Received new version for entry I own!")
ErrInvalidEntry = errors.New("Received invalid state update!")
@@ -82,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)}
@@ -109,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()
@@ -188,6 +190,17 @@ func (r *Ring) Merge(gossip Ring) error {
return err
}
if len(gossip.Seeds) > 0 && len(r.Seeds) > 0 {
if len(gossip.Seeds) != len(r.Seeds) {
return ErrDifferentSeeds
}
for i, seed := range gossip.Seeds {
if seed != r.Seeds[i] {
return ErrDifferentSeeds
}
}
}
if r.Start != gossip.Start || r.End != gossip.End {
return ErrDifferentRange
}
@@ -197,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) {
@@ -254,6 +267,9 @@ func (r *Ring) Merge(gossip Ring) error {
previousOwner = nil
}
if len(r.Seeds) == 0 {
r.Seeds = gossip.Seeds
}
r.Entries = result
return nil
}
@@ -300,9 +316,28 @@ func (r *Ring) OwnedRanges() (result []address.Range) {
return r.splitRangesOverZero(result)
}
// For printing status
type RangeInfo struct {
Peer mesh.PeerName
address.Range
Version uint32
}
func (r *Ring) AllRangeInfo() (result []RangeInfo) {
for i, entry := range r.Entries {
nextEntry := r.Entries.entry(i + 1)
ranges := []address.Range{{Start: entry.Token, End: nextEntry.Token}}
ranges = r.splitRangesOverZero(ranges)
for _, r := range ranges {
result = append(result, RangeInfo{entry.Peer, r, entry.Version})
}
}
return
}
// ClaimForPeers claims the entire ring for the array of peers passed
// in. Only works for empty rings.
func (r *Ring) ClaimForPeers(peers []router.PeerName) {
func (r *Ring) ClaimForPeers(peers []mesh.PeerName) {
common.Assert(r.Empty())
defer r.assertInvariants()
defer r.updateExportedVariables()
@@ -330,9 +365,11 @@ func (r *Ring) ClaimForPeers(peers []router.PeerName) {
}
common.Assert(pos == r.End)
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 {
@@ -347,7 +384,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()
}
@@ -397,7 +434,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
@@ -408,10 +445,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<end.
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []router.PeerName {
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []mesh.PeerName {
var (
sum address.Offset
totalSpacePerPeer = make(map[router.PeerName]address.Offset) // Compute total free space per peer
totalSpacePerPeer = make(map[mesh.PeerName]address.Offset) // Compute total free space per peer
)
// iterate through tokens
@@ -445,25 +482,25 @@ func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []router.Pee
ws = append(ws, weightedPeer{weight: float64(space) * rand.Float64(), peername: peername})
}
sort.Sort(ws)
result := make([]router.PeerName, len(ws))
result := make([]mesh.PeerName, len(ws))
for i, wp := range ws {
result[i] = wp.peername
}
return result
}
func (r *Ring) PickPeerForTransfer() router.PeerName {
func (r *Ring) PickPeerForTransfer(isValid func(mesh.PeerName) bool) mesh.PeerName {
for _, entry := range r.Entries {
if entry.Peer != r.Peer {
if entry.Peer != r.Peer && isValid(entry.Peer) {
return entry.Peer
}
}
return router.UnknownPeerName
return mesh.UnknownPeerName
}
// Transfer will mark all entries associated with 'from' peer as owned by 'to' peer
// and return ranges indicating the new space we picked up
func (r *Ring) Transfer(from, to router.PeerName) ([]address.Range, error) {
func (r *Ring) Transfer(from, to mesh.PeerName) ([]address.Range, error) {
r.assertInvariants()
defer r.assertInvariants()
defer r.updateExportedVariables()
@@ -493,13 +530,13 @@ func (r *Ring) Contains(addr address.Address) bool {
}
// Owner returns the peername which owns the range containing addr
func (r *Ring) Owner(token address.Address) router.PeerName {
func (r *Ring) Owner(token address.Address) mesh.PeerName {
common.Assert(r.Start <= token && token < r.End)
r.assertInvariants()
// There can be no owners on an empty ring
if r.Empty() {
return router.UnknownPeerName
return mesh.UnknownPeerName
}
// Look for the right-most entry, less than or equal to token
@@ -512,8 +549,8 @@ func (r *Ring) Owner(token address.Address) router.PeerName {
}
// Get the set of PeerNames mentioned in the ring
func (r *Ring) PeerNames() map[router.PeerName]struct{} {
res := make(map[router.PeerName]struct{})
func (r *Ring) PeerNames() map[mesh.PeerName]struct{} {
res := make(map[mesh.PeerName]struct{})
for _, entry := range r.Entries {
res[entry.Peer] = struct{}{}
+13 -13
View File
@@ -9,14 +9,14 @@ import (
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/mesh"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
var (
peer1name, _ = router.PeerNameFromString("01:00:00:00:02:00")
peer2name, _ = router.PeerNameFromString("02:00:00:00:02:00")
peer3name, _ = router.PeerNameFromString("03:00:00:00:02:00")
peer1name, _ = mesh.PeerNameFromString("01:00:00:00:02:00")
peer2name, _ = mesh.PeerNameFromString("02:00:00:00:02:00")
peer3name, _ = mesh.PeerNameFromString("03:00:00:00:02:00")
start, end = ParseIP("10.0.0.0"), ParseIP("10.0.0.255")
dot10, dot245 = ParseIP("10.0.0.10"), ParseIP("10.0.0.245")
@@ -351,7 +351,7 @@ func TestGossip(t *testing.T) {
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
}
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []router.PeerName {
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []mesh.PeerName {
peers := ring.ChoosePeersToAskForSpace(start, end)
require.Equal(t, expected, len(peers))
return peers
@@ -375,7 +375,7 @@ func TestFindFree(t *testing.T) {
ring1.assertInvariants()
// We should return others
var peers []router.PeerName
var peers []mesh.PeerName
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
peers = assertPeersWithSpace(t, ring1, start, end, 1)
@@ -493,7 +493,7 @@ func TestOwner(t *testing.T) {
require.True(t, ring1.Contains(start), "start should be in ring")
require.False(t, ring1.Contains(end), "end should not be in ring")
require.Equal(t, router.UnknownPeerName, ring1.Owner(start))
require.Equal(t, mesh.UnknownPeerName, ring1.Owner(start))
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
@@ -517,9 +517,9 @@ func TestFuzzRing(t *testing.T) {
iterations = 1000
)
peers := make([]router.PeerName, numPeers)
peers := make([]mesh.PeerName, numPeers)
for i := 0; i < numPeers; i++ {
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:00:02:00", i))
peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:00:00:00:02:00", i))
peers[i] = peer
}
@@ -601,13 +601,13 @@ func TestFuzzRingHard(t *testing.T) {
var (
numPeers = 100
iterations = 3000
peers []router.PeerName
peers []mesh.PeerName
rings []*Ring
nextPeerID = 0
)
addPeer := func() {
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
common.Log.Debugf("%s: Adding peer", peer)
nextPeerID++
peers = append(peers, peer)
@@ -620,7 +620,7 @@ func TestFuzzRingHard(t *testing.T) {
rings[0].ClaimItAll()
randomPeer := func(exclude int) (int, router.PeerName, *Ring) {
randomPeer := func(exclude int) (int, mesh.PeerName, *Ring) {
var peerIndex int
if exclude >= 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 {
+19 -6
View File
@@ -1,6 +1,7 @@
package ipam
import (
"fmt"
"github.com/weaveworks/weave/ipam/paxos"
"github.com/weaveworks/weave/net/address"
)
@@ -8,6 +9,7 @@ import (
type Status struct {
Paxos *paxos.Status
Range string
RangeNumIPs int
DefaultSubnet string
Entries []EntryStatus
PendingClaims []ClaimStatus
@@ -15,9 +17,12 @@ type Status struct {
}
type EntryStatus struct {
Token string
Peer string
Version uint32
Token string
Size uint32
Peer string
Nickname string
IsKnownPeer bool
Version uint32
}
type ClaimStatus struct {
@@ -40,6 +45,7 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
resultChan <- &Status{
paxosStatus,
allocator.universe.String(),
int(allocator.universe.Size()),
defaultSubnet.String(),
newEntryStatusSlice(allocator),
newClaimStatusSlice(allocator),
@@ -56,8 +62,15 @@ func newEntryStatusSlice(allocator *Allocator) []EntryStatus {
return slice
}
for _, entry := range allocator.ring.Entries {
slice = append(slice, EntryStatus{entry.Token.String(), entry.Peer.String(), entry.Version})
for _, r := range allocator.ring.AllRangeInfo() {
slice = append(slice, EntryStatus{
Token: r.Start.String(),
Size: uint32(r.Size()),
Peer: r.Peer.String(),
Nickname: allocator.nicknames[r.Peer],
IsKnownPeer: allocator.isKnownPeer(r.Peer),
Version: r.Version,
})
}
return slice
@@ -76,7 +89,7 @@ func newAllocateIdentSlice(allocator *Allocator) []string {
var slice []string
for _, op := range allocator.pendingAllocates {
allocate := op.(*allocate)
slice = append(slice, allocate.ident)
slice = append(slice, fmt.Sprintf("%s %s", allocate.ident, allocate.r.String()))
}
return slice
}
+12 -12
View File
@@ -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)
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)
}
+460
View File
@@ -0,0 +1,460 @@
package mesh
import (
"fmt"
"net"
"strconv"
"sync"
"time"
)
type Connection interface {
Local() *Peer
Remote() *Peer
RemoteTCPAddr() string
Outbound() bool
Established() bool
BreakTie(Connection) ConnectionTieBreak
Shutdown(error)
Log(args ...interface{})
}
type ConnectionTieBreak int
const (
TieBreakWon ConnectionTieBreak = iota
TieBreakLost
TieBreakTied
)
var ErrConnectToSelf = fmt.Errorf("Cannot connect to ourself")
type RemoteConnection struct {
local *Peer
remote *Peer
remoteTCPAddr string
outbound bool
established bool
}
type LocalConnection struct {
sync.RWMutex
RemoteConnection
TCPConn *net.TCPConn
TrustRemote bool // is remote on a trusted subnet?
TrustedByRemote bool // does remote trust us?
version byte
tcpSender TCPSender
SessionKey *[32]byte
heartbeatTCP *time.Ticker
Router *Router
uid uint64
actionChan chan<- ConnectionAction
errorChan chan<- error
finished <-chan struct{} // closed to signal that actorLoop has finished
OverlayConn OverlayConnection
}
type ConnectionAction func() error
func NewRemoteConnection(from, to *Peer, tcpAddr string, outbound bool, established bool) *RemoteConnection {
return &RemoteConnection{
local: from,
remote: to,
remoteTCPAddr: tcpAddr,
outbound: outbound,
established: established,
}
}
func (conn *RemoteConnection) Local() *Peer { return conn.local }
func (conn *RemoteConnection) Remote() *Peer { return conn.remote }
func (conn *RemoteConnection) RemoteTCPAddr() string { return conn.remoteTCPAddr }
func (conn *RemoteConnection) Outbound() bool { return conn.outbound }
func (conn *RemoteConnection) Established() bool { return conn.established }
func (conn *RemoteConnection) BreakTie(Connection) ConnectionTieBreak { return TieBreakTied }
func (conn *RemoteConnection) Shutdown(error) {}
func (conn *RemoteConnection) Log(args ...interface{}) {
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
}
func (conn *RemoteConnection) ErrorLog(args ...interface{}) {
log.Errorln(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
}
// 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, router *Router, acceptNewPeer bool) {
if connRemote.local != router.Ourself.Peer {
log.Fatal("Attempt to create local connection from a peer which is not ourself")
}
actionChan := make(chan ConnectionAction, ChannelSize)
errorChan := make(chan error, 1)
finished := make(chan struct{})
conn := &LocalConnection{
RemoteConnection: *connRemote, // NB, we're taking a copy of connRemote here.
Router: router,
TCPConn: tcpConn,
TrustRemote: router.Trusts(connRemote),
uid: randUint64(),
actionChan: actionChan,
errorChan: errorChan,
finished: finished}
go conn.run(actionChan, errorChan, finished, acceptNewPeer)
}
func (conn *LocalConnection) BreakTie(dupConn Connection) ConnectionTieBreak {
dupConnLocal := dupConn.(*LocalConnection)
// conn.uid is used as the tie breaker here, in the knowledge that
// both sides will make the same decision.
if conn.uid < dupConnLocal.uid {
return TieBreakWon
} else if dupConnLocal.uid < conn.uid {
return TieBreakLost
} else {
return TieBreakTied
}
}
func (conn *LocalConnection) Established() bool {
return conn.established
}
func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
if err := conn.sendProtocolMsg(m); err != nil {
conn.Shutdown(err)
}
}
// ACTOR methods
// NB: The conn.* fields are only written by the connection actor
// process, which is the caller of the ConnectionAction funs. Hence we
// do not need locks for reading, and only need write locks for fields
// read by other processes.
// Non-blocking.
func (conn *LocalConnection) Shutdown(err error) {
// err should always be a real error, even if only io.EOF
if err == nil {
panic("nil error")
}
select {
case conn.errorChan <- err:
default:
}
}
// Send an actor request to the actorLoop, but don't block if
// actorLoop has exited - see http://blog.golang.org/pipelines for
// pattern
func (conn *LocalConnection) sendAction(action ConnectionAction) {
select {
case conn.actionChan <- action:
case <-conn.finished:
}
}
// ACTOR server
func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, errorChan <-chan error, finished chan<- struct{}, acceptNewPeer bool) {
var err error // important to use this var and not create another one with 'err :='
defer func() { conn.shutdown(err) }()
defer close(finished)
conn.TCPConn.SetLinger(0)
intro, err := ProtocolIntroParams{
MinVersion: conn.Router.ProtocolMinVersion,
MaxVersion: ProtocolMaxVersion,
Features: conn.makeFeatures(),
Conn: conn.TCPConn,
Password: conn.Router.Password,
Outbound: conn.outbound,
}.DoIntro()
if err != nil {
return
}
conn.SessionKey = intro.SessionKey
conn.tcpSender = intro.Sender
conn.version = intro.Version
remote, err := conn.parseFeatures(intro.Features)
if err != nil {
return
}
if err = conn.registerRemote(remote, acceptNewPeer); err != nil {
return
}
conn.Log("connection ready; using protocol version", conn.version)
// only use negotiated session key for untrusted connections
var sessionKey *[32]byte
if conn.Untrusted() {
sessionKey = conn.SessionKey
}
params := OverlayConnectionParams{
RemotePeer: conn.remote,
LocalAddr: conn.TCPConn.LocalAddr().(*net.TCPAddr),
RemoteAddr: conn.TCPConn.RemoteAddr().(*net.TCPAddr),
Outbound: conn.outbound,
ConnUID: conn.uid,
SessionKey: sessionKey,
SendControlMessage: conn.sendOverlayControlMessage,
Features: intro.Features,
}
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 PrepareConnection
if err = conn.Router.Ourself.AddConnection(conn); err != nil {
return
}
conn.Router.ConnectionMaker.ConnectionCreated(conn)
// 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
// we hear about it (and any peers reachable from it) is
// through topology gossip it sends us on the connection. We
// must ensure that the connection has been added to Ourself
// prior to processing any such gossip, otherwise we risk
// immediately gc'ing part of that newly received portion of
// the topology (though not the remote peer itself, since that
// will have a positive ref count), leaving behind dangling
// references to peers. Hence we must invoke AddConnection,
// which is *synchronous*, first.
conn.heartbeatTCP = time.NewTicker(TCPHeartbeat)
go conn.receiveTCP(intro.Receiver)
// AddConnection must precede actorLoop. More precisely, it
// must precede shutdown, since that invokes DeleteConnection
// and is invoked on termination of this entire
// function. Essentially this boils down to a prohibition on
// running AddConnection in a separate goroutine, at least not
// without some synchronisation. Which in turn requires the
// launching of the receiveTCP goroutine to precede actorLoop.
err = conn.actorLoop(actionChan, errorChan)
}
func (conn *LocalConnection) makeFeatures() map[string]string {
features := map[string]string{
"PeerNameFlavour": PeerNameFlavour,
"Name": conn.local.Name.String(),
"NickName": conn.local.NickName,
"ShortID": fmt.Sprint(conn.local.ShortID),
"UID": fmt.Sprint(conn.local.UID),
"ConnID": fmt.Sprint(conn.uid),
"Trusted": fmt.Sprint(conn.TrustRemote),
}
conn.Router.Overlay.AddFeaturesTo(features)
return features
}
type features map[string]string
func (features features) MustHave(keys []string) error {
for _, key := range keys {
if _, ok := features[key]; !ok {
return fmt.Errorf("Field %s is missing", key)
}
}
return nil
}
func (features features) Get(key string) string {
return features[key]
}
func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
if err := features.MustHave([]string{"PeerNameFlavour", "Name", "NickName", "UID", "ConnID"}); err != nil {
return nil, err
}
remotePeerNameFlavour := features.Get("PeerNameFlavour")
if remotePeerNameFlavour != PeerNameFlavour {
return nil, fmt.Errorf("Peer name flavour mismatch (ours: '%s', theirs: '%s')", PeerNameFlavour, remotePeerNameFlavour)
}
name, err := PeerNameFromString(features.Get("Name"))
if err != nil {
return nil, err
}
nickName := features.Get("NickName")
var shortID uint64
var hasShortID bool
if shortIDStr, present := features["ShortID"]; present {
hasShortID = true
shortID, err = strconv.ParseUint(shortIDStr, 10, PeerShortIDBits)
if err != nil {
return nil, err
}
}
var trusted bool
if trustedStr, present := features["Trusted"]; present {
trusted, err = strconv.ParseBool(trustedStr)
if err != nil {
return nil, err
}
}
conn.TrustedByRemote = trusted
uid, err := ParsePeerUID(features.Get("UID"))
if err != nil {
return nil, err
}
remoteConnID, err := strconv.ParseUint(features.Get("ConnID"), 10, 64)
if err != nil {
return nil, err
}
conn.uid ^= remoteConnID
peer := NewPeer(name, nickName, uid, 0, PeerShortID(shortID))
peer.HasShortID = hasShortID
return peer, nil
}
func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) error {
if acceptNewPeer {
conn.remote = conn.Router.Peers.FetchWithDefault(remote)
} else {
conn.remote = conn.Router.Peers.FetchAndAddRef(remote.Name)
if conn.remote == nil {
return fmt.Errorf("Found unknown remote name: %s at %s", remote.Name, conn.remoteTCPAddr)
}
}
if conn.remote == conn.local {
return ErrConnectToSelf
}
return nil
}
func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction, errorChan <-chan error) (err error) {
fwdErrorChan := conn.OverlayConn.ErrorChannel()
fwdEstablishedChan := conn.OverlayConn.EstablishedChannel()
for err == nil {
select {
case action := <-actionChan:
err = action()
case err = <-errorChan:
case <-conn.heartbeatTCP.C:
err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat)
case <-fwdEstablishedChan:
conn.established = true
fwdEstablishedChan = nil
conn.Router.Ourself.ConnectionEstablished(conn)
case err = <-fwdErrorChan:
}
}
return
}
func (conn *LocalConnection) shutdown(err error) {
if conn.remote == nil {
log.Errorf("->[%s] connection shutting down due to error during handshake: %v", conn.remoteTCPAddr, err)
} else {
conn.ErrorLog("connection shutting down due to error:", err)
}
if conn.TCPConn != nil {
checkWarn(conn.TCPConn.Close())
}
if conn.remote != nil {
conn.Router.Peers.Dereference(conn.remote)
conn.Router.Ourself.DeleteConnection(conn)
}
if conn.heartbeatTCP != nil {
conn.heartbeatTCP.Stop()
}
if conn.OverlayConn != nil {
conn.OverlayConn.Stop()
}
conn.Router.ConnectionMaker.ConnectionTerminated(conn, err)
}
func (conn *LocalConnection) sendOverlayControlMessage(tag byte, msg []byte) error {
return conn.sendProtocolMsg(ProtocolMsg{ProtocolTag(tag), msg})
}
// Helpers
func (conn *LocalConnection) sendSimpleProtocolMsg(tag ProtocolTag) error {
return conn.sendProtocolMsg(ProtocolMsg{tag: tag})
}
func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
return conn.tcpSender.Send(append([]byte{byte(m.tag)}, m.msg...))
}
func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) {
var err error
for {
conn.extendReadDeadline()
var msg []byte
if msg, err = receiver.Receive(); err != nil {
break
}
if len(msg) < 1 {
conn.Log("ignoring blank msg")
continue
}
if err = conn.handleProtocolMsg(ProtocolTag(msg[0]), msg[1:]); err != nil {
break
}
}
conn.Shutdown(err)
}
func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) error {
switch tag {
case ProtocolHeartbeat:
case ProtocolReserved1, ProtocolReserved2, ProtocolReserved3, ProtocolOverlayControlMsg:
conn.OverlayConn.ControlMessage(byte(tag), payload)
case ProtocolGossipUnicast, ProtocolGossipBroadcast, ProtocolGossip:
return conn.Router.handleGossip(tag, payload)
default:
conn.Log("ignoring unknown protocol tag:", tag)
}
return nil
}
func (conn *LocalConnection) extendReadDeadline() {
conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2))
}
func (conn *LocalConnection) Untrusted() bool {
return !conn.TrustRemote || !conn.TrustedByRemote
}
@@ -1,4 +1,4 @@
package router
package mesh
import (
"fmt"
@@ -10,6 +10,7 @@ import (
const (
InitialInterval = 2 * time.Second
MaxInterval = 6 * time.Minute
ResetAfter = 1 * time.Minute
)
type peerAddrs map[string]*net.TCPAddr
@@ -20,13 +21,22 @@ type ConnectionMaker struct {
port int
discovery bool
targets map[string]*Target
connections map[Connection]struct{}
directPeers peerAddrs
actionChan chan<- ConnectionMakerAction
}
type TargetState int
const (
TargetWaiting TargetState = iota // we are waiting to connect there
TargetAttempting // we are attempting to connect there
TargetConnected // we are connected to there
)
// Information about an address where we may find a peer
type Target struct {
attempting bool // are we currently attempting to connect there?
state TargetState
lastError error // reason for disconnection last time
tryAfter time.Time // next time to try this address
tryInterval time.Duration // retry delay on next failure
@@ -43,6 +53,7 @@ func NewConnectionMaker(ourself *LocalPeer, peers *Peers, port int, discovery bo
discovery: discovery,
directPeers: peerAddrs{},
targets: make(map[string]*Target),
connections: make(map[Connection]struct{}),
actionChan: actionChan}
go cm.queryLoop(actionChan)
return cm
@@ -70,8 +81,8 @@ func (cm *ConnectionMaker) InitiateConnections(peers []string, replace bool) []e
for peer, addr := range addrs {
cm.directPeers[peer] = addr
// curtail any existing reconnect interval
if target, found := cm.targets[addr.String()]; found {
target.tryNow()
if target, found := cm.targets[cm.completeAddr(*addr)]; found {
target.nextTryNow()
}
}
return true
@@ -88,12 +99,42 @@ func (cm *ConnectionMaker) ForgetConnections(peers []string) {
}
}
func (cm *ConnectionMaker) ConnectionTerminated(address string, err error) {
func (cm *ConnectionMaker) ConnectionAborted(address string, err error) {
cm.actionChan <- func() bool {
if target, found := cm.targets[address]; found {
target.attempting = false
target := cm.targets[address]
target.state = TargetWaiting
target.lastError = err
target.nextTryLater()
return true
}
}
func (cm *ConnectionMaker) ConnectionCreated(conn Connection) {
cm.actionChan <- func() bool {
cm.connections[conn] = void
if conn.Outbound() {
target := cm.targets[conn.RemoteTCPAddr()]
target.state = TargetConnected
}
return false
}
}
func (cm *ConnectionMaker) ConnectionTerminated(conn Connection, err error) {
cm.actionChan <- func() bool {
delete(cm.connections, conn)
if conn.Outbound() {
target := cm.targets[conn.RemoteTCPAddr()]
target.state = TargetWaiting
target.lastError = err
target.retry()
switch {
case err == ErrConnectToSelf:
target.nextTryNever()
case time.Now().After(target.tryAfter.Add(ResetAfter)):
target.nextTryNow()
default:
target.nextTryLater()
}
}
return true
}
@@ -118,14 +159,18 @@ func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) {
}
}
func (cm *ConnectionMaker) completeAddr(addr net.TCPAddr) string {
if addr.Port == 0 {
addr.Port = cm.port
}
return addr.String()
}
func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
var (
validTarget = make(map[string]struct{})
directTarget = make(map[string]struct{})
)
// Copy the set of things we are connected to, so we can access
// them without locking. Also clear out any entries in cm.targets
// for existing connections.
ourConnectedPeers, ourConnectedTargets, ourInboundIPs := cm.ourConnections()
addTarget := func(address string) {
@@ -136,25 +181,23 @@ func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
if _, found := cm.targets[address]; found {
return
}
target := &Target{}
target.tryNow()
target := &Target{state: TargetWaiting}
target.nextTryNow()
cm.targets[address] = target
}
// Add direct targets that are not connected
for _, addr := range cm.directPeers {
completeAddr := *addr
attempt := true
if completeAddr.Port == 0 {
completeAddr.Port = cm.port
if addr.Port == 0 {
// If a peer was specified w/o a port, then we do not
// attempt to connect to it if we have any inbound
// connections from that IP.
if _, connected := ourInboundIPs[completeAddr.IP.String()]; connected {
if _, connected := ourInboundIPs[addr.IP.String()]; connected {
attempt = false
}
}
address := completeAddr.String()
address := cm.completeAddr(*addr)
directTarget[address] = void
if attempt {
addTarget(address)
@@ -176,9 +219,8 @@ func (cm *ConnectionMaker) ourConnections() (PeerNameSet, map[string]struct{}, m
ourConnectedTargets = make(map[string]struct{})
ourInboundIPs = make(map[string]struct{})
)
for conn := range cm.ourself.Connections() {
for conn := range cm.connections {
address := conn.RemoteTCPAddr()
delete(cm.targets, address)
ourConnectedPeers[conn.Remote().Name] = void
ourConnectedTargets[address] = void
if conn.Outbound() {
@@ -212,7 +254,7 @@ func (cm *ConnectionMaker) addPeerTargets(ourConnectedPeers PeerNameSet, addTarg
} else if ip, _, err := net.SplitHostPort(address); err == nil {
// There is no point connecting to the (likely
// ephemeral) remote port of an inbound connection
// that some peer has. Let's try to connect to on the
// that some peer has. Let's try to connect on the
// weave port instead.
addTarget(fmt.Sprintf("%s:%d", ip, cm.port))
}
@@ -224,7 +266,7 @@ func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, dir
now := time.Now() // make sure we catch items just added
after := MaxDuration
for address, target := range cm.targets {
if target.attempting {
if target.state != TargetWaiting {
continue
}
if _, valid := validTarget[address]; !valid {
@@ -236,7 +278,7 @@ func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, dir
}
switch duration := target.tryAfter.Sub(now); {
case duration <= 0:
target.attempting = true
target.state = TargetAttempting
_, isCmdLineTarget := directTarget[address]
go cm.attemptConnection(address, isCmdLineTarget)
case duration < after:
@@ -250,23 +292,23 @@ func (cm *ConnectionMaker) attemptConnection(address string, acceptNewPeer bool)
log.Printf("->[%s] attempting connection", address)
if err := cm.ourself.CreateConnection(address, acceptNewPeer); err != nil {
log.Errorf("->[%s] error during connection attempt: %v", address, err)
cm.ConnectionTerminated(address, err)
cm.ConnectionAborted(address, err)
}
}
func (t *Target) tryNow() {
func (t *Target) nextTryNever() {
t.tryAfter = time.Time{}
t.tryInterval = MaxInterval
}
func (t *Target) nextTryNow() {
t.tryAfter = time.Now()
t.tryInterval = InitialInterval
}
// The delay at the nth retry is a random value in the range
// [i-i/2,i+i/2], where i = InitialInterval * 1.5^(n-1).
func (t *Target) retry() {
if t.lastError == ErrConnectToSelf {
t.tryAfter = time.Time{}
t.tryInterval = MaxInterval
return
}
func (t *Target) nextTryLater() {
t.tryAfter = time.Now().Add(t.tryInterval/2 + time.Duration(rand.Int63n(int64(t.tryInterval))))
t.tryInterval = t.tryInterval * 3 / 2
if t.tryInterval > MaxInterval {
+4 -5
View File
@@ -1,4 +1,4 @@
package router
package mesh
import (
"fmt"
@@ -6,7 +6,7 @@ import (
type GossipData interface {
Encode() [][]byte
Merge(GossipData)
Merge(GossipData) GossipData
}
type Gossip interface {
@@ -79,8 +79,7 @@ func (sender *GossipSender) Send(data GossipData) {
// NB: this must not be invoked concurrently
select {
case pending := <-sender.cell:
pending.Merge(data)
sender.cell <- pending
sender.cell <- pending.Merge(data)
default:
sender.cell <- data
}
@@ -142,7 +141,7 @@ func (router *Router) SendAllGossip() {
func (router *Router) SendAllGossipDown(conn Connection) {
for channel := range router.gossipChannelSet() {
if gossip := channel.gossiper.Gossip(); gossip != nil {
channel.SendDown(conn, channel.gossiper.Gossip())
channel.SendDown(conn, gossip)
}
}
}
@@ -1,4 +1,4 @@
package router
package mesh
import (
"bytes"
@@ -97,78 +97,6 @@ func (c *GossipChannel) deliver(srcName PeerName, _ []byte, dec *gob.Decoder) er
return nil
}
func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
// do this outside the lock below so we avoid lock nesting
c.routes.EnsureRecalculated()
selectedConnections := make(ConnectionSet)
for name := range c.routes.RandomNeighbours(srcName) {
if conn, found := c.ourself.ConnectionTo(name); found {
selectedConnections[conn] = void
}
}
if len(selectedConnections) == 0 {
return
}
connections := c.ourself.Connections()
c.Lock()
defer c.Unlock()
c.gcSenders(connections)
for conn := range selectedConnections {
c.sendDown(conn, data)
}
}
func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
connections := c.ourself.Connections()
c.Lock()
defer c.Unlock()
c.gcSenders(connections)
c.sendDown(conn, data)
}
// GC - randomly (courtesy of go's map iterator) pick some existing
// senders and stop&remove them if the associated connection is no
// longer active. We stop as soon as we encounter a valid entry; the
// idea being that when there is little or no garbage then this
// executes close to O(1)[1], whereas when there is lots of garbage we
// remove it quickly.
//
// [1] TODO Unfortunately, due to the desire to avoid nested locks,
// instead of simply invoking LocalPeer.ConnectionTo(name), we pass in
// the result of LocalPeer.Connections(). That is O(n_our_connections)
// at best.
func (c *GossipChannel) gcSenders(connections ConnectionSet) {
for conn, sender := range c.senders {
if _, found := connections[conn]; !found {
delete(c.senders, conn)
sender.Stop()
} else {
break
}
}
}
// We have seen a couple of failures which suggest a >128GB slice was encountered.
// 100MB should be enough for anyone.
const maxFeasibleMessageLen = 100 * 1024 * 1024
func (c *GossipChannel) sendDown(conn Connection, data GossipData) {
sender, found := c.senders[conn]
if !found {
sender = NewGossipSender(func(pending GossipData) {
for _, msg := range pending.Encode() {
if len(msg) > maxFeasibleMessageLen {
panic(fmt.Sprintf("Gossip message too large: len=%d bytes; on channel '%s' from %+v", len(msg), c.name, pending))
}
protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, msg)}
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
}
})
c.senders[conn] = sender
}
sender.Send(data)
}
func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error {
return c.relayUnicast(dstPeerName, GobEncode(c.name, c.ourself.Name, dstPeerName, msg))
}
@@ -236,6 +164,81 @@ func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) {
}
}
func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
c.routes.EnsureRecalculated()
destinations := c.routes.RandomNeighbours(srcName)
if len(destinations) == 0 {
return
}
c.sendDown(c.ourself.ConnectionsTo(destinations), data)
}
func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
c.sendDown([]Connection{conn}, data)
}
func (c *GossipChannel) sendDown(selectedConnections []Connection, data GossipData) {
if len(selectedConnections) == 0 {
return
}
connections := c.ourself.Connections()
c.Lock()
defer c.Unlock()
// GC - randomly (courtesy of go's map iterator) pick some
// existing senders and stop&remove them if the associated
// connection is no longer active. We stop as soon as we
// encounter a valid entry; the idea being that when there is
// little or no garbage then this executes close to O(1)[1],
// whereas when there is lots of garbage we remove it quickly.
//
// [1] TODO Unfortunately, due to the desire to avoid nested
// locks, instead of simply invoking LocalPeer.ConnectionTo(name),
// we operate on LocalPeer.Connections(). That is
// O(n_our_connections) at best.
for conn, sender := range c.senders {
if _, found := connections[conn]; !found {
delete(c.senders, conn)
sender.Stop()
} else {
break
}
}
// start senders, if necessary, and send.
for _, conn := range selectedConnections {
sender, found := c.senders[conn]
if !found {
sender = c.makeSender(conn)
c.senders[conn] = sender
}
sender.Send(data)
}
}
// We have seen a couple of failures which suggest a >128GB slice was encountered.
// 100MB should be enough for anyone.
const maxFeasibleMessageLen = 100 * 1024 * 1024
func (c *GossipChannel) makeSender(conn Connection) *GossipSender {
return NewGossipSender(func(pending GossipData) {
for _, msg := range pending.Encode() {
if len(msg) > maxFeasibleMessageLen {
panic(fmt.Sprintf("Gossip message too large: len=%d bytes; on channel '%s' from %+v", len(msg), c.name, pending))
}
protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, msg)}
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
}
})
}
func (c *GossipChannel) log(args ...interface{}) {
log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...)
}
func GobEncode(items ...interface{}) []byte {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
for _, i := range items {
checkFatal(enc.Encode(i))
}
return buf.Bytes()
}
+55 -87
View File
@@ -1,36 +1,40 @@
package router
package mesh
import (
"fmt"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
wt "github.com/weaveworks/weave/testing"
)
// TODO test gossip unicast; atm we only test topology gossip and
// surrogates, neither of which employ unicast.
type mockChannelConnection struct {
type mockGossipConnection struct {
RemoteConnection
dest *Router
dest *Router
start chan struct{}
}
func NewTestRouter(name string) *Router {
peerName, _ := PeerNameFromString(name)
router := NewRouter(Config{}, peerName, "nick")
router := NewRouter(Config{}, peerName, "nick", nil)
router.Start()
return router
}
func (conn *mockChannelConnection) SendProtocolMsg(protocolMsg ProtocolMsg) {
func (conn *mockGossipConnection) SendProtocolMsg(protocolMsg ProtocolMsg) {
<-conn.start
if err := conn.dest.handleGossip(protocolMsg.tag, protocolMsg.msg); err != nil {
panic(err)
}
}
func (conn *mockGossipConnection) Start() {
close(conn.start)
}
func sendPendingGossip(routers ...*Router) {
// Loop until all routers report they didn't send anything
for sentSomething := true; sentSomething; {
@@ -41,52 +45,43 @@ func sendPendingGossip(routers ...*Router) {
}
}
func (router *Router) AddTestChannelConnection(r *Router) {
fromName := router.Ourself.Peer.Name
toName := r.Ourself.Peer.Name
func AddTestGossipConnection(r1, r2 *Router) {
c1 := r1.NewTestGossipConnection(r2)
c2 := r2.NewTestGossipConnection(r1)
c1.Start()
c2.Start()
}
fromPeer := NewPeer(fromName, "", router.Ourself.Peer.UID, 0)
toPeer := NewPeer(toName, "", r.Ourself.Peer.UID, 0)
func (router *Router) NewTestGossipConnection(r *Router) *mockGossipConnection {
to := r.Ourself.Peer
toPeer := NewPeer(to.Name, to.NickName, to.UID, 0, to.ShortID)
toPeer = router.Peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount
router.Peers.FetchWithDefault(toPeer) //
conn := &mockChannelConnection{RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r}
conn := &mockGossipConnection{
RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r, make(chan struct{})}
router.Ourself.handleAddConnection(conn)
router.Ourself.handleConnectionEstablished(conn)
return conn
}
func (router *Router) DeleteTestChannelConnection(r *Router) {
fromName := router.Ourself.Peer.Name
func (router *Router) DeleteTestGossipConnection(r *Router) {
toName := r.Ourself.Peer.Name
fromPeer := r.Peers.Fetch(fromName)
toPeer := router.Peers.Fetch(toName)
r.Peers.Dereference(fromPeer)
router.Peers.Dereference(toPeer)
conn, _ := router.Ourself.ConnectionTo(toName)
router.Peers.Dereference(conn.Remote())
router.Ourself.handleDeleteConnection(conn)
}
func TestGossipTopology(t *testing.T) {
wt.RunWithTimeout(t, 5*time.Second, func() {
implTestGossipTopology(t)
})
}
// Create a Peer representing the receiver router, with connections to
// the routers supplied as arguments, carrying across all UID and
// version information.
func (router *Router) tp(routers ...*Router) *Peer {
peer := NewPeer(router.Ourself.Peer.Name, "", router.Ourself.Peer.UID, 0)
peer := NewPeerFrom(router.Ourself.Peer)
connections := make(map[PeerName]Connection)
for _, r := range routers {
p := NewPeer(r.Ourself.Peer.Name, "", r.Ourself.Peer.UID, r.Ourself.Peer.version)
p := NewPeerFrom(r.Ourself.Peer)
connections[r.Ourself.Peer.Name] = newMockConnection(peer, p)
}
peer.version = router.Ourself.Peer.version
peer.Version = router.Ourself.Peer.Version
peer.connections = connections
return peer
}
@@ -98,69 +93,46 @@ func checkTopology(t *testing.T, router *Router, wantedPeers ...*Peer) {
router.Peers.RUnlock()
}
func implTestGossipTopology(t *testing.T) {
func flushAndCheckTopology(t *testing.T, routers []*Router, wantedPeers ...*Peer) {
sendPendingGossip(routers...)
for _, r := range routers {
checkTopology(t, r, wantedPeers...)
}
}
func TestGossipTopology(t *testing.T) {
// Create some peers that will talk to each other
r1 := NewTestRouter("01:00:00:01:00:00")
r2 := NewTestRouter("02:00:00:02:00:00")
r3 := NewTestRouter("03:00:00:03:00:00")
routers := []*Router{r1, r2, r3}
// Check state when they have no connections
checkTopology(t, r1, r1.tp())
checkTopology(t, r2, r2.tp())
// Now try adding some connections
r1.AddTestChannelConnection(r2)
sendPendingGossip(r1, r2)
checkTopology(t, r1, r1.tp(r2), r2.tp())
checkTopology(t, r2, r1.tp(r2), r2.tp())
r2.AddTestChannelConnection(r1)
AddTestGossipConnection(r1, r2)
sendPendingGossip(r1, r2)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
// Currently, the connection from 2 to 3 is one-way only
r2.AddTestChannelConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp())
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp())
// When r2 gossiped to r3, 1 was unreachable from r3 so it got removed from the
// list of peers, but remains referenced in the connection from 1 to 3.
checkTopology(t, r3, r2.tp(r1, r3), r3.tp())
AddTestGossipConnection(r2, r3)
flushAndCheckTopology(t, routers, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
// Add a connection from 3 to 1 and now r1 is reachable.
r3.AddTestChannelConnection(r1)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r3, r1.tp(), r2.tp(r1, r3), r3.tp(r1))
r1.AddTestChannelConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
AddTestGossipConnection(r3, r1)
flushAndCheckTopology(t, routers, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2))
// Drop the connection from 2 to 3
r2.DeleteTestChannelConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1))
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
r2.DeleteTestGossipConnection(r3)
flushAndCheckTopology(t, routers, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2))
// Drop the connection from 1 to 3
r1.DeleteTestChannelConnection(r3)
r1.DeleteTestGossipConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
checkTopology(t, r1, r1.tp(r2), r2.tp(r1))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
// r3 still thinks r1 has a connection to it
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
// On a timer, r3 will gossip to r1
r3.SendAllGossip()
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2))
}
func TestGossipSurrogate(t *testing.T) {
@@ -168,14 +140,10 @@ func TestGossipSurrogate(t *testing.T) {
r1 := NewTestRouter("01:00:00:01:00:00")
r2 := NewTestRouter("02:00:00:02:00:00")
r3 := NewTestRouter("03:00:00:03:00:00")
r1.AddTestChannelConnection(r2)
r2.AddTestChannelConnection(r1)
r3.AddTestChannelConnection(r2)
r2.AddTestChannelConnection(r3)
sendPendingGossip(r1, r2, r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
checkTopology(t, r3, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
routers := []*Router{r1, r2, r3}
AddTestGossipConnection(r1, r2)
AddTestGossipConnection(r3, r2)
flushAndCheckTopology(t, routers, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
// create a gossiper at either end, but not the middle
g1 := newTestGossiper()
@@ -241,10 +209,10 @@ func (g *testGossiper) OnGossip(update []byte) (GossipData, error) {
g.state[v] = void
}
}
if len(delta) > 0 {
return NewSurrogateGossipData(delta), nil
if len(delta) == 0 {
return nil, nil
}
return nil, nil
return NewSurrogateGossipData(delta), nil
}
func (g *testGossiper) checkHas(t *testing.T, vs ...byte) {
+39 -66
View File
@@ -1,4 +1,4 @@
package router
package mesh
import (
"fmt"
@@ -19,7 +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, 0, 0),
Peer: NewPeer(name, nickName, randomPeerUID(), 0, randomPeerShortID()),
router: router,
actionChan: actionChan,
}
@@ -27,56 +27,6 @@ func NewLocalPeer(name PeerName, nickName string, router *Router) *LocalPeer {
return peer
}
func (peer *LocalPeer) Forward(dstPeer *Peer, frame []byte, dec *EthernetDecoder) error {
return peer.Relay(peer.Peer, dstPeer, frame, dec)
}
func (peer *LocalPeer) Broadcast(frame []byte, dec *EthernetDecoder) {
peer.RelayBroadcast(peer.Peer, frame, dec)
}
func (peer *LocalPeer) Relay(srcPeer, dstPeer *Peer, frame []byte, dec *EthernetDecoder) error {
relayPeerName, found := peer.router.Routes.Unicast(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:", dstPeer)
return nil
}
conn, found := peer.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 nil
}
return conn.(*LocalConnection).Forward(&ForwardedFrame{
srcPeer: srcPeer,
dstPeer: dstPeer,
frame: frame},
dec)
}
func (peer *LocalPeer) RelayBroadcast(srcPeer *Peer, frame []byte, dec *EthernetDecoder) {
nextHops := peer.router.Routes.Broadcast(srcPeer.Name)
if len(nextHops) == 0 {
return
}
for _, conn := range peer.ConnectionsTo(nextHops) {
err := conn.(*LocalConnection).Forward(&ForwardedFrame{
srcPeer: srcPeer,
dstPeer: conn.Remote(),
frame: frame},
dec)
if err != nil {
if ftbe, ok := err.(FrameTooBigError); ok {
log.Warningf("dropping too big DF broadcast frame (%v -> %v): PMTU= %v", dec.IP.DstIP, dec.IP.SrcIP, ftbe.EPMTU)
} else {
log.Errorln(err)
}
}
}
}
func (peer *LocalPeer) Connections() ConnectionSet {
connections := make(ConnectionSet)
peer.RLock()
@@ -112,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
}
@@ -210,7 +155,10 @@ func (peer *LocalPeer) handleAddConnection(conn Connection) error {
conn.Log("connection added (new peer)")
peer.router.SendAllGossipDown(conn)
}
peer.router.Routes.Recalculate()
peer.broadcastPeerUpdate(conn.Remote())
return nil
}
@@ -224,6 +172,8 @@ func (peer *LocalPeer) handleConnectionEstablished(conn Connection) {
}
peer.connectionEstablished(conn)
conn.Log("connection fully established")
peer.router.Routes.Recalculate()
peer.broadcastPeerUpdate()
}
@@ -243,14 +193,20 @@ func (peer *LocalPeer) handleDeleteConnection(conn Connection) {
// Must do garbage collection first to ensure we don't send out an
// update with unreachable peers (can cause looping)
peer.router.Peers.GarbageCollect()
peer.router.Routes.Recalculate()
peer.broadcastPeerUpdate()
}
// helpers
func (peer *LocalPeer) broadcastPeerUpdate(peers ...*Peer) {
peer.router.Routes.Recalculate()
peer.router.TopologyGossip.GossipBroadcast(NewTopologyGossipData(peer.router.Peers, append(peers, peer.Peer)...))
// Some tests run without a router. This should be fixed so
// that the relevant part of Router can be easily run in the
// context of a test, but that will involve significant
// reworking of tests.
if peer.router != nil {
peer.router.BroadcastTopologyUpdate(append(peers, peer.Peer))
}
}
func (peer *LocalPeer) checkConnectionLimit() error {
@@ -265,20 +221,20 @@ func (peer *LocalPeer) addConnection(conn Connection) {
peer.Lock()
defer peer.Unlock()
peer.connections[conn.Remote().Name] = conn
peer.version++
peer.Version++
}
func (peer *LocalPeer) deleteConnection(conn Connection) {
peer.Lock()
defer peer.Unlock()
delete(peer.connections, conn.Remote().Name)
peer.version++
peer.Version++
}
func (peer *LocalPeer) connectionEstablished(conn Connection) {
peer.Lock()
defer peer.Unlock()
peer.version++
peer.Version++
}
func (peer *LocalPeer) connectionCount() int {
@@ -286,3 +242,20 @@ func (peer *LocalPeer) connectionCount() int {
defer peer.RUnlock()
return len(peer.connections)
}
func (peer *LocalPeer) setShortID(shortID PeerShortID) {
peer.Lock()
defer peer.Unlock()
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
}
+8 -8
View File
@@ -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"
@@ -14,8 +14,10 @@ import (
// Add to peers a connection from peers.ourself to p
func (peers *Peers) AddTestConnection(p *Peer) {
toPeer := NewPeer(p.Name, "", p.UID, 0)
peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
summary := p.PeerSummary
summary.Version = 0
toPeer := NewPeerFromSummary(summary)
toPeer = peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
conn := newMockConnection(peers.ourself.Peer, toPeer)
peers.ourself.addConnection(conn)
peers.ourself.connectionEstablished(conn)
@@ -23,11 +25,9 @@ func (peers *Peers) AddTestConnection(p *Peer) {
// Add to peers a connection from p1 to p2
func (peers *Peers) AddTestRemoteConnection(p1, p2 *Peer) {
fromName := p1.Name
fromPeer := NewPeer(fromName, "", p1.UID, 0)
fromPeer := NewPeerFrom(p1)
fromPeer = peers.FetchWithDefault(fromPeer)
toName := p2.Name
toPeer := NewPeer(toName, "", p2.UID, 0)
toPeer := NewPeerFrom(p2)
toPeer = peers.FetchWithDefault(toPeer)
peers.ourself.addConnection(&RemoteConnection{fromPeer, toPeer, "", false, false})
}
@@ -70,7 +70,7 @@ func checkEqualConns(t *testing.T, ourName PeerName, got, wanted map[PeerName]Co
// Get all the peers from a Peers in a slice
func (peers *Peers) allPeers() []*Peer {
var res []*Peer
for _, peer := range peers.table {
for _, peer := range peers.byName {
res = append(res, peer)
}
return res
+116
View File
@@ -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"
}
+87 -17
View File
@@ -1,41 +1,111 @@
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 {
for {
uid := randUint64()
if uid != 0 { // uid 0 is reserved for peer placeholder
return PeerUID(uid)
}
}
}
func ParsePeerUID(s string) (PeerUID, error) {
uid, err := strconv.ParseUint(s, 10, 64)
return PeerUID(uid), err
}
// Short IDs exist for the sake of fast datapath. They are 12 bits,
// randomly assigned, but we detect and recover from collisions. This
// does limit us to 4096 peers, but that should be sufficient for a
// while.
type PeerShortID uint16
const PeerShortIDBits = 12
func randomPeerShortID() PeerShortID {
return PeerShortID(randUint16() & (1<<PeerShortIDBits - 1))
}
type PeerSummary struct {
NameByte []byte
NickName string
UID PeerUID
Version uint64
ShortID PeerShortID
HasShortID bool
}
type Peer struct {
Name PeerName
NameByte []byte
NickName string
UID PeerUID
version uint64
Name PeerName
PeerSummary
localRefCount uint64 // maintained by Peers
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 NewPeer(name PeerName, nickName string, uid PeerUID, version uint64) *Peer {
if uid == 0 {
uid = PeerUID(randUint64())
}
func NewPeerFromSummary(summary PeerSummary) *Peer {
return &Peer{
Name: name,
NameByte: name.Bin(),
NickName: nickName,
UID: uid,
version: version,
connections: make(map[PeerName]Connection)}
Name: PeerNameFromBin(summary.NameByte),
PeerSummary: summary,
connections: make(map[PeerName]Connection),
}
}
func NewPeer(name PeerName, nickName string, uid PeerUID, version uint64, shortID PeerShortID) *Peer {
return NewPeerFromSummary(PeerSummary{
NameByte: name.Bin(),
NickName: nickName,
UID: uid,
Version: version,
ShortID: shortID,
HasShortID: true,
})
}
func NewPeerPlaceholder(name PeerName) *Peer {
return NewPeerFromSummary(PeerSummary{NameByte: name.Bin()})
}
func NewPeerFrom(peer *Peer) *Peer {
return NewPeerFromSummary(peer.PeerSummary)
}
func (peer *Peer) String() string {
@@ -66,7 +136,7 @@ func (peer *Peer) String() string {
// NB: This function should generally be invoked while holding a read
// lock on Peers and LocalPeer.
func (peer *Peer) Routes(stopAt *Peer, establishedAndSymmetric bool) (bool, map[PeerName]PeerName) {
routes := make(map[PeerName]PeerName)
routes := make(unicastRoutes)
routes[peer.Name] = UnknownPeerName
nextWorklist := []*Peer{peer}
for len(nextWorklist) > 0 {
@@ -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"
@@ -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
}
+530
View File
@@ -0,0 +1,530 @@
package mesh
import (
"bytes"
"encoding/gob"
"io"
"math/rand"
"sync"
)
var void = struct{}{}
type Peers struct {
sync.RWMutex
ourself *LocalPeer
byName map[PeerName]*Peer
byShortID map[PeerShortID]ShortIDPeers
onGC []func(*Peer)
// Called when the mapping from short ids to peers changes
onInvalidateShortIDs []func()
}
type ShortIDPeers struct {
// If we know about a single peer with the short id, this is
// that peer. If there is a collision, this is the peer with
// the lowest Name.
peer *Peer
// In case of a collision, this holds the other peers.
others []*Peer
}
type PeerNameSet map[PeerName]struct{}
type ConnectionSummary struct {
NameByte []byte
RemoteTCPAddr string
Outbound bool
Established bool
}
// Pending notifications due to changes to Peers that need to be sent
// out once the Peers is unlocked.
type PeersPendingNotifications struct {
// Peers that have been GCed
removed []*Peer
// The mapping from shorts ids to peers changed
invalidateShortIDs bool
// The local short ID needs reassigning due to a collision
reassignLocalShortID bool
// The local peer was modified
localPeerModified bool
}
func NewPeers(ourself *LocalPeer) *Peers {
peers := &Peers{
ourself: ourself,
byName: make(map[PeerName]*Peer),
byShortID: make(map[PeerShortID]ShortIDPeers),
}
peers.FetchWithDefault(ourself.Peer)
return peers
}
func (peers *Peers) OnGC(callback func(*Peer)) {
peers.Lock()
defer peers.Unlock()
// Although the array underlying peers.onGC might be accessed
// without holding the lock in unlockAndNotify, we don't
// support removing callbacks, so a simple append here is
// safe.
peers.onGC = append(peers.onGC, callback)
}
func (peers *Peers) OnInvalidateShortIDs(callback func()) {
peers.Lock()
defer peers.Unlock()
// Safe, as in OnGC
peers.onInvalidateShortIDs = append(peers.onInvalidateShortIDs, callback)
}
func (peers *Peers) unlockAndNotify(pending *PeersPendingNotifications) {
broadcastLocalPeer := (pending.reassignLocalShortID && peers.reassignLocalShortID(pending)) ||
pending.localPeerModified
onGC := peers.onGC
onInvalidateShortIDs := peers.onInvalidateShortIDs
peers.Unlock()
if pending.removed != nil {
for _, callback := range onGC {
for _, peer := range pending.removed {
callback(peer)
}
}
}
if pending.invalidateShortIDs {
for _, callback := range onInvalidateShortIDs {
callback()
}
}
if broadcastLocalPeer {
peers.ourself.broadcastPeerUpdate()
}
}
func (peers *Peers) addByShortID(peer *Peer, pending *PeersPendingNotifications) {
if !peer.HasShortID {
return
}
entry, ok := peers.byShortID[peer.ShortID]
if !ok {
entry = ShortIDPeers{peer: peer}
} else if entry.peer == nil {
// This short ID is free, but was used in the past.
// Because we are reusing it, it's an invalidation
// event.
entry.peer = peer
pending.invalidateShortIDs = true
} else if peer.Name < entry.peer.Name {
// Short ID collision, this peer becomes the principal
// peer for the short ID, bumping the previous one
// into others.
if entry.peer == peers.ourself.Peer {
// The bumped peer is peers.ourself, so we
// need to look for a new short id
pending.reassignLocalShortID = true
}
entry.others = append(entry.others, entry.peer)
entry.peer = peer
pending.invalidateShortIDs = true
} else {
// Short ID collision, this peer is secondary
entry.others = append(entry.others, peer)
}
peers.byShortID[peer.ShortID] = entry
}
func (peers *Peers) deleteByShortID(peer *Peer, pending *PeersPendingNotifications) {
if !peer.HasShortID {
return
}
entry := peers.byShortID[peer.ShortID]
var otherIndex int
if peer != entry.peer {
// peer is secondary, find its index in others
otherIndex = -1
for i, other := range entry.others {
if peer == other {
otherIndex = i
break
}
}
if otherIndex < 0 {
return
}
} else if len(entry.others) != 0 {
// need to find the peer with the lowest name to
// become the new principal one
otherIndex = 0
minName := entry.others[0].Name
for i := 1; i < len(entry.others); i++ {
otherName := entry.others[i].Name
if otherName < minName {
minName = otherName
otherIndex = i
}
}
entry.peer = entry.others[otherIndex]
pending.invalidateShortIDs = true
} else {
// This is the last peer with the short id. We clear
// the entry, don't delete it, in order to detect when
// it gets re-used.
peers.byShortID[peer.ShortID] = ShortIDPeers{}
return
}
entry.others[otherIndex] = entry.others[len(entry.others)-1]
entry.others = entry.others[:len(entry.others)-1]
peers.byShortID[peer.ShortID] = entry
}
func (peers *Peers) reassignLocalShortID(pending *PeersPendingNotifications) bool {
newShortID, ok := peers.chooseShortID()
if ok {
peers.setLocalShortID(newShortID, pending)
return true
}
// Otherwise we'll try again later on in garbageColleect
return false
}
func (peers *Peers) setLocalShortID(newShortID PeerShortID, pending *PeersPendingNotifications) {
peers.deleteByShortID(peers.ourself.Peer, pending)
peers.ourself.setShortID(newShortID)
peers.addByShortID(peers.ourself.Peer, pending)
}
// Choose an available short id at random
func (peers *Peers) chooseShortID() (PeerShortID, bool) {
rng := rand.New(rand.NewSource(int64(randUint64())))
// First, just try picking some short ids at random, and
// seeing if they are available:
for i := 0; i < 10; i++ {
shortID := PeerShortID(rng.Intn(1 << PeerShortIDBits))
if peers.byShortID[shortID].peer == nil {
return shortID, true
}
}
// Looks like most short ids are used. So count the number of
// unused ones, and pick one at random.
available := int(1 << PeerShortIDBits)
for _, entry := range peers.byShortID {
if entry.peer != nil {
available--
}
}
if available == 0 {
// All short ids are used.
return 0, false
}
n := rng.Intn(available)
var i PeerShortID
for {
if peers.byShortID[i].peer == nil {
if n == 0 {
return PeerShortID(i), true
}
n--
}
i++
}
}
func (peers *Peers) FetchWithDefault(peer *Peer) *Peer {
peers.Lock()
var pending PeersPendingNotifications
defer peers.unlockAndNotify(&pending)
if existingPeer, found := peers.byName[peer.Name]; found {
existingPeer.localRefCount++
return existingPeer
}
peers.byName[peer.Name] = peer
peers.addByShortID(peer, &pending)
peer.localRefCount++
return peer
}
func (peers *Peers) Fetch(name PeerName) *Peer {
peers.RLock()
defer peers.RUnlock()
return peers.byName[name]
}
func (peers *Peers) FetchAndAddRef(name PeerName) *Peer {
peers.Lock()
defer peers.Unlock()
peer := peers.byName[name]
if peer != nil {
peer.localRefCount++
}
return peer
}
func (peers *Peers) FetchByShortID(shortID PeerShortID) *Peer {
peers.RLock()
defer peers.RUnlock()
return peers.byShortID[shortID].peer
}
func (peers *Peers) Dereference(peer *Peer) {
peers.Lock()
defer peers.Unlock()
peer.localRefCount--
}
func (peers *Peers) ForEach(fun func(*Peer)) {
peers.RLock()
defer peers.RUnlock()
for _, peer := range peers.byName {
fun(peer)
}
}
// Merge an incoming update with our own topology.
//
// We add peers hitherto unknown to us, and update peers for which the
// update contains a more recent version than known to us. The return
// value is a) a representation of the received update, and b) an
// "improved" update containing just these new/updated elements.
func (peers *Peers) ApplyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) {
peers.Lock()
var pending PeersPendingNotifications
defer peers.unlockAndNotify(&pending)
newPeers, decodedUpdate, decodedConns, err := peers.decodeUpdate(update)
if err != nil {
return nil, nil, err
}
// Add new peers
for name, newPeer := range newPeers {
peers.byName[name] = newPeer
peers.addByShortID(newPeer, &pending)
}
// Now apply the updates
newUpdate := peers.applyUpdate(decodedUpdate, decodedConns, &pending)
peers.garbageCollect(&pending)
for _, peerRemoved := range pending.removed {
delete(newUpdate, peerRemoved.Name)
}
updateNames := make(PeerNameSet)
for _, peer := range decodedUpdate {
updateNames[peer.Name] = void
}
return updateNames, newUpdate, nil
}
func (peers *Peers) Names() PeerNameSet {
peers.RLock()
defer peers.RUnlock()
names := make(PeerNameSet)
for name := range peers.byName {
names[name] = void
}
return names
}
func (peers *Peers) EncodePeers(names PeerNameSet) []byte {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
peers.RLock()
defer peers.RUnlock()
for name := range names {
if peer, found := peers.byName[name]; found {
if peer == peers.ourself.Peer {
peers.ourself.Encode(enc)
} else {
peer.Encode(enc)
}
}
}
return buf.Bytes()
}
func (peers *Peers) GarbageCollect() {
peers.Lock()
var pending PeersPendingNotifications
defer peers.unlockAndNotify(&pending)
peers.garbageCollect(&pending)
}
func (peers *Peers) garbageCollect(pending *PeersPendingNotifications) {
peers.ourself.RLock()
_, reached := peers.ourself.Routes(nil, false)
peers.ourself.RUnlock()
for name, peer := range peers.byName {
if _, found := reached[peer.Name]; !found && peer.localRefCount == 0 {
delete(peers.byName, name)
peers.deleteByShortID(peer, pending)
pending.removed = append(pending.removed, peer)
}
}
if peers.byShortID[peers.ourself.ShortID].peer != peers.ourself.Peer {
// The local peer doesn't own its short id. Garbage
// collection might have freed some up, so try to
// reassign.
pending.reassignLocalShortID = true
}
}
func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, decodedUpdate []*Peer, decodedConns [][]ConnectionSummary, err error) {
newPeers = make(map[PeerName]*Peer)
decodedUpdate = []*Peer{}
decodedConns = [][]ConnectionSummary{}
decoder := gob.NewDecoder(bytes.NewReader(update))
for {
peerSummary, connSummaries, decErr := decodePeer(decoder)
if decErr == io.EOF {
break
} else if decErr != nil {
err = decErr
return
}
newPeer := NewPeerFromSummary(peerSummary)
decodedUpdate = append(decodedUpdate, newPeer)
decodedConns = append(decodedConns, connSummaries)
if _, found := peers.byName[newPeer.Name]; !found {
newPeers[newPeer.Name] = newPeer
}
}
for _, connSummaries := range decodedConns {
for _, connSummary := range connSummaries {
remoteName := PeerNameFromBin(connSummary.NameByte)
if _, found := newPeers[remoteName]; found {
continue
}
if _, found := peers.byName[remoteName]; found {
continue
}
// Update refers to a peer which we have no knowledge of.
newPeers[remoteName] = NewPeerPlaceholder(remoteName)
}
}
return
}
func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]ConnectionSummary, pending *PeersPendingNotifications) PeerNameSet {
newUpdate := make(PeerNameSet)
for idx, newPeer := range decodedUpdate {
connSummaries := decodedConns[idx]
name := newPeer.Name
// guaranteed to find peer in the peers.byName
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 ||
(newPeer.UID == peer.UID &&
(!newPeer.HasShortID || peer.HasShortID)))) {
continue
}
peer.Version = newPeer.Version
peer.UID = newPeer.UID
peer.NickName = newPeer.NickName
peer.connections = makeConnsMap(peer, connSummaries, peers.byName)
if newPeer.ShortID != peer.ShortID || newPeer.HasShortID != peer.HasShortID {
peers.deleteByShortID(peer, pending)
peer.ShortID = newPeer.ShortID
peer.HasShortID = newPeer.HasShortID
peers.addByShortID(peer, pending)
}
newUpdate[name] = void
}
}
return newUpdate
}
func (peer *Peer) Encode(enc *gob.Encoder) {
checkFatal(enc.Encode(peer.PeerSummary))
connSummaries := []ConnectionSummary{}
for _, conn := range peer.connections {
connSummaries = append(connSummaries, ConnectionSummary{
conn.Remote().NameByte,
conn.RemoteTCPAddr(),
conn.Outbound(),
conn.Established(),
})
}
checkFatal(enc.Encode(connSummaries))
}
func (peer *LocalPeer) Encode(enc *gob.Encoder) {
peer.RLock()
defer peer.RUnlock()
peer.Peer.Encode(enc)
}
func decodePeer(dec *gob.Decoder) (peerSummary PeerSummary, connSummaries []ConnectionSummary, err error) {
if err = dec.Decode(&peerSummary); err != nil {
return
}
if err = dec.Decode(&connSummaries); err != nil {
return
}
return
}
func makeConnsMap(peer *Peer, connSummaries []ConnectionSummary, byName map[PeerName]*Peer) map[PeerName]Connection {
conns := make(map[PeerName]Connection)
for _, connSummary := range connSummaries {
name := PeerNameFromBin(connSummary.NameByte)
remotePeer := byName[name]
conn := NewRemoteConnection(peer, remotePeer, connSummary.RemoteTCPAddr, connSummary.Outbound, connSummary.Established)
conns[name] = conn
}
return conns
}
+376
View File
@@ -0,0 +1,376 @@
package mesh
import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/require"
)
// TODO we should also test:
//
// - applying an incremental update, including the case where that
// leads to an UnknownPeerError
//
// - the "improved update" calculation
//
// - non-gc of peers that are only referenced locally
func newNode(name PeerName) (*Peer, *Peers) {
peer := NewLocalPeer(name, "", nil)
peers := NewPeers(peer)
return peer.Peer, peers
}
// Check that ApplyUpdate copies the whole topology from peers
func checkApplyUpdate(t *testing.T, peers *Peers) {
dummyName, _ := PeerNameFromString("99:00:00:01:00:00")
// We need a new node outside of the network, with a connection
// into it.
_, testBedPeers := newNode(dummyName)
testBedPeers.AddTestConnection(peers.ourself.Peer)
testBedPeers.ApplyUpdate(peers.EncodePeers(peers.Names()))
checkTopologyPeers(t, true, testBedPeers.allPeersExcept(dummyName), peers.allPeers()...)
}
func TestPeersEncoding(t *testing.T) {
const numNodes = 20
const numIters = 1000
var peer [numNodes]*Peer
var ps [numNodes]*Peers
for i := 0; i < numNodes; i++ {
name, _ := PeerNameFromString(fmt.Sprintf("%02d:00:00:01:00:00", i))
peer[i], ps[i] = newNode(name)
}
var conns []struct{ from, to int }
for i := 0; i < numIters; i++ {
oper := rand.Intn(2)
switch oper {
case 0:
from, to := rand.Intn(numNodes), rand.Intn(numNodes)
if from != to {
if _, found := peer[from].connections[peer[to].Name]; !found {
ps[from].AddTestConnection(peer[to])
conns = append(conns, struct{ from, to int }{from, to})
checkApplyUpdate(t, ps[from])
}
}
case 1:
if len(conns) > 0 {
n := rand.Intn(len(conns))
c := conns[n]
ps[c.from].DeleteTestConnection(peer[c.to])
ps[c.from].GarbageCollect()
checkApplyUpdate(t, ps[c.from])
conns = append(conns[:n], conns[n+1:]...)
}
}
}
}
func garbageCollect(peers *Peers) []*Peer {
var removed []*Peer
peers.OnGC(func(peer *Peer) { removed = append(removed, peer) })
peers.GarbageCollect()
return removed
}
func TestPeersGarbageCollection(t *testing.T) {
const (
peer1NameString = "01:00:00:01:00:00"
peer2NameString = "02:00:00:02:00:00"
peer3NameString = "03:00:00:03:00:00"
)
var (
peer1Name, _ = PeerNameFromString(peer1NameString)
peer2Name, _ = PeerNameFromString(peer2NameString)
peer3Name, _ = PeerNameFromString(peer3NameString)
)
// Create some peers with some connections to each other
p1, ps1 := newNode(peer1Name)
p2, ps2 := newNode(peer2Name)
p3, ps3 := newNode(peer3Name)
ps1.AddTestConnection(p2)
ps2.AddTestRemoteConnection(p1, p2)
ps2.AddTestConnection(p1)
ps2.AddTestConnection(p3)
ps3.AddTestConnection(p1)
ps1.AddTestConnection(p3)
ps2.AddTestRemoteConnection(p1, p3)
ps2.AddTestRemoteConnection(p3, p1)
// Every peer is referenced, so nothing should be dropped
require.Empty(t, garbageCollect(ps1), "peers removed")
require.Empty(t, garbageCollect(ps2), "peers removed")
require.Empty(t, garbageCollect(ps3), "peers removed")
// Drop the connection from 2 to 3, and 3 isn't garbage-collected
// because 1 has a connection to 3
ps2.DeleteTestConnection(p3)
require.Empty(t, garbageCollect(ps2), "peers removed")
// Drop the connection from 1 to 3, and 3 will get removed by
// garbage-collection
ps1.DeleteTestConnection(p3)
checkPeerArray(t, garbageCollect(ps1), p3)
}
func TestShortIDCollisions(t *testing.T) {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
_, peers := newNode(PeerName(1 << PeerShortIDBits))
// Make enough peers that short id collisions are
// overwhelmingly likely
ps := make([]*Peer, 1<<PeerShortIDBits)
for i := 0; i < 1<<PeerShortIDBits; i++ {
ps[i] = NewPeer(PeerName(i), "", PeerUID(i), 0,
PeerShortID(rng.Intn(1<<PeerShortIDBits)))
}
shuffle := func() {
for i := range ps {
j := rng.Intn(i + 1)
ps[i], ps[j] = ps[j], ps[i]
}
}
// Fill peers
shuffle()
var pending PeersPendingNotifications
for _, p := range ps {
peers.addByShortID(p, &pending)
}
// Check invariants
counts := make([]int, 1<<PeerShortIDBits)
saw := func(p *Peer) {
if p != peers.ourself.Peer {
counts[p.UID]++
}
}
for shortID, entry := range peers.byShortID {
if entry.peer == nil {
// no principal peer for this short id, so
// others must be empty
require.Empty(t, entry.others)
continue
}
require.Equal(t, shortID, entry.peer.ShortID)
saw(entry.peer)
for _, p := range entry.others {
saw(p)
require.Equal(t, shortID, p.ShortID)
// the principal peer should have the lowest name
require.True(t, p.Name > entry.peer.Name)
}
}
// Check that every peer was seen
for _, n := range counts {
require.Equal(t, 1, n)
}
// Delete all the peers
shuffle()
for _, p := range ps {
peers.deleteByShortID(p, &pending)
}
for _, entry := range peers.byShortID {
if entry.peer != peers.ourself.Peer {
require.Nil(t, entry.peer)
}
require.Empty(t, entry.others)
}
}
// Test the easy case of short id reassignment, when few short ids are taken
func TestShortIDReassignmentEasy(t *testing.T) {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
_, peers := newNode(PeerName(0))
for i := 1; i <= 10; i++ {
peers.FetchWithDefault(NewPeer(PeerName(i), "", PeerUID(i), 0,
PeerShortID(rng.Intn(1<<PeerShortIDBits))))
}
checkShortIDReassignment(t, peers)
}
// Test the hard case of short id reassignment, when most short ids are taken
func TestShortIDReassignmentHard(t *testing.T) {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
_, peers := newNode(PeerName(1 << PeerShortIDBits))
// Take all short ids
ps := make([]*Peer, 1<<PeerShortIDBits)
var pending PeersPendingNotifications
for i := 0; i < 1<<PeerShortIDBits; i++ {
ps[i] = NewPeer(PeerName(i), "", PeerUID(i), 0,
PeerShortID(i))
peers.addByShortID(ps[i], &pending)
}
// As all short ids are taken, an attempted reassigment won't
// do anything
oldShortID := peers.ourself.ShortID
require.False(t, peers.reassignLocalShortID(&pending))
require.Equal(t, oldShortID, peers.ourself.ShortID)
// Free up a few ids
for i := 0; i < 10; i++ {
x := rng.Intn(len(ps))
if ps[x] != nil {
peers.deleteByShortID(ps[x], &pending)
ps[x] = nil
}
}
checkShortIDReassignment(t, peers)
}
func checkShortIDReassignment(t *testing.T, peers *Peers) {
oldShortID := peers.ourself.ShortID
peers.reassignLocalShortID(&PeersPendingNotifications{})
require.NotEqual(t, oldShortID, peers.ourself.ShortID)
require.Equal(t, peers.ourself.Peer, peers.byShortID[peers.ourself.ShortID].peer)
}
func TestShortIDInvalidation(t *testing.T) {
_, peers := newNode(PeerName(1 << PeerShortIDBits))
// need to use a short id that is not the local peer's
shortID := peers.ourself.ShortID + 1
var pending PeersPendingNotifications
requireInvalidateShortIDs := func(expect bool) {
require.Equal(t, expect, pending.invalidateShortIDs)
pending.invalidateShortIDs = false
}
// The use of a fresh short id does not cause invalidation
a := NewPeer(PeerName(1), "", PeerUID(1), 0, shortID)
peers.addByShortID(a, &pending)
requireInvalidateShortIDs(false)
// An addition which does not change the mapping
b := NewPeer(PeerName(2), "", PeerUID(2), 0, shortID)
peers.addByShortID(b, &pending)
requireInvalidateShortIDs(false)
// An addition which does change the mapping
c := NewPeer(PeerName(0), "", PeerUID(0), 0, shortID)
peers.addByShortID(c, &pending)
requireInvalidateShortIDs(true)
// A deletion which does not change the mapping
peers.deleteByShortID(b, &pending)
requireInvalidateShortIDs(false)
// A deletion which does change the mapping
peers.deleteByShortID(c, &pending)
requireInvalidateShortIDs(true)
// Deleting the last peer with a short id does not cause invalidation
peers.deleteByShortID(a, &pending)
requireInvalidateShortIDs(false)
// .. but subsequent reuse of that short id does cause invalidation
peers.addByShortID(a, &pending)
requireInvalidateShortIDs(true)
}
func TestShortIDPropagation(t *testing.T) {
_, peers1 := newNode(PeerName(1))
_, peers2 := newNode(PeerName(2))
peers1.AddTestConnection(peers2.ourself.Peer)
peers1.ApplyUpdate(peers2.EncodePeers(peers2.Names()))
peers12 := peers1.Fetch(PeerName(2))
old := peers12.PeerSummary
require.True(t,
peers2.reassignLocalShortID(&PeersPendingNotifications{}))
peers1.ApplyUpdate(peers2.EncodePeers(peers2.Names()))
require.NotEqual(t, old.Version, peers12.Version)
require.NotEqual(t, old.ShortID, peers12.ShortID)
}
func TestShortIDCollision(t *testing.T) {
// Create 3 peers
_, peers1 := newNode(PeerName(1))
_, peers2 := newNode(PeerName(2))
_, peers3 := newNode(PeerName(3))
var pending PeersPendingNotifications
peers1.setLocalShortID(1, &pending)
peers2.setLocalShortID(2, &pending)
peers3.setLocalShortID(3, &pending)
peers2.AddTestConnection(peers1.ourself.Peer)
peers3.AddTestConnection(peers2.ourself.Peer)
// Propogate from 1 to 2 to 3
peers2.ApplyUpdate(peers1.EncodePeers(peers1.Names()))
peers3.ApplyUpdate(peers2.EncodePeers(peers2.Names()))
// Force the short id of peer 1 to collide with peer 2. Peer
// 1 has the lowest name, so it gets to keep the short id
peers1.setLocalShortID(2, &pending)
oldShortID := peers2.ourself.ShortID
_, updated, _ := peers2.ApplyUpdate(peers1.EncodePeers(peers1.Names()))
// peer 2 should have noticed the collision and resolved it
require.NotEqual(t, oldShortID, peers2.ourself.ShortID)
// The Peers do not have a Router, so broadcastPeerUpdate does
// nothing in the context of this test. So we fake what it
// would do.
updated[PeerName(2)] = void
// the update from peer 2 should include its short id change
peers3.ApplyUpdate(peers2.EncodePeers(updated))
require.Equal(t, peers2.ourself.ShortID,
peers3.Fetch(PeerName(2)).ShortID)
}
// Test the case where all short ids are taken, but then some peers go
// away, so the local peer reassigns
func TestDeferredShortIDReassignment(t *testing.T) {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
_, us := newNode(PeerName(1 << PeerShortIDBits))
// Connect us to other peers occupying all short ids
others := make([]*Peers, 1<<PeerShortIDBits)
var pending PeersPendingNotifications
for i := range others {
_, others[i] = newNode(PeerName(i))
others[i].setLocalShortID(PeerShortID(i), &pending)
us.AddTestConnection(others[i].ourself.Peer)
}
// Check that, as expected, the local peer does not own its
// short id
require.NotEqual(t, us.ourself.Peer,
us.byShortID[us.ourself.ShortID].peer)
// Disconnect one peer, and we should now be able to claim its
// short id
other := others[rng.Intn(1<<PeerShortIDBits)]
us.DeleteTestConnection(other.ourself.Peer)
us.GarbageCollect()
require.Equal(t, us.ourself.Peer, us.byShortID[us.ourself.ShortID].peer)
}
+6 -5
View File
@@ -1,4 +1,4 @@
package router
package mesh
import (
"bytes"
@@ -328,13 +328,14 @@ func (res *ProtocolIntroResults) setupCrypto(params ProtocolIntroParams, remoteP
type ProtocolTag byte
const (
ProtocolHeartbeat ProtocolTag = iota
ProtocolConnectionEstablished
ProtocolFragmentationReceived
ProtocolPMTUVerified
ProtocolHeartbeat = iota
ProtocolReserved1 // legacy overlay control messages
ProtocolReserved2 //
ProtocolReserved3 //
ProtocolGossip
ProtocolGossipUnicast
ProtocolGossipBroadcast
ProtocolOverlayControlMsg
)
type ProtocolMsg struct {
+180
View File
@@ -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
}
@@ -1,4 +1,4 @@
package router
package mesh
import (
"io"
+198
View File
@@ -0,0 +1,198 @@
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
TrustedSubnets []*net.IPNet
}
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 (d *TopologyGossipData) Merge(other GossipData) GossipData {
names := make(PeerNameSet)
for name := range d.update {
names[name] = void
}
for name := range other.(*TopologyGossipData).update {
names[name] = void
}
return &TopologyGossipData{peers: d.peers, update: names}
}
func (d *TopologyGossipData) Encode() [][]byte {
return [][]byte{d.peers.EncodePeers(d.update)}
}
func (router *Router) BroadcastTopologyUpdate(update []*Peer) {
names := make(PeerNameSet)
for _, p := range update {
names[p.Name] = void
}
router.TopologyGossip.GossipBroadcast(
&TopologyGossipData{peers: router.Peers, update: names})
}
func (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 err != nil {
return nil, nil, err
}
if len(newUpdate) > 0 {
router.ConnectionMaker.Refresh()
router.Routes.Recalculate()
}
return origUpdate, newUpdate, nil
}
func (router *Router) Trusts(remote *RemoteConnection) bool {
if tcpAddr, err := net.ResolveTCPAddr("tcp4", remote.remoteTCPAddr); err == nil {
for _, trustedSubnet := range router.TrustedSubnets {
if trustedSubnet.Contains(tcpAddr.IP) {
return true
}
}
} else {
// Should not happen as remoteTCPAddr was obtained from TCPConn
log.Errorf("Unable to parse remote TCP addr: %s", err)
}
return false
}
+94 -27
View File
@@ -1,18 +1,22 @@
package router
package mesh
import (
"math"
"sync"
)
type unicastRoutes map[PeerName]PeerName
type broadcastRoutes map[PeerName][]PeerName
type Routes struct {
sync.RWMutex
ourself *LocalPeer
peers *Peers
unicast map[PeerName]PeerName
unicastAll map[PeerName]PeerName // [1]
broadcast map[PeerName][]PeerName
broadcastAll map[PeerName][]PeerName // [1]
onChange []func()
unicast unicastRoutes
unicastAll unicastRoutes // [1]
broadcast broadcastRoutes
broadcastAll broadcastRoutes // [1]
recalculate chan<- *struct{}
wait chan<- chan struct{}
// [1] based on *all* connections, not just established &
@@ -25,10 +29,10 @@ func NewRoutes(ourself *LocalPeer, peers *Peers) *Routes {
routes := &Routes{
ourself: ourself,
peers: peers,
unicast: make(map[PeerName]PeerName),
unicastAll: make(map[PeerName]PeerName),
broadcast: make(map[PeerName][]PeerName),
broadcastAll: make(map[PeerName][]PeerName),
unicast: make(unicastRoutes),
unicastAll: make(unicastRoutes),
broadcast: make(broadcastRoutes),
broadcastAll: make(broadcastRoutes),
recalculate: recalculate,
wait: wait}
routes.unicast[ourself.Name] = UnknownPeerName
@@ -39,6 +43,12 @@ func NewRoutes(ourself *LocalPeer, peers *Peers) *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()
}
@@ -60,21 +70,13 @@ func (routes *Routes) UnicastAll(name PeerName) (PeerName, bool) {
func (routes *Routes) Broadcast(name PeerName) []PeerName {
routes.RLock()
defer routes.RUnlock()
hops, found := routes.broadcast[name]
if !found {
return []PeerName{}
}
return hops
return routes.broadcast[name]
}
func (routes *Routes) BroadcastAll(name PeerName) []PeerName {
routes.RLock()
defer routes.RUnlock()
hops, found := routes.broadcastAll[name]
if !found {
return []PeerName{}
}
return hops
return routes.broadcastAll[name]
}
// Choose min(log2(n_peers), n_neighbouring_peers) neighbours, with a
@@ -90,20 +92,24 @@ func (routes *Routes) BroadcastAll(name PeerName) []PeerName {
// returns a higher proportion of neighbours than elsewhere. In
// extremis, on peers with fewer than log2(n_peers) neighbours, all
// neighbours are returned.
func (routes *Routes) RandomNeighbours(except PeerName) PeerNameSet {
res := make(PeerNameSet)
func (routes *Routes) RandomNeighbours(except PeerName) []PeerName {
destinations := make(PeerNameSet)
routes.RLock()
defer routes.RUnlock()
count := int(math.Log2(float64(len(routes.unicastAll))))
// depends on go's random map iteration
for _, dst := range routes.unicastAll {
if dst != UnknownPeerName && dst != except {
res[dst] = void
if len(res) >= count {
destinations[dst] = void
if len(destinations) >= count {
break
}
}
}
res := make([]PeerName, 0, len(destinations))
for dst := range destinations {
res = append(res, dst)
}
return res
}
@@ -147,6 +153,8 @@ func (routes *Routes) calculate() {
routes.peers.RLock()
routes.ourself.RLock()
var (
oldUnicast = routes.unicast
oldBroadcast = routes.broadcast
unicast = routes.calculateUnicast(true)
unicastAll = routes.calculateUnicast(false)
broadcast = routes.calculateBroadcast(true)
@@ -160,7 +168,14 @@ 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) {
for _, callback := range onChange {
callback()
}
}
}
// Calculate all the routes for the question: if *we* want to send a
@@ -172,7 +187,7 @@ func (routes *Routes) calculate() {
// any knowledge of the MAC address at all. Thus there's no need
// to exchange knowledge of MAC addresses, nor any constraints on
// the routes that we construct.
func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) map[PeerName]PeerName {
func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) unicastRoutes {
_, unicast := routes.ourself.Routes(nil, establishedAndSymmetric)
return unicast
}
@@ -195,9 +210,9 @@ func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) map[PeerNam
// Y =/= Z /\ X.Routes(Y) <= X.Routes(Z) =>
// X.Routes(Y) u [P | Y.HasSymmetricConnectionTo(P)] <= X.Routes(Z)
// where <= is the subset relationship on keys of the returned map.
func (routes *Routes) calculateBroadcast(establishedAndSymmetric bool) map[PeerName][]PeerName {
broadcast := make(map[PeerName][]PeerName)
for _, peer := range routes.peers.table {
func (routes *Routes) calculateBroadcast(establishedAndSymmetric bool) broadcastRoutes {
broadcast := make(broadcastRoutes)
for _, peer := range routes.peers.byName {
hops := []PeerName{}
if found, reached := peer.Routes(routes.ourself.Peer, establishedAndSymmetric); found {
routes.ourself.ForEachConnectedPeer(establishedAndSymmetric, reached,
@@ -207,3 +222,55 @@ func (routes *Routes) calculateBroadcast(establishedAndSymmetric bool) map[PeerN
}
return broadcast
}
func (a unicastRoutes) equals(b unicastRoutes) bool {
for key, aval := range a {
if bval, ok := b[key]; !ok || bval != aval {
return false
}
}
for key := range b {
if _, ok := a[key]; !ok {
return false
}
}
return true
}
func (a broadcastRoutes) equals(b broadcastRoutes) bool {
set := make(map[PeerName]struct{})
for key, aval := range a {
bval, ok := b[key]
if !ok {
return false
}
for _, peer := range aval {
set[peer] = struct{}{}
}
for _, peer := range bval {
if _, ok := set[peer]; !ok {
return false
}
delete(set, peer)
}
if len(set) != 0 {
return false
}
}
for key := range b {
if _, ok := a[key]; !ok {
return false
}
}
return true
}
+49 -63
View File
@@ -1,7 +1,8 @@
package router
package mesh
import (
"time"
"fmt"
"net"
)
type Status struct {
@@ -13,27 +14,20 @@ type Status struct {
Name string
NickName string
Port int
Interface string
CaptureStats map[string]int
MACs []MACStatus
Peers []PeerStatus
UnicastRoutes []UnicastRouteStatus
BroadcastRoutes []BroadcastRouteStatus
Connections []LocalConnectionStatus
Targets []string
}
type MACStatus struct {
Mac string
Name string
NickName string
LastSeen time.Time
OverlayDiagnostics interface{}
TrustedSubnets []string
}
type PeerStatus struct {
Name string
NickName string
UID PeerUID
ShortID PeerShortID
Version uint64
Connections []ConnectionStatus
}
@@ -63,14 +57,6 @@ type LocalConnectionStatus struct {
}
func NewStatus(router *Router) *Status {
var ifaceName string
if router.Iface != nil {
ifaceName = router.Iface.Name
}
var captureStats map[string]int
if router.PacketSource != nil {
captureStats = router.PacketSource.Stats()
}
return &Status{
Protocol,
ProtocolMinVersion,
@@ -80,30 +66,13 @@ func NewStatus(router *Router) *Status {
router.Ourself.Name.String(),
router.Ourself.NickName,
router.Port,
ifaceName,
captureStats,
NewMACStatusSlice(router.Macs),
NewPeerStatusSlice(router.Peers),
NewUnicastRouteStatusSlice(router.Routes),
NewBroadcastRouteStatusSlice(router.Routes),
NewLocalConnectionStatusSlice(router.ConnectionMaker),
NewTargetSlice(router.ConnectionMaker)}
}
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
NewTargetSlice(router.ConnectionMaker),
router.Overlay.Diagnostics(),
NewTrustedSubnetsSlice(router.TrustedSubnets)}
}
func NewPeerStatusSlice(peers *Peers) []PeerStatus {
@@ -127,7 +96,8 @@ func NewPeerStatusSlice(peers *Peers) []PeerStatus {
peer.Name.String(),
peer.NickName,
peer.UID,
peer.version,
peer.ShortID,
peer.Version,
connections})
})
@@ -170,40 +140,48 @@ func NewBroadcastRouteStatusSlice(routes *Routes) []BroadcastRouteStatus {
}
func NewLocalConnectionStatusSlice(cm *ConnectionMaker) []LocalConnectionStatus {
// We need to Refresh first in order to clear out any 'attempting'
// connections from cm.targets that have been established since
// the last run of cm.checkStateAndAttemptConnections. These
// entries are harmless but do represent stale state that we do
// not want to report.
cm.Refresh()
resultChan := make(chan []LocalConnectionStatus, 0)
cm.actionChan <- func() bool {
var slice []LocalConnectionStatus
for conn := range cm.ourself.Connections() {
for conn := range cm.connections {
state := "pending"
if conn.Established() {
state = "established"
}
slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, conn.Remote().String()})
lc, _ := conn.(*LocalConnection)
info := fmt.Sprintf("%-6v %v", lc.OverlayConn.DisplayName(), conn.Remote())
if lc.Router.UsingPassword() {
if lc.Untrusted() {
info = fmt.Sprintf("%-11v %v", "encrypted", info)
} else {
info = fmt.Sprintf("%-11v %v", "unencrypted", info)
}
}
slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, info})
}
for address, target := range cm.targets {
var state, info string
switch {
case target.lastError == nil:
state = "connecting"
info = ""
case target.attempting:
state = "retrying"
info = target.lastError.Error()
default:
state = "failed"
retry := "never"
add := func(state, info string) {
slice = append(slice, LocalConnectionStatus{address, true, state, info})
}
switch target.state {
case TargetWaiting:
until := "never"
if !target.tryAfter.IsZero() {
retry = target.tryAfter.String()
until = target.tryAfter.String()
}
info = target.lastError.Error() + ", retry: " + retry
if target.lastError == nil { // shouldn't happen
add("waiting", "until: "+until)
} else {
add("failed", target.lastError.Error()+", retry: "+until)
}
case TargetAttempting:
if target.lastError == nil {
add("connecting", "")
} else {
add("retrying", target.lastError.Error())
}
case TargetConnected:
}
slice = append(slice, LocalConnectionStatus{address, true, state, info})
}
resultChan <- slice
return false
@@ -223,3 +201,11 @@ func NewTargetSlice(cm *ConnectionMaker) []string {
}
return <-resultChan
}
func NewTrustedSubnetsSlice(trustedSubnets []*net.IPNet) []string {
trustedSubnetStrs := []string{}
for _, trustedSubnet := range trustedSubnets {
trustedSubnetStrs = append(trustedSubnetStrs, trustedSubnet.String())
}
return trustedSubnetStrs
}
@@ -1,4 +1,4 @@
package router
package mesh
type SurrogateGossipData struct {
messages [][]byte
@@ -12,8 +12,12 @@ func (d *SurrogateGossipData) Encode() [][]byte {
return d.messages
}
func (d *SurrogateGossipData) Merge(other GossipData) {
d.messages = append(d.messages, other.(*SurrogateGossipData).messages...)
func (d *SurrogateGossipData) Merge(other GossipData) GossipData {
o := other.(*SurrogateGossipData)
messages := make([][]byte, 0, len(d.messages)+len(o.messages))
messages = append(messages, d.messages...)
messages = append(messages, o.messages...)
return &SurrogateGossipData{messages: messages}
}
// SurrogateGossiper ignores unicasts and relays broadcasts and gossips.
@@ -1,4 +1,4 @@
package router
package mesh
import (
"time"
+1 -1
View File
@@ -216,7 +216,7 @@ func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
reqCopy := req.Copy()
reqCopy.Id = dns.Id()
response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, h.upstream.Port))
if err != nil || response == nil {
if (err != nil && err != dns.ErrTruncated) || response == nil {
h.ns.debugf("error trying %s: %v", server, err)
continue
}
+9 -9
View File
@@ -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, 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,20 +39,20 @@ 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) *dns.Msg {
doRequest := func(client *dns.Client, request *dns.Msg, port int, expectedErr error) *dns.Msg {
request.SetQuestion("foo.weave.local.", dns.TypeA)
response, _, err := client.Exchange(request, fmt.Sprintf("127.0.0.1:%d", port))
require.Nil(t, err)
require.Equal(t, expectedErr, err)
return response
}
// do a udp query, ensure we get a truncated response
{
udpClient := dns.Client{Net: "udp", UDPSize: minUDPSize}
response := doRequest(&udpClient, &dns.Msg{}, udpPort)
response := doRequest(&udpClient, &dns.Msg{}, udpPort, dns.ErrTruncated)
require.True(t, response.MsgHdr.Truncated)
require.True(t, len(response.Answer) < 100)
}
@@ -62,7 +62,7 @@ func TestTruncation(t *testing.T) {
udpClient := dns.Client{Net: "udp", UDPSize: 65535}
request := &dns.Msg{}
request.SetEdns0(65535, false)
response := doRequest(&udpClient, request, udpPort)
response := doRequest(&udpClient, request, udpPort, nil)
require.False(t, response.MsgHdr.Truncated)
require.Equal(t, len(response.Answer), 100)
}
@@ -70,7 +70,7 @@ func TestTruncation(t *testing.T) {
// do a tcp query, ensure we don't get a truncated response
{
tcpClient := dns.Client{Net: "tcp"}
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort)
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort, nil)
require.False(t, response.MsgHdr.Truncated)
require.Equal(t, len(response.Answer), 100)
}
+111 -38
View File
@@ -5,26 +5,46 @@ import (
"encoding/gob"
"fmt"
"sort"
"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
Hostname string // as supplied
lHostname string // lowercased (not exported, so not encoded by gob)
Version int
Tombstone int64 // timestamp of when it was deleted
}
type Entries []Entry
type CaseSensitive Entries
type CaseInsensitive Entries
type SortableEntries interface {
sort.Interface
Get(i int) Entry
}
func (e1 *Entry) equal(e2 *Entry) bool {
// Gossip messages are sorted in a case sensitive order...
func (es CaseSensitive) Len() int { return len(es) }
func (es CaseSensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
func (es CaseSensitive) Get(i int) Entry { return es[i] }
func (es CaseSensitive) Less(i, j int) bool { return es[i].less(&es[j]) }
// ... but we store entries in a case insensitive order.
func (es CaseInsensitive) Len() int { return len(es) }
func (es CaseInsensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
func (es CaseInsensitive) Get(i int) Entry { return es[i] }
func (es CaseInsensitive) Less(i, j int) bool { return es[i].insensitiveLess(&es[j]) }
func (e1 Entry) equal(e2 Entry) bool {
return e1.ContainerID == e2.ContainerID &&
e1.Origin == e2.Origin &&
e1.Addr == e2.Addr &&
@@ -48,6 +68,24 @@ func (e1 *Entry) less(e2 *Entry) bool {
}
}
func (e1 *Entry) insensitiveLess(e2 *Entry) bool {
// Entries are kept sorted by Hostname, Origin, ContainerID then address
e1Hostname, e2Hostname := e1.lHostname, e2.lHostname
switch {
case e1Hostname != e2Hostname:
return e1Hostname < e2Hostname
case e1.Origin != e2.Origin:
return e1.Origin < e2.Origin
case e1.ContainerID != e2.ContainerID:
return e1.ContainerID < e2.ContainerID
default:
return e1.Addr < e2.Addr
}
}
// returns true to indicate a change
func (e1 *Entry) merge(e2 *Entry) bool {
// we know container id, origin, add and hostname are equal
@@ -63,37 +101,42 @@ func (e1 *Entry) String() string {
return fmt.Sprintf("%s -> %s", e1.Hostname, e1.Addr.String())
}
func (es Entries) Len() int { return len(es) }
func (es Entries) Swap(i, j int) { panic("Swap") }
func (es Entries) Less(i, j int) bool { return es[i].less(&es[j]) }
func (e1 *Entry) addLowercase() {
e1.lHostname = strings.ToLower(e1.Hostname)
}
func (es Entries) check() error {
func check(es SortableEntries) error {
if !sort.IsSorted(es) {
return fmt.Errorf("Not sorted!")
}
for i := 1; i < len(es); i++ {
if es[i].equal(&es[i-1]) {
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es[i-1], i, es[i])
for i := 1; i < es.Len(); i++ {
if es.Get(i).equal(es.Get(i - 1)) {
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es.Get(i-1), i, es.Get(i))
}
}
return nil
}
func (es *Entries) checkAndPanic() {
if err := es.check(); err != nil {
func checkAndPanic(es SortableEntries) {
if err := check(es); err != nil {
panic(err)
}
}
func (es *Entries) add(hostname, containerid string, origin router.PeerName, addr address.Address) Entry {
es.checkAndPanic()
defer es.checkAndPanic()
func (es *Entries) checkAndPanic() *Entries {
checkAndPanic(CaseInsensitive(*es))
return es
}
entry := Entry{Hostname: hostname, Origin: origin, ContainerID: containerid, Addr: addr}
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),
Origin: origin, ContainerID: containerid, Addr: addr}
i := sort.Search(len(*es), func(i int) bool {
return !(*es)[i].less(&entry)
return !(*es)[i].insensitiveLess(&entry)
})
if i < len(*es) && (*es)[i].equal(&entry) {
if i < len(*es) && (*es)[i].equal(entry) {
if (*es)[i].Tombstone > 0 {
(*es)[i].Tombstone = 0
(*es)[i].Version++
@@ -107,16 +150,17 @@ func (es *Entries) add(hostname, containerid string, origin router.PeerName, add
}
func (es *Entries) merge(incoming Entries) Entries {
es.checkAndPanic()
defer es.checkAndPanic()
defer es.checkAndPanic().checkAndPanic()
incoming.checkAndPanic()
newEntries := Entries{}
i := 0
for _, entry := range incoming {
for i < len(*es) && (*es)[i].less(&entry) {
for i < len(*es) && (*es)[i].insensitiveLess(&entry) {
i++
}
if i < len(*es) && (*es)[i].equal(&entry) {
if i < len(*es) && (*es)[i].equal(entry) {
if (*es)[i].merge(&entry) {
newEntries = append(newEntries, entry)
}
@@ -132,9 +176,9 @@ 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 {
es.checkAndPanic()
defer es.checkAndPanic()
func (es *Entries) tombstone(ourname mesh.PeerName, f func(*Entry) bool) Entries {
defer es.checkAndPanic().checkAndPanic()
tombstoned := Entries{}
for i, e := range *es {
if f(&e) && e.Origin == ourname {
@@ -148,8 +192,8 @@ func (es *Entries) tombstone(ourname router.PeerName, f func(*Entry) bool) Entri
}
func (es *Entries) filter(f func(*Entry) bool) {
es.checkAndPanic()
defer es.checkAndPanic()
defer es.checkAndPanic().checkAndPanic()
i := 0
for _, e := range *es {
if !f(&e) {
@@ -163,23 +207,26 @@ func (es *Entries) filter(f func(*Entry) bool) {
func (es Entries) lookup(hostname string) Entries {
es.checkAndPanic()
lowerHostname := strings.ToLower(hostname)
i := sort.Search(len(es), func(i int) bool {
return es[i].Hostname >= hostname
return es[i].lHostname >= lowerHostname
})
if i >= len(es) || es[i].Hostname != hostname {
if i >= len(es) || es[i].lHostname != lowerHostname {
return Entries{}
}
j := sort.Search(len(es)-i, func(j int) bool {
return es[i+j].Hostname > hostname
return es[i+j].lHostname > lowerHostname
})
return es[i : i+j]
}
func (es *Entries) first(f func(*Entry) bool) (*Entry, error) {
func (es Entries) first(f func(*Entry) bool) (*Entry, error) {
es.checkAndPanic()
for _, e := range *es {
for _, e := range es {
if f(&e) {
return &e, nil
}
@@ -187,23 +234,49 @@ func (es *Entries) first(f func(*Entry) bool) (*Entry, error) {
return nil, fmt.Errorf("Not found")
}
func (es Entries) addLowercase() {
for i := range es {
es[i].addLowercase()
}
}
type GossipData struct {
Timestamp int64
Entries
}
func (g *GossipData) Merge(o router.GossipData) {
func (g *GossipData) Merge(o mesh.GossipData) mesh.GossipData {
other := o.(*GossipData)
g.Entries.merge(other.Entries)
if g.Timestamp < other.Timestamp {
g.Timestamp = other.Timestamp
gossip := g.copy()
gossip.Entries.merge(other.Entries)
if gossip.Timestamp < other.Timestamp {
gossip.Timestamp = other.Timestamp
}
return gossip
}
func (g *GossipData) Decode(msg []byte) error {
if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(g); err != nil {
return err
}
g.Entries.addLowercase() // lowercase strings not sent on the wire
sort.Sort(CaseInsensitive(g.Entries))
return nil
}
func (g *GossipData) Encode() [][]byte {
g2 := g.copy()
sort.Sort(CaseSensitive(g2.Entries))
buf := &bytes.Buffer{}
if err := gob.NewEncoder(buf).Encode(g); err != nil {
if err := gob.NewEncoder(buf).Encode(g2); err != nil {
panic(err)
}
return [][]byte{buf.Bytes()}
}
func (g *GossipData) copy() *GossipData {
g2 := &GossipData{Timestamp: g.Timestamp, Entries: make(Entries, len(g.Entries))}
copy(g2.Entries, g.Entries)
return g2
}
+55 -64
View File
@@ -5,79 +5,70 @@ 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 {
es.addLowercase()
return es
}
func makeEntries(values string) Entries {
entries := make(Entries, len(values))
for i, c := range values {
entries[i] = Entry{Hostname: string(c)}
}
return l(entries)
}
func TestAdd(t *testing.T) {
oldNow := now
defer func() { now = oldNow }()
now = func() int64 { return 1234 }
entries := Entries{}
entries.add("A", "", router.UnknownPeerName, address.Address(0))
expected := Entries{
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0)},
}
entries.add("A", "", mesh.UnknownPeerName, address.Address(0))
expected := l(Entries{
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" })
expected = Entries{
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 1, Tombstone: 1234},
}
entries.tombstone(mesh.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "A" })
expected = l(Entries{
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))
expected = Entries{
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 2},
}
entries.add("A", "", mesh.UnknownPeerName, address.Address(0))
expected = l(Entries{
Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0), Version: 2},
})
require.Equal(t, entries, expected)
}
func TestMerge(t *testing.T) {
e1 := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "C"},
Entry{Hostname: "D"},
Entry{Hostname: "F"},
}
e2 := Entries{
Entry{Hostname: "B"},
Entry{Hostname: "E"},
Entry{Hostname: "F"},
}
e1 := makeEntries("ACDF")
e2 := makeEntries("BEF")
diff := e1.merge(e2)
expectedDiff := Entries{
Entry{Hostname: "B"},
Entry{Hostname: "E"},
}
require.Equal(t, expectedDiff, diff)
expected := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B"},
Entry{Hostname: "C"},
Entry{Hostname: "D"},
Entry{Hostname: "E"},
Entry{Hostname: "F"},
}
require.Equal(t, expected, e1)
require.Equal(t, makeEntries("BE"), diff)
require.Equal(t, makeEntries("ABCDEF"), e1)
diff = e1.merge(e1)
require.Equal(t, Entries{}, diff)
}
func TestOldMerge(t *testing.T) {
e1 := Entries{Entry{Hostname: "A", Version: 0}}
diff := e1.merge(Entries{Entry{Hostname: "A", Version: 1}})
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, diff)
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
e1 := l(Entries{Entry{Hostname: "A", Version: 0}})
diff := e1.merge(l(Entries{Entry{Hostname: "A", Version: 1}}))
require.Equal(t, l(Entries{Entry{Hostname: "A", Version: 1}}), diff)
require.Equal(t, l(Entries{Entry{Hostname: "A", Version: 1}}), e1)
diff = e1.merge(Entries{Entry{Hostname: "A", Version: 0}})
diff = e1.merge(l(Entries{Entry{Hostname: "A", Version: 0}}))
require.Equal(t, Entries{}, diff)
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
require.Equal(t, l(Entries{Entry{Hostname: "A", Version: 1}}), e1)
}
func TestTombstone(t *testing.T) {
@@ -85,48 +76,48 @@ func TestTombstone(t *testing.T) {
defer func() { now = oldNow }()
now = func() int64 { return 1234 }
es := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B"},
}
es := makeEntries("AB")
es.tombstone(router.UnknownPeerName, func(e *Entry) bool {
es.tombstone(mesh.UnknownPeerName, func(e *Entry) bool {
return e.Hostname == "B"
})
expected := Entries{
expected := l(Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B", Version: 1, Tombstone: 1234},
}
})
require.Equal(t, expected, es)
}
func TestDelete(t *testing.T) {
es := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B"},
}
es := makeEntries("AB")
es.filter(func(e *Entry) bool {
return e.Hostname != "A"
})
expected := Entries{
Entry{Hostname: "B"},
}
require.Equal(t, expected, es)
require.Equal(t, makeEntries("B"), es)
}
func TestLookup(t *testing.T) {
es := Entries{
es := l(Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B", ContainerID: "bar"},
Entry{Hostname: "B", ContainerID: "foo"},
Entry{Hostname: "C"},
}
})
have := es.lookup("B")
want := Entries{
want := l(Entries{
Entry{Hostname: "B", ContainerID: "bar"},
Entry{Hostname: "B", ContainerID: "foo"},
}
})
require.Equal(t, have, want)
}
func TestGossipDataMerge(t *testing.T) {
g1 := GossipData{Entries: makeEntries("AcDf")}
g2 := GossipData{Entries: makeEntries("BEf")}
g3 := g1.Merge(&g2).(*GossipData)
require.Equal(t, GossipData{Entries: makeEntries("ABcDEf")}, *g3)
}
+8 -2
View File
@@ -8,6 +8,7 @@ import (
"github.com/gorilla/mux"
"github.com/miekg/dns"
"github.com/weaveworks/weave/common/docker"
"github.com/weaveworks/weave/net/address"
)
@@ -16,7 +17,7 @@ func (n *Nameserver) badRequest(w http.ResponseWriter, err error) {
n.infof("%v", err)
}
func (n *Nameserver) HandleHTTP(router *mux.Router) {
func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) {
router.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, n.domain)
})
@@ -34,12 +35,17 @@ func (n *Nameserver) HandleHTTP(router *mux.Router) {
return
}
if !dns.IsSubDomain(n.domain, hostname) {
n.infof("Ignoring registration %s %s %s (not a subdomain of %s)", hostname, ipStr, container, n.domain)
return
}
if err := n.AddEntry(hostname, container, n.ourName, ip); err != nil {
n.badRequest(w, fmt.Errorf("Unable to add entry: %v", err))
return
}
if r.FormValue("check-alive") == "true" && n.docker.IsContainerNotRunning(container) {
if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(container) {
n.infof("container '%s' is not running: removing", container)
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
n.infof("failed to remove: %v", err)
+36 -52
View File
@@ -1,8 +1,6 @@
package nameserver
import (
"bytes"
"encoding/gob"
"fmt"
"sync"
"time"
@@ -10,9 +8,8 @@ import (
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
"github.com/weaveworks/weave/mesh"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
const (
@@ -37,30 +34,24 @@ const (
// - Update is O(n) for now
type Nameserver struct {
sync.RWMutex
ourName router.PeerName
domain string
gossip router.Gossip
docker *docker.Client
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, docker *docker.Client, domain string) *Nameserver {
ns := &Nameserver{
ourName: ourName,
domain: dns.Fqdn(domain),
peers: peers,
docker: docker,
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
}
@@ -83,16 +74,16 @@ func (n *Nameserver) Stop() {
}
func (n *Nameserver) broadcastEntries(es ...Entry) error {
if n.gossip != nil {
return n.gossip.GossipBroadcast(&GossipData{
Entries: Entries(es),
Timestamp: now(),
})
if n.gossip == nil || len(es) == 0 {
return nil
}
return nil
return n.gossip.GossipBroadcast(&GossipData{
Entries: Entries(es),
Timestamp: now(),
})
}
func (n *Nameserver) AddEntry(hostname, containerid string, origin 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)
@@ -130,6 +121,8 @@ func (n *Nameserver) ReverseLookup(ip address.Address) (string, error) {
return match.Hostname, nil
}
func (n *Nameserver) ContainerStarted(ident string) {}
func (n *Nameserver) ContainerDied(ident string) {
n.Lock()
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
@@ -140,19 +133,17 @@ func (n *Nameserver) ContainerDied(ident string) {
return false
})
n.Unlock()
if len(entries) > 0 {
if err := n.broadcastEntries(entries...); err != nil {
n.errorf("failed to broadcast container %s death: %v", ident, err)
}
if err := n.broadcastEntries(entries...); err != nil {
n.errorf("failed to broadcast container %s death: %v", ident, err)
}
}
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
})
}
@@ -188,7 +179,7 @@ func (n *Nameserver) deleteTombstones() {
})
}
func (n *Nameserver) Gossip() router.GossipData {
func (n *Nameserver) Gossip() mesh.GossipData {
n.RLock()
defer n.RUnlock()
gossip := &GossipData{
@@ -199,32 +190,25 @@ 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 := gob.NewDecoder(bytes.NewReader(msg)).Decode(&gossip); err != nil {
if err := gossip.Decode(msg); err != nil {
return nil, nil, err
}
if delta := gossip.Timestamp - now(); delta > gossipWindow || delta < -gossipWindow {
return nil, nil, fmt.Errorf("clock skew of %d detected", delta)
}
if err := gossip.Entries.check(); err != nil {
return nil, nil, err
}
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 {
@@ -235,14 +219,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
}
+39 -23
View File
@@ -11,19 +11,22 @@ 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, 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 +43,7 @@ func stopNetwork(nameservers []*Nameserver, grouter *gossip.TestRouter) {
}
type pair struct {
origin router.PeerName
origin mesh.PeerName
addr address.Address
}
@@ -72,18 +75,16 @@ func (m mapping) Addrs() []address.Address {
}
func TestNameservers(t *testing.T) {
wt.RunWithTimeout(t, time.Minute, func() {
testNameservers(t)
})
}
func testNameservers(t *testing.T) {
//common.SetLogLevel("debug")
lookupTimeout := 10 // ms
nameservers, grouter := makeNetwork(50)
nameservers, grouter := makeNetwork(30)
defer stopNetwork(nameservers, grouter)
nameserversByName := map[router.PeerName]*Nameserver{}
// This subset will sometimes lose touch with some of the others
badNameservers := nameservers[25:]
// This subset will remain well-connected, and we will deal mainly with them
nameservers = nameservers[:25]
nameserversByName := map[mesh.PeerName]*Nameserver{}
for _, n := range nameservers {
nameserversByName[n.ourName] = n
}
@@ -106,7 +107,13 @@ func testNameservers(t *testing.T) {
addMapping := func() {
nameserver := nameservers[rand.Intn(len(nameservers))]
addr := address.Address(rand.Int31())
hostname := fmt.Sprintf("hostname%d", rand.Int63())
// Create a hostname which has some upper and lowercase letters,
// and a unique number so we don't have to check if we allocated it already
randomBits := rand.Int63()
firstLetter := 'H' + (randomBits&1)*32
secondLetter := 'O' + (randomBits&2)*16
randomBits = randomBits >> 2
hostname := fmt.Sprintf("%c%cstname%d", firstLetter, secondLetter, randomBits)
mapping := mapping{hostname, []pair{{nameserver.ourName, addr}}}
mappings = append(mappings, mapping)
@@ -129,6 +136,12 @@ func testNameservers(t *testing.T) {
check(nameserver, mapping)
}
loseConnection := func() {
nameserver1 := badNameservers[rand.Intn(len(badNameservers))]
nameserver2 := nameservers[rand.Intn(len(nameservers))]
nameserver1.PeerGone(nameserver2.ourName)
}
deleteMapping := func() {
if len(mappings) <= 0 {
return
@@ -179,7 +192,7 @@ func testNameservers(t *testing.T) {
require.Equal(t, mapping.hostname, hostname)
}
for i := 0; i < 1000; i++ {
for i := 0; i < 800; i++ {
r := rand.Float32()
switch {
case r < 0.1:
@@ -191,7 +204,10 @@ func testNameservers(t *testing.T) {
case 0.2 <= r && r < 0.3:
deleteMapping()
case 0.3 <= r && r < 0.9:
case 0.3 <= r && r < 0.35:
loseConnection()
case 0.35 <= r && r < 0.9:
doLookup()
case 0.9 <= r:
@@ -203,9 +219,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, nil, "")
nameserver := makeNameserver(peername)
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
require.Nil(t, err)
@@ -218,7 +234,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"))
}
@@ -227,9 +243,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, nil, "")
nameserver := makeNameserver(peername)
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
require.Nil(t, err)
@@ -241,14 +257,14 @@ func TestTombstoneDeletion(t *testing.T) {
err = nameserver.Delete("hostname", "containerid", "", address.Address(0))
require.Nil(t, err)
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
require.Equal(t, Entries{{
require.Equal(t, l(Entries{Entry{
ContainerID: "containerid",
Origin: peername,
Addr: address.Address(0),
Hostname: "hostname",
Version: 1,
Tombstone: 1234,
}}, nameserver.entries)
}}), nameserver.entries)
now = func() int64 { return 1234 + int64(tombstoneTimeout/time.Second) + 1 }
nameserver.deleteTombstones()
+6 -4
View File
@@ -1,10 +1,11 @@
package nameserver
type Status struct {
Domain string
Address string
TTL uint32
Entries []EntryStatus
Domain string
Upstream []string
Address string
TTL uint32
Entries []EntryStatus
}
type EntryStatus struct {
@@ -37,6 +38,7 @@ func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status {
return &Status{
dnsServer.domain,
dnsServer.upstream.Servers,
dnsServer.address,
dnsServer.ttl,
entryStatusSlice}
+1 -1
View File
@@ -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 }
+28 -54
View File
@@ -5,28 +5,33 @@ import (
"net"
"syscall"
"github.com/vishvananda/netlink/nl"
"github.com/vishvananda/netlink"
)
// Wait for an interface to come up.
func EnsureInterface(ifaceName string) (*net.Interface, error) {
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK)
if err != nil {
return nil, err
}
defer s.Close()
iface, err := ensureInterface(s, ifaceName)
iface, err := ensureInterface(ifaceName)
if err != nil {
return nil, err
}
return iface, err
}
func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, error) {
func ensureInterface(ifaceName string) (*net.Interface, error) {
if iface, err := findInterface(ifaceName); err == nil {
return iface, nil
}
netlinkReceiveUntil(s, matchIfName(ifaceName))
ch := make(chan netlink.LinkUpdate)
done := make(chan struct{})
defer close(done)
if err := netlink.LinkSubscribe(ch, done); err != nil {
return nil, err
}
for update := range ch {
if ifaceName == update.Link.Attrs().Name && update.IfInfomsg.Flags&syscall.IFF_UP != 0 {
break
}
}
iface, err := findInterface(ifaceName)
return iface, err
}
@@ -35,12 +40,7 @@ func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, err
// This matches the behaviour in 'weave attach', which is the only context in which
// we expect this to be called. If you change one, change the other to match.
func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) {
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK, syscall.RTNLGRP_IPV4_ROUTE)
if err != nil {
return nil, err
}
defer s.Close()
iface, err := ensureInterface(s, ifaceName)
iface, err := ensureInterface(ifaceName)
if err != nil {
return nil, err
}
@@ -48,8 +48,19 @@ func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) {
if CheckRouteExists(ifaceName, dest) {
return iface, err
}
netlinkReceiveUntil(s, matchRoute(ifaceName, dest))
return iface, err
ch := make(chan netlink.RouteUpdate)
done := make(chan struct{})
defer close(done)
if err := netlink.RouteSubscribe(ch, done); err != nil {
return nil, err
}
for update := range ch {
link, _ := netlink.LinkByIndex(update.Route.LinkIndex)
if link.Attrs().Name == ifaceName && update.Route.Dst.IP.Equal(dest) {
break
}
}
return iface, nil
}
func findInterface(ifaceName string) (iface *net.Interface, err error) {
@@ -61,40 +72,3 @@ func findInterface(ifaceName string) (iface *net.Interface, err error) {
}
return
}
func netlinkReceiveUntil(s *nl.NetlinkSocket, f func(syscall.NetlinkMessage) (bool, error)) error {
for {
msgs, err := s.Receive()
if err != nil {
return err
}
for _, m := range msgs {
if done, err := f(m); done {
return err
}
}
}
}
func matchIfName(ifaceName string) func(m syscall.NetlinkMessage) (bool, error) {
return func(m syscall.NetlinkMessage) (bool, error) {
switch m.Header.Type {
case syscall.RTM_NEWLINK: // receive this type for link 'up'
ifmsg := nl.DeserializeIfInfomsg(m.Data)
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
if err != nil {
return true, err
}
name := ""
for _, attr := range attrs {
if attr.Attr.Type == syscall.IFA_LABEL {
name = string(attr.Value[:len(attr.Value)-1])
}
}
if ifaceName == name && ifmsg.Flags&syscall.IFF_UP != 0 {
return true, nil
}
}
return false, nil
}
}
-50
View File
@@ -3,10 +3,8 @@ package net
import (
"fmt"
"net"
"syscall"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
)
// A network is considered free if it does not overlap any existing
@@ -65,51 +63,3 @@ func CheckRouteExists(ifaceName string, dest net.IP) bool {
})
return found
}
func matchRoute(ifaceName string, dest net.IP) func(m syscall.NetlinkMessage) (bool, error) {
return func(m syscall.NetlinkMessage) (bool, error) {
switch m.Header.Type {
case syscall.RTM_NEWROUTE:
route, err := deserializeRoute(m.Data)
if err != nil {
return true, err
}
link, _ := netlink.LinkByIndex(route.LinkIndex)
if link.Attrs().Name == ifaceName && route.Dst.IP.Equal(dest) {
return true, nil
}
}
return false, nil
}
}
// This code proposed for addition to the vishvananda/netlink library
// deserializeRoute decodes a binary netlink message into a Route struct
func deserializeRoute(m []byte) (netlink.Route, error) {
route := netlink.Route{}
msg := nl.DeserializeRtMsg(m)
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
if err != nil {
return route, err
}
route.Scope = netlink.Scope(msg.Scope)
native := nl.NativeEndian()
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.RTA_GATEWAY:
route.Gw = net.IP(attr.Value)
case syscall.RTA_PREFSRC:
route.Src = net.IP(attr.Value)
case syscall.RTA_DST:
route.Dst = &net.IPNet{
IP: attr.Value,
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)),
}
case syscall.RTA_OIF:
routeIndex := int(native.Uint32(attr.Value[0:4]))
route.LinkIndex = routeIndex
}
}
return route, nil
}
+9
View File
@@ -0,0 +1,9 @@
# Weave network driver extension for Docker
A [plugin](http://docs.docker.com/engine/extend/plugin_api/) to
integrate [weave Net](http://weave.works/net/) with Docker.
The plugin runs automatically when you `weave launch`, provided your
Docker daemon is version 1.9 or newer.
More detail [here](http://docs.weave.works/weave/latest_release/plugin.html).
+58
View File
@@ -0,0 +1,58 @@
package ipamplugin
import (
"net"
"github.com/docker/libnetwork/ipamapi"
"github.com/docker/libnetwork/netlabel"
"github.com/weaveworks/weave/api"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
)
const (
WeaveContainer = "weave"
)
type ipam struct {
client *docker.Client
weave *api.Client
}
func NewIpam(client *docker.Client, version string) (ipamapi.Ipam, error) {
resolver := func() (string, error) { return client.GetContainerIP(WeaveContainer) }
return &ipam{client: client, weave: api.NewClientWithResolver(resolver)}, nil
}
func (i *ipam) GetDefaultAddressSpaces() (string, string, error) {
Log.Debugln("GetDefaultAddressSpaces")
return "weavelocal", "weaveglobal", nil
}
func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
Log.Debugln("RequestPool", addressSpace, pool, subPool, options)
cidr, err := i.weave.DefaultSubnet()
Log.Debugln("RequestPool returning ", cidr, err)
// Pass back a fake "gateway address"; we don't actually use it,
// so just give the network address.
data := map[string]string{netlabel.Gateway: cidr.String()}
return "weavepool", cidr, data, err
}
func (i *ipam) ReleasePool(poolID string) error {
Log.Debugln("ReleasePool", poolID)
return nil
}
func (i *ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) {
Log.Debugln("RequestAddress", poolID, address, options)
// Pass magic string to weave IPAM, which then stores the address under its own string
ip, err := i.weave.AllocateIP("_")
Log.Debugln("allocateIP returned", ip, err)
return ip, nil, err
}
func (i *ipam) ReleaseAddress(poolID string, address net.IP) error {
Log.Debugln("ReleaseAddress", poolID, address)
return i.weave.ReleaseIP(address.String())
}
+206
View File
@@ -0,0 +1,206 @@
package plugin
import (
"fmt"
"sync"
"github.com/docker/libnetwork/drivers/remote/api"
"github.com/docker/libnetwork/types"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
"github.com/weaveworks/weave/common/odp"
"github.com/weaveworks/weave/plugin/skel"
"github.com/vishvananda/netlink"
)
const (
WeaveContainer = "weave"
WeaveBridge = "weave"
)
type driver struct {
version string
nameserver string
scope string
noMulticastRoute bool
sync.RWMutex
endpoints map[string]struct{}
}
func New(client *docker.Client, version string, nameserver string, scope string, noMulticastRoute bool) (skel.Driver, error) {
driver := &driver{
nameserver: nameserver,
noMulticastRoute: noMulticastRoute,
version: version,
scope: scope,
endpoints: make(map[string]struct{}),
}
_, err := NewWatcher(client, driver)
if err != nil {
return nil, err
}
return driver, nil
}
func errorf(format string, a ...interface{}) error {
Log.Errorf(format, a...)
return fmt.Errorf(format, a...)
}
// === protocol handlers
func (driver *driver) GetCapabilities() (*api.GetCapabilityResponse, error) {
var caps = &api.GetCapabilityResponse{
Scope: driver.scope,
}
Log.Debugf("Get capabilities: responded with %+v", caps)
return caps, nil
}
func (driver *driver) CreateNetwork(create *api.CreateNetworkRequest) error {
Log.Debugf("Create network request %+v", create)
Log.Infof("Create network %s", create.NetworkID)
return nil
}
func (driver *driver) DeleteNetwork(delete *api.DeleteNetworkRequest) error {
Log.Debugf("Delete network request: %+v", delete)
Log.Infof("Destroy network %s", delete.NetworkID)
return nil
}
func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error) {
Log.Debugf("Create endpoint request %+v", create)
endID := create.EndpointID
if create.Interface == nil {
return nil, fmt.Errorf("Not supported: creating an interface from within CreateEndpoint")
}
driver.Lock()
driver.endpoints[endID] = struct{}{}
driver.Unlock()
resp := &api.CreateEndpointResponse{}
Log.Infof("Create endpoint %s %+v", endID, resp)
return resp, nil
}
func (driver *driver) DeleteEndpoint(deleteReq *api.DeleteEndpointRequest) error {
Log.Debugf("Delete endpoint request: %+v", deleteReq)
Log.Infof("Delete endpoint %s", deleteReq.EndpointID)
driver.Lock()
delete(driver.endpoints, deleteReq.EndpointID)
driver.Unlock()
return nil
}
func (driver *driver) HasEndpoint(endpointID string) bool {
driver.Lock()
_, found := driver.endpoints[endpointID]
driver.Unlock()
return found
}
func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error) {
Log.Debugf("Endpoint info request: %+v", req)
Log.Infof("Endpoint info %s", req.EndpointID)
return &api.EndpointInfoResponse{Value: map[string]interface{}{}}, nil
}
func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error) {
endID := j.EndpointID
maybeBridge, err := netlink.LinkByName(WeaveBridge)
if err != nil {
return nil, errorf(`bridge "%s" not present; did you launch weave?`, WeaveBridge)
}
// create and attach local name to the bridge
local := vethPair(endID[:5])
local.Attrs().MTU = maybeBridge.Attrs().MTU
if err := netlink.LinkAdd(local); err != nil {
return nil, errorf("could not create veth pair: %s", err)
}
switch maybeBridge.(type) {
case *netlink.Bridge:
if err := netlink.LinkSetMasterByIndex(local, maybeBridge.Attrs().Index); err != nil {
return nil, errorf(`unable to set master: %s`, err)
}
case *netlink.GenericLink:
if maybeBridge.Type() != "openvswitch" {
Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge)
return nil, errorf(`device "%s" is of type "%s"`, WeaveBridge, maybeBridge.Type())
}
odp.AddDatapathInterface(WeaveBridge, local.Name)
case *netlink.Device:
Log.Warnf("kernel does not report what kind of device %s is, just %+v", WeaveBridge, maybeBridge)
// Assume it's our openvswitch device, and the kernel has not been updated to report the kind.
odp.AddDatapathInterface(WeaveBridge, local.Name)
default:
Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge)
return nil, errorf(`device "%s" not a bridge`, WeaveBridge)
}
if err := netlink.LinkSetUp(local); err != nil {
return nil, errorf(`unable to bring veth up: %s`, err)
}
ifname := &api.InterfaceName{
SrcName: local.PeerName,
DstPrefix: "ethwe",
}
response := &api.JoinResponse{
InterfaceName: ifname,
}
if driver.nameserver != "" {
routeToDNS := api.StaticRoute{
Destination: driver.nameserver + "/32",
RouteType: types.CONNECTED,
NextHop: "",
}
response.StaticRoutes = []api.StaticRoute{routeToDNS}
}
if !driver.noMulticastRoute {
multicastRoute := api.StaticRoute{
Destination: "224.0.0.0/4",
RouteType: types.CONNECTED,
}
response.StaticRoutes = append(response.StaticRoutes, multicastRoute)
}
Log.Infof("Join endpoint %s:%s to %s", j.NetworkID, j.EndpointID, j.SandboxKey)
return response, nil
}
func (driver *driver) LeaveEndpoint(leave *api.LeaveRequest) error {
Log.Debugf("Leave request: %+v", leave)
local := vethPair(leave.EndpointID[:5])
if err := netlink.LinkDel(local); err != nil {
Log.Warningf("unable to delete veth on leave: %s", err)
}
Log.Infof("Leave %s:%s", leave.NetworkID, leave.EndpointID)
return nil
}
func (driver *driver) DiscoverNew(disco *api.DiscoveryNotification) error {
Log.Debugf("Dicovery new notification: %+v", disco)
return nil
}
func (driver *driver) DiscoverDelete(disco *api.DiscoveryNotification) error {
Log.Debugf("Dicovery delete notification: %+v", disco)
return nil
}
// ===
func vethPair(suffix string) *netlink.Veth {
return &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: "vethwl" + suffix},
PeerName: "vethwg" + suffix,
}
}
+57
View File
@@ -0,0 +1,57 @@
package plugin
import (
"fmt"
weaveapi "github.com/weaveworks/weave/api"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
)
const (
WeaveDomain = "weave.local"
)
type watcher struct {
client *docker.Client
weave *weaveapi.Client
driver *driver
}
type Watcher interface {
}
func NewWatcher(client *docker.Client, driver *driver) (Watcher, error) {
resolver := func() (string, error) { return client.GetContainerIP(WeaveContainer) }
w := &watcher{client: client, driver: driver,
weave: weaveapi.NewClientWithResolver(resolver)}
err := client.AddObserver(w)
if err != nil {
return nil, err
}
return w, nil
}
func (w *watcher) ContainerStarted(id string) {
Log.Debugf("Container started %s", id)
info, err := w.client.InspectContainer(id)
if err != nil {
Log.Warningf("error inspecting container: %s", err)
return
}
// check that it's on our network, via the endpointID
for _, net := range info.NetworkSettings.Networks {
if w.driver.HasEndpoint(net.EndpointID) {
fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname)
if err := w.weave.RegisterWithDNS(id, fqdn, net.IPAddress); err != nil {
Log.Warningf("unable to register with weaveDNS: %s", err)
}
}
}
}
func (w *watcher) ContainerDied(id string) {
// don't need to do this as WeaveDNS removes names on container died anyway
// (note by the time we get this event we can't see the EndpointID)
}
+298
View File
@@ -0,0 +1,298 @@
package skel
import (
"encoding/json"
"fmt"
"net"
"net/http"
"github.com/gorilla/mux"
"github.com/docker/libnetwork/drivers/remote/api"
"github.com/docker/libnetwork/ipamapi"
iapi "github.com/docker/libnetwork/ipams/remote/api"
)
const (
networkReceiver = "NetworkDriver"
ipamReceiver = ipamapi.PluginEndpointType
)
type Driver interface {
GetCapabilities() (*api.GetCapabilityResponse, error)
CreateNetwork(create *api.CreateNetworkRequest) error
DeleteNetwork(delete *api.DeleteNetworkRequest) error
CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error)
DeleteEndpoint(delete *api.DeleteEndpointRequest) error
EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error)
JoinEndpoint(j *api.JoinRequest) (response *api.JoinResponse, error error)
LeaveEndpoint(leave *api.LeaveRequest) error
DiscoverNew(discover *api.DiscoveryNotification) error
DiscoverDelete(delete *api.DiscoveryNotification) error
}
type listener struct {
d Driver
i ipamapi.Ipam
}
func Listen(socket net.Listener, driver Driver, ipamDriver ipamapi.Ipam) error {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFound)
listener := &listener{driver, ipamDriver}
router.Methods("POST").Path("/Plugin.Activate").HandlerFunc(listener.handshake)
handleMethod := func(receiver, method string, h http.HandlerFunc) {
router.Methods("POST").Path(fmt.Sprintf("/%s.%s", receiver, method)).HandlerFunc(h)
}
handleMethod(networkReceiver, "GetCapabilities", listener.getCapabilities)
if driver != nil {
handleMethod(networkReceiver, "CreateNetwork", listener.createNetwork)
handleMethod(networkReceiver, "DeleteNetwork", listener.deleteNetwork)
handleMethod(networkReceiver, "CreateEndpoint", listener.createEndpoint)
handleMethod(networkReceiver, "DeleteEndpoint", listener.deleteEndpoint)
handleMethod(networkReceiver, "EndpointOperInfo", listener.infoEndpoint)
handleMethod(networkReceiver, "Join", listener.joinEndpoint)
handleMethod(networkReceiver, "Leave", listener.leaveEndpoint)
}
if ipamDriver != nil {
handleMethod(ipamReceiver, "GetDefaultAddressSpaces", listener.getDefaultAddressSpaces)
handleMethod(ipamReceiver, "RequestPool", listener.requestPool)
handleMethod(ipamReceiver, "ReleasePool", listener.releasePool)
handleMethod(ipamReceiver, "RequestAddress", listener.requestAddress)
handleMethod(ipamReceiver, "ReleaseAddress", listener.releaseAddress)
}
return http.Serve(socket, router)
}
func decode(w http.ResponseWriter, r *http.Request, v interface{}) error {
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
}
return err
}
// === protocol handlers
type handshakeResp struct {
Implements []string
}
func (listener *listener) handshake(w http.ResponseWriter, r *http.Request) {
var resp handshakeResp
if listener.d != nil {
resp.Implements = append(resp.Implements, "NetworkDriver")
}
if listener.i != nil {
resp.Implements = append(resp.Implements, "IpamDriver")
}
err := json.NewEncoder(w).Encode(&resp)
if err != nil {
sendError(w, "encode error", http.StatusInternalServerError)
return
}
}
func (listener *listener) getCapabilities(w http.ResponseWriter, r *http.Request) {
caps, err := listener.d.GetCapabilities()
objectOrErrorResponse(w, caps, err)
}
func (listener *listener) createNetwork(w http.ResponseWriter, r *http.Request) {
var create api.CreateNetworkRequest
err := json.NewDecoder(r.Body).Decode(&create)
if err != nil {
sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.CreateNetwork(&create))
}
func (listener *listener) deleteNetwork(w http.ResponseWriter, r *http.Request) {
var delete api.DeleteNetworkRequest
if err := json.NewDecoder(r.Body).Decode(&delete); err != nil {
sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.DeleteNetwork(&delete))
}
func (listener *listener) createEndpoint(w http.ResponseWriter, r *http.Request) {
var create api.CreateEndpointRequest
if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
sendError(w, "unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
return
}
res, err := listener.d.CreateEndpoint(&create)
objectOrErrorResponse(w, res, err)
}
func (listener *listener) deleteEndpoint(w http.ResponseWriter, r *http.Request) {
var delete api.DeleteEndpointRequest
if err := json.NewDecoder(r.Body).Decode(&delete); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.DeleteEndpoint(&delete))
}
func (listener *listener) infoEndpoint(w http.ResponseWriter, r *http.Request) {
var req api.EndpointInfoRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
info, err := listener.d.EndpointInfo(&req)
objectOrErrorResponse(w, info, err)
}
func (listener *listener) joinEndpoint(w http.ResponseWriter, r *http.Request) {
var join api.JoinRequest
if err := json.NewDecoder(r.Body).Decode(&join); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
res, err := listener.d.JoinEndpoint(&join)
objectOrErrorResponse(w, res, err)
}
func (listener *listener) leaveEndpoint(w http.ResponseWriter, r *http.Request) {
var l api.LeaveRequest
if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.LeaveEndpoint(&l))
}
func (listener *listener) discoverNew(w http.ResponseWriter, r *http.Request) {
var disco api.DiscoveryNotification
if err := json.NewDecoder(r.Body).Decode(&disco); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.DiscoverNew(&disco))
}
func (listener *listener) discoverDelete(w http.ResponseWriter, r *http.Request) {
var disco api.DiscoveryNotification
if err := json.NewDecoder(r.Body).Decode(&disco); err != nil {
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
return
}
emptyOrErrorResponse(w, listener.d.DiscoverDelete(&disco))
}
// ===
func (listener *listener) getDefaultAddressSpaces(w http.ResponseWriter, r *http.Request) {
local, global, err := listener.i.GetDefaultAddressSpaces()
response := &iapi.GetAddressSpacesResponse{
LocalDefaultAddressSpace: local,
GlobalDefaultAddressSpace: global,
}
objectOrErrorResponse(w, response, err)
}
func (listener *listener) requestPool(w http.ResponseWriter, r *http.Request) {
var rq iapi.RequestPoolRequest
if err := decode(w, r, &rq); err != nil {
return
}
poolID, pool, data, err := listener.i.RequestPool(rq.AddressSpace, rq.Pool, rq.SubPool, rq.Options, rq.V6)
if err != nil {
errorResponse(w, err.Error())
return
}
response := &iapi.RequestPoolResponse{
PoolID: poolID,
Pool: pool.String(),
Data: data,
}
objectResponse(w, response)
}
func (listener *listener) releasePool(w http.ResponseWriter, r *http.Request) {
var rq iapi.ReleasePoolRequest
if err := decode(w, r, &rq); err != nil {
return
}
err := listener.i.ReleasePool(rq.PoolID)
emptyOrErrorResponse(w, err)
}
func (listener *listener) requestAddress(w http.ResponseWriter, r *http.Request) {
var rq iapi.RequestAddressRequest
if err := decode(w, r, &rq); err != nil {
return
}
address, data, err := listener.i.RequestAddress(rq.PoolID, net.ParseIP(rq.Address), rq.Options)
if err != nil {
errorResponse(w, err.Error())
return
}
response := &iapi.RequestAddressResponse{
Address: address.String(),
Data: data,
}
objectResponse(w, response)
}
func (listener *listener) releaseAddress(w http.ResponseWriter, r *http.Request) {
var rq iapi.ReleaseAddressRequest
if err := decode(w, r, &rq); err != nil {
return
}
err := listener.i.ReleaseAddress(rq.PoolID, net.ParseIP(rq.Address))
emptyOrErrorResponse(w, err)
}
// ===
func notFound(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}
func sendError(w http.ResponseWriter, msg string, code int) {
http.Error(w, msg, code)
}
func errorResponse(w http.ResponseWriter, fmtString string, item ...interface{}) {
json.NewEncoder(w).Encode(map[string]string{
"Err": fmt.Sprintf(fmtString, item...),
})
}
func objectResponse(w http.ResponseWriter, obj interface{}) {
if err := json.NewEncoder(w).Encode(obj); err != nil {
sendError(w, "Could not JSON encode response", http.StatusInternalServerError)
return
}
}
func emptyResponse(w http.ResponseWriter) {
json.NewEncoder(w).Encode(map[string]string{})
}
func objectOrErrorResponse(w http.ResponseWriter, obj interface{}, err error) {
if err != nil {
errorResponse(w, err.Error())
return
}
objectResponse(w, obj)
}
func emptyOrErrorResponse(w http.ResponseWriter, err error) {
if err != nil {
errorResponse(w, err.Error())
return
}
emptyResponse(w)
}
+77
View File
@@ -0,0 +1,77 @@
// docker_tls_args: find the docker daemon's tls args
// This reimplements pgrep, because pgrep will only look in /proc, but not in
// $PROCFS.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
func main() {
procRoot := os.Getenv("PROCFS")
if procRoot == "" {
procRoot = "/proc"
}
dirEntries, err := ioutil.ReadDir(procRoot)
checkErr(err)
for _, dirEntry := range dirEntries {
dirName := dirEntry.Name()
if _, err := strconv.Atoi(dirName); err != nil {
continue
}
if comm, err := ioutil.ReadFile(filepath.Join(procRoot, dirName, "comm")); err != nil || string(comm) != "docker\n" {
continue
}
cmdline, err := ioutil.ReadFile(filepath.Join(procRoot, dirName, "cmdline"))
if err != nil {
continue
}
isDaemon := false
tlsArgs := []string{}
args := bytes.Split(cmdline, []byte{'\000'})
for i := 0; i < len(args); i++ {
arg := string(args[i])
switch {
case arg == "-d" || arg == "daemon":
isDaemon = true
break
case arg == "--tls", arg == "--tlsverify":
tlsArgs = append(tlsArgs, arg)
case strings.HasPrefix(arg, "--tls"):
tlsArgs = append(tlsArgs, arg)
if len(args) > i+1 &&
!strings.Contains(arg, "=") &&
!strings.HasPrefix(string(args[i+1]), "-") {
tlsArgs = append(tlsArgs, string(args[i+1]))
i++
}
}
}
if !isDaemon {
continue
}
fmt.Println(strings.Join(tlsArgs, " "))
os.Exit(0)
}
os.Exit(1)
}
func checkErr(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
+3
View File
@@ -0,0 +1,3 @@
FROM weaveworks/weaveexec
ADD ./plugin /home/weave/
ENTRYPOINT ["/home/weave/plugin"]
+163
View File
@@ -0,0 +1,163 @@
package main
import (
"flag"
"fmt"
"net"
"os"
"os/signal"
"strings"
"syscall"
"github.com/docker/libnetwork/ipamapi"
go_docker "github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
ipamplugin "github.com/weaveworks/weave/plugin/ipam"
netplugin "github.com/weaveworks/weave/plugin/net"
"github.com/weaveworks/weave/plugin/skel"
)
var version = "(unreleased version)"
func main() {
var (
justVersion bool
address string
nameserver string
meshAddress string
logLevel string
meshNetworkName string
noMulticastRoute bool
removeNetwork bool
)
flag.BoolVar(&justVersion, "version", false, "print version and exit")
flag.StringVar(&logLevel, "log-level", "info", "logging level (debug, info, warning, error)")
flag.StringVar(&address, "socket", "/run/docker/plugins/weave.sock", "socket on which to listen")
flag.StringVar(&nameserver, "nameserver", "", "nameserver to provide to containers")
flag.StringVar(&meshAddress, "meshsocket", "/run/docker/plugins/weavemesh.sock", "socket on which to listen in mesh mode")
flag.StringVar(&meshNetworkName, "mesh-network-name", "weave", "network name to create in mesh mode")
flag.BoolVar(&noMulticastRoute, "no-multicast-route", false, "do not add a multicast route to network endpoints")
flag.BoolVar(&removeNetwork, "remove-network", false, "remove mesh network and exit")
flag.Parse()
if justVersion {
fmt.Printf("weave plugin %s\n", version)
os.Exit(0)
}
SetLogLevel(logLevel)
// API 1.21 is the first version that supports docker network commands
dockerClient, err := docker.NewVersionedClientFromEnv("1.21")
if err != nil {
Log.Fatalf("unable to connect to docker: %s", err)
}
if removeNetwork {
if _, err = dockerClient.Client.NetworkInfo(meshNetworkName); err == nil {
err = dockerClient.Client.RemoveNetwork(meshNetworkName)
if err != nil {
Log.Fatalf("unable to remove network: %s", err)
}
}
os.Exit(0)
}
Log.Println("Weave plugin", version, "Command line options:", os.Args[1:])
Log.Info(dockerClient.Info())
var globalListener, meshListener net.Listener
endChan := make(chan error, 1)
if address != "" {
globalListener, err := listenAndServe(dockerClient, address, nameserver, noMulticastRoute, endChan, "global", false)
if err != nil {
Log.Fatalf("unable to create driver: %s", err)
}
defer globalListener.Close()
}
if meshAddress != "" {
meshListener, err := listenAndServe(dockerClient, meshAddress, nameserver, noMulticastRoute, endChan, "local", true)
if err != nil {
Log.Fatalf("unable to create driver: %s", err)
}
defer meshListener.Close()
}
if meshNetworkName != "" {
createNetwork(dockerClient, meshNetworkName, meshAddress)
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGTERM)
select {
case sig := <-sigChan:
Log.Debugf("Caught signal %s; shutting down", sig)
if err := dockerClient.Client.RemoveNetwork(meshNetworkName); err != nil {
Log.Fatal(err)
}
case err := <-endChan:
if err != nil {
Log.Errorf("Error from listener: %s", err)
globalListener.Close()
meshListener.Close()
os.Exit(1)
}
}
}
func listenAndServe(dockerClient *docker.Client, address, nameserver string, noMulticastRoute bool, endChan chan<- error, scope string, withIpam bool) (net.Listener, error) {
d, err := netplugin.New(dockerClient, version, nameserver, scope, noMulticastRoute)
if err != nil {
return nil, err
}
var i ipamapi.Ipam
if withIpam {
if i, err = ipamplugin.NewIpam(dockerClient, version); err != nil {
return nil, err
}
}
var listener net.Listener
// remove sockets from last invocation
if err := os.Remove(address); err != nil && !os.IsNotExist(err) {
return nil, err
}
listener, err = net.Listen("unix", address)
if err != nil {
return nil, err
}
Log.Printf("Listening on %s for %s scope", address, scope)
go func() {
endChan <- skel.Listen(listener, d, i)
}()
return listener, nil
}
func createNetwork(dockerClient *docker.Client, networkName, address string) {
if _, err := dockerClient.Client.NetworkInfo(networkName); err == nil {
Log.Printf("Docker network '%s' already exists", networkName)
} else if _, ok := err.(*go_docker.NoSuchNetwork); ok {
driverName := strings.TrimSuffix(address, ".sock")
if i := strings.LastIndex(driverName, "/"); i >= 0 {
driverName = driverName[i+1:]
}
options := go_docker.CreateNetworkOptions{
Name: networkName,
CheckDuplicate: true,
Driver: driverName,
IPAM: go_docker.IPAMOptions{Driver: driverName},
}
_, err := dockerClient.Client.CreateNetwork(options)
if err != nil {
Log.Fatalf("Error creating network: %s", err)
}
}
}
+7 -3
View File
@@ -1,10 +1,12 @@
FROM gliderlabs/alpine
FROM alpine
MAINTAINER Weaveworks Inc <help@weave.works>
LABEL works.weave.role=system
WORKDIR /home/weave
VOLUME /w
VOLUME /w-noop
VOLUME /w-nomcast
ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"]
RUN apk add --update \
@@ -17,7 +19,9 @@ RUN apk add --update \
bind-tools \
&& rm -rf /var/cache/apk/*
ADD ./weave ./sigproxy ./weaveproxy /home/weave/
ADD ./netcheck ./weavehosts /usr/bin/
ADD ./weave ./sigproxy ./weaveproxy ./symlink /home/weave/
ADD ./netcheck ./docker_tls_args /usr/bin/
ADD ./weavewait /w/w
ADD ./weavewait_noop /w-noop/w
ADD ./weavewait_nomcast /w-nomcast/w
ADD ./docker.tgz /
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# This lets us force-symlink a bunch of stuff all at once, so we can minimize
# running containers.
set -e
[ -z "$WEAVE_DEBUG" ] || set -x
target="$1"
user="$(stat -c '%u:%g' "$target")"
mode="$(stat -c '%a' "$target")"
shift 1
for link in "$@" ; do
ln -sfT "$target" "$link"
done
chown -h "$user" "$@"
chmod "$mode" "$@"
-120
View File
@@ -1,120 +0,0 @@
/* weavehosts: rewrite /etc/hosts as necessary to put the weave address(es) at the top */
package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net"
"os"
"sort"
"strings"
)
func main() {
args := os.Args
if len(args) < 4 {
fmt.Fprintln(os.Stderr, "usage: weavehosts hostpath hostname cidr [cidr ...]")
os.Exit(1)
}
checkErr(updateHosts(args[1], args[2], args[3:]))
os.Exit(0)
}
func checkErr(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func updateHosts(path, hostname string, cidrs []string) error {
hosts, err := parseHosts(path)
if err != nil {
return err
}
// Remove existing ips pointing to our hostname
toRemove := []string{}
for ip, addrs := range hosts {
for _, addr := range addrs {
if addr == hostname {
toRemove = append(toRemove, ip)
break
}
}
}
for _, ip := range toRemove {
delete(hosts, ip)
}
// Add the weave ip(s)
for _, cidr := range cidrs {
ip, _, err := net.ParseCIDR(cidr)
checkErr(err)
ipStr := ip.String()
hosts[ipStr] = append(hosts[ipStr], hostname)
}
return writeHosts(path, hosts)
}
func parseHosts(path string) (map[string][]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
ips := map[string][]string{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
// Remove any comments
if i := strings.IndexByte(line, '#'); i != -1 {
line = line[:i]
}
fields := strings.Fields(line)
if len(fields) > 0 {
ips[fields[0]] = append(ips[fields[0]], fields[1:]...)
}
}
if scanner.Err() != nil {
return nil, scanner.Err()
}
return ips, nil
}
func writeHosts(path string, contents map[string][]string) error {
ips := []string{}
for ip := range contents {
ips = append(ips, ip)
}
sort.Strings(ips)
buf := &bytes.Buffer{}
fmt.Fprintln(buf, "# modified by weave")
for _, ip := range ips {
if addrs := contents[ip]; len(addrs) > 0 {
fmt.Fprintf(buf, "%s\t%s\n", ip, strings.Join(uniqueStrs(addrs), " "))
}
}
return ioutil.WriteFile(path, buf.Bytes(), 644)
}
func uniqueStrs(s []string) []string {
m := map[string]struct{}{}
result := []string{}
for _, str := range s {
if _, ok := m[str]; !ok {
m[str] = struct{}{}
result = append(result, str)
}
}
sort.Strings(result)
return result
}
+25 -10
View File
@@ -12,14 +12,16 @@ import (
)
var (
version = "(unreleased version)"
version = "(unreleased version)"
defaultDockerHost = "unix:///var/run/docker.sock"
)
func main() {
var (
justVersion bool
logLevel = "info"
c = proxy.Config{ListenAddrs: []string{}}
c = proxy.Config{Image: "weaveworks/weaveexec"}
withDNS bool
)
c.Version = version
@@ -35,32 +37,45 @@ func main() {
mflag.BoolVar(&c.NoRewriteHosts, []string{"-no-rewrite-hosts"}, false, "do not automatically rewrite /etc/hosts. Use if you need the docker IP to remain in /etc/hosts")
mflag.StringVar(&c.TLSConfig.CACert, []string{"#tlscacert", "-tlscacert"}, "", "Trust certs signed only by this CA")
mflag.StringVar(&c.TLSConfig.Cert, []string{"#tlscert", "-tlscert"}, "", "Path to TLS certificate file")
mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tls-verify")
mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tlsverify")
mflag.StringVar(&c.TLSConfig.Key, []string{"#tlskey", "-tlskey"}, "", "Path to TLS key file")
mflag.BoolVar(&c.TLSConfig.Verify, []string{"#tlsverify", "-tlsverify"}, false, "Use TLS and verify the remote")
mflag.BoolVar(&c.WithDNS, []string{"-with-dns", "w"}, false, "instruct created containers to always use weaveDNS as their nameserver")
mflag.BoolVar(&withDNS, []string{"#-with-dns", "#w"}, false, "option removed")
mflag.BoolVar(&c.WithoutDNS, []string{"-without-dns"}, false, "instruct created containers to never use weaveDNS as their nameserver")
mflag.BoolVar(&c.NoMulticastRoute, []string{"-no-multicast-route"}, false, "do not add a multicast route via the weave interface when attaching containers")
mflag.Parse()
if justVersion {
fmt.Printf("weave proxy %s\n", version)
fmt.Printf("weave proxy %s\n", version)
os.Exit(0)
}
if c.WithDNS && c.WithoutDNS {
Log.Fatalf("Cannot use both '--with-dns' and '--without-dns' flags")
}
SetLogLevel(logLevel)
if image := os.Getenv("EXEC_IMAGE"); image != "" {
c.Image = image
}
Log.Infoln("weave proxy", version)
Log.Infoln("Command line arguments:", strings.Join(os.Args[1:], " "))
if withDNS {
Log.Warning("--with-dns option has been removed; DNS is on by default")
}
c.DockerHost = defaultDockerHost
if dockerHost := os.Getenv("DOCKER_HOST"); dockerHost != "" {
c.DockerHost = dockerHost
}
p, err := proxy.NewProxy(c)
if err != nil {
Log.Fatalf("Could not start proxy: %s", err)
}
go p.ListenAndServe()
listeners := p.Listen()
p.AttachExistingContainers()
go p.Serve(listeners)
go p.ListenAndServeStatus("/home/weave/status.sock")
SignalHandlerLoop()
}
+121 -36
View File
@@ -1,17 +1,20 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gorilla/mux"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam"
"github.com/weaveworks/weave/nameserver"
"github.com/weaveworks/weave/net/address"
weave "github.com/weaveworks/weave/router"
"net/http"
"strings"
"text/template"
"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"
)
var rootTemplate = template.New("root").Funcs(map[string]interface{}{
@@ -24,14 +27,74 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{
}
return count
},
"printConnectionCounts": func(conns []weave.LocalConnectionStatus) string {
"printList": func(list []string) string {
if len(list) == 0 {
return "none"
}
return strings.Join(list, ", ")
},
"printIPAMRanges": func(router weave.NetworkRouterStatus, status ipam.Status) string {
var buffer bytes.Buffer
type stats struct {
ips uint32
nickname string
reachable bool
}
peerStats := make(map[string]*stats)
for _, entry := range status.Entries {
s, found := peerStats[entry.Peer]
if !found {
s = &stats{nickname: entry.Nickname, reachable: entry.IsKnownPeer}
peerStats[entry.Peer] = s
}
s.ips += entry.Size
}
printOwned := func(name string, nickName string, reachable bool, ips uint32) {
reachableStr := ""
if !reachable {
reachableStr = "- unreachable!"
}
percentageRanges := float32(ips) * 100.0 / float32(status.RangeNumIPs)
displayName := name + "(" + nickName + ")"
fmt.Fprintf(&buffer, "%-37v %8d IPs (%04.1f%% of total) %s\n",
displayName, ips, percentageRanges, reachableStr)
}
// print the local info first
if ourStats := peerStats[router.Name]; ourStats != nil {
printOwned(router.Name, ourStats.nickname, true, ourStats.ips)
}
// and then the rest
for peer, stats := range peerStats {
if peer != router.Name {
printOwned(peer, stats.nickname, stats.reachable, stats.ips)
}
}
return buffer.String()
},
"allIPAMOwnersUnreachable": func(status ipam.Status) bool {
for _, entry := range status.Entries {
if entry.Size > 0 && entry.IsKnownPeer {
return false
}
}
return true
},
"printConnectionCounts": func(conns []mesh.LocalConnectionStatus) string {
counts := make(map[string]int)
for _, conn := range conns {
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 {
@@ -75,40 +138,48 @@ func defTemplate(name string, text string) *template.Template {
}
var statusTemplate = defTemplate("status", `\
Version: {{.Version}}
Version: {{.Version}}
Service: router
Protocol: {{.Router.Protocol}} \
Service: router
Protocol: {{.Router.Protocol}} \
{{if eq .Router.ProtocolMinVersion .Router.ProtocolMaxVersion}}\
{{.Router.ProtocolMaxVersion}}\
{{else}}\
{{.Router.ProtocolMinVersion}}..{{.Router.ProtocolMaxVersion}}\
{{end}}
Name: {{.Router.Name}}({{.Router.NickName}})
Encryption: {{printState .Router.Encryption}}
PeerDiscovery: {{printState .Router.PeerDiscovery}}
Targets: {{len .Router.Targets}}
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections between them){{end}}
Name: {{.Router.Name}}({{.Router.NickName}})
Encryption: {{printState .Router.Encryption}}
PeerDiscovery: {{printState .Router.PeerDiscovery}}
Targets: {{len .Router.Targets}}
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections){{end}}
TrustedSubnets: {{printList .Router.TrustedSubnets}}
{{if .IPAM}}\
Service: ipam
Service: ipam
{{if .IPAM.Entries}}\
Consensus: achieved
{{else if .IPAM.Paxos}}\
Consensus: waiting (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
{{if allIPAMOwnersUnreachable .IPAM}}\
Status: all IP ranges owned by unreachable peers - use 'rmpeer' if they are dead
{{else if len .IPAM.PendingAllocates}}\
Status: waiting for IP range grant from peers
{{else}}\
Consensus: deferred
Status: ready
{{end}}\
Range: {{.IPAM.Range}}
DefaultSubnet: {{.IPAM.DefaultSubnet}}
{{else if .IPAM.Paxos}}\
Status: awaiting consensus (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
{{else}}\
Status: idle
{{end}}\
Range: {{.IPAM.Range}}
DefaultSubnet: {{.IPAM.DefaultSubnet}}
{{end}}\
{{if .DNS}}\
Service: dns
Domain: {{.DNS.Domain}}
TTL: {{.DNS.TTL}}
Entries: {{countDNSEntries .DNS.Entries}}
Service: dns
Domain: {{.DNS.Domain}}
Upstream: {{printList .DNS.Upstream}}
TTL: {{.DNS.TTL}}
Entries: {{countDNSEntries .DNS.Entries}}
{{end}}\
`)
@@ -128,7 +199,7 @@ var peersTemplate = defTemplate("peers", `\
{{.Name}}({{.NickName}})
{{range .Connections}}\
{{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} \
{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-32v" $nameNickName}} \
{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-37v" $nameNickName}} \
{{if .Established}}established{{else}}pending{{end}}
{{end}}\
{{end}}\
@@ -144,31 +215,44 @@ var dnsEntriesTemplate = defTemplate("dnsEntries", `\
{{end}}\
`)
var ipamTemplate = defTemplate("ipamTemplate", `{{printIPAMRanges .Router .IPAM}}`)
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)}
}
muxRouter.Methods("GET").Path("/report").Headers("Accept", "application/json").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
json, _ := json.MarshalIndent(status(), "", " ")
json, err := json.MarshalIndent(status(), "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
Log.Error("Error during report marshalling: ", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(json)
})
muxRouter.Methods("GET").Path("/report").Queries("format", "{format}").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
formatTemplate, err := template.New("format").Parse(mux.Vars(r)["format"])
funcs := template.FuncMap{
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}
formatTemplate, err := template.New("format").Funcs(funcs).Parse(mux.Vars(r)["format"])
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -194,5 +278,6 @@ func HandleHTTP(muxRouter *mux.Router, version string, router *weave.Router, all
defHandler("/status/connections", connectionsTemplate)
defHandler("/status/peers", peersTemplate)
defHandler("/status/dns", dnsEntriesTemplate)
defHandler("/status/ipam", ipamTemplate)
}
+211 -86
View File
@@ -1,7 +1,6 @@
package main
import (
"crypto/sha256"
"fmt"
"net"
"net/http"
@@ -17,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"
@@ -36,36 +37,54 @@ func main() {
runtime.GOMAXPROCS(procs)
var (
config weave.Config
justVersion bool
protocolMinVersion int
ifaceName string
routerName string
nickName string
password string
pktdebug bool
logLevel string
prof string
bufSzMB int
noDiscovery bool
httpAddr string
iprangeCIDR string
ipsubnetCIDR string
peerCount int
apiPath string
peers []string
// flags that cause immediate exit
justVersion bool
createDatapath bool
deleteDatapath bool
addDatapathInterface string
config mesh.Config
networkConfig weave.NetworkConfig
protocolMinVersion int
ifaceName string
routerName string
nickName string
password string
pktdebug bool
logLevel string
prof string
bufSzMB int
noDiscovery bool
httpAddr string
iprangeCIDR string
ipsubnetCIDR string
peerCount int
dockerAPI string
peers []string
noDNS bool
dnsDomain string
dnsListenAddress string
dnsTTL int
dnsClientTimeout time.Duration
dnsEffectiveListenAddress string
iface *net.Interface
datapathName string
trustedSubnetStr string
defaultDockerHost = "unix:///var/run/docker.sock"
)
if val := os.Getenv("DOCKER_HOST"); val != "" {
defaultDockerHost = val
}
mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
mflag.IntVar(&config.Port, []string{"#port", "-port"}, weave.Port, "router port")
mflag.IntVar(&protocolMinVersion, []string{"-min-protocol-version"}, weave.ProtocolMinVersion, "minimum weave protocol version")
mflag.BoolVar(&createDatapath, []string{"-create-datapath"}, false, "create ODP datapath and exit")
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"}, 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)")
@@ -76,17 +95,20 @@ 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)")
mflag.StringVar(&apiPath, []string{"#api", "-api"}, "unix:///var/run/docker.sock", "Path to Docker API socket")
mflag.StringVar(&dockerAPI, []string{"#api", "#-api", "-docker-api"}, defaultDockerHost, "Docker API endpoint")
mflag.BoolVar(&noDNS, []string{"-no-dns"}, false, "disable DNS server")
mflag.StringVar(&dnsDomain, []string{"-dns-domain"}, nameserver.DefaultDomain, "local domain to server requests for")
mflag.StringVar(&dnsListenAddress, []string{"-dns-listen-address"}, nameserver.DefaultListenAddress, "address to listen on for DNS requests")
mflag.IntVar(&dnsTTL, []string{"-dns-ttl"}, nameserver.DefaultTTL, "TTL for DNS request from our domain")
mflag.DurationVar(&dnsClientTimeout, []string{"-dns-fallback-timeout"}, nameserver.DefaultClientTimeout, "timeout for fallback DNS requests")
mflag.StringVar(&dnsEffectiveListenAddress, []string{"-dns-effective-listen-address"}, "", "address DNS will actually be listening, after Docker port mapping")
mflag.StringVar(&datapathName, []string{"-datapath"}, "", "ODP datapath name")
mflag.StringVar(&trustedSubnetStr, []string{"-trusted-subnets"}, "", "Command separated list of trusted subnets in CIDR notation")
// crude way of detecting that we probably have been started in a
// container, with `weave launch` --> suppress misleading paths in
@@ -95,59 +117,115 @@ func main() {
os.Args[0] = "weave"
mflag.CommandLine.Init("weave", mflag.ExitOnError)
}
mflag.Parse()
peers = mflag.Args()
SetLogLevel(logLevel)
if justVersion {
switch {
case justVersion:
fmt.Printf("weave router %s\n", version)
os.Exit(0)
case createDatapath:
err, odp_supported := odp.CreateDatapath(datapathName)
if !odp_supported {
if err != nil {
Log.Error(err)
}
// When the kernel lacks ODP support, exit
// with a special status to distinguish it for
// the weave script.
os.Exit(17)
}
checkFatal(err)
os.Exit(0)
case deleteDatapath:
checkFatal(odp.DeleteDatapath(datapathName))
os.Exit(0)
case 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 err error
var fastDPOverlay weave.NetworkOverlay
if datapathName != "" {
// A datapath name implies that "Bridge" and "Overlay"
// packet handling use fast datapath, although other
// options can override that below. Even if both
// things are overridden, we might need bridging on
// the datapath.
fastdp, err := weave.NewFastDatapath(weave.FastDatapathConfig{
DatapathName: datapathName,
Port: config.Port,
})
checkFatal(err)
networkConfig.Bridge = fastdp.Bridge()
fastDPOverlay = fastdp.Overlay()
}
if ifaceName != "" {
config.Iface, err = weavenet.EnsureInterface(ifaceName)
if err != nil {
Log.Fatal(err)
}
}
// -iface can coexist with -datapath, because
// pcap-based packet capture is a bit more efficient
// than capture via ODP misses, even when using an
// ODP-based bridge. So when using weave encyption,
// it's preferable to use -iface.
var err error
iface, err = weavenet.EnsureInterface(ifaceName)
checkFatal(err)
if routerName == "" {
if config.Iface == nil {
Log.Fatal("Either an interface must be specified with --iface or a name with -name")
}
routerName = config.Iface.HardwareAddr.String()
}
name, err := weave.PeerNameFromUserInput(routerName)
if err != nil {
Log.Fatal(err)
}
if nickName == "" {
nickName, err = os.Hostname()
if err != nil {
Log.Fatal(err)
}
// bufsz flag is in MB
networkConfig.Bridge, err = weave.NewPcap(iface, bufSzMB*1024*1024)
checkFatal(err)
}
if password == "" {
password = os.Getenv("WEAVE_PASSWORD")
}
if password == "" {
Log.Println("Communication between peers is unencrypted.")
} else {
config.Password = []byte(password)
Log.Println("Communication between peers is encrypted.")
Log.Println("Communication between peers via untrusted networks is encrypted.")
}
overlays := weave.NewOverlaySwitch()
if fastDPOverlay != nil {
overlays.Add("fastdp", fastDPOverlay)
}
sleeve := weave.NewSleeveOverlay(config.Port)
overlays.Add("sleeve", sleeve)
overlays.SetCompatOverlay(sleeve)
if routerName == "" {
if iface == nil {
Log.Fatal("Either an interface must be specified with --iface or a name with -name")
}
routerName = iface.HardwareAddr.String()
}
name, err := mesh.PeerNameFromUserInput(routerName)
checkFatal(err)
if nickName == "" {
nickName, err = os.Hostname()
checkFatal(err)
}
if prof != "" {
@@ -157,25 +235,46 @@ func main() {
defer profile.Start(&p).Stop()
}
config.BufSz = bufSzMB * 1024 * 1024
config.LogFrame = logFrameFunc(pktdebug)
config.PeerDiscovery = !noDiscovery
router := weave.NewRouter(config, name, nickName)
Log.Println("Our name is", router.Ourself)
dockerCli, err := docker.NewClient(apiPath)
if err != nil {
Log.Fatal("Unable to start docker client: ", err)
if pktdebug {
networkConfig.PacketLogging = packetLogging{}
} else {
networkConfig.PacketLogging = nopPacketLogging{}
}
if config.TrustedSubnets, err = parseTrustedSubnets(trustedSubnetStr); err != nil {
Log.Fatal("Unable to parse trusted subnets: ", err)
}
router := weave.NewNetworkRouter(config, networkConfig, name, nickName, overlays)
Log.Println("Our name is", router.Ourself)
var dockerCli *docker.Client
if dockerAPI != "" {
dc, err := docker.NewClient(dockerAPI)
if err != nil {
Log.Fatal("Unable to start docker client: ", err)
} else {
Log.Info(dc.Info())
}
dockerCli = dc
}
observeContainers := func(o docker.ContainerObserver) {
if dockerCli != nil {
if err = dockerCli.AddObserver(o); err != nil {
Log.Fatal("Unable to start watcher", err)
}
}
}
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))
if err = dockerCli.AddObserver(allocator); err != nil {
Log.Fatal("Unable to start watcher", err)
}
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")
}
@@ -185,11 +284,10 @@ func main() {
dnsserver *nameserver.DNSServer
)
if !noDNS {
ns = nameserver.New(router.Ourself.Peer.Name, router.Peers, dockerCli, 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))
if err = dockerCli.AddObserver(ns); err != nil {
Log.Fatal("Unable to start watcher", err)
}
observeContainers(ns)
ns.Start()
defer ns.Stop()
dnsserver, err = nameserver.NewDNSServer(ns, dnsDomain, dnsListenAddress,
@@ -197,6 +295,11 @@ func main() {
if err != nil {
Log.Fatal("Unable to start dns server: ", err)
}
listenAddr := dnsListenAddress
if dnsEffectiveListenAddress != "" {
listenAddr = dnsEffectiveListenAddress
}
Log.Println("Listening for DNS queries on", listenAddr)
dnsserver.ActivateAndServe()
defer dnsserver.Stop()
}
@@ -215,11 +318,12 @@ func main() {
allocator.HandleHTTP(muxRouter, defaultSubnet, dockerCli)
}
if ns != nil {
ns.HandleHTTP(muxRouter)
ns.HandleHTTP(muxRouter, dockerCli)
}
router.HandleHTTP(muxRouter)
HandleHTTP(muxRouter, version, router, allocator, defaultSubnet, ns, dnsserver)
http.Handle("/", muxRouter)
Log.Println("Listening for HTTP control messages on", httpAddr)
go listenAndServeHTTP(httpAddr, muxRouter)
}
@@ -248,38 +352,35 @@ func canonicalName(f *mflag.Flag) string {
return ""
}
func logFrameFunc(debug bool) weave.LogFrameFunc {
if !debug {
return func(prefix string, frame []byte, dec *weave.EthernetDecoder) {}
}
return func(prefix string, frame []byte, dec *weave.EthernetDecoder) {
h := fmt.Sprintf("%x", sha256.Sum256(frame))
parts := []interface{}{prefix, len(frame), "bytes (", h, ")"}
type packetLogging struct{}
if dec != nil {
parts = append(parts, dec.Eth.SrcMAC, "->", dec.Eth.DstMAC)
func (packetLogging) LogPacket(msg string, key weave.PacketKey) {
Log.Println(msg, key.SrcMAC, "->", key.DstMAC)
}
if dec.DF() {
parts = append(parts, "(DF)")
}
}
func (packetLogging) LogForwardPacket(msg string, key weave.ForwardPacketKey) {
Log.Println(msg, key.SrcPeer, key.SrcMAC, "->", key.DstPeer, key.DstMAC)
}
Log.Println(parts...)
}
type nopPacketLogging struct{}
func (nopPacketLogging) LogPacket(string, weave.PacketKey) {
}
func (nopPacketLogging) LogForwardPacket(string, weave.ForwardPacketKey) {
}
func parseAndCheckCIDR(cidrStr string) address.CIDR {
_, cidr, err := address.ParseCIDR(cidrStr)
if err != nil {
Log.Fatal(err)
}
checkFatal(err)
if cidr.Size() < ipam.MinSubnetSize {
Log.Fatalf("Allocation range smaller than minimum size %d: %s", ipam.MinSubnetSize, cidrStr)
}
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 != "" {
@@ -288,7 +389,7 @@ 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)
}
}
allocator := ipam.NewAllocator(router.Ourself.Peer.Name, router.Ourself.Peer.UID, router.Ourself.Peer.NickName, ipRange.Range(), quorum)
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))
allocator.Start()
@@ -316,6 +417,24 @@ func determineQuorum(initPeerCountFlag int, peers []string) uint {
return quorum
}
func parseTrustedSubnets(trustedSubnetStr string) ([]*net.IPNet, error) {
trustedSubnets := []*net.IPNet{}
if trustedSubnetStr == "" {
return trustedSubnets, nil
}
for _, subnetStr := range strings.Split(trustedSubnetStr, ",") {
_, subnet, err := net.ParseCIDR(subnetStr)
if err != nil {
return nil, err
}
trustedSubnets = append(trustedSubnets, subnet)
}
return trustedSubnets, nil
}
func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) {
protocol := "tcp"
if strings.HasPrefix(httpAddr, "/") {
@@ -331,3 +450,9 @@ func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) {
Log.Fatal("Unable to create http server", err)
}
}
func checkFatal(e error) {
if e != nil {
Log.Fatal(e)
}
}
+7
View File
@@ -0,0 +1,7 @@
// +build !iface,!mcast
package main
func checkNetwork() error {
return nil
}
+12
View File
@@ -0,0 +1,12 @@
// +build iface,!mcast
package main
import (
weavenet "github.com/weaveworks/weave/net"
)
func checkNetwork() error {
_, err := weavenet.EnsureInterface("ethwe")
return err
}
@@ -0,0 +1,12 @@
// +build iface,mcast
package main
import (
weavenet "github.com/weaveworks/weave/net"
)
func checkNetwork() error {
_, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe")
return err
}
+1 -4
View File
@@ -6,8 +6,6 @@ import (
"os"
"os/exec"
"syscall"
weavenet "github.com/weaveworks/weave/net"
)
var (
@@ -19,8 +17,7 @@ func main() {
args = os.Args[1:]
)
_, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe")
checkErr(err)
checkErr(checkNetwork())
if len(args) == 0 {
checkErr(ErrNoCommandSpecified)
+45 -4
View File
@@ -20,19 +20,35 @@ import (
var (
containerIDRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/([^/]*)/.*")
weaveWaitEntrypoint = []string{"/w/w"}
weaveEntrypoint = "/home/weave/weaver"
weaveContainerName = "/weave"
)
func callWeave(args ...string) ([]byte, []byte, error) {
args = append([]string{"--local"}, args...)
Log.Debug("Calling weave", args)
cmd := exec.Command("./weave", args...)
cmd.Env = []string{
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PROCFS=/hostproc",
}
if bridge := os.Getenv("DOCKER_BRIDGE"); bridge != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_BRIDGE=%s", bridge))
propagateEnv := func(key string) {
if val := os.Getenv(key); val != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, val))
}
}
propagateEnv("DOCKER_BRIDGE")
propagateEnv("DOCKER_HOST")
// Propogage WEAVE_DEBUG, to make debugging easier.
propagateEnv("WEAVE_DEBUG")
// This prevents the code coverage contortions in our
// integration test suite breaking things.
propagateEnv("COVERAGE")
Log.Debug("Calling weave args: ", args, "env: ", cmd.Env)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
@@ -45,6 +61,7 @@ func unmarshalRequestBody(r *http.Request, target interface{}) error {
if err != nil {
return err
}
Log.Debugf("->requestBody: %s", body)
if err := r.Body.Close(); err != nil {
return err
}
@@ -60,6 +77,7 @@ func marshalRequestBody(r *http.Request, body interface{}) error {
if err != nil {
return err
}
Log.Debugf("<-requestBody: %s", newBody)
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
r.ContentLength = int64(len(newBody))
return nil
@@ -70,6 +88,7 @@ func unmarshalResponseBody(r *http.Response, target interface{}) error {
if err != nil {
return err
}
Log.Debugf("->responseBody: %s", body)
if err := r.Body.Close(); err != nil {
return err
}
@@ -85,12 +104,12 @@ func marshalResponseBody(r *http.Response, body interface{}) error {
if err != nil {
return err
}
Log.Debugf("<-responseBody: %s", newBody)
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
r.ContentLength = int64(len(newBody))
// Stop it being chunked, because that hangs
r.TransferEncoding = nil
return nil
}
func inspectContainerInPath(client *docker.Client, path string) (*docker.Container, error) {
@@ -137,3 +156,25 @@ func weaveContainerIPs(containerID string) (mac string, ips []net.IP, nets []*ne
}
return
}
func addVolume(hostConfig jsonObject, source, target, mode string) error {
configBinds, err := hostConfig.StringArray("Binds")
if err != nil {
return err
}
var binds []string
for _, bind := range configBinds {
s := strings.Split(bind, ":")
if len(s) >= 2 && s[1] == target {
continue
}
binds = append(binds, bind)
}
bind := source + ":" + target
if mode != "" {
bind += ":" + mode
}
hostConfig["Binds"] = append(binds, bind)
return nil
}
+96 -96
View File
@@ -2,16 +2,11 @@ package proxy
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/nameserver"
"github.com/weaveworks/weave/router"
)
const MaxDockerHostname = 64
@@ -22,12 +17,6 @@ var (
type createContainerInterceptor struct{ proxy *Proxy }
type createContainerRequestBody struct {
*docker.Config
HostConfig *docker.HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
}
// ErrNoSuchImage replaces docker.NoSuchImage, which does not contain the image
// name, which in turn breaks docker clients post 1.7.0 since they expect the
// image name to be present in errors.
@@ -40,35 +29,54 @@ func (err *ErrNoSuchImage) Error() string {
}
func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
container := createContainerRequestBody{}
container := jsonObject{}
if err := unmarshalRequestBody(r, &container); err != nil {
return err
}
if cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig); err != nil {
hostConfig, err := container.Object("HostConfig")
if err != nil {
return err
}
networkMode, err := hostConfig.String("NetworkMode")
if err != nil {
return err
}
env, err := container.StringArray("Env")
if err != nil {
return err
}
if cidrs, err := i.proxy.weaveCIDRs(networkMode, env); err != nil {
Log.Infof("Leaving container alone because %s", err)
} else {
Log.Infof("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " "))
if container.HostConfig == nil {
container.HostConfig = &docker.HostConfig{}
}
if container.Config == nil {
container.Config = &docker.Config{}
}
i.addWeaveWaitVolume(container.HostConfig)
if err := i.setWeaveWaitEntrypoint(container.Config); err != nil {
return err
}
hostname := r.URL.Query().Get("name")
if i.proxy.Config.HostnameFromLabel != "" {
if labelValue, ok := container.Labels[i.proxy.Config.HostnameFromLabel]; ok {
hostname = labelValue
if i.proxy.NoMulticastRoute {
if err := addVolume(hostConfig, i.proxy.weaveWaitNomcastVolume, "/w", "ro"); err != nil {
return err
}
} else {
if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil {
return err
}
}
hostname = i.proxy.hostnameMatchRegexp.ReplaceAllString(hostname, i.proxy.HostnameReplacement)
if err := i.setWeaveDNS(&container, hostname); err != nil {
if err := i.setWeaveWaitEntrypoint(container); err != nil {
return err
}
hostname, err := i.containerHostname(r, container)
if err != nil {
return err
}
if dnsDomain := i.proxy.getDNSDomain(); dnsDomain != "" {
if err := i.setHostname(container, hostname, dnsDomain); err != nil {
return err
}
if err := i.proxy.setWeaveDNS(hostConfig, hostname, dnsDomain); err != nil {
return err
}
}
return marshalRequestBody(r, container)
}
@@ -76,105 +84,97 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
return nil
}
func (i *createContainerInterceptor) addWeaveWaitVolume(hostConfig *docker.HostConfig) {
var binds []string
for _, bind := range hostConfig.Binds {
s := strings.Split(bind, ":")
if len(s) >= 2 && s[1] == "/w" {
continue
}
binds = append(binds, bind)
func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container jsonObject) error {
var entrypoint []string
entrypoint, err := container.StringArray("Entrypoint")
if err != nil {
return err
}
hostConfig.Binds = append(binds, fmt.Sprintf("%s:/w:ro", i.proxy.weaveWaitVolume))
}
func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error {
if len(container.Entrypoint) == 0 {
image, err := i.proxy.client.InspectImage(container.Image)
cmd, err := container.StringArray("Cmd")
if err != nil {
return err
}
if len(entrypoint) == 0 {
containerImage, err := container.String("Image")
if err != nil {
return err
}
image, err := i.proxy.client.InspectImage(containerImage)
if err == docker.ErrNoSuchImage {
return &ErrNoSuchImage{container.Image}
return &ErrNoSuchImage{containerImage}
} else if err != nil {
return err
}
if len(container.Cmd) == 0 {
container.Cmd = image.Config.Cmd
if len(cmd) == 0 {
cmd = image.Config.Cmd
container["Cmd"] = cmd
}
if container.Entrypoint == nil {
container.Entrypoint = image.Config.Entrypoint
if entrypoint == nil {
entrypoint = image.Config.Entrypoint
container["Entrypoint"] = entrypoint
}
}
if len(container.Entrypoint) == 0 && len(container.Cmd) == 0 {
if len(entrypoint) == 0 && len(cmd) == 0 {
return ErrNoCommandSpecified
}
if len(container.Entrypoint) == 0 || container.Entrypoint[0] != weaveWaitEntrypoint[0] {
container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...)
if len(entrypoint) == 0 || entrypoint[0] != weaveWaitEntrypoint[0] {
container["Entrypoint"] = append(weaveWaitEntrypoint, entrypoint...)
}
return nil
}
func (i *createContainerInterceptor) setWeaveDNS(container *createContainerRequestBody, name string) error {
if i.proxy.WithoutDNS {
return nil
func (i *createContainerInterceptor) setHostname(container jsonObject, name, dnsDomain string) error {
hostname, err := container.String("Hostname")
if err != nil {
return err
}
dnsDomain, dnsRunning := i.getDNSDomain()
if !(dnsRunning || i.proxy.WithDNS) {
return nil
}
container.HostConfig.DNS = append(container.HostConfig.DNS, i.proxy.dockerBridgeIP)
if container.Hostname == "" && name != "" {
if hostname == "" && name != "" {
// Strip trailing period because it's unusual to see it used on the end of a host name
trimmedDNSDomain := strings.TrimSuffix(dnsDomain, ".")
if len(name)+1+len(trimmedDNSDomain) > MaxDockerHostname {
Log.Warningf("Container name [%s] too long to be used as hostname", name)
} else {
container.Hostname = name
container.Domainname = trimmedDNSDomain
}
}
if len(container.HostConfig.DNSSearch) == 0 {
if container.Hostname == "" {
container.HostConfig.DNSSearch = []string{dnsDomain}
} else {
container.HostConfig.DNSSearch = []string{"."}
container["Hostname"] = name
container["Domainname"] = trimmedDNSDomain
}
}
return nil
}
func (i *createContainerInterceptor) getDNSDomain() (domain string, running bool) {
domain = nameserver.DefaultDomain
weaveContainer, err := i.proxy.client.InspectContainer("weave")
if err != nil ||
weaveContainer.NetworkSettings == nil ||
weaveContainer.NetworkSettings.IPAddress == "" {
return
}
url := fmt.Sprintf("http://%s:%d/domain", weaveContainer.NetworkSettings.IPAddress, router.HTTPPort)
resp, err := http.Get(url)
if err != nil || resp.StatusCode != http.StatusOK {
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return string(b), true
}
func (i *createContainerInterceptor) InterceptResponse(r *http.Response) error {
return nil
}
func (i *createContainerInterceptor) containerHostname(r *http.Request, container jsonObject) (hostname string, err error) {
hostname = r.URL.Query().Get("name")
if i.proxy.Config.HostnameFromLabel != "" {
hostname, err = i.hostnameFromLabel(hostname, container)
}
hostname = i.proxy.hostnameMatchRegexp.ReplaceAllString(hostname, i.proxy.HostnameReplacement)
return
}
func (i *createContainerInterceptor) hostnameFromLabel(hostname string, container jsonObject) (string, error) {
labels, err := container.Object("Labels")
if err != nil {
return "", err
}
label, err := labels.String(i.proxy.Config.HostnameFromLabel)
if err != nil {
return "", err
}
if label == "" {
return hostname, nil
}
return label, nil
}
+8 -4
View File
@@ -4,14 +4,13 @@ import (
"net/http"
"strings"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
type createExecInterceptor struct{ proxy *Proxy }
func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
options := docker.CreateExecOptions{}
options := jsonObject{}
if err := unmarshalRequestBody(r, &options); err != nil {
return err
}
@@ -25,14 +24,19 @@ func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
return nil
}
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
cidrs, err := i.proxy.weaveCIDRs(container.HostConfig.NetworkMode, container.Config.Env)
if err != nil {
Log.Infof("Leaving container %s alone because %s", container.ID, err)
return nil
}
cmd, err := options.StringArray("Cmd")
if err != nil {
return err
}
Log.Infof("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " "))
options.Cmd = append(weaveWaitEntrypoint, options.Cmd...)
options["Cmd"] = append(weaveWaitEntrypoint, cmd...)
return marshalRequestBody(r, options)
}
+3 -24
View File
@@ -13,39 +13,18 @@ func (i *inspectContainerInterceptor) InterceptRequest(r *http.Request) error {
}
func (i *inspectContainerInterceptor) InterceptResponse(r *http.Response) error {
if !i.proxy.RewriteInspect {
if !i.proxy.RewriteInspect || r.StatusCode != 200 {
return nil
}
container := map[string]interface{}{}
container := jsonObject{}
if err := unmarshalResponseBody(r, &container); err != nil {
return err
}
if err := updateContainerNetworkSettings(container); err != nil {
if err := i.proxy.updateContainerNetworkSettings(container); err != nil {
Log.Warningf("Inspecting container %s failed: %s", container["Id"], err)
}
return marshalResponseBody(r, container)
}
func updateContainerNetworkSettings(container map[string]interface{}) error {
containerID, err := lookupString(container, "Id")
if err != nil {
return err
}
mac, ips, nets, err := weaveContainerIPs(containerID)
if err != nil || len(ips) == 0 {
return err
}
networkSettings, err := lookupObject(container, "NetworkSettings")
if err != nil {
return err
}
networkSettings["MacAddress"] = mac
networkSettings["IPAddress"] = ips[0].String()
networkSettings["IPPrefixLen"], _ = nets[0].Mask.Size()
return nil
}
+3 -3
View File
@@ -17,17 +17,17 @@ func (i *inspectExecInterceptor) InterceptResponse(r *http.Response) error {
return nil
}
exec := map[string]interface{}{}
exec := jsonObject{}
if err := unmarshalResponseBody(r, &exec); err != nil {
return err
}
container, err := lookupObject(exec, "Container")
container, err := exec.Object("Container")
if err != nil {
return err
}
if err := updateContainerNetworkSettings(container); err != nil {
if err := i.proxy.updateContainerNetworkSettings(container); err != nil {
Log.Warningf("Inspecting exec %s failed: %s", exec["Id"], err)
}
+35 -9
View File
@@ -13,12 +13,13 @@ func (e *UnmarshalWrongTypeError) Error() string {
return fmt.Sprintf("Wrong type for %s field, expected %s, but got %T", e.Field, e.Expected, e.Got)
}
// For parsing json objects. Look up (or create) a child object in the given map.
func lookupObject(m map[string]interface{}, key string) (map[string]interface{}, error) {
iface, ok := m[key]
type jsonObject map[string]interface{}
func (j jsonObject) Object(key string) (jsonObject, error) {
iface, ok := j[key]
if !ok || iface == nil {
result := map[string]interface{}{}
m[key] = result
result := jsonObject{}
j[key] = result
return result, nil
}
@@ -27,12 +28,11 @@ func lookupObject(m map[string]interface{}, key string) (map[string]interface{},
return nil, &UnmarshalWrongTypeError{key, "object", iface}
}
return result, nil
return jsonObject(result), nil
}
// For parsing json objects. Look up (or create) a string in the given map.
func lookupString(m map[string]interface{}, key string) (string, error) {
iface, ok := m[key]
func (j jsonObject) String(key string) (string, error) {
iface, ok := j[key]
if !ok || iface == nil {
return "", nil
}
@@ -44,3 +44,29 @@ func lookupString(m map[string]interface{}, key string) (string, error) {
return result, nil
}
func (j jsonObject) StringArray(key string) ([]string, error) {
iface, ok := j[key]
if !ok || iface == nil {
return nil, nil
}
switch o := iface.(type) {
case string:
return []string{o}, nil
case []string:
return o, nil
case []interface{}:
result := []string{}
for _, s := range o {
if s, ok := s.(string); ok {
result = append(result, s)
} else {
return nil, &UnmarshalWrongTypeError{key, "string or array of strings", iface}
}
}
return result, nil
}
return nil, &UnmarshalWrongTypeError{key, "string or array of strings", iface}
}
+116
View File
@@ -0,0 +1,116 @@
package proxy
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLookupObject(t *testing.T) {
tests := []struct {
root jsonObject
key string
result jsonObject
err error
}{
{
jsonObject{},
"a",
jsonObject{},
nil,
},
{
jsonObject{"a": map[string]interface{}{"b": int(1)}},
"a",
jsonObject{"b": int(1)},
nil,
},
{
jsonObject{"nonObject": int(1)},
"nonObject",
nil,
&UnmarshalWrongTypeError{Field: "nonObject", Expected: "object", Got: 1},
},
}
for _, test := range tests {
gotResult, gotErr := test.root.Object(test.key)
msg := fmt.Sprintf("%q.Object(%q) => %q, %q", test.root, test.key, gotResult, gotErr)
assert.Equal(t, test.result, gotResult, msg)
assert.Equal(t, test.err, gotErr, msg)
}
}
func TestLookupString(t *testing.T) {
tests := []struct {
root jsonObject
key string
result string
err error
}{
{
jsonObject{},
"a",
"",
nil,
},
{
jsonObject{"nonString": int(1)},
"nonString",
"",
&UnmarshalWrongTypeError{Field: "nonString", Expected: "string", Got: 1},
},
}
for _, test := range tests {
gotResult, gotErr := test.root.String(test.key)
msg := fmt.Sprintf("%q.String(%q) => %q, %q", test.root, test.key, gotResult, gotErr)
assert.Equal(t, test.result, gotResult, msg)
assert.Equal(t, test.err, gotErr, msg)
}
}
func TestLookupStringArray(t *testing.T) {
tests := []struct {
root jsonObject
key string
result []string
err error
}{
{
jsonObject{},
"a",
nil,
nil,
},
{
jsonObject{"a": []string{"foo"}},
"a",
[]string{"foo"},
nil,
},
{
jsonObject{"a": []string{}},
"a",
[]string{},
nil,
},
{
jsonObject{"a": "foo"},
"a",
[]string{"foo"},
nil,
},
{
jsonObject{"int": 5},
"int",
nil,
&UnmarshalWrongTypeError{Field: "int", Expected: "string or array of strings", Got: 5},
},
}
for _, test := range tests {
gotResult, gotErr := test.root.StringArray(test.key)
msg := fmt.Sprintf("%q.String(%q) => %q, %q", test.root, test.key, gotResult, gotErr)
assert.Equal(t, test.result, gotResult, msg)
assert.Equal(t, test.err, gotErr, msg)
}
}
+417 -38
View File
@@ -1,26 +1,32 @@
package proxy
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"syscall"
"github.com/fsouza/go-dockerclient"
docker "github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
weavedocker "github.com/weaveworks/weave/common/docker"
)
const (
defaultCaFile = "ca.pem"
defaultKeyFile = "key.pem"
defaultCertFile = "cert.pem"
dockerSock = "/var/run/docker.sock"
dockerSockUnix = "unix://" + dockerSock
weaveSock = "/var/run/weave/weave.sock"
weaveSockUnix = "unix://" + weaveSock
)
var (
@@ -42,41 +48,56 @@ type Config struct {
HostnameFromLabel string
HostnameMatch string
HostnameReplacement string
Image string
ListenAddrs []string
RewriteInspect bool
NoDefaultIPAM bool
NoRewriteHosts bool
TLSConfig TLSConfig
Version string
WithDNS bool
WithoutDNS bool
NoMulticastRoute bool
DockerHost string
}
type wait struct {
ident string
ch chan error
done bool
}
type Proxy struct {
sync.Mutex
Config
client *docker.Client
dockerBridgeIP string
hostnameMatchRegexp *regexp.Regexp
weaveWaitVolume string
client *docker.Client
dockerBridgeIP string
hostnameMatchRegexp *regexp.Regexp
weaveWaitVolume string
weaveWaitNoopVolume string
weaveWaitNomcastVolume string
normalisedAddrs []string
waiters map[*http.Request]*wait
}
func NewProxy(c Config) (*Proxy, error) {
p := &Proxy{Config: c}
p := &Proxy{Config: c, waiters: make(map[*http.Request]*wait)}
if err := p.TLSConfig.loadCerts(); err != nil {
if err := p.TLSConfig.LoadCerts(); err != nil {
Log.Fatalf("Could not configure tls for proxy: %s", err)
}
// We pin the protocol version to 1.15 (which corresponds to
// Docker 1.3.x; the earliest version supported by weave) in order
// We pin the protocol version to 1.18 (which corresponds to
// Docker 1.6.x; the earliest version supported by weave) in order
// to insulate ourselves from breaking changes to the API, as
// happened in 1.20 (Docker 1.8.0) when the presentation of
// volumes changed in `inspect`.
client, err := docker.NewVersionedClient(dockerSockUnix, "1.15")
client, err := weavedocker.NewVersionedClient(c.DockerHost, "1.18")
if err != nil {
return nil, err
} else {
Log.Info(client.Info())
}
p.client = client
p.client = client.Client
if !p.WithoutDNS {
dockerBridgeIP, stderr, err := callWeave("docker-bridge-ip")
@@ -92,34 +113,72 @@ func NewProxy(c Config) (*Proxy, error) {
return nil, err
}
if err = p.findWeaveWaitVolume(); err != nil {
if err = p.findWeaveWaitVolumes(); err != nil {
return nil, err
}
client.AddObserver(p)
return p, nil
}
func (proxy *Proxy) Dial() (net.Conn, error) {
return net.Dial("unix", dockerSock)
func (proxy *Proxy) AttachExistingContainers() {
containers, _ := proxy.client.ListContainers(docker.ListContainersOptions{})
for _, c := range containers {
container, err := proxy.client.InspectContainer(c.ID)
if err != nil {
if _, ok := err.(*docker.NoSuchContainer); !ok {
Log.Warningf("unable to attach existing container %s since inspecting it failed: %v", c.ID, err)
}
continue
}
if containerShouldAttach(container) && (container.State.Running || container.State.Paused) {
proxy.attach(container, false)
}
}
}
func (proxy *Proxy) findWeaveWaitVolume() error {
func (proxy *Proxy) Dial() (net.Conn, error) {
proto := "tcp"
addr := proxy.Config.DockerHost
switch {
case strings.HasPrefix(addr, "unix://"):
proto = "unix"
addr = strings.TrimPrefix(addr, "unix://")
case strings.HasPrefix(addr, "tcp://"):
addr = strings.TrimPrefix(addr, "tcp://")
}
return net.Dial(proto, addr)
}
func (proxy *Proxy) findWeaveWaitVolumes() error {
var err error
if proxy.weaveWaitVolume, err = proxy.findVolume("/w"); err != nil {
return err
}
if proxy.weaveWaitNoopVolume, err = proxy.findVolume("/w-noop"); err != nil {
return err
}
proxy.weaveWaitNomcastVolume, err = proxy.findVolume("/w-nomcast")
return err
}
func (proxy *Proxy) findVolume(v string) (string, error) {
container, err := proxy.client.InspectContainer("weaveproxy")
if err != nil {
return fmt.Errorf("Could not find the weavewait volume: %s", err)
return "", fmt.Errorf("Could not find the weavewait volume: %s", err)
}
if container.Volumes == nil {
return fmt.Errorf("Could not find the weavewait volume")
return "", fmt.Errorf("Could not find the weavewait volume")
}
volume, ok := container.Volumes["/w"]
volume, ok := container.Volumes[v]
if !ok {
return fmt.Errorf("Could not find the weavewait volume")
return "", fmt.Errorf("Could not find the weavewait volume")
}
proxy.weaveWaitVolume = volume
return nil
return volume, nil
}
func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -143,22 +202,44 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
proxy.Intercept(i, w, r)
}
func (proxy *Proxy) ListenAndServe() {
func (proxy *Proxy) Listen() []net.Listener {
listeners := []net.Listener{}
addrs := []string{}
proxy.normalisedAddrs = []string{}
unixAddrs := []string{}
for _, addr := range proxy.ListenAddrs {
if strings.HasPrefix(addr, "unix://") || strings.HasPrefix(addr, "/") {
unixAddrs = append(unixAddrs, addr)
continue
}
listener, normalisedAddr, err := proxy.listen(addr)
if err != nil {
Log.Fatalf("Cannot listen on %s: %s", addr, err)
}
listeners = append(listeners, listener)
addrs = append(addrs, normalisedAddr)
proxy.normalisedAddrs = append(proxy.normalisedAddrs, normalisedAddr)
}
for _, addr := range addrs {
if len(unixAddrs) > 0 {
listener, _, err := proxy.listen(weaveSockUnix)
if err != nil {
Log.Fatalf("Cannot listen on %s: %s", weaveSockUnix, err)
}
listeners = append(listeners, listener)
if err := proxy.symlink(unixAddrs); err != nil {
Log.Fatalf("Cannot listen on unix sockets: %s", err)
}
proxy.normalisedAddrs = append(proxy.normalisedAddrs, weaveSockUnix)
}
for _, addr := range proxy.normalisedAddrs {
Log.Infoln("proxy listening on", addr)
}
return listeners
}
func (proxy *Proxy) Serve(listeners []net.Listener) {
errs := make(chan error)
for _, listener := range listeners {
go func(listener net.Listener) {
@@ -173,6 +254,26 @@ func (proxy *Proxy) ListenAndServe() {
}
}
func (proxy *Proxy) ListenAndServeStatus(socket string) {
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
Log.Fatalf("Error removing existing status socket: %s", err)
}
listener, err := net.Listen("unix", socket)
if err != nil {
Log.Fatalf("ListenAndServeStatus failed: %s", err)
}
handler := http.HandlerFunc(proxy.StatusHTTP)
if err := (&http.Server{Handler: handler}).Serve(listener); err != nil {
Log.Fatalf("ListenAndServeStatus failed: %s", err)
}
}
func (proxy *Proxy) StatusHTTP(w http.ResponseWriter, r *http.Request) {
for _, addr := range proxy.normalisedAddrs {
fmt.Fprintln(w, addr)
}
}
func copyOwnerAndPermissions(from, to string) error {
stat, err := os.Stat(from)
if err != nil {
@@ -215,7 +316,7 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) {
if err != nil {
return nil, "", err
}
if proxy.TLSConfig.enabled() {
if proxy.TLSConfig.IsEnabled() {
listener = tls.NewListener(listener, proxy.TLSConfig.Config)
}
@@ -228,8 +329,10 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) {
if err != nil {
return nil, "", err
}
if err = copyOwnerAndPermissions(dockerSock, addr); err != nil {
return nil, "", err
if strings.HasPrefix(proxy.Config.DockerHost, "unix://") {
if err = copyOwnerAndPermissions(strings.TrimPrefix(proxy.Config.DockerHost, "unix://"), addr); err != nil {
return nil, "", err
}
}
default:
@@ -239,15 +342,143 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) {
return listener, fmt.Sprintf("%s://%s", proto, addr), nil
}
func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *docker.HostConfig) ([]string, error) {
netMode := ""
if hostConfig != nil {
netMode = hostConfig.NetworkMode
// weavedocker.ContainerObserver interface
func (proxy *Proxy) ContainerStarted(ident string) {
container, err := proxy.client.InspectContainer(ident)
if err == nil {
if containerShouldAttach(container) {
err = proxy.attach(container, true)
} else if containerIsWeaveRouter(container) {
err = proxy.attachRouter(container)
}
}
if netMode == "host" || strings.HasPrefix(netMode, "container:") {
return nil, fmt.Errorf("the container was created with the '--net=%s'", netMode)
if _, ok := err.(*docker.NoSuchContainer); err != nil && !ok {
Log.Warningf("unable to attach new container %s since inspecting it failed: %v", ident, err)
}
for _, e := range config.Env {
proxy.notifyWaiters(ident, err)
}
func containerShouldAttach(container *docker.Container) bool {
return len(container.Config.Entrypoint) > 0 && container.Config.Entrypoint[0] == weaveWaitEntrypoint[0]
}
func containerIsWeaveRouter(container *docker.Container) bool {
return container.Name == weaveContainerName &&
len(container.Config.Entrypoint) > 0 && container.Config.Entrypoint[0] == weaveEntrypoint
}
func (proxy *Proxy) createWait(r *http.Request, ident string) {
proxy.Lock()
proxy.waiters[r] = &wait{ident: ident, ch: make(chan error, 1)}
proxy.Unlock()
}
func (proxy *Proxy) removeWait(r *http.Request) {
proxy.Lock()
delete(proxy.waiters, r)
proxy.Unlock()
}
func (proxy *Proxy) notifyWaiters(ident string, err error) {
proxy.Lock()
for _, wait := range proxy.waiters {
if ident == wait.ident && !wait.done {
wait.ch <- err
close(wait.ch)
wait.done = true
}
}
proxy.Unlock()
}
func (proxy *Proxy) waitForStart(r *http.Request) error {
var ch chan error
proxy.Lock()
wait, found := proxy.waiters[r]
if found {
ch = wait.ch
}
proxy.Unlock()
if ch != nil {
Log.Debugf("Wait for start of container %s", wait.ident)
return <-ch
}
return nil
}
// If some other operation is waiting for a container to start, join in the wait
func (proxy *Proxy) waitForStartByIdent(ident string) error {
var ch chan error
proxy.Lock()
for _, wait := range proxy.waiters {
if ident == wait.ident && !wait.done {
ch = wait.ch
break
}
}
proxy.Unlock()
if ch != nil {
Log.Debugf("Wait for start of container %s", ident)
return <-ch
}
return nil
}
func (proxy *Proxy) ContainerDied(ident string) {
}
func (proxy *Proxy) attach(container *docker.Container, orDie bool) error {
cidrs, err := proxy.weaveCIDRs(container.HostConfig.NetworkMode, container.Config.Env)
if err != nil {
Log.Infof("Leaving container %s alone because %s", container.ID, err)
return nil
}
Log.Infof("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
args := []string{"attach"}
args = append(args, cidrs...)
if !proxy.NoRewriteHosts {
args = append(args, "--rewrite-hosts")
if container.HostConfig != nil {
for _, eh := range container.HostConfig.ExtraHosts {
args = append(args, fmt.Sprintf("--add-host=%s", eh))
}
}
}
if proxy.NoMulticastRoute {
args = append(args, "--no-multicast-route")
}
if orDie {
args = append(args, "--or-die")
}
args = append(args, container.ID)
if _, stderr, err := callWeave(args...); err != nil {
Log.Warningf("Attaching container %s to weave network failed: %s", container.ID, string(stderr))
return errors.New(string(stderr))
} else if len(stderr) > 0 {
Log.Warningf("Attaching container %s to weave network: %s", container.ID, string(stderr))
}
return nil
}
func (proxy *Proxy) attachRouter(container *docker.Container) error {
Log.Infof("Attaching weave router container: %s", container.ID)
args := []string{"attach-router"}
if _, stderr, err := callWeave(args...); err != nil {
Log.Warningf("Attaching container %s to weave network failed: %s", container.ID, string(stderr))
return errors.New(string(stderr))
} else if len(stderr) > 0 {
Log.Warningf("Attaching container %s to weave network: %s", container.ID, string(stderr))
}
return nil
}
func (proxy *Proxy) weaveCIDRs(networkMode string, env []string) ([]string, error) {
if networkMode == "host" || strings.HasPrefix(networkMode, "container:") {
return nil, fmt.Errorf("the container has '--net=%s'", networkMode)
}
for _, e := range env {
if strings.HasPrefix(e, "WEAVE_CIDR=") {
if e[11:] == "none" {
return nil, ErrWeaveCIDRNone
@@ -260,3 +491,151 @@ func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *dock
}
return nil, nil
}
func (proxy *Proxy) setWeaveDNS(hostConfig jsonObject, hostname, dnsDomain string) error {
dns, err := hostConfig.StringArray("Dns")
if err != nil {
return err
}
hostConfig["Dns"] = append(dns, proxy.dockerBridgeIP)
dnsSearch, err := hostConfig.StringArray("DnsSearch")
if err != nil {
return err
}
if len(dnsSearch) == 0 {
if hostname == "" {
hostConfig["DnsSearch"] = []string{dnsDomain}
} else {
hostConfig["DnsSearch"] = []string{"."}
}
}
return nil
}
func (proxy *Proxy) getDNSDomain() (domain string) {
if proxy.WithoutDNS {
return ""
}
weaveContainer, err := proxy.client.InspectContainer("weave")
var weaveIP string
if err == nil && weaveContainer.NetworkSettings != nil {
weaveIP = weaveContainer.NetworkSettings.IPAddress
}
if weaveIP == "" {
weaveIP = "127.0.0.1"
}
url := fmt.Sprintf("http://%s:6784/domain", weaveIP)
resp, err := http.Get(url)
if err != nil || resp.StatusCode != http.StatusOK {
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return string(b)
}
func (proxy *Proxy) updateContainerNetworkSettings(container jsonObject) error {
containerID, err := container.String("Id")
if err != nil {
return err
}
if err := proxy.waitForStartByIdent(containerID); err != nil {
return err
}
mac, ips, nets, err := weaveContainerIPs(containerID)
if err != nil || len(ips) == 0 {
return err
}
networkSettings, err := container.Object("NetworkSettings")
if err != nil {
return err
}
networkSettings["MacAddress"] = mac
networkSettings["IPAddress"] = ips[0].String()
networkSettings["IPPrefixLen"], _ = nets[0].Mask.Size()
return nil
}
func (proxy *Proxy) symlink(unixAddrs []string) (err error) {
var container *docker.Container
binds := []string{"/var/run/weave:/var/run/weave"}
froms := []string{}
for _, addr := range unixAddrs {
if addr == weaveSockUnix {
continue
}
from := strings.TrimPrefix(addr, "unix://")
dir := filepath.Dir(from)
binds = append(binds, dir+":"+filepath.Join("/host", dir))
froms = append(froms, filepath.Join("/host", from))
proxy.normalisedAddrs = append(proxy.normalisedAddrs, addr)
}
if len(froms) == 0 {
return
}
env := []string{
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
}
if val := os.Getenv("WEAVE_DEBUG"); val != "" {
env = append(env, fmt.Sprintf("%s=%s", "WEAVE_DEBUG", val))
}
container, err = proxy.client.CreateContainer(docker.CreateContainerOptions{
Config: &docker.Config{
Image: proxy.Image,
Entrypoint: []string{"/home/weave/symlink", weaveSock},
Cmd: froms,
Env: env,
},
HostConfig: &docker.HostConfig{Binds: binds},
})
if err != nil {
return
}
defer func() {
err2 := proxy.client.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID})
if err == nil {
err = err2
}
}()
err = proxy.client.StartContainer(container.ID, nil)
if err != nil {
return
}
var buf bytes.Buffer
err = proxy.client.AttachToContainer(docker.AttachToContainerOptions{
Container: container.ID,
ErrorStream: &buf,
Logs: true,
Stderr: true,
})
if err != nil {
return
}
var rc int
rc, err = proxy.client.WaitContainer(container.ID)
if err != nil {
return
}
if rc != 0 {
err = errors.New(buf.String())
}
return
}
+59 -30
View File
@@ -1,47 +1,76 @@
package proxy
import (
"errors"
"net/http"
"strings"
. "github.com/weaveworks/weave/common"
)
type startContainerInterceptor struct{ proxy *Proxy }
func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error {
return nil
}
func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
if r.StatusCode < 200 || r.StatusCode >= 300 { // Docker didn't do the start
return nil
}
container, err := inspectContainerInPath(i.proxy.client, r.Request.URL.Path)
container, err := inspectContainerInPath(i.proxy.client, r.URL.Path)
if err != nil {
return err
}
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
if err != nil {
Log.Infof("Leaving container %s alone because %s", container.ID, err)
return nil
}
Log.Infof("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
args := []string{"attach"}
args = append(args, cidrs...)
if !i.proxy.NoRewriteHosts {
args = append(args, "--rewrite-hosts")
}
args = append(args, "--or-die", container.ID)
if _, stderr, err := callWeave(args...); err != nil {
Log.Warningf("Attaching container %s to weave network failed: %s", container.ID, string(stderr))
return errors.New(string(stderr))
} else if len(stderr) > 0 {
Log.Warningf("Attaching container %s to weave network: %s", container.ID, string(stderr))
}
// If the client has sent some JSON which might be a HostConfig, add our
// parameters back into it, otherwise Docker will consider them overwritten
if containerShouldAttach(container) && r.Header.Get("Content-Type") == "application/json" {
params := map[string]interface{}{}
if err := unmarshalRequestBody(r, &params); err != nil {
return err
}
// HostConfig can be sent either as a struct named HostConfig, or unnamed at top level
var hostConfig map[string]interface{}
if subParam, found := params["HostConfig"]; found {
if typecast, ok := subParam.(map[string]interface{}); ok {
hostConfig = typecast
}
// We can't reliably detect what Docker will see as a top-level HostConfig,
// so just assume any parameter at top level indicates there is one.
} else if len(params) > 0 {
hostConfig = params
}
if hostConfig != nil {
networkMode, err := jsonObject(hostConfig).String("NetworkMode")
if err != nil {
return err
}
if strings.HasPrefix(networkMode, "container:") || networkMode == "host" {
if err := addVolume(hostConfig, i.proxy.weaveWaitNoopVolume, "/w", "ro"); err != nil {
return err
}
} else {
if i.proxy.NoMulticastRoute {
if err := addVolume(hostConfig, i.proxy.weaveWaitNomcastVolume, "/w", "ro"); err != nil {
return err
}
} else {
if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil {
return err
}
}
if dnsDomain := i.proxy.getDNSDomain(); dnsDomain != "" {
if err := i.proxy.setWeaveDNS(hostConfig, container.Config.Hostname, dnsDomain); err != nil {
return err
}
}
}
// Note we marshal the original top-level dictionary to avoid disturbing anything else
if err := marshalRequestBody(r, params); err != nil {
return err
}
}
}
i.proxy.createWait(r, container.ID)
return nil
}
func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
defer i.proxy.removeWait(r.Request)
if r.StatusCode < 200 || r.StatusCode >= 300 { // Docker didn't do the start
return nil
}
return i.proxy.waitForStart(r.Request)
}
+5 -3
View File
@@ -17,15 +17,17 @@ type TLSConfig struct {
*tls.Config
}
func (c *TLSConfig) enabled() bool {
// IsEnabled returns true if TLS is enable, according to the config.
func (c *TLSConfig) IsEnabled() bool {
if c == nil {
return false
}
return c.Enabled || c.Verify
}
func (c *TLSConfig) loadCerts() error {
if !c.enabled() {
// LoadCerts loads the certificates into c.Config, if TLS is enabled.
func (c *TLSConfig) LoadCerts() error {
if !c.IsEnabled() {
return nil
}
+35
View File
@@ -0,0 +1,35 @@
package router
// Interface to packet handling on the local virtual bridge
type Bridge interface {
// Inject a packet to be delivered locally
InjectPacket(PacketKey) FlowOp
// Start consuming packets from the bridge. Injected packets
// should not be included.
StartConsumingPackets(BridgeConsumer) error
String() string
Stats() map[string]int
}
// A function that determines how to handle locally captured packets.
type BridgeConsumer func(PacketKey) FlowOp
type NullBridge struct{}
func (NullBridge) InjectPacket(PacketKey) FlowOp {
return nil
}
func (NullBridge) StartConsumingPackets(BridgeConsumer) error {
return nil
}
func (NullBridge) String() string {
return "<no bridge networking>"
}
func (NullBridge) Stats() map[string]int {
return nil
}
-589
View File
@@ -1,589 +0,0 @@
package router
import (
"encoding/binary"
"errors"
"fmt"
"net"
"strconv"
"sync"
"time"
)
type Connection interface {
Local() *Peer
Remote() *Peer
RemoteTCPAddr() string
Outbound() bool
Established() bool
BreakTie(Connection) ConnectionTieBreak
Shutdown(error)
Log(args ...interface{})
}
type ConnectionTieBreak int
const (
TieBreakWon ConnectionTieBreak = iota
TieBreakLost
TieBreakTied
)
var ErrConnectToSelf = errors.New("Cannot connect to ourself")
type RemoteConnection struct {
local *Peer
remote *Peer
remoteTCPAddr string
outbound bool
established bool
}
type LocalConnection struct {
sync.RWMutex
RemoteConnection
TCPConn *net.TCPConn
version byte
tcpSender TCPSender
remoteUDPAddr *net.UDPAddr
receivedHeartbeat bool
stackFrag bool
effectivePMTU int
SessionKey *[32]byte
heartbeatTCP *time.Ticker
heartbeatTimeout *time.Timer
heartbeatFrame []byte
heartbeat *time.Ticker
fragTest *time.Ticker
forwarder *Forwarder
forwarderDF *ForwarderDF
Decryptor Decryptor
Router *Router
uid uint64
actionChan chan<- ConnectionAction
finished <-chan struct{} // closed to signal that actorLoop has finished
}
type ConnectionAction func() error
func NewRemoteConnection(from, to *Peer, tcpAddr string, outbound bool, established bool) *RemoteConnection {
return &RemoteConnection{
local: from,
remote: to,
remoteTCPAddr: tcpAddr,
outbound: outbound,
established: established,
}
}
func (conn *RemoteConnection) Local() *Peer { return conn.local }
func (conn *RemoteConnection) Remote() *Peer { return conn.remote }
func (conn *RemoteConnection) RemoteTCPAddr() string { return conn.remoteTCPAddr }
func (conn *RemoteConnection) Outbound() bool { return conn.outbound }
func (conn *RemoteConnection) Established() bool { return conn.established }
func (conn *RemoteConnection) BreakTie(Connection) ConnectionTieBreak { return TieBreakTied }
func (conn *RemoteConnection) Shutdown(error) {}
func (conn *RemoteConnection) Log(args ...interface{}) {
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
}
func (conn *RemoteConnection) ErrorLog(args ...interface{}) {
log.Errorln(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
}
// 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) {
if connRemote.local != router.Ourself.Peer {
log.Fatal("Attempt to create local connection from a peer which is not ourself")
}
// NB, we're taking a copy of connRemote here.
actionChan := make(chan ConnectionAction, ChannelSize)
finished := make(chan struct{})
conn := &LocalConnection{
RemoteConnection: *connRemote,
Router: router,
TCPConn: tcpConn,
remoteUDPAddr: udpAddr,
effectivePMTU: DefaultPMTU,
uid: randUint64(),
actionChan: actionChan,
finished: finished}
go conn.run(actionChan, finished, acceptNewPeer)
}
func (conn *LocalConnection) BreakTie(dupConn Connection) ConnectionTieBreak {
dupConnLocal := dupConn.(*LocalConnection)
// conn.uid is used as the tie breaker here, in the knowledge that
// both sides will make the same decision.
if conn.uid < dupConnLocal.uid {
return TieBreakWon
} else if dupConnLocal.uid < conn.uid {
return TieBreakLost
} else {
return TieBreakTied
}
}
// Read by the forwarder processes when in the UDP senders
func (conn *LocalConnection) RemoteUDPAddr() *net.UDPAddr {
conn.RLock()
defer conn.RUnlock()
return conn.remoteUDPAddr
}
func (conn *LocalConnection) Established() bool {
conn.RLock()
defer conn.RUnlock()
return conn.established
}
// Called by forwarder processes, read in Forward (by sniffer and udp
// listener process in router).
func (conn *LocalConnection) setEffectivePMTU(pmtu int) {
conn.Lock()
defer conn.Unlock()
if conn.effectivePMTU != pmtu {
conn.effectivePMTU = pmtu
conn.Log("Effective PMTU set to", pmtu)
}
}
// Called by the connection's actor process, and by the connection's
// TCP receiver process. StackFrag is read in conn.forward
func (conn *LocalConnection) setStackFrag(frag bool) {
conn.Lock()
defer conn.Unlock()
conn.stackFrag = frag
}
// Called by the connection's TCP receiver process.
func (conn *LocalConnection) pmtuVerified(pmtu int) {
conn.RLock()
fwd := conn.forwarderDF
conn.RUnlock()
if fwd != nil {
fwd.PMTUVerified(pmtu)
}
}
// Send directly, not via the Actor. If it goes via the Actor we can
// get a deadlock where LocalConnection is blocked talking to
// LocalPeer and LocalPeer is blocked trying send a ProtocolMsg via
// LocalConnection, and the channels are full in both directions so
// nothing can proceed.
func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
if err := conn.sendProtocolMsg(m); err != nil {
conn.Shutdown(err)
}
}
// ACTOR methods
// NB: The conn.* fields are only written by the connection actor
// process, which is the caller of the ConnectionAction funs. Hence we
// do not need locks for reading, and only need write locks for fields
// read by other processes.
// Async
func (conn *LocalConnection) Shutdown(err error) {
// err should always be a real error, even if only io.EOF
if err == nil {
panic("nil error")
}
// Run on its own goroutine in case the channel is backed up
go func() { conn.sendAction(func() error { return err }) }()
}
// Async
//
// Heartbeating serves two purposes: a) keeping NAT paths alive, and
// b) updating a remote peer's knowledge of our address, in the event
// it changes (e.g. because NAT paths expired).
func (conn *LocalConnection) ReceivedHeartbeat(remoteUDPAddr *net.UDPAddr, connUID uint64) {
if remoteUDPAddr == nil || connUID != conn.uid {
return
}
conn.sendAction(func() error {
oldRemoteUDPAddr := conn.remoteUDPAddr
old := conn.receivedHeartbeat
conn.Lock()
conn.remoteUDPAddr = remoteUDPAddr
conn.receivedHeartbeat = true
conn.Unlock()
conn.heartbeatTimeout.Reset(HeartbeatTimeout)
if !old {
if err := conn.sendSimpleProtocolMsg(ProtocolConnectionEstablished); err != nil {
return err
}
}
if oldRemoteUDPAddr == nil {
return conn.sendFastHeartbeats()
} else if oldRemoteUDPAddr.String() != remoteUDPAddr.String() {
log.Println("Peer", conn.remote, "moved from", oldRemoteUDPAddr, "to", remoteUDPAddr)
}
return nil
})
}
// Async
func (conn *LocalConnection) SetEstablished() {
conn.sendAction(func() error {
stopTicker(conn.heartbeat)
old := conn.established
conn.Lock()
conn.established = true
conn.Unlock()
if old {
return nil
}
conn.Router.Ourself.ConnectionEstablished(conn)
if err := conn.ensureForwarders(); err != nil {
return err
}
// Send a large frame down the DF channel in order to prompt
// PMTU discovery to start.
conn.Send(true, PMTUDiscovery)
conn.heartbeat = time.NewTicker(SlowHeartbeat)
conn.fragTest = time.NewTicker(FragTestInterval)
// avoid initial waits for timers to fire
conn.Send(true, conn.heartbeatFrame)
conn.performFragTest()
return nil
})
}
// Send an actor request to the actorLoop, but don't block if
// actorLoop has exited - see http://blog.golang.org/pipelines for
// pattern
func (conn *LocalConnection) sendAction(action ConnectionAction) {
select {
case conn.actionChan <- action:
case <-conn.finished:
}
}
// ACTOR server
func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished chan<- struct{}, acceptNewPeer bool) {
var err error // important to use this var and not create another one with 'err :='
defer func() { conn.shutdown(err) }()
defer close(finished)
conn.TCPConn.SetLinger(0)
intro, err := ProtocolIntroParams{
MinVersion: conn.Router.ProtocolMinVersion,
MaxVersion: ProtocolMaxVersion,
Features: conn.makeFeatures(),
Conn: conn.TCPConn,
Password: conn.Router.Password,
Outbound: conn.outbound,
}.DoIntro()
if err != nil {
return
}
conn.SessionKey = intro.SessionKey
conn.tcpSender = intro.Sender
conn.version = intro.Version
remote, err := conn.parseFeatures(intro.Features)
if err != nil {
return
}
if err = conn.registerRemote(remote, acceptNewPeer); err != nil {
return
}
if conn.SessionKey == nil {
conn.Decryptor = NewNonDecryptor()
} else {
conn.Decryptor = NewNaClDecryptor(conn.SessionKey, conn.outbound)
}
conn.Log("connection ready; using protocol version", conn.version)
// The ordering of the following is very important. [1]
if conn.remoteUDPAddr != nil {
if err = conn.ensureForwarders(); err != nil {
return
}
}
if err = conn.Router.Ourself.AddConnection(conn); err != nil {
return
}
if err = conn.initHeartbeats(); err != nil {
return
}
go conn.receiveTCP(intro.Receiver)
err = conn.actorLoop(actionChan)
}
// [1] Ordering constraints:
//
// (a) AddConnections must precede initHeartbeats. It is only after
// the former completes that we know the connection is valid, in
// particular 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
// invald.
//
// (b) AddConnection must precede receiveTCP. In the absence of any
// indirect connectivity to the remote peer, the first we hear about
// it (and any peers reachable from it) is through topology gossip it
// sends us on the connection. We must ensure that the connection has
// been added to Ourself prior to processing any such gossip,
// otherwise we risk immediately gc'ing part of that newly received
// portion of the topology (though not the remote peer itself, since
// that will have a positive ref count), leaving behind dangling
// references to peers. Hence we must invoke AddConnection, which is
// *synchronous*, first.
//
// (c) AddConnection must precede actorLoop. More precisely, it must
// precede shutdown, since that invokes DeleteConnection and is
// invoked on termination of this entire function. Essentially this
// boils down to a prohibition on running AddConnection in a separate
// goroutine, at least not without some synchronisation. Which in turn
// requires us the launching of the receiveTCP goroutine to precede
// actorLoop.
//
// (d) AddConnection should precede receiveTCP. There is no point
// starting the latter if the former fails.
//
// (e) initHeartbeats should precede actorLoop. The former is setting
// LocalConnection fields accessed by the latter. Since the latter
// runs in a separate goroutine, we'd have to add some synchronisation
// if initHeartbeats isn't run first.
//
// (f) ensureForwarders should precede AddConnection. As soon as a
// connection has been added to LocalPeer by the latter, it becomes
// visible to the packet routing logic, which will end up dropping
// packets if the forwarders haven't been created yet. We cannot
// prevent that completely, since, for example, forwarder can only be
// created when we know the remote UDP address, but it helps to try.
func (conn *LocalConnection) makeFeatures() map[string]string {
return map[string]string{
"PeerNameFlavour": PeerNameFlavour,
"Name": conn.local.Name.String(),
"NickName": conn.local.NickName,
"UID": fmt.Sprint(conn.local.UID),
"ConnID": fmt.Sprint(conn.uid),
}
}
type features map[string]string
func (features features) MustHave(keys []string) error {
for _, key := range keys {
if _, ok := features[key]; !ok {
return fmt.Errorf("Field %s is missing", key)
}
}
return nil
}
func (features features) Get(key string) string {
return features[key]
}
func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
if err := features.MustHave([]string{"PeerNameFlavour", "Name", "NickName", "UID", "ConnID"}); err != nil {
return nil, err
}
remotePeerNameFlavour := features.Get("PeerNameFlavour")
if remotePeerNameFlavour != PeerNameFlavour {
return nil, fmt.Errorf("Peer name flavour mismatch (ours: '%s', theirs: '%s')", PeerNameFlavour, remotePeerNameFlavour)
}
name, err := PeerNameFromString(features.Get("Name"))
if err != nil {
return nil, err
}
nickName := features.Get("NickName")
uid, err := ParsePeerUID(features.Get("UID"))
if err != nil {
return nil, err
}
remoteConnID, err := strconv.ParseUint(features.Get("ConnID"), 10, 64)
if err != nil {
return nil, err
}
conn.uid ^= remoteConnID
return NewPeer(name, nickName, uid, 0), nil
}
func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) error {
if acceptNewPeer {
conn.remote = conn.Router.Peers.FetchWithDefault(remote)
} else {
conn.remote = conn.Router.Peers.FetchAndAddRef(remote.Name)
if conn.remote == nil {
return fmt.Errorf("Found unknown remote name: %s at %s", remote.Name, conn.remoteTCPAddr)
}
}
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
}
return nil
}
func (conn *LocalConnection) initHeartbeats() error {
conn.heartbeatTCP = time.NewTicker(TCPHeartbeat)
conn.heartbeatTimeout = time.NewTimer(HeartbeatTimeout)
conn.heartbeatFrame = make([]byte, EthernetOverhead+8)
binary.BigEndian.PutUint64(conn.heartbeatFrame[EthernetOverhead:], conn.uid)
if conn.remoteUDPAddr == nil {
return nil
}
return conn.sendFastHeartbeats()
}
func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err error) {
for err == nil {
select {
case action := <-actionChan:
err = action()
case <-conn.heartbeatTCP.C:
err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat)
case <-conn.heartbeatTimeout.C:
err = fmt.Errorf("timed out waiting for UDP heartbeat")
case <-tickerChan(conn.heartbeat):
conn.Send(true, conn.heartbeatFrame)
case <-tickerChan(conn.fragTest):
conn.performFragTest()
}
}
return
}
func (conn *LocalConnection) shutdown(err error) {
if conn.remote == nil {
log.Errorf("->[%s] connection shutting down due to error during handshake: %v", conn.remoteTCPAddr, err)
} else {
conn.ErrorLog("connection shutting down due to error:", err)
}
if conn.TCPConn != nil {
checkWarn(conn.TCPConn.Close())
}
if conn.remote != nil {
conn.Router.Peers.Dereference(conn.remote)
conn.Router.Ourself.DeleteConnection(conn)
}
if conn.heartbeatTimeout != nil {
conn.heartbeatTimeout.Stop()
}
stopTicker(conn.heartbeatTCP)
stopTicker(conn.heartbeat)
stopTicker(conn.fragTest)
// blank out the forwardChan so that the router processes don't
// try to send any more
conn.stopForwarders()
conn.Router.ConnectionMaker.ConnectionTerminated(conn.remoteTCPAddr, err)
}
// Helpers
func (conn *LocalConnection) sendSimpleProtocolMsg(tag ProtocolTag) error {
return conn.sendProtocolMsg(ProtocolMsg{tag: tag})
}
func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
return conn.tcpSender.Send(Concat([]byte{byte(m.tag)}, m.msg))
}
func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) {
var err error
for {
conn.extendReadDeadline()
var msg []byte
if msg, err = receiver.Receive(); err != nil {
break
}
if len(msg) < 1 {
conn.Log("ignoring blank msg")
continue
}
if err = conn.handleProtocolMsg(ProtocolTag(msg[0]), msg[1:]); err != nil {
break
}
}
conn.Shutdown(err)
}
func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) error {
switch tag {
case ProtocolHeartbeat:
case ProtocolConnectionEstablished:
// We sent fast heartbeats to the remote peer, which has now
// received at least one of them and told us via this message.
// We can now consider the connection as established from our
// end.
conn.SetEstablished()
case ProtocolFragmentationReceived:
conn.setStackFrag(true)
case ProtocolPMTUVerified:
conn.pmtuVerified(int(binary.BigEndian.Uint16(payload)))
case ProtocolGossipUnicast, ProtocolGossipBroadcast, ProtocolGossip:
return conn.Router.handleGossip(tag, payload)
default:
conn.Log("ignoring unknown protocol tag:", tag)
}
return nil
}
func (conn *LocalConnection) extendReadDeadline() {
conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2))
}
func (conn *LocalConnection) sendFastHeartbeats() error {
err := conn.ensureForwarders()
if err == nil {
conn.heartbeat = time.NewTicker(FastHeartbeat)
conn.Send(true, conn.heartbeatFrame) // avoid initial wait
}
return err
}
func (conn *LocalConnection) performFragTest() {
conn.setStackFrag(false)
conn.Send(false, FragTest)
}
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()
}
}
-34
View File
@@ -1,34 +0,0 @@
package router
import (
"math"
"time"
)
const (
EthernetOverhead = 14
UDPOverhead = 28 // 20 bytes for IPv4, 8 bytes for UDP
Port = 6783
HTTPPort = Port + 1
DefaultPMTU = 65535
MaxUDPPacketSize = 65535
ChannelSize = 16
FragTestSize = 60001
PMTUDiscoverySize = 60000
TCPHeartbeat = 30 * time.Second
FastHeartbeat = 500 * time.Millisecond
SlowHeartbeat = 10 * time.Second
FragTestInterval = 5 * time.Minute
GossipInterval = 30 * time.Second
PMTUVerifyAttempts = 8
PMTUVerifyTimeout = 10 * time.Millisecond // gets doubled with every attempt
MaxDuration = time.Duration(math.MaxInt64)
MaxMissedHeartbeats = 6
HeartbeatTimeout = MaxMissedHeartbeats * SlowHeartbeat
MaxTCPMsgSize = 10 * 1024 * 1024
)
var (
FragTest = make([]byte, FragTestSize)
PMTUDiscovery = make([]byte, PMTUDiscoverySize)
)
+11 -11
View File
@@ -29,7 +29,13 @@ func (dec *EthernetDecoder) DecodeLayers(data []byte) {
dec.parser.DecodeLayers(data, &dec.decoded)
}
func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) error) error {
func (dec *EthernetDecoder) PacketKey() (key PacketKey) {
copy(key.SrcMAC[:], dec.Eth.SrcMAC)
copy(key.DstMAC[:], dec.Eth.DstMAC)
return
}
func (dec *EthernetDecoder) makeICMPFragNeeded(mtu int) ([]byte, error) {
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
FixLengths: true,
@@ -57,23 +63,17 @@ func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) e
Seq: uint16(mtu)},
&payload)
if err != nil {
return err
return nil, err
}
log.Printf("Sending ICMP 3,4 (%v -> %v): PMTU= %v", dec.IP.DstIP, dec.IP.SrcIP, mtu)
return sendFrame(buf.Bytes())
log.Printf("Sending ICMP 3,4 (%v -> %v): PMTU=%v", dec.IP.DstIP, dec.IP.SrcIP, mtu)
return buf.Bytes(), nil
}
var (
// see http://en.wikipedia.org/wiki/Multicast_address#Ethernet
stpMACPrefix = []byte{0x01, 0x80, 0xC2, 0x00, 0x00}
zeroMAC, _ = net.ParseMAC("00:00:00:00:00:00")
zeroMAC, _ = net.ParseMAC("00:00:00:00:00:00")
)
func (dec *EthernetDecoder) DropFrame() bool {
return bytes.Equal(stpMACPrefix, dec.Eth.DstMAC[:len(stpMACPrefix)])
}
func (dec *EthernetDecoder) IsSpecial() bool {
return dec.Eth.Length == 0 && dec.Eth.EthernetType == layers.EthernetTypeLLC &&
bytes.Equal(zeroMAC, dec.Eth.SrcMAC) && bytes.Equal(zeroMAC, dec.Eth.DstMAC)
+1197
View File
File diff suppressed because it is too large. Load diff
+105
View File
@@ -0,0 +1,105 @@
package router
import (
"net"
"github.com/weaveworks/weave/mesh"
)
// Just enough flow machinery for the weave router
type MAC [6]byte
func (mac MAC) String() string {
return net.HardwareAddr(mac[:]).String()
}
type PacketKey struct {
SrcMAC MAC
DstMAC MAC
}
type ForwardPacketKey struct {
SrcPeer *mesh.Peer
DstPeer *mesh.Peer
PacketKey
}
type FlowOp interface {
// The caller must supply an EthernetDecoder specific to this
// thread, which has already been used to decode the frame.
// The broadcast parameter is a hint whether the packet is
// being broadcast.
Process(frame []byte, dec *EthernetDecoder, broadcast bool)
// Does the FlowOp discard the packet?
Discards() bool
}
type DiscardingFlowOp struct{}
func (DiscardingFlowOp) Process([]byte, *EthernetDecoder, bool) {
}
func (DiscardingFlowOp) Discards() bool {
return true
}
type NonDiscardingFlowOp struct{}
func (NonDiscardingFlowOp) Discards() bool {
return false
}
type MultiFlowOp struct {
broadcast bool
ops []FlowOp
}
func NewMultiFlowOp(broadcast bool, ops ...FlowOp) *MultiFlowOp {
mfop := &MultiFlowOp{broadcast: broadcast}
for _, op := range ops {
mfop.Add(op)
}
return mfop
}
func (mfop *MultiFlowOp) Add(op FlowOp) {
mfop.ops = append(mfop.ops, op)
}
func (mfop *MultiFlowOp) Process(frame []byte, dec *EthernetDecoder, broadcast bool) {
for _, op := range mfop.ops {
op.Process(frame, dec, mfop.broadcast)
}
}
func (mfop *MultiFlowOp) Discards() bool {
for _, op := range mfop.ops {
if !op.Discards() {
return false
}
}
return true
}
func FlattenFlowOp(fop FlowOp) []FlowOp {
return collectFlowOps(nil, fop)
}
func collectFlowOps(into []FlowOp, fop FlowOp) []FlowOp {
if fop == nil {
return into
}
if mfop, ok := fop.(*MultiFlowOp); ok {
for _, op := range mfop.ops {
into = collectFlowOps(into, op)
}
return into
}
return append(into, fop)
}
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff. Show more