Compare commits

..
391 Commits
Author SHA1 Message Date
Adam Harrison 93c1e755e6 Add release 0.11.2 to CHANGELOG 2015-06-08 16:31:24 +01:00
Alvaro Saurin 5f4756aa80 Do not overwrite negLocal entries in the cache
Unit tests
2015-06-08 13:53:24 +01:00
Ilya Dmitrichenko 106f1c4434 Respect DOCKER_CLIENT_ARGS in weave run (close #855) 2015-06-08 13:53:24 +01:00
Paul Bellamy 4f86a20824 Add release 0.11.1 to CHANGELOG 2015-06-01 14:26:33 +01:00
Matthias Radestock 49ad336a35 Merge pull request #809 from paulbellamy/0.11
Rewording and clarifying proxy docs for 0.11.1 release
2015-06-01 13:32:28 +01:00
Paul Bellamy 6feaa72abd [docs] elaborating on proxy IPAM and DNS interaction 2015-06-01 13:24:55 +01:00
Paul Bellamy 71d6f9cdfb [docs] re-ordering proxy doc sections 2015-06-01 13:24:52 +01:00
Matthias Radestock 341d5ecbb6 remove out-of-date info from ipam docs 2015-06-01 12:15:14 +01:00
Matthias Radestock 2e208b4921 cosmetic 2015-06-01 12:15:05 +01:00
Matthias Radestock c983d9f39f cosmetic 2015-06-01 12:14:55 +01:00
Matthias Radestock adc5784fc9 expand weavedns example to cover multi-host 2015-06-01 12:14:35 +01:00
Alex Chan a8c3551040 Minor spelling corrections in docs 2015-06-01 12:14:20 +01:00
Matthias Radestock 3332f94a00 Merge pull request #801 from bboreham/787-better-status
Improve ipam status message before and after pending consensus

Fixes #787.
2015-06-01 11:17:19 +01:00
Bryan Boreham cd1cca3df2 Reformat output of pending ipam requests 2015-06-01 10:55:01 +01:00
Matthias Radestock 158fb36c8c Merge remote-tracking branch 'upstream/0.11' into 0.11 2015-05-31 11:54:08 +01:00
Matthias Radestock 5f385a297c remove bashism
This was introduced in #780.
2015-05-31 11:51:34 +01:00
Bryan Boreham 1d6f1f7561 Improve ipam status message before and after pending consensus 2015-05-29 17:34:17 +01:00
Matthias Radestock 1769cdad7c Merge pull request #799 from paulbellamy/proxy-attach
Proxy kills container and returns error when attach fails

Fixes #788.
2015-05-29 16:35:25 +01:00
Paul Bellamy 4f73ef73bb weavewait should exit if it times out
To match behaviour of proxy when interface fails to attach
2015-05-29 14:37:58 +01:00
Paul Bellamy 0cb2e80e09 Proxy kills container and returns error when attach fails 2015-05-29 14:37:33 +01:00
Matthias Radestock 10a9f58d13 Merge pull request #780 from paulbellamy/proxy-launch-2
simplifying and standardizing `launch-proxy`

Fixes #767. Fixes #755. Closes #757.
2015-05-29 13:30:30 +01:00
Paul Bellamy f919ef12d0 fix bug with WEAVEPROXY_DOCKER_ARGS not being passed through 2015-05-29 09:52:43 +01:00
Paul Bellamy 20c9bc04a7 simplifying and standardizing launch-proxy 2015-05-29 09:52:42 +01:00
Bryan Boreham 10c3f47bd1 Merge pull request #783 from bboreham/782-space-panic
Fixes #782
2015-05-29 09:42:44 +01:00
Bryan Boreham 915bf392e9 Improve error reporting of too much free space 2015-05-28 18:15:04 +01:00
Bryan Boreham af5531cbf6 Fix incorrect calculation of free space
when granting a range that spans a token and inserting a new token, the
free space on the last token that we are handing over has to change.
Add unit test for this too.
2015-05-28 18:12:30 +01:00
Matthias Radestock 32c35da402 Merge pull request #777 from paulbellamy/proxy-versionless
allow proxy to intercept unversioned (i.e. 1.0) urls

Closes #770. Closes #778.
2015-05-28 17:19:46 +01:00
Paul Bellamy 147c7f503c add curl test for proxy v1.0 api support 2015-05-28 16:39:32 +01:00
Paul Bellamy c8b95a2527 fix bug in proxy handling of minimal request bodies 2015-05-28 16:24:04 +01:00
Paul Bellamy 169eb19523 allow proxy to intercept unversioned (i.e. 1.0) urls 2015-05-28 16:24:02 +01:00
Matthias Radestock b26e2128e4 Merge pull request #752 from paulbellamy/proxy-ipam
Fix bug of --with-ipam not setting entrypoints

Fixes #744.
2015-05-27 13:51:06 +01:00
Paul Bellamy cb73b73862 Fix bug of --with-ipam not setting entrypoints 2015-05-27 13:38:28 +01:00
Ilya Dmitrichenko 84aa629010 Must quoute WEAVE_CIDR when it is a list 2015-05-27 07:47:57 +01:00
Ilya Dmitrichenko 536d55a226 Cosmetic markdown fix in proxy docs 2015-05-27 07:13:06 +01:00
Bryan Boreham 69ac0950fe Add release 0.11 to CHANGELOG 2015-05-26 13:50:26 +01:00
Matthias Radestock a77ffc4ca5 Merge pull request #740 from awh/revise_weavedns_docs_further
Address weaveDNS doc review comments
2015-05-26 12:46:18 +01:00
Adam Harrison 3d8c8012cf Address review comments 2015-05-26 12:41:14 +01:00
Matthias Radestock 6c5075b9a1 Merge pull request #738 from bboreham/proxy-docs
Reword weave proxy intro
2015-05-26 12:14:01 +01:00
Alvaro de46758eba Merge pull request #739 from weaveworks/revert-615-weave-583-2
Revert "Background updates for names in the Zone database"
2015-05-26 13:07:24 +02:00
Alvaro 21a1cd0469 Revert "Background updates for names in the Zone database" 2015-05-26 12:56:44 +02:00
Bryan Boreham 1e128c142e Links to Docker docs 2015-05-26 11:42:48 +01:00
Bryan Boreham aafc1a56f9 Reword weave proxy intro 2015-05-26 11:40:39 +01:00
Adam Harrison 5382037c4e Merge pull request #615 from inercia/weave-583-2
Background updates for names in the Zone database. Fixes #226.
2015-05-26 10:37:32 +01:00
Alvaro Saurin ee6bbe7925 Smoke tests 2015-05-26 11:30:40 +02:00
Alvaro Saurin cfcc08a914 Limit the number of answers we provide to users. 2015-05-26 11:30:35 +02:00
Alvaro Saurin 0cb6a6269d Unit tests for multiple IPs through the HTTP API 2015-05-26 10:53:13 +02:00
Alvaro Saurin db0ef5cf5c Remove duplicates in answers 2015-05-26 10:53:13 +02:00
Alvaro Saurin 174b47d1db Shuffle all the replies, not only the ones from the cache... 2015-05-26 10:53:13 +02:00
Alvaro Saurin 87a2886539 Refactored the mocked clock.
Refactored some common code in the Zone lookups
2015-05-26 10:53:12 +02:00
Alvaro Saurin 04e4ef214c Check we can use our mDNS client with our mDNS server 2015-05-26 10:53:12 +02:00
Alvaro Saurin 2783f02790 Make Stop() idempotent 2015-05-26 10:53:12 +02:00
Alvaro Saurin 761eb787ff Unit tests for cache invalidations as a result of names updates 2015-05-26 10:53:12 +02:00
Alvaro Saurin 6fa2f2142f Command line arg for changing the number of background updaters
Default number of background updaters raised to 4
2015-05-26 10:53:12 +02:00
Alvaro Saurin 72b55c82ec Do not invoke the mDNS lookups from the server: they are used from the Zone database now...
Improved DNS server:
- simplify the handlers code, removing duplicated code
- more configuration parameters (eg, we can now disable the cache)
- we can now bind to port 0 and let the server get a free port (so we can get rid of the mess we had in the unit tests...)
2015-05-26 10:53:12 +02:00
Alvaro Saurin 44cdf3bca3 Command line option for disabling the cache 2015-05-26 10:53:12 +02:00
Alvaro Saurin fe672eae63 Unit tests for the zone lookups and names updates 2015-05-26 10:53:12 +02:00
Alvaro Saurin 267fddc474 Improved mocks 2015-05-26 10:53:12 +02:00
Alvaro Saurin 2b6388359b Background updates for names 2015-05-26 10:53:12 +02:00
Alvaro Saurin a2dad42ef6 Domain lookups, using mDNS
Added DomainLookup* to the Zone interface
2015-05-26 10:53:12 +02:00
Alvaro Saurin 8a509168ee New zone database 2015-05-26 10:53:12 +02:00
Alvaro Saurin 9c48c83701 Interfaces for the mDNS client and server 2015-05-26 10:53:12 +02:00
Matthias Radestock 6197f98c26 reword proxy intro a bit 2015-05-24 16:27:34 +01:00
Matthias Radestock 213208bf12 Merge branch 'bboreham-723-check-bridge'
Fixes #723.
2015-05-24 11:30:47 +01:00
Matthias Radestock 5148ed7936 symmetry with create_bridge 2015-05-24 11:30:04 +01:00
Matthias Radestock add68afe0a simplify 2015-05-24 11:29:47 +01:00
Matthias Radestock ec59d5abee Merge branch '723-check-bridge' of https://github.com/bboreham/weave into bboreham-723-check-bridge 2015-05-24 11:20:43 +01:00
Matthias Radestock a7fccf053f Merge pull request #719 from bboreham/705-hide-404
Don't talk to IPAM unless 0 addresses supplied to 'hide'

Fixes #705.
2015-05-24 11:15:00 +01:00
Matthias Radestock b1af1f752d refactor: extract a helper 2015-05-23 12:28:43 +01:00
Matthias Radestock 01bc994822 refactor: extract image creation 2015-05-23 12:14:58 +01:00
Matthias Radestock 2b8a1fe177 rename 2015-05-23 11:59:47 +01:00
Matthias Radestock 1c4beb5ef0 improve entrypoint/command selection tests 2015-05-23 11:58:00 +01:00
Matthias Radestock b42b045c08 cosmetic: vertical alignment 2015-05-23 10:30:45 +01:00
Matthias Radestock c904a650b6 cosmetic: reflow 2015-05-23 10:27:38 +01:00
Matthias Radestock 0612f27f1f fix typo 2015-05-23 10:27:07 +01:00
Matthias Radestock 5387eb77ab Merge pull request #721 from paulbellamy/proxy-ipam
Proxy support for IPAM

Closes #657.
2015-05-23 10:24:53 +01:00
Matthias Radestock 0c01bb758e Merge pull request #722 from errordeveloper/docs-branch-heads
Create `weave/<branch>/head` for latest branch docs

Closes #668.
2015-05-22 18:48:14 +01:00
Paul Bellamy 11931f3ea2 [tests] extract gliderlabs/alpine and aanand/docker-dnsutils into var in
config.sh

@bboreham made me do it.
2015-05-22 15:52:36 +01:00
Paul Bellamy cc82213147 add 'weave launch-proxy --with-ipam' flag 2015-05-22 15:52:36 +01:00
Matthias Radestock f36af211c0 cosmetic 2015-05-22 15:41:29 +01:00
Matthias Radestock b20b4a8675 Merge pull request #731 from dpw/connection-select-break
Fix use of "break" within "select" in LocalConnection actorLoop
2015-05-22 15:40:00 +01:00
David Wragg dfa93436c2 Fix use of "break" within "select" in LocalConnection actorLoop
From the golang spec: 'A "break" statement terminates execution of the
innermost "for", "switch", or "select" statement within the same
function.'  But the LocalConnection actorLoop seems to assume it
skips the "select" and terminates the enclosing "for".

As a result, if something did conn.Shutdown(nil), it would not actually
shutdown the connection. It turns out this is never a problem, because
Shutdown is always called with a non-nil error, even though there is code
in a few places to handle the nil case. By cleaning that up, the dubious
"break" is eliminated.
2015-05-22 11:26:30 +01:00
Paul Bellamy d0320b43f9 realigning the weave help output columns 2015-05-22 10:04:10 +01:00
Paul Bellamy 73071f2c62 cleanup proxy test helpers 2015-05-22 10:04:09 +01:00
Paul Bellamy c9409c5342 [proxy] Adding basic docs about IPAM integration 2015-05-21 15:37:37 +01:00
Paul Bellamy 46bdd8c67d Proxy support for IPAM
Pass `-e WEAVE_CIDR=` to your container to use IPAM. If IPAM is not
enabled on the weave router, the container will be booted without weave
networking.

I'm not crazy about adding *another* undocumented weave cli command
(ipam-cidr), but it saves the proxy messing around finding container
IPs.
2015-05-21 15:37:37 +01:00
Bryan Boreham 22b8f482ea Fix a couple of mistakes in get_ipam_cidr calls
(first one only caused a slight slow-down; second one only mattered if
your 'weave' container had a non-standard name)
2015-05-21 12:05:48 +01:00
Adam Harrison 387ec3fdd5 Add run-smoketests target to Makefile 2015-05-21 11:40:12 +01:00
Tom Wilkie 449bf45c2b Try and ignore pull request pseudo-branches for circle build. 2015-05-21 09:50:38 +00:00
Bryan Boreham b771528942 Check if Docker using weave bridge before inserting DROP rule
and comment the use of -I
2015-05-21 10:14:15 +01:00
Bryan Boreham f727e56bad Revert "consistency"
This reverts commit c5d5dd5fdc.
2015-05-21 09:54:46 +01:00
Matthias Radestock c5d5dd5fdc consistency 2015-05-21 05:46:07 +01:00
Matthias Radestock 6f68eb50aa get rid of a silly redirect 2015-05-20 16:54:07 +01:00
Paul Bellamy c63d2e531a Merge pull request #712 from errordeveloper/vagrantfile-more-cleanup
Improve readability of Vagrantfile and cleanup this official image
2015-05-20 16:47:28 +01:00
Ilya Dmitrichenko eb6f9faae7 Create weave/<branch>/head for latest branch docs (close #668) 2015-05-20 16:44:02 +01:00
Matthias Radestock 23dd02a05a remove redundant check 2015-05-20 15:58:22 +01:00
Paul Bellamy d24e75f76b extract vagrantfile shared bits 2015-05-20 15:42:08 +01:00
Matthias Radestock b119872b1b Merge pull request #718 from paulbellamy/proxy-check-up
use grep in /sys to verify ethwe is up in tests

Closes #707.
2015-05-20 13:47:48 +01:00
Paul Bellamy eeaa6c9e2c use grep in /sys to verify ethwe is up in tests
Closes #707. `ip link show` seems to "lag" the real state. In tests
checking the physical link state is good enough.
2015-05-20 13:36:36 +01:00
Matthias Radestock 0a7211988a Merge pull request #714 from paulbellamy/proxy-cmd-merge
refactor proxy entrypoint rewriting to match docker daemon

Fixes #709.
2015-05-20 13:19:03 +01:00
Bryan Boreham 9e79d7dcca Don't talk to IPAM unless 0 addresses supplied to 'hide' 2015-05-20 12:59:04 +01:00
Paul Bellamy 369466d3d4 refactor proxy entrypoint rewriting to match docker daemon
Closes #709
2015-05-20 11:41:15 +01:00
Matthias Radestock f642db0462 Merge pull request #704 from bboreham/620-drop-docker
Drop traffic from docker0 bridge to weave bridge

Fixes #620.
2015-05-20 11:24:41 +01:00
Bryan Boreham 906be31483 Regularise the ping tests across different subnets 2015-05-20 11:08:39 +01:00
Matthias Radestock 0220f9aaca Merge pull request #713 from paulbellamy/proxy-exec
use exec instead of run, to replace weavewait with subprocess

Closes #708.
2015-05-20 10:48:01 +01:00
Ilya Dmitrichenko a48777e482 Clean test VMs as well 2015-05-20 10:47:15 +01:00
Bryan Boreham 4ee32cc12d Extend smoke test for new drop rule
Note that we have to wrap the '!' in 'sh -c' to get the negation to work as expected.
2015-05-20 10:45:18 +01:00
Paul Bellamy 818e162fff [proxy] use exec instead of run, to replace weavewait with subprocess
Closes #708
2015-05-20 10:35:52 +01:00
Bryan Boreham 6739e6c166 iptables rule to drop traffic from docker0 bridge to weave bridge 2015-05-20 10:27:38 +01:00
Ilya Dmitrichenko 3a0b341770 Improve readability of Vagrantfile and cleanup this official image 2015-05-20 08:24:18 +01:00
Matthias Radestock 7396f61222 move some tests 2015-05-19 21:38:50 +01:00
Matthias Radestock f7f0369bff improve logging in proxy
- always info-log when we encounter an 'interesting' container
- s/CIDR/WEAVE_CIDR
- some other minor tweaks
2015-05-19 19:46:53 +01:00
Matthias Radestock fe36da2041 Merge pull request #703 from paulbellamy/proxy-minimal
proxy needs to add weavewait to execs as well
2015-05-19 18:54:15 +01:00
Paul Bellamy 610bc46970 proxy should provide reasonable error when execing on a container which doesn't exist 2015-05-19 17:37:23 +01:00
Paul Bellamy e6b587ea8d proxy needs to add weavewait to execs as well
Bug came up in a test where we are restarting and doing an exec checking
a network interface right away. weavewait is only added if the container
has a WEAVE_CIDR var set, the same as at container creation.
2015-05-19 17:36:53 +01:00
Bryan Boreham 70b595c4a2 Clarify status message when allocator is awaiting consensus 2015-05-19 14:12:08 +01:00
Matthias Radestock f59facf5a6 Merge pull request #701 from bboreham/684-improve-errors
Improve ipam error messages

Closes #684.
2015-05-19 13:34:22 +01:00
Bryan Boreham 0e55f75d86 Oops 2015-05-19 13:16:36 +01:00
Bryan Boreham ee7dac9e28 Improve error messages
Make clear that they come from the IP allocator, and reduce repetition in the text.
Also include the nickname for other peer, if known.
2015-05-19 12:37:57 +01:00
Matthias Radestock 9f85cbc07d Merge pull request #696 from bboreham/692-ipam-status
Document ipam additions to `weave status`

Closes #692.
2015-05-19 12:05:53 +01:00
Bryan Boreham 454bbdbc68 Make reference into a link 2015-05-19 11:48:11 +01:00
Paul Bellamy 62242cb640 fixup bad golint refactor 2015-05-19 11:46:03 +01:00
Bryan Boreham 9363193fe8 Review feedback 2015-05-19 11:43:59 +01:00
Matthias Radestock 29601610c0 Merge pull request #698 from paulbellamy/proxy-minimal
remove usage of backticks in sh script
2015-05-19 11:33:00 +01:00
Paul Bellamy fdd0bd3b35 remove usage of backticks in bash script 2015-05-19 11:26:55 +01:00
Bryan Boreham 4d3234aa36 Merge pull request #695 from rade/merge_ipam_tests_with_rest
LGTM
2015-05-19 11:25:50 +01:00
Matthias Radestock 8b66b8ae47 Merge pull request #655 from paulbellamy/proxy-minimal
Minimal implementation of the proxy

closes #47, closes #230, closes #251, closes #400.
2015-05-19 10:59:00 +01:00
Paul Bellamy ee07d645b4 golint cleanup of else block 2015-05-19 10:47:57 +01:00
Bryan Boreham bffe9f811a Document 'weave status' section for IPAM 2015-05-19 10:41:45 +01:00
Bryan Boreham b6a8c48097 Tidy up IPAM parts of 'weave status'
- removed detailed "free" and "owned" printout of 'space'
 - removed "free" counts from ring lines - limited usefulness and often out of date.
 - removed trailing newline
 - changed "Ring" to "Owned Ranges"
 - print subnet in CIDR format
2015-05-19 10:38:26 +01:00
Paul Bellamy b71a428369 proxy should be sending error code to client, instead of exiting 2015-05-19 10:34:46 +01:00
Paul Bellamy 6911dc8d38 updating proxy with-dns description to match docs 2015-05-19 10:34:46 +01:00
Paul Bellamy 1e7c82bb58 use busybox instead of ubuntu for proxy image pulling test
busybox is smaller. gliderlabs/alpine is even smaller, but is used by
weaveexec, so we can't remove it.
2015-05-19 10:34:46 +01:00
Paul Bellamy 2f8c5d7bd9 proxy interceptors implicitly modify arguments, instead of returning them 2015-05-19 10:34:46 +01:00
Matthias Radestock 126d4f4079 some refactoring and tidying
- some inlining
- compile regexps once
- consistent error messages
- always mention container id in startContainerInterceptor errors
2015-05-19 10:34:46 +01:00
Paul Bellamy ca4b8e6f8e add proxy support for 'docker restart' 2015-05-19 10:34:46 +01:00
Paul Bellamy 2a120e5565 refactor proxy rawStream handler 2015-05-19 10:34:46 +01:00
Paul Bellamy 6fa0a9d70c moving weaveproxy docs off 'experimental-features' 2015-05-19 10:34:46 +01:00
Paul Bellamy 55d17ba3bf separate proxy package into weaveproxy and proxy 2015-05-19 10:34:46 +01:00
Paul Bellamy bedc20c7c1 extract docker-bridge-ip fetching from proxy into bash script 2015-05-19 10:34:46 +01:00
Paul Bellamy 96a69d2303 Add dns support for the proxy
In the proxy, I swapped the 'flag' library for
'code.google.com/p/getopt', which uses double-dash flags.

proxy lets 'weave attach' do dns registration on container start

proxy needs to set DNSSearch if not specified
2015-05-19 10:34:45 +01:00
Paul Bellamy 444b1482fa add weavewait
binocarlos/wait-for-weave is Apache2 licensed, so attribution/etc applies. Thus, we
re-invent the wheel here.
2015-05-19 10:34:45 +01:00
Paul Bellamy 9389001a1d [docs] Initial version of proxy docs 2015-05-19 10:34:45 +01:00
Paul Bellamy 5a6a67f632 Add integration test for the proxy, using docker-py client 2015-05-19 10:34:45 +01:00
Michael Bridgen 6c4271f15d Add launch-proxy command to cli
Use curl /weave to wait for the proxy to boot before returning.
2015-05-19 09:48:12 +01:00
Michael Bridgen 6d7de3ddff Proxy implementation
This adds a docker remote API proxy to the weaveexec image. The proxy
has hooks for (post-) container start and (pre-) container create; the
former attaches a weave interface to a container given a WEAVE_CIDR
environment entry, and the latter adjusts the entrypoint of a container
to wait for the ethwe interface.

Notes:

None of the hooks need to inspect/change large bodies. In order to avoid
a crash when handling images larger than memory we need to ensure that
the entire request body is never loaded into memory (i.e. No
ioutils.ReadAll).

When proxying a raw-stream response, we can't use http.WriteHeader. It
sets the Transfer-Encoding to chunked, which will throw off the clients.
Instead we have to hijack the socket, and write out http status code
manually.

For chunked transfer-encodings, clients (docker-py) depend on each http
chunk containing exactly one JSON object. Therefore, we cannot re-chunk
the body, and have to pass it across verbatim.

Proxy needs to pass multi-cidrs separately when calling weave. Otherwise
they get smashed together on the command line, and the call fails.

When creating the image, if no entrypoint is specified on the container,
it should use the entrypoint of the image. Code for this is based on
powerstrip-weave.

We handle WEAVE_CIDR='' by acting like it is not set. The reason being,
that because WEAVE_CIDR is inherited from images (along with env vars),
setting it to blank is equivalent to unsetting it.
2015-05-19 09:48:12 +01:00
Paul Bellamy d8e1d0798e replace test usage of ifconfig with ip link show 2015-05-19 09:48:12 +01:00
Matthias Radestock e9963945d6 cosmetic 2015-05-18 23:38:37 +01:00
Matthias Radestock 9017888b9c merge IPAM tests with rest
keeping them separate leads to too much duplication
2015-05-18 23:28:35 +01:00
Matthias Radestock 8e1ac2b300 Merge pull request #693 from bboreham/677-document-rmpeer
Illustrate the usage of 'weave rmpeer'

Closes #677.
2015-05-18 17:37:19 +01:00
Bryan Boreham 25c70cfad6 Illustrate the usage of 'weave rmpeer' 2015-05-18 17:26:24 +01:00
Matthias Radestock cf34335ec7 Merge pull request #691 from bboreham/681-ipam-docs
Expand documentation of 'weave launch' parameters for IPAM

Closes #669 and #681.
2015-05-18 17:09:26 +01:00
Bryan Boreham 4ea9cfca48 Expanded description of 'weave launch' parameters for IPAM 2015-05-18 16:49:45 +01:00
Matthias Radestock 94525f0497 Merge pull request #690 from bboreham/and_true
Fix typo '&& true' in weave script, that could cause partial population of DNS
2015-05-18 16:47:28 +01:00
Bryan Boreham 0227f7e727 '&& true' was a typo - replaced with '|| true' 2015-05-18 16:06:38 +01:00
Bryan Boreham bb4dd716c8 Document -iprange a bit more, and warn not to use its addresses elsewhere 2015-05-18 16:03:34 +01:00
Matthias Radestock d53ec858a2 Merge pull request #689 from bboreham/671-hide-errors
Silence complaints from http_call when at end of 'weave hide'

Fixes #671.
2015-05-18 15:59:45 +01:00
Bryan Boreham 286c46653c Add smoke tests for 'weave expose' and 'weave hide' when weave isn't running 2015-05-18 15:43:44 +01:00
Bryan Boreham 7cf8234d9f Silence complaints from http_call when at end of 'weave hide' 2015-05-18 14:54:46 +01:00
Matthias Radestock fd0282ee05 Merge pull request #686 from bboreham/672-ipam-run-local
Fetch an address from IPAM for 'weave --local run'

Fixes #672.
2015-05-18 14:44:46 +01:00
Bryan Boreham 5cc3036498 Extend smoke test for 'weave run --local' to include IPAM 2015-05-18 14:38:12 +01:00
Matthias Radestock 957c8a1954 Merge pull request #665 from pradeepchhetri/dockerfile
Merged two run commands into one in weavexec Dockerfile
2015-05-18 12:44:17 +01:00
Matthias Radestock 94c7e7f646 Merge pull request #567 from bboreham/pktdebug
Rename per-packet debugging flag to `-pktdebug`
2015-05-18 12:21:40 +01:00
Matthias Radestock 5f16854e1f Merge pull request #685 from bboreham/679-ipam-docs
Give 'weave ps' example with IPAM and link to docs

Closes #679.
2015-05-18 12:20:03 +01:00
Bryan Boreham 48e9c53dda Fetch an address from IPAM for 'weave --local run' 2015-05-18 12:06:06 +01:00
Bryan Boreham 0ec4417992 Rename per-packet debugging flag to -pktdebug
to allow `-debug` to mean "more verbose logging"
2015-05-18 11:52:50 +01:00
Adam Harrison eecb540141 Merge pull request #683 from rade/refactor_smoke_tests
Refactor smoke tests
2015-05-18 11:37:40 +01:00
Bryan Boreham 14c690d9f5 Give 'weave ps' example with IPAM and link to docs 2015-05-18 11:37:38 +01:00
Matthias Radestock c08bfd2205 always quote $@ 2015-05-18 11:16:12 +01:00
Adam Harrison d9eea28f17 Merge pull request #667 from rade/revise_weavedns_docs
revise weaveDNS docs
2015-05-18 10:46:09 +01:00
Matthias Radestock 06abd62af0 always spell weaveDNS lower-case, except in titles/headings 2015-05-18 10:43:07 +01:00
Matthias Radestock f732252f22 eliminate ambiguity 2015-05-18 10:36:13 +01:00
Matthias Radestock a01d33452e revise weaveDNS docs
- add ToC and anchors for all headings
- move docker bridge settings into "How it works" - it's a rarely used
  feature
- highlight a bunch of features, extracting them from obscurely titled
  sub-sections of "manual usage"
  - basic rendundancy
  - adding/removing extra DNS entries
  - service hot-swapping
  - ability to retain entries for stopped containers
  - using a different local domain
- rename remainder of "manual usage" section to "Using weaveDNS
  without `weave run`"
- remove "Unregistering" section - it was out of date and is not
  covered by the "add/remove" section
2015-05-18 10:36:13 +01:00
Matthias Radestock 6dd6d9d6f0 one ping to rule them all 2015-05-16 21:53:50 +01:00
Matthias Radestock ccd468f80c drop -debug from launch-dns
It wasn't being applied consistently in any case.
2015-05-16 21:06:08 +01:00
Matthias Radestock 4e7ddedf75 extract startup of container with dns
There is now just one mention of the aanand/docker-dnsutils image; in
config.sh.
2015-05-16 21:00:27 +01:00
Matthias Radestock 8261c7f782 extract hostname as a constant 2015-05-16 21:00:27 +01:00
Matthias Radestock c3c5b76007 use already-defined constants 2015-05-16 21:00:27 +01:00
Matthias Radestock 1c1bd817e1 extract startup of simple container
There is now just one mention of the gliderlabs/alpine image outside config.sh.
2015-05-16 21:00:07 +01:00
Matthias Radestock ab244f76f6 consistency 2015-05-16 20:37:16 +01:00
Matthias Radestock 535a6ae199 consistent docker run parameter order 2015-05-16 20:37:16 +01:00
Matthias Radestock 822cab7750 use assert_dns_record in ipam test
instead of homegrown version that uses dig
2015-05-16 19:22:31 +01:00
Matthias Radestock 2647fd4c92 extract helper for obtaining container ip address
thus keeping the ghastly regexp in one place instead of three.
2015-05-16 19:14:23 +01:00
Matthias Radestock bb8a745431 cosmetic: helper functions come first 2015-05-16 19:05:19 +01:00
Matthias Radestock 352e31c88b remove all the weave_on hacks
that existed because it was echoing a message to stdout, which it no
longer does.

As a result there is now no mention of the docker tcp host/port
outside config.sh.
2015-05-16 19:00:35 +01:00
Matthias Radestock a94d2ef277 remove pointless quoting 2015-05-16 18:22:55 +01:00
Matthias Radestock ad34a56e56 simplify NXDOMAIN test
- remove premature abstraction
- only do one check
2015-05-16 18:22:55 +01:00
Matthias Radestock 970af56765 less noise, and use lower-case var names 2015-05-16 18:22:02 +01:00
Paul Bellamy defcfa5d26 test *_on helpers should send non-result output to &2, so that asserts work
Previously, the result output would include:
Docker on: <command>
result output text

etc...
But this would cause: assert "docker_on ..." to fail
2015-05-16 18:03:21 +01:00
Matthias Radestock 11d241eccd cosmetic: better error message 2015-05-16 03:17:07 +01:00
Matthias Radestock dbcdace361 tiny refactor: outdent 2015-05-16 02:50:02 +01:00
Matthias Radestock 47727e23f1 fix quoting, unbreaking multi-cidr weave start/attach/hide
Fixes #670.
2015-05-16 02:42:10 +01:00
Matthias Radestock 2bd197e12c cosmetic 2015-05-16 01:31:47 +01:00
Matthias Radestock 2934e89e14 fetch dependencies of sigproxy
There aren't any right now, but might be in the future.
2015-05-16 01:15:47 +01:00
Matthias Radestock dc96fc17a9 correct protocol version
let us not get ahead of ourselves
2015-05-16 01:09:53 +01:00
Matthias Radestock 35211f65c9 pull IPAM docs onto separate page
and link to them from main docs page
2015-05-15 17:08:24 +01:00
David Wragg 977666c2c9 Fix test when run on circleci
'docker exec -i' hangs there.
2015-05-15 15:48:55 +01:00
Matthias Radestock 7737c7f34f move weavedns link 2015-05-15 14:02:57 +01:00
Matthias Radestock dcac8d8316 cosmetic: move weavedns-readme.md to weavedns.md 2015-05-15 13:59:40 +01:00
David Wragg f95b3971ab Merge bboreham/22_ipam (PR #563): IP Allocation Management
Fixes #22.
2015-05-15 13:34:57 +01:00
Pradeep Chhetri 91bb28b47b Merged two run commands into one in Dockerfile 2015-05-15 18:04:20 +05:30
David Wragg 4b942e8ed5 populate_1_ipam -> ipam_claim 2015-05-15 13:12:36 +01:00
Matthias Radestock fbbc2bbf79 fix typo 2015-05-15 12:56:04 +01:00
Matthias Radestock 483c565fac better titles for WeaveDNS links 2015-05-15 11:59:27 +01:00
Matthias Radestock 210101932c fix broken link 2015-05-15 11:56:46 +01:00
David Wragg 0eee999d7c Unify common call to wait_for_status upon "weave launch" 2015-05-15 11:49:36 +01:00
David Wragg a027337be2 Tidy up IPAM setup 2015-05-15 11:44:50 +01:00
Bryan Boreham f6ac069bc5 Remove some less-necessary test helper functions 2015-05-14 17:57:13 +01:00
Bryan Boreham 3cd1a17c23 IP Allocation Management
Using the Ring CRDT and Weave gossip, provides an interface
for the weave script to allocate IP addresses in `weave run`,
`weave start` and `weave expose`.

See docs/ipam.md for implementation details, and site/features.md
for user-visible changes.
2015-05-14 17:44:06 +01:00
David Wragg 72112d7aef Implementation of Basic Paxos 2015-05-14 17:33:20 +01:00
Tom Wilkie 4f3dcf8dc9 Ring CRDT for IP Allocation Management
The basic data structure used to share information about
IP allocations between peers
2015-05-14 17:33:20 +01:00
Matthias Radestock c158b86dc6 refactor: shorten a method name 2015-05-14 15:13:10 +01:00
Matthias Radestock 79d3a2821f simplify test
less clever, but more readable
2015-05-14 14:52:35 +01:00
Matthias Radestock c7dd48d769 introduce PeerUID type 2015-05-14 12:35:01 +01:00
Matthias Radestock ace56da7b3 allow Gossiper.Gossip to return nil
...to indicate 'no data'
2015-05-14 12:02:56 +01:00
Ilya Dmitrichenko af21f5eaf6 Markdown cosmetic 2015-05-13 16:53:49 +01:00
Ilya Dmitrichenko 0881fa0c69 Add instructions for building on a Vargant VM without logging in 2015-05-13 16:45:58 +01:00
Ilya Dmitrichenko db28286a4b Vagrnant should not do everything as root 2015-05-13 16:41:25 +01:00
Ilya Dmitrichenko ccd111d512 Cleanup Vagrantfile and fix path in non-interactive shell 2015-05-13 16:24:36 +01:00
Fintan Ryan d106c56c00 Merge pull request #651 from weaveworks/move-weavedns-docs
Update docs index: move WeaveDNS docs and add Getting Started Guides
2015-05-13 14:44:54 +01:00
Paul Bellamy f0578f5f66 forgot to give Overview section an anchor 2015-05-13 14:17:56 +01:00
Ilya Dmitrichenko d63a9785bd Point to new location for WeaveDNS docs 2015-05-13 13:59:41 +01:00
rade 8f38a48a1f Merge pull request #653 from paulbellamy/docs-how-it-works-toc
Add ToC to how-it-works docs page
2015-05-13 13:53:37 +01:00
Paul Bellamy 39f87a27e5 Add ToC to how-it-works docs page 2015-05-13 13:45:06 +01:00
Ilya Dmitrichenko ca4d813b71 Cleanup documentation index, add getting started guides 2015-05-13 12:47:43 +01:00
Ilya Dmitrichenko 2199c5c273 Add Jekyll "frontmatter", it does not build it otherwise 2015-05-13 10:36:22 +01:00
Ilya Dmitrichenko 366adfb8f9 Move WeaveDNS docs to docs.weave.works (close #471) 2015-05-13 10:21:51 +01:00
Ilya Dmitrichenko 17925a0c0b gsutil rsync does not imply recursion 2015-05-13 10:21:32 +01:00
Matthias Radestock 55077d5059 cosmetic: it's lower-case 'weave' everywhere except in titles 2015-05-12 17:26:28 +01:00
David Wragg 3e4f7358a7 Mention "CIDR notation" rather than "CIDR" in error message
It's more web-searchable.
2015-05-12 15:48:33 +01:00
Matthias Radestock 9195894d2a Show error message when DNS population fails part way through.
Closes #393.
2015-05-12 11:49:48 +01:00
Matthias Radestock 953449c215 refactor: extract Peer.ForEachConnectedPeer helper 2015-05-11 21:06:07 +01:00
Matthias Radestock c5c93d3a77 refactor: increase similarity betweeen parts of route calculation 2015-05-11 20:14:24 +01:00
Matthias Radestock 039446f756 cosmetic: remove outdated comment 2015-05-11 20:00:04 +01:00
Matthias Radestock 03c1f49850 make profiling work again
The weave SIGINT signal handler was racing the profiler SIGINT signal
handler. So now we a) make the former return instead of exit, and b)
disable the latter, as a result of which the deferred profile.Stop in
weaver/main gets called.
2015-05-11 19:38:06 +01:00
Matthias Radestock 43133b5851 increase tolerance for missed udp heartbeats
3 are quite easy to miss when there is contention. 6 less so.
2015-05-11 18:35:06 +01:00
David Wragg df40c5b97e Merge rade/643_eliminate_route_calculcation_deadlock (PR #644)
eliminate route calculcation deadlock

Fixes #643.
2015-05-11 17:57:53 +01:00
Matthias Radestock e246e6acc6 coarser lock granularity
- get rid of individual Peer locks
- hold Peers read (or write) lock and LocalPeer read lock while
  performing route calculations

This eliminates the deadlock resulting from the inconsistent lock
acquisition order in route calculcation.

Fixes #643.
2015-05-11 17:19:54 +01:00
Matthias Radestock 5bd8730353 make Peer.Encode lock-free 2015-05-11 17:19:51 +01:00
Matthias Radestock c210f0e3c1 make Peer.Info lock-free 2015-05-11 17:19:46 +01:00
Matthias Radestock 426d8edb31 remove Peer.SetVersionAndConnections
further shrinking Peer API
2015-05-11 17:19:43 +01:00
Matthias Radestock 86ba3be4f7 move Connections from Peer to LocalPeer
further shrinking the former's API
2015-05-11 17:19:38 +01:00
Matthias Radestock 5bf6889250 remove Peer.Version
further shrinking the Peer API
2015-05-11 17:19:35 +01:00
Matthias Radestock 0229185ffe move ConnectionTo from Peer to LocalPeer
further shrinking the former's API
2015-05-11 17:19:31 +01:00
Matthias Radestock 505ced5cc8 move localRefCount accounting responsibility from Peer to Peers
This makes for a more symmetric API - Peers.FetchWithDefault does the
increment and Peers.Dereference does the decrement, whereas previously
the latter was done by Peer.DecrementLocalRefCount.

This does increase lock granularity, i.e. modifying any ref count now
requires a write lock on Peers.
2015-05-11 17:19:23 +01:00
Matthias Radestock 3e6eef5ebd refactor(ish): move a bunch of methods from Peer to LocalPeer
in order to reduce the former's API surface
2015-05-11 17:19:15 +01:00
Paul Bellamy 8f1f6741e3 Merge pull request #642 from errordeveloper/post-docs-move
Fixes post #612
2015-05-11 17:02:35 +01:00
Matthias Radestock 52a89a318a cosmetic: resolve yet more linter complaints 2015-05-11 02:44:15 +01:00
Matthias Radestock b43a3e9ab2 cosmetic: resolve some more linter complaints 2015-05-11 02:37:59 +01:00
Matthias Radestock 4bef9d90fb cosmetic: resolve some linter complaints 2015-05-11 02:33:56 +01:00
Matthias Radestock e5a87d1ab1 cosmetic: resolve some linter complaints 2015-05-11 02:31:34 +01:00
Matthias Radestock ef2c55141b keep linter happy 2015-05-11 02:25:01 +01:00
Matthias Radestock 0cbbe9bac3 make Peer.String() return the common case
When we want a string representation of a Peer, we nearly always just
want the full name. So make String() return that and introduce Info()
to return expanded info.
2015-05-10 23:58:23 +01:00
Matthias Radestock db198652a5 refactor: get rid of test-launch 2015-05-09 17:29:13 +01:00
Matthias Radestock 7b0f432339 cosmetic: fix typo 2015-05-09 17:11:52 +01:00
Matthias Radestock 5472d868aa refactor
we don't need to extract the -nickname option since when the same
option is specified more than once, the last value wins
2015-05-08 18:28:22 +01:00
Matthias Radestock 5a3ff111ef cosmetic 2015-05-08 18:24:43 +01:00
Ilya Dmitrichenko 35ca82dc0c Use gsutil rsync 2015-05-08 16:44:40 +01:00
Ilya Dmitrichenko 86fdfef6f1 No redirect required for latest_release_doc_updates 2015-05-08 15:41:49 +01:00
Paul Bellamy 9c5323ee4f Merge pull request #612 from errordeveloper/post-docs-move
Post docs move
2015-05-08 13:40:35 +01:00
Ilya Dmitrichenko 2daf805e95 Point to released docs for WeaveDNS 2015-05-08 11:16:35 +01:00
Paul Bellamy 5e5b3ba65e Merge pull request #638 from weaveworks/circle
More CircleCI Improvements
2015-05-08 10:19:18 +01:00
Tom Wilkie 28a2c0d8a9 Parallelise integration tests. 2015-05-07 19:03:59 +00:00
Tom Wilkie 8d1b161d4f Use circleci build number instead of git hash, as we can have multiple builds for a given hash. 2015-05-07 18:33:02 +00:00
Tom Wilkie 5094e84af3 Use gliderlabs/alpine over ubuntu in attempt to speed up tests. 2015-05-07 17:40:44 +00:00
Tom Wilkie a5750dda2a Tidy up try_connect function 2015-05-07 17:15:14 +00:00
Tom Wilkie f9c43fecf4 Wait for VMs to come up before connecting 2015-05-07 17:00:36 +00:00
Paul Bellamy 93012a1cfd Merge pull request #637 from weaveworks/circle
Improvements to circle ci integration
2015-05-07 17:32:53 +01:00
Tom Wilkie 5ed8ad46ee Don't need gsutil on circle; use a prebuild weave-build image and cache it locally. 2015-05-07 16:23:08 +00:00
rade 5e5e932584 Merge pull request #627 from grkvlt/feature/add-dns-entry
add `weave dns-add/remove` commands

Closes #364.
2015-05-07 13:05:45 +01:00
Tom Wilkie 26b6b743f5 Use a template with everything installed for GCE tests. 2015-05-07 11:15:13 +00:00
Andrew Kennedy 633d539b60 New commands to add and remove DNS entries
- Commands dns-add and dns-remove added to weave script
- Updated documentation for WeaveDNS
- Added test script for dns-add and dns-remove
- Added IP address functions matching existing CIDR ones
2015-05-07 12:13:59 +01:00
Tom Wilkie 432662bc86 Create multiple GCE VMs at a time. 2015-05-07 10:20:26 +00:00
Matthias Radestock 04ceb5620b allow user to supply args to docker for remote execution
DOCKER_CLIENT_ARGS - for args to docker
WEAVEEXEC_DOCKER_ARGS - for extra args to `docker run`

Closes #626.
2015-05-07 09:27:22 +01:00
Matthias Radestock 63829a07c5 fix typo 2015-05-06 23:35:32 +01:00
Matthias Radestock 2028861a58 refactor
0 is the default
2015-05-06 22:13:02 +01:00
rade efd704c798 Merge pull request #589 from inercia/weave-588
return NXDOMAIN instead of NOTIMP

Fixes #588.
2015-05-06 19:50:51 +01:00
Matthias Radestock d6f4ebec50 Merge branch 'inercia-weave-common-signals' 2015-05-06 19:40:24 +01:00
Matthias Radestock 14b99fac2a minor refactors 2015-05-06 19:39:44 +01:00
Matthias Radestock 8caa58af26 Merge branch 'weave-common-signals' of https://github.com/inercia/weave into inercia-weave-common-signals 2015-05-06 19:33:06 +01:00
Ilya Dmitrichenko c9bff8b5a7 Publishing updates to the docs on latest release
This uses a special branch `latest_release_doc_updates`, as don't
require docs to be updated in retrospect for releases other then
the latest.
2015-05-06 16:43:28 +01:00
Ilya Dmitrichenko b16d4dff5e Update documentation URLs 2015-05-06 16:42:12 +01:00
Ilya Dmitrichenko a3164db1bf Remove scripts for publishing to gh-pages 2015-05-06 16:42:12 +01:00
Tom Wilkie 213337c210 Fix circle badge 2015-05-06 15:34:01 +00:00
Tom Wilkie f8ce0354ae Merge pull request #634 from weaveworks/circle
Add circle ci badge to README
2015-05-06 16:28:06 +01:00
Tom Wilkie 3c813f71f6 Add circle ci badge to README 2015-05-06 15:15:14 +00:00
Tom Wilkie 9f75e4eba5 Merge pull request #624 from weaveworks/circle
Spin up test VMs on GCE during a circle ci run and use them to run the integration tests.
2015-05-06 15:28:26 +01:00
Tom Wilkie e5ae053b4f Spin up test VMs on GCE during a circle ci run and use them to run the integration tests. 2015-05-06 14:01:41 +00:00
Alvaro Saurin f723d5afd8 Move the router to the new signals handler 2015-05-06 15:07:36 +02:00
Alvaro Saurin dbc4b68301 Use a general signals handler 2015-05-06 15:06:54 +02:00
Matthias Radestock dfac7ea564 Merge branch 'awh-reduced_gob' 2015-05-05 22:10:38 +01:00
Matthias Radestock e656d74633 cosmetic 2015-05-05 22:08:03 +01:00
Matthias Radestock 22e612a738 Merge branch 'reduced_gob' of https://github.com/awh/weave into awh-reduced_gob 2015-05-05 21:36:09 +01:00
Alvaro Saurin ba98135e03 Return NOTIMP for some unimplemented query types, NXDOMAIN otherwise 2015-05-05 21:31:11 +02:00
Matthias Radestock e23c15dab2 hostname->fqdn 2015-05-05 18:58:44 +01:00
Adam Harrison 1703250e87 Merge pull request #628 from rade/605_expose_with_multiple_subnets
prevent interference of expose in other subnets
2015-05-05 17:48:57 +01:00
Matthias Radestock d8f1a04467 only NAT when crossing between subnets
otherwise we can end up NATing traffic inside a subnet, placing it
outside the subnet and thus breaking connectivity.
2015-05-05 16:00:37 +01:00
Matthias Radestock 12ce7d4c18 add some tests of weave expose/hide functionality 2015-05-05 16:00:37 +01:00
Adam Harrison ab68a1c325 Introduce PeerSummary 2015-05-05 10:59:09 +01:00
Matthias Radestock 56038674b1 enable weave script debugging with WEAVE_DEBUG
Closes #618
2015-05-04 10:28:24 +01:00
Matthias Radestock 72e8b3dcc0 fix comment 2015-05-03 21:42:26 +01:00
Matthias Radestock 5aa912c6e9 Merge branch 'dpw-check-libpcap-version'
Fixes #623.
2015-05-02 17:49:00 +01:00
Matthias Radestock e40f1fe9a7 fix typo and reformat 2015-05-02 17:46:21 +01:00
David Wragg be983931fc Produce a better error message when there is no real SetImmediateMode
If gopacket is compiled against an older pcap.h that doesn't have
pcap_set_immediate_mode, it supplies a dummy definition that
always returns PCAP_ERROR.  That becomes "Generic error", which
is not very helpful.  The real pcap_set_set_immediate never
returns PCAP_ERROR, so this turns it into a more informative
message.
2015-05-01 21:40:53 +01:00
Matthias Radestock 64f7e04e14 Merge remote-tracking branch 'upstream/master' 2015-05-01 10:10:11 +01:00
Matthias Radestock 221c07067e Accurately describe WeaveDNS status and link to correct docs 2015-05-01 10:08:45 +01:00
David Wragg 17be43ff9e Merge rade/counter_nonce (PR #617)
replace random nonces with message sequence numbers

Fixes #564.
2015-04-30 14:54:10 +01:00
Matthias Radestock cdf7542c25 update docs 2015-04-30 14:47:16 +01:00
Matthias Radestock 137776bcb2 remove decryptor shutdown
we only needed it because of the nonce channels
2015-04-30 14:47:16 +01:00
Matthias Radestock e223899982 remove nonce transmission from protocol 2015-04-30 14:47:16 +01:00
Matthias Radestock b365c3806b replace random nonces with message sequence numbers in udp crypto 2015-04-30 14:47:16 +01:00
Matthias Radestock 696bfe464c replace random nonces with message sequence numbers in tcp crypto
Since TCP is reliable, we do not need to transmit the nonce itself;
the recipient can simply construct the nonce from its own message
counter. Hence there is no message envelope and we can thus remove a
whole layer of encoding.
2015-04-30 14:47:16 +01:00
rade 87f47a60ae Merge pull request #621 from errordeveloper/fix-binding-hostproc-within-container-where-is-already-bound
Should not pass value of `$PROCFS`

Fixes #622.
2015-04-30 14:06:19 +01:00
Ilya Dmitrichenko 9d0c9f1264 Move definition of $PROCFS (cosmetic)
It's not required to be set at the top, it's only looked at down below.
2015-04-30 13:49:39 +01:00
Michael Bridgen d710684f8e Should not pass value of $PROCFS (close #622)
This is picked from squaremo/weave@d3cc606d5b

The environment variable `PROCFS` was originally added so that the script
could be run in a container. The host's /proc is bind-mounted as /hostproc,
and the script must be told this.

The remote weaving changes got this a bit wrong, by using the value of
`$PROCFS` in a "volume" mount argument (`-v`) to docker -- but when
invoking a container, the left-hand-side of the volume mount should
*always* be `/proc` (and the right-hand-side always `/hostproc`),
since it will always be binding the host's `/proc` to the container's
`/hostproc`.

The gist is that docker looks at host's root filesystem and if you
pass `-v /hostporc:/hostproc` when running from a container where
you have already bind-mounted it, it will end-up creating empty
directory in the target container as there is no `/hostproc` from
docker's perspective.
2015-04-30 13:49:29 +01:00
Adam Harrison b48a93c483 Remove obsolete connsIterator 2015-04-30 13:06:57 +01:00
Adam Harrison 047614b540 Encode/decode []ConnectionSummary instead of []byte 2015-04-30 13:03:18 +01:00
Matthias Radestock 0fc0b9a671 cosmetic 2015-04-30 08:54:42 +01:00
Adam Harrison 26dfc42034 Bump protocol version due to gob restructuring 2015-04-29 16:39:14 +01:00
rade 153731c1f3 Merge pull request #614 from awh/fix_weave_version
`weave version` continues after missing image

Fixes #597.
2015-04-28 18:23:09 +01:00
rade df9e188867 Merge pull request #613 from awh/fix_weave_detach
Detach of unattached container succeeds silently
2015-04-28 18:20:38 +01:00
rade cc62f92988 Merge pull request #616 from awh/require_kernel_3.8.0
Require Linux kernel 3.8.0 or above

Closes #585.
2015-04-28 18:16:40 +01:00
Matthias Radestock 61a5640a95 do not connect to weave port of outbound connection endpoints
There is no point.
2015-04-28 16:07:34 +01:00
Adam Harrison c0d6eb4a22 Require Linux kernel 3.8.0 or above 2015-04-28 15:05:47 +01:00
Adam Harrison 5835838202 Merge pull request #610 from inercia/weave-583-1
External clock provider for the cache
2015-04-28 11:58:35 +01:00
Adam Harrison bafea4bc0e weave version continues after missing image 2015-04-28 11:47:32 +01:00
Adam Harrison 19fce2aa8c Only output colour escapes when attached to a tty 2015-04-28 11:35:08 +01:00
Adam Harrison cf299f59de Detach of unattached container succeeds silently 2015-04-28 11:26:31 +01:00
Alvaro Saurin 3bdfa9b050 Make lint happy 2015-04-28 12:05:24 +02:00
Alvaro Saurin 42fa953053 Minor: logs messages... 2015-04-28 12:05:24 +02:00
Alvaro Saurin 18b7110c31 Stringer'ify some structs 2015-04-28 12:05:24 +02:00
Alvaro Saurin 4aa5e0a97e Minor: logging 2015-04-28 12:05:24 +02:00
Alvaro Saurin ce12b36433 Unit test for multiple answers from a mDNS server 2015-04-28 12:05:21 +02:00
Alvaro Saurin 9e5fe255a5 Improved mocks 2015-04-28 12:02:51 +02:00
Alvaro Saurin d7c154ea41 Removed the "now" parameters and use an external clock provider 2015-04-28 12:02:49 +02:00
Alvaro Saurin 7de2080b9e Added a TTL to the zone records 2015-04-28 11:59:21 +02:00
Alvaro Saurin 22a229c305 Utility function raddrToIP() 2015-04-28 11:52:08 +02:00
Tom Wilkie 840a493e62 Merge pull request #604 from weaveworks/build-and-pubslish-docs-site
Basic automation for publishing documentation
2015-04-28 09:48:26 +01:00
Alvaro c2f55fc836 Merge pull request #609 from rade/combine_nameserver_query_handlers
refactor: eliminate duplication in nameserver query handlers
2015-04-27 19:50:14 +02:00
Ilya Dmitrichenko cfb8e75411 Use shorter URLs for tags 2015-04-27 17:47:59 +01:00
Ilya Dmitrichenko 81ff18565f Generate pointer to latest revision 2015-04-27 17:47:28 +01:00
Ilya Dmitrichenko 83c2bcd418 Build and publish docs on push, not on pull-requests 2015-04-27 17:47:25 +01:00
Ilya Dmitrichenko 0b4b849583 Refactor how sectrets are setup 2015-04-27 17:46:34 +01:00
Ilya Dmitrichenko 6bc601bbfc Basic automation for publishing documentation 2015-04-27 17:46:34 +01:00
Matthias Radestock e45ea65e45 tiny simplifying refactor 2015-04-27 15:51:25 +01:00
Matthias Radestock b74348e59d d'oh 2015-04-27 08:58:02 +01:00
Matthias Radestock ce03f27044 refactor: use 'copy' instead of for loop for slice->array copy 2015-04-27 07:42:52 +01:00
Matthias Radestock 1de86f4312 cosmetic 2015-04-27 07:30:17 +01:00
Matthias Radestock 02a7875eb8 refactor: remove last dependency of crypto on LocalConnection 2015-04-27 07:27:31 +01:00
Matthias Radestock ea4e84cdd3 refactor: (almost) remove dependency of crypto on LocalConnection 2015-04-27 07:14:30 +01:00
Matthias Radestock 29c02d2724 refactor: lift error handling out of Encryptor.Bytes()
in order to reduce dependency on LocalConnection
2015-04-27 07:12:21 +01:00
Matthias Radestock fcd44c37e1 refactor: reduce coupling between connections and crypto 2015-04-27 06:13:41 +01:00
Matthias Radestock ce4c79ed93 refactor: extract commonQueryHandler
to remove duplication between queryHandler and rdnsHandler
2015-04-26 12:40:34 +01:00
Matthias Radestock cb42ec358e even more alignment between queryHandler and rdnsHandler
in preparation of extracting common helper function
2015-04-26 11:40:28 +01:00
Matthias Radestock 1a35b5aa2b align queryHandler and rdnsHandler
increasing their similarity in preparation of refactor
2015-04-26 11:20:44 +01:00
Matthias Radestock 4cc0cf795d explain relationship to TLS 2015-04-26 09:04:09 +01:00
Matthias Radestock ff49bb5125 document why we carry the DF flag in payload 2015-04-25 12:36:05 +01:00
Matthias Radestock d82f60f18b cosmetic 2015-04-25 12:06:20 +01:00
Matthias Radestock d15575212a refactor: no need to record 'df' in two places in state 2015-04-25 12:05:17 +01:00
Matthias Radestock e414f6d059 cosmetic: reformat 2015-04-25 09:17:38 +01:00
rade f217d7badf Merge pull request #606 from awh/592_smoke_test_teardown
Factor out test fixture teardown

Fixes #592.
2015-04-24 12:10:15 +01:00
Adam Harrison d0d8144ef9 Factor out test fixture teardown 2015-04-24 12:03:57 +01:00
Adam Harrison 443b11646d Merge pull request #559 from inercia/weave-338
Support for multiple responses in mDNS
2015-04-24 12:01:43 +01:00
Matthias Radestock 252dacb278 update topology docs to reflect recent changes 2015-04-23 20:46:27 +01:00
rade 2b50a183bb Merge pull request #603 from rade/600_early_death_in_wait_for_status
guard against early container death in wait_for_status

Fixes #600.
2015-04-23 17:31:43 +01:00
Matthias Radestock ed894d52c9 guard against early container death in wait_for_status 2015-04-23 17:28:03 +01:00
Matthias Radestock 4a8fe43036 refactor: simplify "return 1 on error" code 2015-04-23 17:21:56 +01:00
Matthias Radestock af4b7daa77 weave no longer needs to be run as root, so don't say that 2015-04-23 16:40:09 +01:00
Matthias Radestock edf92ef958 add tests for resolving unqualified names 2015-04-23 15:21:26 +01:00
Adam Harrison dd21cfad7f Merge pull request #595 from rade/shrink_dns_tests
shrink dns tests and prevent exit on failure
2015-04-23 15:04:00 +01:00
rade d3128808b9 Merge pull request #601 from fintanr/readme_update
Highlight DNS in Readme
2015-04-23 12:28:23 +01:00
Fintan Ryan ae80d08935 Highlight DNS in Readme 2015-04-23 12:27:07 +01:00
Alvaro Saurin 41ae6e0b7a Make "go vet" happy 2015-04-23 13:01:29 +02:00
Alvaro Saurin 56859a9c30 Run gofmt 2015-04-23 13:01:29 +02:00
Alvaro Saurin 58c8945b32 Support "insistent" queries in the mDNS client, used for obtaining all possible responses from mDNS servers.
Support for multiple responses from Lookup*() methods
Support for multiple records when building packets with make*() methods
Use a common "ZoneRecord" interface for structs that contain names and IPs
2015-04-23 13:01:29 +02:00
Matthias Radestock ec29c2c776 cosmetic 2015-04-23 08:04:12 +01:00
Matthias Radestock 22c04bfc24 don't use $(exec_on ...)
since that will cause the test to exit on failure
2015-04-22 20:09:34 +01:00
Matthias Radestock eebb674232 simplify dns tests
- use 'host' to lookup MX record. It's shorter
- use getent everywhere else for dns lookups, instead of dig; this
  tests more of the o/s name resolution machinery and is generally
  shorter.
- introduce assert_dns_record helper that performs forward and reverse
- lookup introduce some vars for holding constants, so we don't have
  to repeat them
2015-04-22 19:38:04 +01:00
rade 5749d19fee Merge pull request #594 from awh/591_smoke_test_robustness
Improved smoke test robustness

Fixes #591.
2015-04-22 16:52:37 +01:00
Adam Harrison 6a06d0f95f Simplify ping tests to avoid set -e precipitated exit in failure case 2015-04-22 16:46:35 +01:00
Adam Harrison d850ea41a2 Report overall status after running all suites 2015-04-22 16:46:22 +01:00
Adam Harrison 5e01021cba Avoid use of subshell in whitely/greyly 2015-04-22 16:43:38 +01:00
Adam Harrison f3619367a2 Make sourcing of config.sh idempotent 2015-04-22 14:54:33 +01:00
Adam Harrison 849bd52096 Merge pull request #590 from rade/simplify_test_docker_exec
add exec_on test helper
2015-04-22 10:31:37 +01:00
Matthias Radestock a7c9fd272c add exec_on helper
so we don't repeat the remote docker invocation command (complete with
hard-coded port) over and over again.
2015-04-22 09:25:42 +01:00
Matthias Radestock 3ad186d727 make vagrant provision idempotent
Closes #586.
2015-04-21 23:27:46 +01:00
Matthias Radestock 7135508d66 bump docker version requirement for --with-dns
and explain why

Fixes #587.
2015-04-21 22:46:13 +01:00
Matthias Radestock 7221744d9a Merge remote-tracking branch 'upstream/master' 2015-04-21 19:47:20 +01:00
rade 5178e1a66c Merge pull request #580 from awh/use_nsenter
Replace `ip netns exec` with `nsenter`

Closes #458.
2015-04-21 18:23:34 +01:00
Adam Harrison 8326768b9f Upgrade Vagrantfiles to Ubuntu 14.10 2015-04-21 17:28:17 +01:00
Adam Harrison 89e0a6098c Replace ip netns exec calls with nsenter 2015-04-21 17:28:06 +01:00
Adam Harrison 8b9a2142c9 Include util-linux package in weaveexec container 2015-04-21 15:19:56 +01:00
123 changed files with 8645 additions and 1653 deletions

No files matched your search

+4
View File
@@ -32,7 +32,9 @@ coverage.html
# Project specific
weaver/weaver
weavedns/weavedns
weaveproxy/weaveproxy
sigproxy/sigproxy
weavewait/weavewait
tools/bin
tools/build
releases
@@ -40,6 +42,8 @@ weave.tar
weavedns.tar
weaveexec.tar
weaveexec/weave
weaveexec/weaveproxy
weaveexec/weavewait
weaveexec/sigproxy
weaveexec/docker*.tgz
Vagrantfile.local
+6
View File
@@ -3,6 +3,9 @@ sudo: false
go:
- 1.4
env:
- PATH="${PATH}:${HOME}/.local/bin"
addons:
apt:
packages:
@@ -11,6 +14,8 @@ addons:
install:
- go clean -i net
- go install -tags netgo std
- pip install --user --upgrade gcloud gsutil
- bin/setup-ci-secrets $encrypted_5ba036e89377_key $encrypted_5ba036e89377_iv
- make travis
script:
@@ -20,3 +25,4 @@ script:
after_success:
- go get github.com/mattn/goveralls
- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci
- test "${TRAVIS_PULL_REQUEST}" = "false" && site/build-and-publish.sh
+76
View File
@@ -1,3 +1,79 @@
## Release 0.11.2
This is a bug fix release, addressing the following issues:
- `weave run` did not respect DOCKER_CLIENT_ARGS. #855
- Negative result cache did not expire if requeried within TTL. #845
More details in the [change
log](https://github.com/weaveworks/weave/issues?q=milestone%3A0.11.2).
*NB: This release does not change the weave protocol version.
Therefore, when upgrading an existing 0.11 installation incrementally,
connectivity between peers will be retained.*
## Release 0.11.1
This is a bug fix release, addressing the following issues:
- The IP Allocator could crash in some relatively rare
circumstances. #782/#783.
- When the proxy failed to attach a container to the weave network,
there was no failure indication and descriptive error anywhere, and
the application process would still start. Now an error is reported
to the client (i.e. typically the Docker CLI), recorded in the proxy
logs, and the container is terminated. #788/#799.
- `weave launch-proxy --with-ipam` failed to configure the entrypoint
and DNS unless a (possibly blank) `WEAVE_CIDR` was
specified. Affected containers could start the application process
w/o the weave network interface being available, and without
functioning name resolution for the weaveDNS domain.
#744/#747/#751/#752
- The `weave status` output for the IP Allocator was misleadingly
conveying a sense of brokenness when no IP allocation requests had
been made yet. #787/#801
- When invoking `weave launch-proxy` twice, the second invocation
would output a blank line and terminate with a success exit
status. Now it reports that the proxy is already running and exits
with a non-zero status. #767/#780
- `weave launch-proxy` was not respecting WEAVEPROXY_DOCKER_ARGS, so
any user-supplied custom configuration for the weaveproxy container
was ignored. #755/#780
- The proxy was not intercepting API calls to the unversioned (1.0)
Docker Remote API. Hence none of the weave functionality was
available when Docker clients were using that version of the
API. #770/#774/#777,#809
- The proxy would crash when certain elements of the
`/containers/create` JSON were missing. We are not aware of any
Docker clients doing this, but it's been fixed regardless. #778/#777
More details in the [change
log](https://github.com/weaveworks/weave/issues?q=milestone%3A0.11.1).
*NB: This release does not change the weave protocol version.
Therefore, when upgrading an existing 0.11 installation incrementally,
connectivity between peers will be retained.*
## Release 0.11.0
**Highlights**:
- **automatic IP Address Management (IPAM)**, which allows application
containers to be started and attached to the weave network without
needing to supply an IP address.
- **proxy** for automatically attaching containers started with
ordinary `docker run`, or the Docker remote API, to the weave
network.
- ability to **add/remove extra DNS records**.
- performance and scalability improvements
- fixes for a small number of bugs discovered during testing
More detail in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A0.11.0)
*NB: This release changes the weave protocol version. Therefore, when
upgrading an existing installation, all hosts need to be upgraded in
order to for them to be able to communicate and form a network.*
## Release 0.10.0
**Highlights**:
+16 -7
View File
@@ -1,7 +1,7 @@
PUBLISH=publish_weave publish_weavedns publish_weaveexec
.DEFAULT: all
.PHONY: all update tests publish $(PUBLISH) clean prerequisites build travis
.PHONY: all update tests publish $(PUBLISH) clean prerequisites build travis run-smoketests
# If you can use docker without being root, you can do "make SUDO="
SUDO=sudo
@@ -10,7 +10,9 @@ DOCKERHUB_USER=weaveworks
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
WEAVER_EXE=weaver/weaver
WEAVEDNS_EXE=weavedns/weavedns
WEAVEPROXY_EXE=weaveproxy/weaveproxy
SIGPROXY_EXE=sigproxy/sigproxy
WEAVEWAIT_EXE=weavewait/weavewait
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
WEAVEDNS_IMAGE=$(DOCKERHUB_USER)/weavedns
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
@@ -27,9 +29,9 @@ all: $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
travis: $(WEAVER_EXE) $(WEAVEDNS_EXE)
update:
go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE))
go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE)) ./$(dir $(WEAVEPROXY_EXE))
$(WEAVER_EXE) $(WEAVEDNS_EXE): common/*.go
$(WEAVER_EXE) $(WEAVEDNS_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE): common/*.go
go get -tags netgo ./$(@D)
go build -ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo -o $@ ./$(@D)
@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
@@ -41,8 +43,10 @@ $(WEAVER_EXE) $(WEAVEDNS_EXE): common/*.go
false; \
}
$(WEAVER_EXE): router/*.go weaver/main.go
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go weaver/main.go
$(WEAVEDNS_EXE): nameserver/*.go weavedns/main.go
$(WEAVEPROXY_EXE): proxy/*.go weaveproxy/main.go
$(WEAVEWAIT_EXE): weavewait/*.go weavewait/main.go
# Sigproxy needs separate rule as it fails the netgo check in the main
# build stanza due to not importing net package
@@ -57,9 +61,11 @@ $(WEAVEDNS_EXPORT): weavedns/Dockerfile $(WEAVEDNS_EXE)
$(SUDO) docker build -t $(WEAVEDNS_IMAGE) weavedns
$(SUDO) docker save $(WEAVEDNS_IMAGE):latest > $@
$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE)
$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE)
cp weave weaveexec/weave
cp sigproxy/sigproxy weaveexec/sigproxy
cp $(SIGPROXY_EXE) weaveexec/sigproxy
cp $(WEAVEWAIT_EXE) weaveexec/weavewait
cp $(WEAVEPROXY_EXE) weaveexec/weaveproxy
cp $(DOCKER_DISTRIB) weaveexec/docker.tgz
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) weaveexec
$(SUDO) docker save $(WEAVEEXEC_IMAGE):latest > $@
@@ -92,9 +98,12 @@ publish: $(PUBLISH)
clean:
-$(SUDO) docker rmi $(WEAVER_IMAGE) $(WEAVEDNS_IMAGE) $(WEAVEEXEC_IMAGE)
rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
build:
$(SUDO) go clean -i net
$(SUDO) go install -tags netgo std
$(MAKE)
run-smoketests: all
cd test && ./setup.sh && ./run_all.sh
+17 -17
View File
@@ -1,9 +1,9 @@
# Weave - the Docker network
[![Build Status](https://travis-ci.org/weaveworks/weave.svg?branch=master)](https://travis-ci.org/weaveworks/weave) [![Coverage Status](https://coveralls.io/repos/weaveworks/weave/badge.svg)](https://coveralls.io/r/weaveworks/weave)
[![Build Status](https://travis-ci.org/weaveworks/weave.svg?branch=master)](https://travis-ci.org/weaveworks/weave) [![Integration Tests](https://circleci.com/gh/weaveworks/weave/tree/master.svg?style=shield&circle-token=4933c7dabb3d0383e62117565cb9d16df7b1a811)](https://circleci.com/gh/weaveworks/weave) [![Coverage Status](https://coveralls.io/repos/weaveworks/weave/badge.svg)](https://coveralls.io/r/weaveworks/weave)
Weave creates a virtual network that connects Docker containers
deployed across multiple hosts.
deployed across multiple hosts and enables their automatic discovery.
![Weave Virtual Network](/docs/virtual-network.png?raw=true "Weave Virtual Network")
@@ -29,7 +29,7 @@ capabilities, so these can continue to be used by containers.
## Installation
Ensure you are running Linux (kernel 3.5 or later) and have Docker
Ensure you are running Linux (kernel 3.8 or later) and have Docker
(version 1.3.1 or later) installed. Then install weave with
sudo wget -O /usr/local/bin/weave \
@@ -53,10 +53,10 @@ Say you have docker running on two hosts, accessible to each other as
`$HOST1` and `$HOST2`, and want to deploy an application consisting of
two containers, one on each host.
On `$HOST1` run (as root)
On `$HOST1` run
host1# weave launch
host1# C=$(weave run 10.2.1.1/24 -t -i ubuntu)
host1$ weave launch
host1$ C=$(weave run 10.2.1.1/24 -t -i ubuntu)
The first line starts the weave router, in a container. This needs to
be done once on each host. The required docker image for the weave
@@ -87,8 +87,8 @@ addresses must, of course, be unique.
We repeat similar steps on `$HOST2`...
host2# weave launch $HOST1
host2# C=$(weave run 10.2.1.2/24 -t -i ubuntu)
host2$ weave launch $HOST1
host2$ C=$(weave run 10.2.1.2/24 -t -i ubuntu)
The only difference, apart from the choice of IP address for the
application container, is that we tell our weave that it should peer
@@ -103,14 +103,14 @@ Note that we could instead have told the weave on `$HOST1` to connect to
weave automatically (re)connects to peers when they become
available. Also, we can tell weave to connect to multiple peers by
supplying multiple addresses, separated by spaces. And we can
[add peers dynamically](http://weaveworks.github.io/weave/features.html#dynamic-topologies).
[add peers dynamically](http://docs.weave.works/weave/latest_release/features.html#dynamic-topologies).
Now that we've got everything set up, let's see whether our containers
can talk to each other...
On `$HOST1`...
host1# docker attach $C
host1$ docker attach $C
root@28841bd02eff:/# ping -c 1 -q 10.2.1.2
PING 10.2.1.2 (10.2.1.2): 48 data bytes
--- 10.2.1.2 ping statistics ---
@@ -119,7 +119,7 @@ On `$HOST1`...
Similarly, on `$HOST2`...
host2# docker attach $C
host2$ docker attach $C
root@f76829496120:/# ping -c 1 -q 10.2.1.1
PING 10.2.1.1 (10.2.1.1): 48 data bytes
--- 10.2.1.1 ping statistics ---
@@ -131,12 +131,12 @@ to each other.
## Find out more
* [Documentation homepage](http://weaveworks.github.io/weave/)
* [Features](http://weaveworks.github.io/weave/features.html)
* [Troubleshooting](http://weaveworks.github.io/weave/troubleshooting.html)
* [Building](http://weaveworks.github.io/weave/building.html)
* [How it works](http://weaveworks.github.io/weave/how-it-works.html)
* [WeaveDNS README](https://github.com/weaveworks/weave/tree/master/weavedns#readme)
* [Documentation homepage](http://docs.weave.works/weave/latest_release/)
* [Features](http://docs.weave.works/weave/latest_release/features.html)
* [Automatic Discovery with WeaveDNS](http://docs.weave.works/weave/latest_release/weavedns.html)
* [Troubleshooting](http://docs.weave.works/weave/latest_release/troubleshooting.html)
* [Building](http://docs.weave.works/weave/latest_release/building.html)
* [How it works](http://docs.weave.works/weave/latest_release/how-it-works.html)
## Contact Us
Vendored
+21 -23
View File
@@ -1,12 +1,25 @@
VAGRANTFILE_API_VERSION = "2"
require './vagrant-common.rb'
vm_ip = "172.16.0.3" # arbitrary private IP
pkgs = "lxc-docker aufs-tools build-essential ethtool iputils-arping libpcap-dev git mercurial bc"
pkgs = %w(
lxc-docker
aufs-tools
build-essential
ethtool
iputils-arping
libpcap-dev
git
mercurial
bc
)
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "phusion/ubuntu-14.04-amd64"
config.vm.box_url = "https://oss-binaries.phusionpassenger.com/vagrant/boxes/latest/ubuntu-14.04-amd64-vbox.box"
config.vm.box = "ubuntu/ubuntu-14.10-amd64"
config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/utopic/current/utopic-server-cloudimg-amd64-vagrant-disk1.box"
config.vm.network "private_network", ip: vm_ip
config.vm.provider :virtualbox do |vb|
@@ -16,27 +29,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.synced_folder ".", "/home/vagrant/src/github.com/weaveworks/weave"
config.vm.provision :shell, :inline => "ln -s /home/vagrant/src/github.com/weaveworks/weave /home/vagrant/weave"
config.vm.provision :shell, :inline => "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9"
config.vm.provision :shell, :inline => "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
pkg_cmd = "export DEBIAN_FRONTEND=noninteractive; " \
"apt-get update -qq; " \
"apt-get install -qq --no-install-recommends #{pkgs}"
install_go_toolchain = "curl -s https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | tar xz -C /usr/local"
config.vm.provision :shell, :inline => pkg_cmd
config.vm.provision :shell, :inline => install_go_toolchain
config.vm.provision :shell, :inline => "usermod -a -G docker vagrant; "
config.vm.provision :shell, :inline => "echo export GOPATH=/home/vagrant >> /home/vagrant/.bashrc"
config.vm.provision :shell, :inline => "echo export PATH=/usr/local/go/bin:${PATH} >> /home/vagrant/.bashrc"
config.vm.provision :shell, :inline => "chown -R vagrant:vagrant /home/vagrant/src"
config.vm.provision :shell, :inline => "/usr/local/go/bin/go clean -i net; /usr/local/go/bin/go install -tags netgo std"
config.vm.provision :shell, :inline => "echo 'DOCKER_OPTS=\"-H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375\"' >> /etc/default/docker"
config.vm.provision :shell, :inline => "service docker restart"
install_build_deps config.vm, pkgs
install_go_toochain config.vm
tweak_user_env config.vm
tweak_docker_daemon config.vm
cleanup config.vm
end
-20
View File
@@ -1,20 +0,0 @@
#! /bin/bash
set -e
# For info on why/how to use this script, see
# https://github.com/weaveworks/weave/wiki/WorkingOnWeave#working-on-docs
MERGE_ARGS="-X subtree=site"
merge_dir=$(mktemp -d -t weave-merge.XXXXXX)/weave
git clone -b gh-pages . $merge_dir
pushd $merge_dir
git pull --ff-only git@github.com:weaveworks/weave gh-pages
git fetch origin master
git merge $MERGE_ARGS -e -m "Merge master into gh-pages" origin/master
popd
git fetch $merge_dir gh-pages:gh-pages
# rm -rf $merge_dir
+10 -1
View File
@@ -14,14 +14,23 @@ set -e
START=${FIRST_WEAVE:-1}
COUNT=${NUM_WEAVES:-28}
FINISH=$((START+COUNT-1))
PORT=6783
weavedir=$(dirname $0)/..
# Generate a random MAC value - copied from 'weave' script
random_mac() {
# In the first byte of the MAC, the 'multicast' bit should be
# clear and 'locally administered' bit should be set. All other
# bits should be random.
od -txC -An -N6 /dev/urandom | sed 's|^ ||;s| |:|;s|[0-3]:|2:|;s|[4-7]:|6:|;s|[89ab]:|a:|;s|[c-f]:|e:|;s| |:|g'
}
case "$1" in
launch)
shift 1
for i in $(seq $START $FINISH); do
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave test-launch "$@"
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch -iface '' -name $(random_mac) -nickname weave$i -port $PORT "$@"
done
if [ $START -ne 1 ] ; then
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1)))
-3
View File
@@ -1,3 +0,0 @@
#! /bin/sh
set -e
git push git@github.com:weaveworks/weave gh-pages:gh-pages
+57
View File
@@ -0,0 +1,57 @@
#!/bin/sh
## This is the shape of unencrypted `do-setup-ci-secrets`
##
## cat > /tmp/boto_gsutil_cert.pem << EOF
## -----BEGIN PRIVATE KEY-----
## <...>
## -----END PRIVATE KEY-----
## EOF
##
## cat > ~/.boto << EOF
## [Credentials]
## gs_service_client_id = <...>
## gs_service_key_file = /tmp/boto_gsutil_cert.pem
##
## [Boto]
## https_validate_certificates = True
##
## [GSUtil]
## content_language = en
## default_api_version = 2
## default_project_id = <...>
## EOF
openssl base64 -d << EOF \
| openssl aes-256-cbc -d -K $1 -iv $2 \
-out bin/do-setup-ci-secrets
mOYNzNsAdwva0LbB2YFMY9VgL0SITFu1bPrbOujyLtOrBNjmOYbzR5d/sG+ARMFr
7NZ5OKcvMbItllz0n7TtNkzkCOMztMC870Wcx3nGJzbBephdcYwos4tBlp5bihTU
AXgxLUiDT1broYAFKnbE1aZC/XyNxuLTN+nXJWIWeijaXTgvhkEEm3tab+z7ioCb
tlg71W1BrUuocctAJAuVwH/uTZAaXaJGJj1s2S7F2DrFnG6sFlrp8rnCucPLWKnC
4IwJFC5saR5gxK6wECffwmwbpeXVOoQ58OgzxoZTEyAr9nhVbaBt5UZz3tfHlx8W
Rn5jHehACw5kg5eMtGPqMEksJ63LX4h3mfSTLJXJHEmOrzCRpFrigVrsbA408U2u
8jJTHJmKrGfwBh5ZFzBg6NuV4VplOp+ZquJLgegv2WH/iu/ciopXqB2YDCdx5ZBi
nHV2Puth7kjS4EMCKUGfeqpesjL9Djb8cygzv/J3ZBmLjNH9dqlSXWy2DcFZA1Iy
pFTTHkVU3w5sNz3nYOuT2UPQvULBW0FUndDKX6olyVa0mlxPwGBP9N3yGg918/rw
7rNV/r54lA334F5QMhDrVxar6sl9wfLA+Z+8qemBbNFlLog1uQGhuwWK2nFu1tRM
9uyvQIq0oq5NHKpogWc7NEuk7CPe1JyfRIcbMVj8+ilmKjUPB2HPC43IDmKt9/56
rY3IXAcxZ/ClGfGpeNHXqop9T/WhfaowponOZFcdGSUddG0OswqkTE4KIv71mgTF
jXooMkTQBOG+gBnCe+8bCRdRCG3anxHX74za4dGD58cOJtl6DYWnjWuR91NWnxdT
1Zr3iUe41txg9hCNY9LHo2QHGLPxe6pmNnSClQ1mcFhuY6NF8RmCX3HAGKJQkxJb
vpLGbXTXjAIFgIYgLNgKP2RLvCif/LmSWgkvE3ZU9GXtCUbm6xgJodwOqXiI9uuO
4H4+/vpGHnSFENiclkgMFrLG4yiMu8ArgmnCBzg9E2AoOX5zCrh/2ZRTU8Dk4mt7
jUd5SKQLsjB9QtB+y3tRO7zY9RDvR6iBOPRsjx6t8EJMCfPb06KchfMJw+VGxvMM
48jtmepREFnhd/8XDRGnw5bznjRuwohGik8BToltjIviMkkpifKWaLwlaOiDXaHh
VBFNXnUKvOBwRMU+PZqv4+DP2qff1gSXnGKqA5GIURYrK9P7TqHHNTnmjBdfJLzc
0x+W1DTiYhLT2vrLrkEpZ99o6qrg+lwvjat6IsJF8xoQEo4pLD/ZRPJ0WzoDyctT
oWZ5rMsN1TzTvJrBrTAWIJyhIzZcuNwChMrU6qogMr0fd6TSxWhWn0sOJ5y8AaTR
ej4JHx0qroS/9JPmgFYU61pX3c0ypdef/sSHWG23mcFzLTgwe2d4ty9VakypZy3T
sXCl6EHVVXlUFWhY4zQCjdZpYJv/rtoKL1BL4fPE2NV5WTjCL2GNvaO5SfHR7pA0
yQJR/iU+cS2n8UXsWNjyV+/qEn6+QXuyVHGLbxyH8EeqlliAbyHsc2rzVI8Xctzo
W5DuIqxwHAkNNurhmx27ktIIQ6HhaCp028SWwvLttkbBBgF4v1mmd4u/2bKbKjUd
dOaetaIUx1hU9OAeaePvVkeTDcc3mlFtPARCS9Fqj16hgJQ43onZyC0H1K2WAxmU
GsmzjCHlTNquzFTuuQihYGZKfsWIh9z6HEOkvNJ3VEQ=
EOF
exec sh bin/do-setup-ci-secrets
+115
View File
@@ -0,0 +1,115 @@
#!/bin/bash
set -eu
# openssl enc -in do-setup-circleci-secrets.orig -out setup-circleci-secrets.orig -e -aes256 -pass stdin
# openssl base64 < setup-circleci-secrets.orig
openssl base64 -d << EOF \
| openssl enc \
-out bin/do-setup-circleci-secrets \
-d -aes256 -pass pass:$1
U2FsdGVkX1/3QHnDgpZn0srp+GiEQBTlag0CBdsw6Bp6iaIKy6Jy8gFshrsmvl3+
c+7hn1nqsBVpzVH0S5QA3Ex5nx9rl7/dcGoogsySFcwMmBFcauT0UTMK1bvX0Hqx
QrshYSndUkww72KA7EsSnjR1BBJWm3AhCDOTIcnO6nQhig1Fc+SBEj5FGS5xTQ5Z
/8hDsKf54Ms49vOO6QeE5w5ndZ+k7IvVJfq9Evhb7htD20RntDhR86foAwMnLV7R
Wh9qAqI3gnRq0GmbeRKaHmB7pcLq9f0OdScqM/Tn1D+q0556fqJjBziXpZiGbxVs
DxEp8zDJ8yNDAi0Wb2NXjoA79SXJ8Q7iTieaST4y2OmXQpY+Vw8RVDXxi6ERyVnH
41OeMzyLm0qvCoMl32pe1sSG5DRCUiAm7wOtDTVvNDRqmGJYUgwwexyayecJI1wQ
LlhYLJavUvw3UGDZOld8BaMQhfOKBVCdFrjmA4HuVxmOf3029qkgzOCSZiQVNqs+
fvM6J05zBuk+GPy8YInvRkzfxum/x00vmUyGjMs/lHDFfrmYDFK3ux41EoVqvWLG
S5uYxdZJFdR3Xsrs+sMr51XNBQGMmz6KoEEzFYyHs5fZgtdpRdPAuQS1m6FqgZyi
wSz6+EKsALtmGorEp+CF8JN5YzKJCrKzNkg0fILBqsqmJZGP64lmX69950lRLNGM
/l3KVR2zH31b4AnnzTcvldVSCKDXjaMw3JA2Ce/MK6XhOWOcpEv4oDt/62lbhUKP
QUQTPbfpQRvVfBcoIrfAm3ghi/CZc2TDVEuD7DhasLTZD3r7LoK261BWC9O5Wubi
vlXuFIF+uyrpTDf6EJKE/+XywtArppffMP6RkHu1Iqnd8JtsqPaIZsmMmZbjgb6j
BX4ZLb5U9R7jYoSgIke4+u6geFcrMQ+Lny4r7MxXaS0Y1UJ6Jo1bwpYw1TBIobZc
eQ30+xhbSeRC9vEw1NXI6qM2Ai2gIHtAZvEiBkBBXTG5Ld7N/mBAmDIJEzVRxh6F
jRkB3dzESF6eeTBm61OQepwV3aW6lslMaf26drPGv2W2q+K/WOpQOejfggtHUgVs
9SWqtn7hHYSpIASe1Ihi14HAWv3kPhQien0zY1DLKJWf4ilbRaICn1QSJjNuKwtl
A2ZwEw7FFddFcsF4okHPkxEoCre3shq0DEw9UA1gnwEaEF1Q+i9cvLtQg56zK3+R
MfTzFUv2K1z3j1s5tC6Z6LzL8j682EFOll7MkihqiIWh+po1zcNs6Ln0SkjB3v9T
OFRYVJis9GqXYO9PUW1mI69udR4nXbKlBl5tWNyAxn9hufJHLSAmbGMwActKLRM5
kROapKrN1Ra/sHUmhoSSDUK9AXeYKkqPXVMbhly9xC1Kya4hFaFokjRQIMLPmXdQ
lP46RL3HOq0v1BkXg05poSwDm+BtitZkcIhOzcmeucUqn1whsz39y3VoOhJqQu2j
0dUcj19Fue1CDZfwzACb9mLZr643h0yCIHQaVT7is/yiN2vEt0S0zynvOA1D+Xgy
4bFdjqed9DgBShLwF/lh4/hur5iZPpVr9A8rt3Vq0k5A1tdjsZpXP3qv5jLc7lJv
cAQxdVLEuv8WPwwTV/EQsoH7lpoEdjktw/gu0v5PkUgR/WBxEqJq4liTekViQ+8X
BAmdm/FGWeIxSSEm0a8I+muiBpIuKjuQoJit1f0k+lLUohoVDqpDDfeYHh1TxXHE
xSKAKNmFSc2XOr5Zg2mOInxeH7WoxH6iylXKADBgEgAQ4C55DoAPRjFNtGGz2iJl
WkzhQOfgsDss1VxpPZ4SWDQO+xxuuEnYcCYdJ6Tyi5hAwZ0+RZmXx0g9BCK+5qzc
Ffei066kCItBcayekwCs0i86r8JvDtsMUUkzEKDQTSWHpP8ypIZoz3P+Skdqcs6p
IcZHy2fODIsClctRAsHOjZ9BnWSY2rqf1lysqvRfOkNTViTOUVx2hf+LuL+8rT6g
K4GvS39Q+c9FYSMjCNxv1m37NfHJv2W7AlqVV95gMBEFB97M3388T7PfvmSLsyPv
qAKDE7genTOycQvEX9oeEACxPo08ai2Mgs/YXCig0g0btZoScoKPelQKtD0HLpZ+
j7ruaKFy4/vySDMostB3AKSkCE/SkGu/W9oI37n1guSYC/9yJjtTcwZULXAClfwt
LyHvLAw4+7BdGJqDe9Tf8z4zqVLax+BcBHkW7pbP+pvxC6fhIA4PO8k5HwwAVlRw
68lG6e3z9+Vst4zrFLewHJvkDRSOTSecs9h9SPOjReTK0ovlDzMHVakfUvqpk9mv
nCj8SWaMPF70z138AuRuKN7X4exNXPBFnOcseKp/NpT1DyvgoHm/oIO65mq9vvo+
42uSbyyegYiTdVK4SaXBc2hixkhaTteqdEy6GxfWqKCvHtYuRD2LyUSjZn/0Y6CG
3wZapylKL1sxfH+b1gogv2wAA8KtZAfiTlMISuymwQr5RbOFma7UVMVecCa7XrNG
fZwO8R+6++ru0efDzeC/gtLOItjn+yVfsbk2gfu0FikHIV1sogOjnsv3dl6IpH/s
w6WbuK/yZhyrrmhIIueHF30ES512Peogf/Gxp8U/ZAmqBa7p4qsv93XWDR52IPYz
yU1MtboLD02J39daBNrYZuO4Yfo/xAvU7Ijg2KIR9HS0bSWNuSFAZaZXlJ2bSag3
8l0dZMx2fZa0FALxWvY2+nd1tWcH+Wo99RSxX+4TH3guCpe1x/58yKOKL4d1xRpu
02nGaqerV9bHU6WjCUYTrrZurTp6yqivpSFCALDMhAU5Ihd/Qjz69zg4uZ7iqbNd
WqIYJ3SAOei2tSfoOJbdh8mohotnMFFbupck3Fv+lXAXYDp0V/sSuK32MSeJxan1
q3t0eenAh6zoBP0Tj53N8B9YmrgZ1i1AljQqYsAdb8N5Z3vecuzQa946U8H4axEc
BL5fgmqSfsbj1EjJVUrw3cE9hgqeqAYBBvZsyW5zSAsLq/RJKYtBxDGADqr0TOOu
TjVqfPObb1u9EAQj15ALBhhPORa2gBAK5gnhJWW5deNgA0UOYoWARFYfATuQvr1T
9xTVbqzZ0VJR4n7X5JyxJo2tZ44QYVzA0fqX0lt+uRkarrkbZMgplKp4MJTFCp2N
tLciWS0YSHPbKTm7svw5mROazUvEoj1N3SYrJT+Ai9UgSL1cA4FmQ5Jsi+GOeQbH
7E4pzjeST4b1aATY9ZZSy4TzK+UNYBcCVqY1dN5Xnk0e5yPMnrkkcYI41s/6oOaW
lQkp/yrHkvpzQWZa7vF2yWg54j3eX8YWx/TsLvmuJmSQqiIHfu9TXcAIPDispCpF
gp4ZbYPZ8RDQLw3ROc+bZ9E0J8e+FHwgXEELsMLW9n4d3PTj9pLFej1vmBKa03o3
g+JViNxb1gUPNmC6ADk4o7TQzBrjV2pmmuCza16kOmq6oAbJPOeCLtVWA5HJTX8d
AuOzBLO37tWP457dNqoNDaCMik3LCy8cslZjAz02D6VG/QGOAXP9x8bJ3zg9N7vW
EOn1BMtIeMI4yIxpC6HgotNWDNbUBWremAFWYQRgYQqo9L1SdW4hz6X8FqdlBFvK
ml7sdUwjEy2tNW+aiZI4cgAlI/VvMWYr5kPfhvaUu1wGYeS1TD1B+DPcjcXpOGWo
vB1f/uvGc61zZWD0ph9f3S9OnvEko5qV3VhxOnxYUZTrqlM7LVgy/sHrSaPdR7Eo
nb4QGdi5wEGMxVgQlDGwOmEmS5nCaA0RcfHbkQV4SebRleocLB9qcSwj5JzPICAB
sn1qeSv45LnGdTzUnCQErw1EzgrK1FXOknujjZ3GX5dM7a6Gag0E6cAxoQeC8Mh0
aUWolyBo9aSXltn7Vnywoe4X3v96eVH6tUQtN3wt51LBlfSDTWOQDeXLFyW57oYL
/ErbmJLaSHpANBcC9InI+rxNYePTTba7K8rdPDppmMEmdOXjaL+5aPay2Bay9K8T
FUyZdoyrbzxFZYT9/YyVJV/ukVcT9ru3HpzLTg9Vgc/CyRfIvCZY/Gzh+pPUbRsR
pHYDT09BFS+mjszTkcdbceweYXJvknXpajMiISrPUUkb9rwCTmLS4QR4y7I9zAgJ
9U72mCZB9yhhzS17kyKAANihXvOFrMXCO7QTWZbtSkobMBS2SybDxYP3rM5b6824
HQaY4MZW5qCndydfz4fa908XbPTDka9Nu3CZfI3fhGiiGDFWViotGEYE4i9BuGZJ
vmtzII7zeIoHwM4t5fb5mYDd9H+Rkct1yATGEIFEx7cu1fj4Ht2177EzhdkF3Q6I
Fccn43yGDW06nFvEjnjb+Lz4EvtGp6dDefUhNBohdzd4Rm2o+etKobm0lZ1xOX74
2gOGBLfXS6PF+msNkWP76mqKE6hG0+R1iPrN06Arti+ZRBhNGJDp1pnOERl7IyB0
hiLUqae7xVSMVRDeq4O04EUpMXh5ixs3RJXjthYILt7vX+6qskJC3rMuyThrsvMi
Kk0wErDpzR5U7KFnM2fk4jY5by/0b/jHZ/PWFGnaFrXNOiwK/mV1RIJtg5FCrvMv
xJN5Qo7o2ZYzUL+IBf5mP5O49FCnz3e++9r4ySY/K57ybhQft1l+cQPWS7xuvVAg
wZGM9DPl9AhBus0I3V3XzfkcWWHkMynMRcfTnkBHrmKjMopIOggsq05JPalM9b0V
NfwZngVvTFbQejbGjgQQQ+G7spav/icCIA8Iu8JPHaKZt2IX77L1xMcQOouqMrx8
JCXnJ7zsuU+8W3GQGm70H2kcen4bf4beA8TSkFmbVsaqDNtGaG+ZopWO8ctZX3CP
jFgIYgEGi137IWvs1KD5QNFHZI6l0JMzglmvcfnViRPDHLi+EQXAoL/98j/YzA9t
a9L4xMO0oucovKkqYOS2cdFaQKVIUM9L5ibcbW7Tol4ugMo5ibIsTgjZoyFohG6t
acg+h2wTTyxtrIapvOOBeAo9V/iQowTAPQf38LKQZ5tApksiCG1qvJerKALTMp1J
yUHjTCPCPsaiXVVO0mfa9OPtzVvi/icLmlz7umk82mew13djedgMS3/ghq5aEHbg
e7rtI5snFlm/GQnV6IaCDytb+jYxqQ0DZ9Mb0CHcMpnLyiBO6nzHfadpZYlrxrpx
dPx9MVqfLavj1UVjKuVj4Mgec9J38IBBRGMJZ4MGhde2hrw/s2jVzG1BzaIzcvFa
bWiJOkKKRC4BY9DBDYfae5c/obwzkxLmvkbWAVzoFXc+46K1IK6bbC2a4EerOAxc
C7B3X8BucD8OtiPORwXgjctd9AJ7cXq0EEVGoZZ8jkILnklsK6RXsxrT4gSOGFpx
cBU7TF4kHpH+o4KGQmjlFA2VYP1tXirjEep57LOHfL+zy+HiSXdsbexf6odkqLYA
1RR0TzSkmqJYvP0GfHPMvp2xredx1kP934fhTIUcWvErrG3NPd/Dod4BzQye1nNK
8Y8aFYF6fACcD4SFZ11ecK9DJmB+WXGlsxtO3w1LeEysny2lVkkH6HuMRIzPyXAM
/sEpwh3hwPJosAh6vGM6JxuL7CXP8hC1oUvK91VR0hxeDcO/LgdcHtDnlNMqt1wC
jvYt9Hg+3aY5GehBnQPRppnIMaGhiBrRC6HjBg8Umt6UTUr5PXNY2aia5wRcx4M6
Tj0O0dAt8WWZ9ySgzOoq4SRoaAp6m4E7CpRNMgXH031OWEeHqtt8rsYqAcvcQLuW
vIwts5hiXZOfgXyR6SBY3ANE1SjDY46na2G4oiG8FI7eAqYgehjHrXqCnGrzjefE
V1eVcIr7DBYuM8wq7C9x+qdMir6RNV4MrVD+7rO/drivnd3MaqDHlG0LfMZo6GIK
QZGIy0z1W5lOXm9x3RAz7/Ea11ynkZyPDDxBE5NRcV6JfukdjDrvsaQA/Qk2Wo7h
PdUZjv17H4syMdap6tUDTc8Cohut6n1UxnDnhaAYEDGR3TgPqI5vEaJ+yoaM9itv
b2I/K+0uK6GfWwca7PeTtYn92/5etO4czG99xEBFzw7tiEmBTA8KfseZIUe/i3fr
9LO+rKEGalFB4mVTYyCKPYYx4hOIvjdCox/jEBTFyYUEhgGnZvArgw9XabvII7eM
c1FmwnN2CM/MHUzM0Kw7cS/oaoRFcG/PqioO/krL2Bd+xThwSH4Zn+Y1UVO7aXNy
90MOehHSc2nhN984Y2re3ffVsjPhC3KdWxr8jbho/Az65rUkYg64roiO2ZqdqRvf
aYtX9fCe8N8SzgLTlWZt+l8sjY8HP2k9NoajphV1nG4iC/pX0ZJiVQ+VBRNaGhSl
6IhM9gM1ZsI86cULJpOTAhNu1fZBv+Mq8IXYD8SqlNVwVtjvshtlzBN4InSGEKiO
HM7coo7aDuu+Go5AEiIVMbR09jAa0VINsCVScfSbTFhlGRTrZr2DSO/s8WxbV+Mb
EQAfZmyrLF71Z3MfIukg+ps5iuqFhZ+gJSXXSFwumpg=
EOF
exec sh bin/do-setup-circleci-secrets
+46
View File
@@ -0,0 +1,46 @@
general:
branches:
ignore:
- gh\-pages
- /pull\/.*/
machine:
services:
- docker
environment:
GOPATH: /home/ubuntu:$GOPATH
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
dependencies:
cache_directories:
- "~/docker"
override:
- if [[ -e "$WEAVE_BUILD" ]]; then
docker load -i $WEAVE_BUILD;
else
docker pull weaveworks/weave-build;
mkdir -p $(dirname "$WEAVE_BUILD");
docker save weaveworks/weave-build >$WEAVE_BUILD;
fi
post:
- curl https://sdk.cloud.google.com | bash
- bin/setup-circleci-secrets "$SECRET_PASSWORD"
- mkdir -p $(dirname $SRCDIR)
- cp -r $(pwd)/ $SRCDIR
test:
override:
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go weaveworks/weave-build:
parallel: true
- cd $SRCDIR/test; ./gce.sh setup:
parallel: true
- cd $SRCDIR/test; . ./gce.sh hosts; ./setup.sh:
parallel: true
- cd $SRCDIR/test; . ./gce.sh hosts; ./run_all.sh:
parallel: true
post:
- cd $SRCDIR/test; ./gce.sh destroy:
parallel: true
+7
View File
@@ -11,3 +11,10 @@ func CheckWarn(e error) {
Warning.Println(e)
}
}
// Assert test is true, panic otherwise
func Assert(test bool) {
if !test {
panic("Assertion failure")
}
}
+37
View File
@@ -0,0 +1,37 @@
package common
import (
"os"
"os/signal"
"runtime"
"syscall"
)
// A subsystem/server/... that can be stopped or queried about the status with a signal
type SignalReceiver interface {
Stop() error
Status() string
}
func SignalHandlerLoop(ss ...SignalReceiver) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGUSR1)
buf := make([]byte, 1<<20)
for {
switch <-sigs {
case syscall.SIGINT:
Info.Printf("=== received SIGINT ===\n*** exiting\n")
for _, subsystem := range ss {
subsystem.Stop()
}
return
case syscall.SIGQUIT:
stacklen := runtime.Stack(buf, true)
Info.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf[:stacklen])
case syscall.SIGUSR1:
for _, subsystem := range ss {
Info.Printf("=== received SIGUSR1 ===\n*** status...\n%s\n*** end\n", subsystem.Status())
}
}
}
}
+5 -5
View File
@@ -160,7 +160,7 @@ if a node receives a packet that is bigger than the next MTU, it is
required to drop the packet and send back an ICMP 3,4 packet which
informs the sending node of the next hop MTU (RFC 1191). In theory,
these ICMP packets should have all the reverse NAT and so forth
applied, to make it all the way back to the sender. In practise this
applied, to make it all the way back to the sender. In practice, this
does often work, but in some cases all ICMP are sometimes blocked,
often by idiots who don't understand that ICMP is really the error
channel of all network traffic.
@@ -187,16 +187,16 @@ If weave determines that fragmentation is reliable then when weave
sniffs large packets (i.e. packets larger than the MTU and thus don't
have DF set) then weave will encapsulate these packets as necessary
and similarly send them out as-is, without DF set, trusting to the
network to do all necessary fragementation and reassembly.
network to do all necessary fragmentation and reassembly.
However, if weave determines that fragementation is not reliable
However, if weave determines that fragmentation is not reliable
between any two peers then it will manually fragment larger packets
correctly according to the IP spec, and will then send them between
weave peers with the DF flag set (i.e. the fragmentation will ensure
that the encapsulatated traffic will not be greater than the PMTU
that the encapsulated traffic will not be greater than the PMTU
between weave peers). Because the fragmentation is done according to
the IP spec, we don't need to do reassembly ourselves - on the
receiving weave, we jus inject all the fragments and rely on the
receiving weave, we just inject all the fragments and rely on the
stack to do reassembly.
Sometimes PMTU discovery doesn't work as ICMP packets sometimes get
+214
View File
@@ -0,0 +1,214 @@
See the [requirements](https://github.com/zettio/weave/wiki/IP-allocation-requirements).
At its highest level, the idea is that we start with a certain IP
address space, known to all peers, and divide it up between the
peers. This allows peers to allocate and free individual IPs locally
until they run out.
We use a CRDT to represent shared knowledge about the space,
transmitted over the Weave Gossip mechanism, together with
point-to-point messages for one peer to request more space from
another.
The allocator running at each peer also has an http interface which
the container infrastructure (e.g. the Weave script, or a Docker
plugin) uses to request IP addresses.
![Schematic of IP allocation in operation](https://docs.google.com/drawings/d/1-EUIRKYxwfKTpBJ7v_LMcdvSpodIMSz4lT3wgEfWKl4/pub?w=701&h=310)
## Commands
The commands supported by the allocator via the http interface are:
- Allocate: request one IP address for a container
- Free: return an IP address that is currently allocated
- Claim: request a specific IP address for a container (e.g. because
it is already using that address)
The allocator also watches via the Docker event mechanism: if a
container dies then all IP addresses allocated to that container are
freed.
## Definitions
1. Allocations. We use the word 'allocation' to refer to a specific
IP address being assigned, e.g. so it can be given to a container.
2. Range. Most of the time, instead of dealing with individual IP
addresses, we operate on them in contiguous groups, for which we
use the word "range".
3. Ring. We consider the address space as a ring, so ranges wrap
around from the highest address to the lowest address.
4. Peer. A Peer is a node on the Weave network. It can own zero or
more ranges.
### The Allocation Process
When a peer owns some range(s), it can allocate freely from within
those ranges to containers on the same machine. If it runs out of
space (all owned ranges are full), it will ask another peer for space:
- it picks a peer to ask at random, weighted by the amount of space
owned by each peer
- if the target peer decides to give up space, it unicasts a message
back to the asker with the newly-updated ring.
- if the target peer has no space, it unicasts a message back to the
asker with its current copy of the ring, on the basis that the
requestor must have acted on out-of-date information.
- it will continue to ask for space until it receives some, or its
copy of the ring tells it all peers are full.
### Claiming an address
If a Weave process is restarted, in most cases it will hear from
another peer which ranges it used to own, but it needs to know which
individual IP addresses are assigned to containers in order to avoid
giving the same address out in subsequent allocation requests. The
weave script invokes the `claim` command in `populate_ipam` to do
this.
When the Allocator is told to claim an address, there are four
possibilities:
- the address is outside of the space managed by this Allocator, in
which case we ignore the request.
- the address is owned by this peer, in which case we record the
address as being assigned to a particular container and return
success.
- the address is owned by a different peer, in which case we return
failure.
- we have not yet heard of any address ranges being owned by anyone,
in which case we wait until we do hear.
This approach fails if the peer does not hear from another peer about
the ranges it used to own, e.g. if all peers in a network partition
are restarted at once.
### The Ring CRDT
We use a Convergent Replicated Data Type - a CRDT - so that peers can
make changes concurrently and communicate them without central
coordination. To achieve this, we arrange that peers only make changes
to the data structure in ranges that they own (except under
administrator command - see later).
The data structure is a set of tokens, each containing the name of an
owning peer. A peer can own many ranges. Each token is placed at the
start address of a range, and the set is kept ordered so each range
goes from one token to the next. Each range on the ring includes the
start address but does not include the end address (which is the start
of the next range). Ranges wrap, so the 'next' token after the last
one is the first token.
![Tokens on the Ring](https://docs.google.com/drawings/d/1hp--q2vmxbBAnPjhza4Kqjr1ugrw2iS1M1GerhH-IKY/pub?w=960&h=288)
In more detail:
- Each token is a tuple {peer name, version}, placed
at an IP address.
- Peer names are taken from Weave: they are unique and survive across restarts.
- The contents of a token can only be updated by the owning peer, and
when this is done the version is incremented
- The ring data structure is always gossiped in its entirety
- The merge operation when a peer receives a ring via gossip is:
- Tokens with unique addresses are just copied into the combined ring
- For tokens at the same address, pick the one with the highest
version number
- The data gossiped about the ring also includes the amount of free
space in each range: this is not essential but it improves the
selection of which peer to ask for space.
- When a peer is asked for space, there are four scenarios:
1. It has an empty range; it can change the peer associated with
the token at the beginning of the range and increment the version.
2. It has a range which can be subdivided by a single token to form
a free range. It inserts said token, owned by the peer requesting
space.
3. It has a 'hole' in the middle of a range; an empty range can be
created by inserting two tokens, one at the beginning of the hole
owned by the peer requesting the space, and one at the end of the
hole owned by the requestee.
4. It has no space.
## Initialisation
The previous sections describe how peers coordinate changes to the
ring. But how is the initial state of the ring established? If a new
peer joins a long-standing cluster, it can learn about the ring state
from other peers. But in a freshly started cluster, the initial ring
state must be established from a clean slate. And it must be
consistent across all peers. This is a distributed consensus problem,
and we solve it using the Paxos algorithm.
Although Paxos has a reputation for being hard to understand and to
implement, the implementation used for ring initialization is
relatively straightforward:
- We only need to establish consensus for a single value, rather than
a succession of values (as in a replicated transaction log). Thus
we implement basic Paxos, rather than multi-Paxos, and the
implementation closely follows the outline of basic Paxos described
[on
wikipedia](http://en.wikipedia.org/wiki/Paxos_%28computer_science%29#Basic_Paxos).
- All peers play all the roles described in basic Paxos: Proposer,
acceptor and listener.
- Paxos is usually described in terms of unicast messages from one
agent to another, although three of the four messages are fanned out
to all agents in a certain role ("prepare" and "accept request" from
a proposer to all acceptors; "accepted" from an acceptor to all
learners). So most implementations involve a communications layer
to implement the required communication patterns. But our
implementation is built on top of the weave gossip layer, which
gives us a broadcast medium. Using broadcast for the one truly
unicast message ("promise") may seem wasteful, but as we only
establish consensus for a single value, it is not a major concern.
The value the peers obtain consensus on is the set of peers that will
be represented in the initial ring (with each peer getting an equal
share of the address space). When a proposing peer gets to choose the
value, it includes all the peers it has heard from during the Paxos
phase, which is at least a quorum.
Once a consensus is reached, it is used to initialize the ring. If a
peer hears about an initialized ring via gossip, that implies that a
consensus was reached, so it will stop participating in Paxos.
Paxos requires that we know the quorum size for the cluster. As weave
clusters are a dynamic entity, there is no fixed quorum size. But the
Paxos phase used to initialize the ring should normally be
short-lived, so we just need to know the initial cluster size. A
heuristic is used to derive this from the list of addresses passed to
`weave launch`, but the user can set it explicitly.
## Peer shutdown
When a peer leaves (a `weave reset` command), it grants all its own
tokens to another peer, then broadcasts the updated ring.
After sending the message, the peer terminates - it does not wait for
any response.
Failures:
- message lost
- the space will be unusable by other peers because it will still be
seen as owned.
To cope with the situation where a peer has left or died without
managing to tell its peers, an administrator may go to any other peer
and command that it take over the dead peer's tokens (with `weave
rmpeer`). This information will then be gossipped out to the network.
(This operation is not guaranteed to be safe - if the dead peer made
some transfers which have are known to other peers but have not
reached the peer that does the `rmpeer`, then we get an inconsistent
ring. We could make it safe, up to partitions, by inquiring of every
live peer what its view of the dead peer is, and making sure we use
the latest info.)
## Limitations
Nothing is persisted. If one peer restarts it should receive gossip
from other peers showing what it used to own, and in that case it can
recover quite well. If, however, there are no peers left alive, or
this peer cannot establish network communication with those that are,
then it cannot recover.
+61
View File
@@ -0,0 +1,61 @@
package address
import (
"github.com/weaveworks/weave/common"
"net"
)
// Using 32-bit integer to represent IPv4 address
type Address uint32
type Offset uint32
type Range struct {
Start, End Address // [Start, End); Start <= End
}
func ParseIP(s string) (Address, error) {
if ip := net.ParseIP(s); ip == nil {
return 0, &net.ParseError{Type: "IP Address", Text: s}
} else {
return FromIP4(ip), nil
}
}
// FromIP4 converts an ipv4 address to our integer address type
func FromIP4(ip4 net.IP) (r Address) {
for _, b := range ip4.To4() {
r <<= 8
r |= Address(b)
}
return
}
// IP4 converts our integer address type to an ipv4 address
func (addr Address) IP4() (r net.IP) {
r = make([]byte, net.IPv4len)
for i := 3; i >= 0; i-- {
r[i] = byte(addr)
addr >>= 8
}
return
}
func (addr Address) String() string {
return addr.IP4().String()
}
func Add(addr Address, i Offset) Address {
return addr + Address(i)
}
func Subtract(a, b Address) Offset {
common.Assert(a >= b)
return Offset(a - b)
}
func Min(a, b Offset) Offset {
if a > b {
return b
}
return a
}
+58
View File
@@ -0,0 +1,58 @@
package ipam
import (
"fmt"
"github.com/weaveworks/weave/ipam/address"
)
type allocateResult struct {
addr address.Address
err error
}
type allocate struct {
resultChan chan<- allocateResult
hasBeenCancelled func() bool
ident string
}
// Try returns true if the request is completed, false if pending
func (g *allocate) Try(alloc *Allocator) bool {
if g.hasBeenCancelled() {
g.Cancel()
return true
}
// If we have previously stored an address for this container, return it.
if addr, found := alloc.owned[g.ident]; found {
g.resultChan <- allocateResult{addr, nil}
return true
}
if ok, addr := alloc.space.Allocate(); ok {
alloc.debugln("Allocated", addr, "for", g.ident)
alloc.addOwned(g.ident, addr)
g.resultChan <- allocateResult{addr, nil}
return true
}
// out of space
if donor, err := alloc.ring.ChoosePeerToAskForSpace(); err == nil {
alloc.debugln("Decided to ask peer", donor, "for space")
alloc.sendRequest(donor, msgSpaceRequest)
}
return false
}
func (g *allocate) Cancel() {
g.resultChan <- allocateResult{0, fmt.Errorf("Allocate request for %s cancelled", g.ident)}
}
func (g *allocate) String() string {
return fmt.Sprintf("Allocate for %s", g.ident)
}
func (g *allocate) ForContainer(ident string) bool {
return g.ident == ident
}
+696
View File
@@ -0,0 +1,696 @@
package ipam
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"net"
"sort"
"time"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/ipam/paxos"
"github.com/weaveworks/weave/ipam/ring"
"github.com/weaveworks/weave/ipam/space"
"github.com/weaveworks/weave/router"
)
// Kinds of message we can unicast to other peers
const (
msgSpaceRequest = iota
msgRingUpdate
paxosInterval = time.Second * 5
)
// operation represents something which Allocator wants to do, but
// which may need to wait until some other message arrives.
type operation interface {
// Try attempts this operations and returns false if needs to be tried again.
Try(alloc *Allocator) bool
Cancel()
String() string
// Does this operation pertain to the given container id?
// Used for tidying up pending operations when containers die.
ForContainer(ident string) bool
}
// Allocator brings together Ring and space.Set, and does the
// necessary plumbing. Runs as a single-threaded Actor, so no locks
// are used around data structures.
type Allocator struct {
actionChan chan<- func()
ourName router.PeerName
subnetStart address.Address // start address of space all peers are allocating from
subnetSize address.Offset // length of space all peers are allocating from
prefixLen int // network prefix length, e.g. 24 for a /24 network
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 address, indexed by container-ID
nicknames map[router.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
paxos *paxos.Node
paxosTicker *time.Ticker
shuttingDown bool // to avoid doing any requests while trying to shut down
now func() time.Time
}
// NewAllocator creates and initialises a new Allocator
func NewAllocator(ourName router.PeerName, ourUID router.PeerUID, ourNickname string, subnetCIDR string, quorum uint) (*Allocator, error) {
_, subnet, err := net.ParseCIDR(subnetCIDR)
if err != nil {
return nil, err
}
if subnet.IP.To4() == nil {
return nil, errors.New("Non-IPv4 address not supported")
}
// Get the size of the network from the mask
ones, bits := subnet.Mask.Size()
var subnetSize address.Offset = 1 << uint(bits-ones)
if subnetSize < 4 {
return nil, errors.New("Allocation subnet too small")
}
subnetStart := address.FromIP4(subnet.IP)
alloc := &Allocator{
ourName: ourName,
subnetStart: subnetStart,
subnetSize: subnetSize,
prefixLen: ones,
// per RFC 1122, don't allocate the first and last address in the subnet
ring: ring.New(address.Add(subnetStart, 1), address.Add(subnetStart, subnetSize-1), ourName),
owned: make(map[string]address.Address),
paxos: paxos.NewNode(ourName, ourUID, quorum),
nicknames: map[router.PeerName]string{ourName: ourNickname},
now: time.Now,
}
return alloc, nil
}
// Start runs the allocator goroutine
func (alloc *Allocator) Start() {
actionChan := make(chan func(), router.ChannelSize)
alloc.actionChan = actionChan
go alloc.actorLoop(actionChan)
}
// Stop makes the actor routine exit, for test purposes ONLY because any
// calls after this is processed will hang. Async.
func (alloc *Allocator) Stop() {
alloc.actionChan <- nil
}
// Operation life cycle
// Given an operation, try it, and add it to the pending queue if it didn't succeed
func (alloc *Allocator) doOperation(op operation, ops *[]operation) {
alloc.actionChan <- func() {
if alloc.shuttingDown {
op.Cancel()
return
}
alloc.establishRing()
if !op.Try(alloc) {
*ops = append(*ops, op)
}
}
}
// Given an operation, remove it from the pending queue
// Note the op may not be on the queue; it may have
// already succeeded. If it is on the queue, we call
// cancel on it, allowing callers waiting for the resultChans
// to unblock.
func (alloc *Allocator) cancelOp(op operation, ops *[]operation) {
for i, op := range *ops {
if op == op {
*ops = append((*ops)[:i], (*ops)[i+1:]...)
op.Cancel()
break
}
}
}
// Cancel all operations in a queue
func (alloc *Allocator) cancelOps(ops *[]operation) {
for _, op := range *ops {
op.Cancel()
}
*ops = []operation{}
}
// Cancel all operations for a given container id, returns true
// if we found any.
func (alloc *Allocator) cancelOpsFor(ops *[]operation, ident string) bool {
var found bool
for i, op := range *ops {
if op.ForContainer(ident) {
found = true
op.Cancel()
*ops = append((*ops)[:i], (*ops)[i+1:]...)
}
}
return found
}
// Try all pending operations
func (alloc *Allocator) tryPendingOps() {
// The slightly different semantics requires us to operate on 'claims' and
// 'allocates' separately:
// Claims must be tried before Allocates
for i := 0; i < len(alloc.pendingClaims); {
op := alloc.pendingClaims[i]
if !op.Try(alloc) {
i++
continue
}
alloc.pendingClaims = append(alloc.pendingClaims[:i], alloc.pendingClaims[i+1:]...)
}
// When the first Allocate fails, bail - no need to
// send too many begs for space.
for i := 0; i < len(alloc.pendingAllocates); {
op := alloc.pendingAllocates[i]
if !op.Try(alloc) {
break
}
alloc.pendingAllocates = append(alloc.pendingAllocates[:i], alloc.pendingAllocates[i+1:]...)
}
}
func hasBeenCancelled(cancelChan <-chan bool) func() bool {
return func() bool {
select {
case <-cancelChan:
return true
default:
return false
}
}
}
// Actor client API
// Allocate (Sync) - get IP address for container with given name
// if there isn't any space we block indefinitely
func (alloc *Allocator) Allocate(ident string, cancelChan <-chan bool) (address.Address, error) {
resultChan := make(chan allocateResult)
op := &allocate{resultChan: resultChan, ident: ident,
hasBeenCancelled: hasBeenCancelled(cancelChan)}
alloc.doOperation(op, &alloc.pendingAllocates)
result := <-resultChan
return result.addr, result.err
}
// Claim an address that we think we should own (Sync)
func (alloc *Allocator) Claim(ident string, addr address.Address, cancelChan <-chan bool) error {
resultChan := make(chan error)
op := &claim{resultChan: resultChan, ident: ident, addr: addr,
hasBeenCancelled: hasBeenCancelled(cancelChan)}
alloc.doOperation(op, &alloc.pendingClaims)
return <-resultChan
}
// Free (Sync) - release IP address for container with given name
func (alloc *Allocator) Free(ident string) error {
return alloc.free(ident)
}
// ContainerDied is provided to satisfy the updater interface; does a free underneath. Async.
func (alloc *Allocator) ContainerDied(ident string) error {
alloc.debugln("Container", ident, "died; releasing addresses")
return alloc.free(ident)
}
func (alloc *Allocator) free(ident string) error {
errChan := make(chan error)
alloc.actionChan <- func() {
addr, found := alloc.owned[ident]
if found {
alloc.space.Free(addr)
}
delete(alloc.owned, ident)
// Also remove any pending ops
found = alloc.cancelOpsFor(&alloc.pendingAllocates, ident) || found
found = alloc.cancelOpsFor(&alloc.pendingClaims, ident) || found
if !found {
errChan <- fmt.Errorf("Free: no addresses for %s", ident)
return
}
errChan <- nil
}
return <-errChan
}
// Sync.
func (alloc *Allocator) String() string {
resultChan := make(chan string)
alloc.actionChan <- func() {
resultChan <- alloc.string()
}
return <-resultChan
}
// Shutdown (Sync)
func (alloc *Allocator) Shutdown() {
alloc.infof("Shutdown")
doneChan := make(chan struct{})
alloc.actionChan <- func() {
alloc.shuttingDown = true
alloc.cancelOps(&alloc.pendingClaims)
alloc.cancelOps(&alloc.pendingAllocates)
if heir := alloc.ring.PickPeerForTransfer(); heir != router.UnknownPeerName {
alloc.ring.Transfer(alloc.ourName, heir)
alloc.space.Clear()
alloc.gossip.GossipBroadcast(alloc.Gossip())
time.Sleep(100 * time.Millisecond)
}
doneChan <- struct{}{}
}
<-doneChan
}
// AdminTakeoverRanges (Sync) - take over the ranges owned by a given peer.
// Only done on adminstrator command.
func (alloc *Allocator) AdminTakeoverRanges(peerNameOrNickname string) error {
resultChan := make(chan error)
alloc.actionChan <- func() {
peername, err := alloc.lookupPeername(peerNameOrNickname)
if err != nil {
resultChan <- fmt.Errorf("Cannot find peer '%s'", peerNameOrNickname)
return
}
alloc.debugln("AdminTakeoverRanges:", peername)
if peername == alloc.ourName {
resultChan <- fmt.Errorf("Cannot take over ranges from yourself!")
return
}
delete(alloc.nicknames, peername)
err, newRanges := alloc.ring.Transfer(peername, alloc.ourName)
alloc.space.AddRanges(newRanges)
resultChan <- err
}
return <-resultChan
}
// Lookup a PeerName by nickname or stringified PeerName. We can't
// 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) {
for peername, nickname := range alloc.nicknames {
if nickname == name {
return peername, nil
}
}
return router.PeerNameFromString(name)
}
// Restrict the peers in "nicknames" to those in the ring and our own
func (alloc *Allocator) pruneNicknames() {
ringPeers := alloc.ring.PeerNames()
for name, _ := range alloc.nicknames {
if _, ok := ringPeers[name]; !ok && name != alloc.ourName {
delete(alloc.nicknames, name)
}
}
}
// OnGossipUnicast (Sync)
func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) error {
alloc.debugln("OnGossipUnicast from", sender, ": ", len(msg), "bytes")
resultChan := make(chan error)
alloc.actionChan <- func() {
switch msg[0] {
case msgSpaceRequest:
// some other peer asked us for space
alloc.donateSpace(sender)
resultChan <- nil
case msgRingUpdate:
resultChan <- alloc.update(msg[1:])
}
}
return <-resultChan
}
// OnGossipBroadcast (Sync)
func (alloc *Allocator) OnGossipBroadcast(msg []byte) (router.GossipData, error) {
alloc.debugln("OnGossipBroadcast:", len(msg), "bytes")
resultChan := make(chan error)
alloc.actionChan <- func() {
resultChan <- alloc.update(msg)
}
return alloc.Gossip(), <-resultChan
}
type gossipState struct {
// We send a timstamp along with the information to be
// gossipped in order to do detect skewed clocks
Now int64
Nicknames map[router.PeerName]string
Paxos paxos.GossipState
Ring *ring.Ring
}
func (alloc *Allocator) encode() []byte {
data := gossipState{
Now: alloc.now().Unix(),
Nicknames: alloc.nicknames,
}
// We're only interested in Paxos until we have a Ring.
if alloc.ring.Empty() {
data.Paxos = alloc.paxos.GossipState()
} else {
data.Ring = alloc.ring
}
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
if err := enc.Encode(data); err != nil {
panic(err)
}
return buf.Bytes()
}
// Encode (Sync)
func (alloc *Allocator) Encode() []byte {
resultChan := make(chan []byte)
alloc.actionChan <- func() {
resultChan <- alloc.encode()
}
return <-resultChan
}
// OnGossip (Sync)
func (alloc *Allocator) OnGossip(msg []byte) (router.GossipData, error) {
alloc.debugln("Allocator.OnGossip:", len(msg), "bytes")
resultChan := make(chan error)
alloc.actionChan <- func() {
resultChan <- alloc.update(msg)
}
return nil, <-resultChan // for now, we never propagate updates. TBD
}
// GossipData implementation is trivial - we always gossip the latest
// data we have at time of sending
type ipamGossipData struct {
alloc *Allocator
}
func (d *ipamGossipData) Merge(other router.GossipData) {
// no-op
}
func (d *ipamGossipData) Encode() []byte {
return d.alloc.Encode()
}
// 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 {
return &ipamGossipData{alloc}
}
// SetInterfaces gives the allocator two interfaces for talking to the outside world
func (alloc *Allocator) SetInterfaces(gossip router.Gossip) {
alloc.gossip = gossip
}
// ACTOR server
func (alloc *Allocator) actorLoop(actionChan <-chan func()) {
for {
var tickChan <-chan time.Time
if alloc.paxosTicker != nil {
tickChan = alloc.paxosTicker.C
}
select {
case action := <-actionChan:
if action == nil {
return
}
action()
case <-tickChan:
alloc.propose()
}
alloc.assertInvariants()
alloc.reportFreeSpace()
}
}
// Helper functions
func (alloc *Allocator) string() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "Allocator subnet %s/%d", alloc.subnetStart.String(), alloc.prefixLen)
if alloc.ring.Empty() {
if alloc.paxosTicker != nil {
fmt.Fprintf(&buf, " awaiting consensus: %s", alloc.paxos.String())
}
} else {
localFreeSpace := alloc.space.NumFreeAddresses()
remoteFreeSpace := alloc.ring.TotalRemoteFree()
percentFree := 100 * float64(localFreeSpace+remoteFreeSpace) / float64(alloc.subnetSize)
fmt.Fprintf(&buf, " Free IPs: ~%.1f%%, %d local, ~%d remote\n",
percentFree, localFreeSpace, remoteFreeSpace)
fmt.Fprint(&buf, "Owned Ranges:")
alloc.ring.FprintWithNicknames(&buf, alloc.nicknames)
}
if len(alloc.pendingAllocates)+len(alloc.pendingClaims) > 0 {
fmt.Fprintf(&buf, "\nPending requests:")
for _, op := range alloc.pendingAllocates {
fmt.Fprintf(&buf, "\n %s", op.String())
}
for _, op := range alloc.pendingClaims {
fmt.Fprintf(&buf, "\n %s", op.String())
}
}
return buf.String()
}
func (alloc *Allocator) consensus() []router.PeerName {
_, val := alloc.paxos.Consensus()
return val.Value
}
// Ensure we are making progress towards an established ring
func (alloc *Allocator) establishRing() {
if !alloc.ring.Empty() || alloc.paxosTicker != nil {
return
}
alloc.propose()
if cons := alloc.consensus(); cons != nil {
// If the quorum was 1, then proposing immediately
// leads to consensus
alloc.createRing(cons)
} else {
// re-try until we get consensus
alloc.paxosTicker = time.NewTicker(paxosInterval)
}
}
func (alloc *Allocator) createRing(peers []router.PeerName) {
alloc.debugln("Paxos consensus:", peers)
alloc.ring.ClaimForPeers(normalizeConsensus(peers))
alloc.gossip.GossipBroadcast(alloc.Gossip())
alloc.ringUpdated()
}
func (alloc *Allocator) ringUpdated() {
// When we have a ring, we don't need paxos any more
if alloc.paxos != nil {
alloc.paxos = nil
if alloc.paxosTicker != nil {
alloc.paxosTicker.Stop()
alloc.paxosTicker = nil
}
}
alloc.space.UpdateRanges(alloc.ring.OwnedRanges())
alloc.tryPendingOps()
}
// For compatibility with sort.Interface
type peerNames []router.PeerName
func (a peerNames) Len() int { return len(a) }
func (a peerNames) Less(i, j int) bool { return a[i] < a[j] }
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 {
if len(consensus) == 0 {
return nil
}
peers := make(peerNames, len(consensus))
copy(peers, consensus)
sort.Sort(peers)
dst := 0
for src := 1; src < len(peers); src++ {
if peers[dst] != peers[src] {
dst++
peers[dst] = peers[src]
}
}
return peers[:dst+1]
}
func (alloc *Allocator) propose() {
alloc.debugf("Paxos proposing")
alloc.paxos.Propose()
alloc.gossip.GossipBroadcast(alloc.Gossip())
}
func (alloc *Allocator) sendRequest(dest router.PeerName, kind byte) {
msg := router.Concat([]byte{kind}, alloc.encode())
alloc.gossip.GossipUnicast(dest, msg)
}
func (alloc *Allocator) update(msg []byte) error {
reader := bytes.NewReader(msg)
decoder := gob.NewDecoder(reader)
var data gossipState
var err error
if err := decoder.Decode(&data); err != nil {
return err
}
deltat := time.Unix(data.Now, 0).Sub(alloc.now())
if deltat > time.Hour || -deltat > time.Hour {
return fmt.Errorf("clock skew of %v detected, ignoring update", deltat)
}
// Merge nicknames
for peer, nickname := range data.Nicknames {
alloc.nicknames[peer] = nickname
}
// only one of Ring and Paxos should be present. And we
// shouldn't get updates for a empty Ring. But tolerate
// them just in case.
if data.Ring != nil {
err = alloc.ring.Merge(*data.Ring)
if !alloc.ring.Empty() {
alloc.pruneNicknames()
alloc.ringUpdated()
}
return err
}
if data.Paxos != nil && alloc.ring.Empty() {
if alloc.paxos.Update(data.Paxos) {
if alloc.paxos.Think() {
// If something important changed, broadcast
alloc.gossip.GossipBroadcast(alloc.Gossip())
}
if cons := alloc.consensus(); cons != nil {
alloc.createRing(cons)
}
}
}
return nil
}
func (alloc *Allocator) donateSpace(to router.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
// have given him, or tell him where he might find some
// more.
defer alloc.sendRequest(to, msgRingUpdate)
alloc.debugln("Peer", to, "asked me for space")
start, size, ok := alloc.space.Donate()
if !ok {
free := alloc.space.NumFreeAddresses()
common.Assert(free == 0)
alloc.debugln("No space to give to peer", to)
return
}
end := address.Add(start, size)
alloc.debugln("Giving range", start, end, size, "to", to)
alloc.ring.GrantRangeToHost(start, end, to)
}
func (alloc *Allocator) assertInvariants() {
// We need to ensure all ranges the ring thinks we own have
// a corresponding space in the space set, and vice versa
checkSpace := space.New()
checkSpace.AddRanges(alloc.ring.OwnedRanges())
ranges := checkSpace.OwnedRanges()
spaces := alloc.space.OwnedRanges()
common.Assert(len(ranges) == len(spaces))
for i := 0; i < len(ranges); i++ {
r := ranges[i]
s := spaces[i]
common.Assert(s.Start == r.Start && s.End == r.End)
}
}
func (alloc *Allocator) reportFreeSpace() {
ranges := alloc.ring.OwnedRanges()
if len(ranges) == 0 {
return
}
freespace := make(map[address.Address]address.Offset)
for _, r := range ranges {
freespace[r.Start] = alloc.space.NumFreeAddressesInRange(r.Start, r.End)
}
alloc.ring.ReportFree(freespace)
}
// Owned addresses
func (alloc *Allocator) addOwned(ident string, addr address.Address) {
alloc.owned[ident] = addr
}
func (alloc *Allocator) findOwner(addr address.Address) string {
for ident, candidate := range alloc.owned {
if candidate == addr {
return ident
}
}
return ""
}
// Logging
func (alloc *Allocator) infof(fmt string, args ...interface{}) {
common.Info.Printf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
}
func (alloc *Allocator) debugln(args ...interface{}) {
common.Debug.Println(append([]interface{}{fmt.Sprintf("[allocator %s]:", alloc.ourName)}, args...)...)
}
func (alloc *Allocator) debugf(fmt string, args ...interface{}) {
common.Debug.Printf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
}
+453
View File
@@ -0,0 +1,453 @@
package ipam
import (
"fmt"
"math/rand"
"sync"
"testing"
"time"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/router"
wt "github.com/weaveworks/weave/testing"
)
const (
testStart1 = "10.0.1.0"
testStart2 = "10.0.2.0"
testStart3 = "10.0.3.0"
)
func TestAllocFree(t *testing.T) {
const (
container1 = "abcdef"
container2 = "baddf00d"
container3 = "b01df00d"
universe = "10.0.3.0/28"
testAddr1 = "10.0.3.1" // first address allocated should be .1 because .0 is network addr
spaceSize = 14 // 16 IP addresses in /28, minus .0 and .15
)
alloc := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
defer alloc.Stop()
alloc.claimRingForTesting()
addr1, _ := alloc.Allocate(container1, nil)
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
// Ask for another address for a different container and check it's different
addr2, _ := alloc.Allocate(container2, nil)
if addr2.String() == testAddr1 {
t.Fatalf("Expected different address but got %s", addr2.String())
}
// Ask for the first container again and we should get the same address again
addr1a, _ := alloc.Allocate(container1, nil)
wt.AssertEqualString(t, addr1a.String(), testAddr1, "address")
// Now free the first one, and we should get it back when we ask
wt.AssertSuccess(t, alloc.Free(container1))
addr3, _ := alloc.Allocate(container3, nil)
wt.AssertEqualString(t, addr3.String(), testAddr1, "address")
alloc.ContainerDied(container2)
alloc.ContainerDied(container3)
alloc.String() // force sync-up after async call
wt.AssertEquals(t, alloc.space.NumFreeAddresses(), address.Offset(spaceSize))
}
func TestBootstrap(t *testing.T) {
common.InitDefaultLogging(false)
const (
donateSize = 5
donateStart = "10.0.1.7"
ourNameString = "01:00:00:01:00:00"
peerNameString = "02:00:00:02:00:00"
)
alloc1 := makeAllocatorWithMockGossip(t, ourNameString, testStart1+"/22", 2)
defer alloc1.Stop()
// Simulate another peer on the gossip network
alloc2 := makeAllocatorWithMockGossip(t, peerNameString, testStart1+"/22", 2)
defer alloc2.Stop()
alloc1.OnGossipBroadcast(alloc2.encode())
alloc1.tryPendingOps()
ExpectBroadcastMessage(alloc1, nil) // alloc1 will try to form consensus
done := make(chan bool)
go func() {
alloc1.Allocate("somecontainer", nil)
done <- true
}()
time.Sleep(100 * time.Millisecond)
AssertNothingSent(t, done)
CheckAllExpectedMessagesSent(alloc1, alloc2)
alloc1.tryPendingOps()
AssertNothingSent(t, done)
CheckAllExpectedMessagesSent(alloc1, alloc2)
// alloc2 receives paxos update and broadcasts its reply
ExpectBroadcastMessage(alloc2, nil)
alloc2.OnGossipBroadcast(alloc1.encode())
ExpectBroadcastMessage(alloc1, nil)
alloc1.OnGossipBroadcast(alloc2.encode())
// both nodes will get consensus now so initialize the ring
ExpectBroadcastMessage(alloc2, nil)
ExpectBroadcastMessage(alloc2, nil)
alloc2.OnGossipBroadcast(alloc1.encode())
CheckAllExpectedMessagesSent(alloc1, alloc2)
alloc1.OnGossipBroadcast(alloc2.encode())
// now alloc1 should have space
AssertSent(t, done)
CheckAllExpectedMessagesSent(alloc1, alloc2)
}
func TestAllocatorClaim(t *testing.T) {
const (
container1 = "abcdef"
container2 = "baddf00d"
container3 = "b01df00d"
universe = "10.0.3.0/30"
testAddr1 = "10.0.3.1" // first address allocated should be .1 because .0 is network addr
)
alloc := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
defer alloc.Stop()
alloc.claimRingForTesting()
addr1, _ := alloc.Allocate(container1, nil)
alloc.Allocate(container2, nil)
// Now free the first one, and try to claim it
wt.AssertSuccess(t, alloc.Free(container1))
t.Log(alloc)
err := alloc.Claim(container3, addr1, nil)
wt.AssertNoErr(t, err)
addr3, _ := alloc.Allocate(container3, nil)
wt.AssertEqualString(t, addr3.String(), testAddr1, "address")
}
func (alloc *Allocator) pause() func() {
paused := make(chan struct{})
alloc.actionChan <- func() {
paused <- struct{}{}
<-paused
}
<-paused
return func() {
close(paused)
}
}
func TestCancel(t *testing.T) {
common.InitDefaultLogging(false)
const (
CIDR = "10.0.1.7/26"
)
router := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
alloc1 := makeAllocator("01:00:00:02:00:00", CIDR, 2)
alloc1.SetInterfaces(router.connect(alloc1.ourName, alloc1))
alloc2 := makeAllocator("02:00:00:02:00:00", CIDR, 2)
alloc2.SetInterfaces(router.connect(alloc2.ourName, alloc2))
alloc1.claimRingForTesting(alloc1, alloc2)
alloc2.claimRingForTesting(alloc1, alloc2)
alloc1.Start()
alloc2.Start()
// tell peers about each other
alloc1.OnGossipBroadcast(alloc2.encode())
// Get some IPs, so each allocator has some space
res1, _ := alloc1.Allocate("foo", nil)
common.Debug.Printf("res1 = %s", res1.String())
res2, _ := alloc2.Allocate("bar", nil)
common.Debug.Printf("res2 = %s", res2.String())
if res1 == res2 {
wt.Fatalf(t, "Error: got same ips!")
}
// Now we're going to pause alloc2 and ask alloc1
// for an allocation
unpause := alloc2.pause()
// Use up all the IPs that alloc1 owns, so the allocation after this will prompt a request to alloc2
for i := 0; alloc1.space.NumFreeAddresses() > 0; i++ {
alloc1.Allocate(fmt.Sprintf("tmp%d", i), nil)
}
cancelChan := make(chan bool, 1)
doneChan := make(chan bool)
go func() {
_, ok := alloc1.Allocate("baz", cancelChan)
doneChan <- ok == nil
}()
AssertNothingSent(t, doneChan)
time.Sleep(100 * time.Millisecond)
AssertNothingSent(t, doneChan)
cancelChan <- true
unpause()
if <-doneChan {
wt.Fatalf(t, "Error: got result from Allocate")
}
}
func TestGossipShutdown(t *testing.T) {
const (
container1 = "abcdef"
container2 = "baddf00d"
universe = "10.0.3.0/30"
testAddr1 = "10.0.3.1" // first address allocated should be .1 because .0 is network addr
)
alloc := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
defer alloc.Stop()
alloc.claimRingForTesting()
addr1, _ := alloc.Allocate(container1, nil)
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
alloc.Shutdown()
_, err := alloc.Allocate(container2, nil) // trying to allocate after shutdown should fail
wt.AssertFalse(t, err == nil, "no address")
CheckAllExpectedMessagesSent(alloc)
}
func TestTransfer(t *testing.T) {
const (
cidr = "10.0.1.7/22"
)
allocs, router := makeNetworkOfAllocators(3, cidr)
alloc1 := allocs[0]
alloc2 := allocs[1]
alloc3 := allocs[2] // This will be 'master' and get the first range
_, err := alloc2.Allocate("foo", nil)
wt.AssertTrue(t, err == nil, "Failed to get address")
_, err = alloc3.Allocate("bar", nil)
wt.AssertTrue(t, err == nil, "Failed to get address")
router.GossipBroadcast(alloc2.Gossip())
router.GossipBroadcast(alloc3.Gossip())
router.removePeer(alloc2.ourName)
router.removePeer(alloc3.ourName)
alloc2.Stop()
alloc3.Stop()
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc2.ourName.String()))
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc3.ourName.String()))
wt.AssertEquals(t, alloc1.space.NumFreeAddresses(), address.Offset(1022))
_, err = alloc1.Allocate("foo", nil)
wt.AssertTrue(t, err == nil, "Failed to get address")
alloc1.Stop()
}
func TestFakeRouterSimple(t *testing.T) {
common.InitDefaultLogging(false)
const (
cidr = "10.0.1.7/22"
)
allocs, _ := makeNetworkOfAllocators(2, cidr)
defer stopNetworkOfAllocators(allocs)
alloc1 := allocs[0]
//alloc2 := allocs[1]
addr, _ := alloc1.Allocate("foo", nil)
println("Got addr", addr)
}
func TestAllocatorFuzz(t *testing.T) {
common.InitDefaultLogging(false)
const (
firstpass = 1000
secondpass = 5000
nodes = 10
maxAddresses = 1000
concurrency = 10
cidr = "10.0.1.7/22"
)
allocs, _ := makeNetworkOfAllocators(nodes, cidr)
defer stopNetworkOfAllocators(allocs)
// Test state
// For each IP issued we store the allocator
// that issued it and the name of the container
// it was issued to.
type result struct {
name string
alloc int32
}
stateLock := sync.Mutex{}
state := make(map[string]result)
// Keep a list of addresses issued, so we
// Can pick random ones
var addrs []string
numPending := 0
rand.Seed(0)
// Remove item from list by swapping it with last
// and reducing slice length by 1
rm := func(xs []string, i int32) []string {
ls := len(xs) - 1
xs[i] = xs[ls]
return xs[:ls]
}
// Do a Allocate and check the address
// is unique. Needs a unique container
// name.
allocate := func(name string) {
stateLock.Lock()
if len(addrs)+numPending >= maxAddresses {
stateLock.Unlock()
return
}
numPending++
stateLock.Unlock()
allocIndex := rand.Int31n(nodes)
alloc := allocs[allocIndex]
//common.Info.Printf("Allocate: asking allocator %d", allocIndex)
addr, err := alloc.Allocate(name, nil)
if err != nil {
panic(fmt.Sprintf("Could not allocate addr"))
}
//common.Info.Printf("Allocate: got address %s for name %s", addr, name)
addrStr := addr.String()
stateLock.Lock()
defer stateLock.Unlock()
if res, existing := state[addrStr]; existing {
panic(fmt.Sprintf("Dup found for address %s - %s and %s", addrStr,
name, res.name))
}
state[addrStr] = result{name, allocIndex}
addrs = append(addrs, addrStr)
numPending--
}
// Free a random address.
free := func() {
stateLock.Lock()
if len(addrs) == 0 {
stateLock.Unlock()
return
}
// Delete an existing allocation
// Pick random addr
addrIndex := rand.Int31n(int32(len(addrs)))
addr := addrs[addrIndex]
res := state[addr]
addrs = rm(addrs, addrIndex)
delete(state, addr)
stateLock.Unlock()
alloc := allocs[res.alloc]
//common.Info.Printf("Freeing %s on allocator %d", addr, res.alloc)
wt.AssertSuccess(t, alloc.Free(res.name))
}
// Do a Allocate on an existing container & allocator
// and check we get the right answer.
allocateAgain := func() {
stateLock.Lock()
addrIndex := rand.Int31n(int32(len(addrs)))
addr := addrs[addrIndex]
res := state[addr]
stateLock.Unlock()
alloc := allocs[res.alloc]
//common.Info.Printf("Asking for %s on allocator %d again", addr, res.alloc)
newAddr, _ := alloc.Allocate(res.name, nil)
oldAddr, _ := address.ParseIP(addr)
if newAddr != oldAddr {
panic(fmt.Sprintf("Got different address for repeat request"))
}
}
// Run function _f_ _iterations_ times, in _concurrency_
// number of goroutines
doConcurrentIterations := func(iterations int, f func(int)) {
iterationsPerThread := iterations / concurrency
wg := sync.WaitGroup{}
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
for k := 0; k < iterationsPerThread; k++ {
f((j * iterationsPerThread) + k)
}
}(i)
}
wg.Wait()
}
// First pass, just allocate a bunch of ips
doConcurrentIterations(firstpass, func(iteration int) {
name := fmt.Sprintf("first%d", iteration)
allocate(name)
})
// Second pass, random ask for more allocations,
// or remove existing ones, or ask for allocation
// again.
doConcurrentIterations(secondpass, func(iteration int) {
r := rand.Float32()
switch {
case 0.0 <= r && r < 0.4:
// Ask for a new allocation
name := fmt.Sprintf("second%d", iteration)
allocate(name)
case (0.4 <= r && r < 0.8):
// free a random addr
free()
case 0.8 <= r && r < 1.0:
// ask for an existing name again, check we get same ip
allocateAgain()
}
})
}
func TestGossipSkew(t *testing.T) {
alloc1 := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", "10.0.1.0/22", 2)
defer alloc1.Stop()
alloc2 := makeAllocatorWithMockGossip(t, "02:00:00:02:00:00", "10.0.1.0/22", 2)
alloc2.now = func() time.Time { return time.Now().Add(time.Hour * 2) }
defer alloc2.Stop()
if _, err := alloc1.OnGossipBroadcast(alloc2.encode()); err == nil {
t.Fail()
}
}
+79
View File
@@ -0,0 +1,79 @@
package ipam
import (
"fmt"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/router"
)
type claim struct {
resultChan chan<- error
hasBeenCancelled func() bool
ident string
addr address.Address
}
// Try returns true for success (or failure), false if we need to try again later
func (c *claim) Try(alloc *Allocator) bool {
if (c.hasBeenCancelled)() {
c.Cancel()
return true
}
if !alloc.ring.Contains(c.addr) {
// Address not within our universe; assume user knows what they are doing
alloc.infof("Ignored address %s claimed by %s - not in our universe\n", c.addr, c.ident)
c.resultChan <- nil
return true
}
// If our ring doesn't know, it must be empty. We will have initiated the
// bootstrap of the ring, so wait until we find some owner for this
// range (might be us).
owner := alloc.ring.Owner(c.addr)
if owner == router.UnknownPeerName {
alloc.infof("Ring is empty; will try later.\n", c.addr, owner)
return false
}
if owner != alloc.ourName {
name, found := alloc.nicknames[owner]
if found {
name = " (" + name + ")"
}
c.resultChan <- fmt.Errorf("address %s is owned by other peer %s%s", c.addr.String(), owner, name)
return true
}
// We are the owner, check we haven't given it to another container
existingIdent := alloc.findOwner(c.addr)
if existingIdent == c.ident {
// same identifier is claiming same address; that's OK
c.resultChan <- nil
return true
}
if existingIdent == "" {
err := alloc.space.Claim(c.addr)
if err != nil {
c.resultChan <- err
return true
}
alloc.addOwned(c.ident, c.addr)
c.resultChan <- nil
return true
}
// Addr already owned by container on this machine
c.resultChan <- fmt.Errorf("address %s is already owned by %s", c.addr.String(), existingIdent)
return true
}
func (c *claim) Cancel() {
c.resultChan <- fmt.Errorf("Operation cancelled.")
}
func (c *claim) String() string {
return fmt.Sprintf("Claim %s -> %s", c.ident, c.addr.String())
}
func (c *claim) ForContainer(ident string) bool {
return c.ident == ident
}
+36
View File
@@ -0,0 +1,36 @@
package ipam
import (
"errors"
"github.com/weaveworks/weave/router"
)
// Exists solely for the purpose of detecting a network in which some
// peers are configured with IPAM on and some with IPAM off.
type DummyAllocator struct {
}
var (
ErrMixedConfig = errors.New("Mixed IPAM/non-IPAM configuration not supported")
)
func (alloc *DummyAllocator) OnGossipUnicast(sender router.PeerName, msg []byte) error {
return ErrMixedConfig
}
func (alloc *DummyAllocator) OnGossipBroadcast(msg []byte) (router.GossipData, error) {
return nil, ErrMixedConfig
}
func (alloc *DummyAllocator) OnGossip(msg []byte) (router.GossipData, error) {
return nil, ErrMixedConfig
}
func (alloc *DummyAllocator) Gossip() router.GossipData {
return nil
}
func (alloc *DummyAllocator) Encode() []byte {
return nil
}
+72
View File
@@ -0,0 +1,72 @@
package ipam
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
)
func badRequest(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
common.Warning.Println("[allocator]:", err.Error())
}
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
func (alloc *Allocator) HandleHTTP(router *mux.Router) {
router.Methods("PUT").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
closedChan := w.(http.CloseNotifier).CloseNotify()
vars := mux.Vars(r)
ident := vars["id"]
ipStr := vars["ip"]
if ip, err := address.ParseIP(ipStr); err != nil {
badRequest(w, err)
return
} else if err := alloc.Claim(ident, ip, closedChan); err != nil {
badRequest(w, fmt.Errorf("Unable to claim: %s", err))
return
}
w.WriteHeader(204)
})
router.Methods("POST").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
closedChan := w.(http.CloseNotifier).CloseNotify()
ident := mux.Vars(r)["id"]
newAddr, err := alloc.Allocate(ident, closedChan)
if err != nil {
badRequest(w, err)
return
}
fmt.Fprintf(w, "%s/%d", newAddr.String(), alloc.prefixLen)
})
router.Methods("DELETE").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ident := mux.Vars(r)["id"]
if err := alloc.Free(ident); err != nil {
badRequest(w, err)
return
}
w.WriteHeader(204)
})
router.Methods("DELETE").Path("/peer").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
alloc.Shutdown()
w.WriteHeader(204)
})
router.Methods("DELETE").Path("/peer/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ident := mux.Vars(r)["id"]
if err := alloc.AdminTakeoverRanges(ident); err != nil {
badRequest(w, err)
return
}
w.WriteHeader(204)
})
}
+164
View File
@@ -0,0 +1,164 @@
package ipam
import (
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"strings"
"testing"
"time"
"github.com/gorilla/mux"
"github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
)
func HTTPPost(t *testing.T, url string) string {
resp, err := http.Post(url, "", nil)
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return string(body)
}
func doHTTP(method string, url string) (resp *http.Response, err error) {
req, _ := http.NewRequest(method, url, nil)
return http.DefaultClient.Do(req)
}
func listenHTTP(port int, alloc *Allocator) {
router := mux.NewRouter()
router.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, fmt.Sprintln(alloc))
})
alloc.HandleHTTP(router)
srv := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: router,
}
if err := srv.ListenAndServe(); err != nil {
common.Error.Fatal("Unable to create http listener: ", err)
}
}
func allocURL(port int, containerID string) string {
return fmt.Sprintf("http://localhost:%d/ip/%s", port, containerID)
}
func TestHttp(t *testing.T) {
var (
containerID = "deadbeef"
container2 = "baddf00d"
container3 = "b01df00d"
testAddr1 = "10.0.3.9"
netSuffix = "/29"
testCIDR1 = "10.0.3.8" + netSuffix
)
alloc := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
port := rand.Intn(10000) + 32768
fmt.Println("Http test on port", port)
go listenHTTP(port, alloc)
time.Sleep(100 * time.Millisecond) // Allow for http server to get going
alloc.claimRingForTesting()
// Ask the http server for a new address
cidr1 := HTTPPost(t, allocURL(port, containerID))
wt.AssertEqualString(t, cidr1, testAddr1+netSuffix, "address")
// Ask the http server for another address and check it's different
cidr2 := HTTPPost(t, allocURL(port, container2))
wt.AssertFalse(t, cidr2 == testAddr1+netSuffix, "address")
// Ask for the first container again and we should get the same address again
cidr1a := HTTPPost(t, allocURL(port, containerID))
wt.AssertEqualString(t, cidr1a, testAddr1+netSuffix, "address")
// Now free the first one, and we should get it back when we ask
doHTTP("DELETE", allocURL(port, containerID))
cidr3 := HTTPPost(t, allocURL(port, container3))
wt.AssertEqualString(t, cidr3, testAddr1+netSuffix, "address")
// Would like to shut down the http server at the end of this test
// but it's complicated.
// See https://groups.google.com/forum/#!topic/golang-nuts/vLHWa5sHnCE
}
func TestBadHttp(t *testing.T) {
var (
containerID = "deadbeef"
testCIDR1 = "10.0.0.0/8"
)
alloc := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
defer alloc.Stop()
port := rand.Intn(10000) + 32768
fmt.Println("BadHttp test on port", port)
go listenHTTP(port, alloc)
alloc.claimRingForTesting()
cidr1 := HTTPPost(t, allocURL(port, containerID))
parts := strings.Split(cidr1, "/")
testAddr1 := parts[0]
// Verb that's not handled
resp, err := doHTTP("HEAD", fmt.Sprintf("http://localhost:%d/ip/%s/%s", port, containerID, testAddr1))
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
// Mis-spelled URL
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/xip/%s/", port, containerID))
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
// Malformed URL
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/ip/%s/foo/bar/baz", port, containerID))
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
}
func TestHTTPCancel(t *testing.T) {
wt.RunWithTimeout(t, 2*time.Second, func() {
impTestHTTPCancel(t)
})
}
func impTestHTTPCancel(t *testing.T) {
common.InitDefaultLogging(true)
var (
containerID = "deadbeef"
testCIDR1 = "10.0.3.5/29"
)
alloc := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
defer alloc.Stop()
alloc.claimRingForTesting()
port := rand.Intn(10000) + 32768
fmt.Println("Http test on port", port)
go listenHTTP(port, alloc)
time.Sleep(100 * time.Millisecond) // Allow for http server to get going
// Stop the alloc so nothing actually works
unpause := alloc.pause()
// Ask the http server for a new address
done := make(chan *http.Response)
req, _ := http.NewRequest("POST", allocURL(port, containerID), nil)
go func() {
res, _ := http.DefaultClient.Do(req)
done <- res
}()
time.Sleep(100 * time.Millisecond)
fmt.Println("Cancelling allocate")
http.DefaultTransport.(*http.Transport).CancelRequest(req)
unpause()
res := <-done
if res != nil {
wt.Fatalf(t, "Error: Allocate returned non-nil")
}
}
+261
View File
@@ -0,0 +1,261 @@
package paxos
import (
"fmt"
"github.com/weaveworks/weave/router"
)
// The node identifier. The use of the UID here is important: Paxos
// acceptors must not forget their promises, so it's important that a
// 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
}
// note all fields exported in structs so we can Gob them
type ProposalID struct {
// round numbers begin at 1. round 0 indicates an
// uninitialized ProposalID, and precedes all other ProposalIDs
Round uint
Proposer NodeID
}
func (a ProposalID) precedes(b ProposalID) bool {
switch {
case a.Round != b.Round:
return a.Round < b.Round
case a.Proposer.Name != b.Proposer.Name:
return a.Proposer.Name < b.Proposer.Name
case a.Proposer.UID != b.Proposer.UID:
return a.Proposer.UID < b.Proposer.UID
default:
return false
}
}
func (a ProposalID) valid() bool {
return a.Round > 0
}
// For seeding IPAM, the value we want consensus on is a set of peer names
type Value []router.PeerName
// An AcceptedValue is a Value plus the proposal which originated that
// Value. The origin is not essential, but makes comparing
// AcceptedValues easy even if comparing Values is not.
type AcceptedValue struct {
Value Value
Origin ProposalID
}
type NodeClaims struct {
// The node promises not to accept a proposal with id less
// than this.
Promise ProposalID
// The accepted proposal, if valid
Accepted ProposalID
AcceptedVal AcceptedValue
}
func (a NodeClaims) equals(b NodeClaims) bool {
return a.Promise == b.Promise && a.Accepted == b.Accepted &&
a.AcceptedVal.Origin == b.AcceptedVal.Origin
}
type GossipState map[NodeID]NodeClaims
type Node struct {
id NodeID
quorum uint
knows GossipState
}
func NewNode(name router.PeerName, uid router.PeerUID, quorum uint) *Node {
return &Node{
id: NodeID{name, uid},
quorum: quorum,
knows: map[NodeID]NodeClaims{},
}
}
func (node *Node) GossipState() GossipState {
return node.knows
}
// Update this node's information about what other nodes know.
// Returns true if we learned something new.
func (node *Node) Update(from GossipState) bool {
changed := false
for i, fromClaims := range from {
claims, ok := node.knows[i]
if ok {
if claims.Promise.precedes(fromClaims.Promise) {
claims.Promise = fromClaims.Promise
changed = true
}
if claims.Accepted.precedes(fromClaims.Accepted) {
claims.Accepted = fromClaims.Accepted
claims.AcceptedVal = fromClaims.AcceptedVal
changed = true
}
} else {
claims = fromClaims
changed = true
}
node.knows[i] = claims
}
return changed
}
func max(a uint, b uint) uint {
if a > b {
return a
} else {
return b
}
}
// Initiate a new proposal, i.e. the Paxos "Prepare" step. This is
// simply a matter of gossipping a new proposal that supersedes all
// others.
func (node *Node) Propose() {
// Find the highest round number around
round := uint(0)
for _, claims := range node.knows {
round = max(round, claims.Promise.Round)
round = max(round, claims.Accepted.Round)
}
ourClaims := node.knows[node.id]
ourClaims.Promise = ProposalID{
Round: round + 1,
Proposer: node.id,
}
node.knows[node.id] = ourClaims
// With a quorum of 1, we can immediately accept our proposal
if node.quorum == 1 {
node.Think()
}
}
// The heart of the consensus algorithm. Return true if we have
// changed our claims.
func (node *Node) Think() bool {
ourClaims := node.knows[node.id]
// The "Promise" step of Paxos: Copy the highest known
// promise.
for _, claims := range node.knows {
if ourClaims.Promise.precedes(claims.Promise) {
ourClaims.Promise = claims.Promise
}
}
// The "Accept Request" step of Paxos: Acting as a proposer,
// do we have a proposal that has been promised by a quorum?
//
// In Paxos, the "proposer" and "acceptor" roles are distinct,
// so in principle a node acting as a proposer could continue
// trying to get its proposal acccepted even after the same
// node as an acceptor has superseded that proposal. But
// that's pointless in a gossip context: If our promise
// supersedes our own proposal, then anyone who hears about
// that promise will not accept that proposal. So our
// proposal is only in the running if it is also our promise.
if ourClaims.Promise.Proposer == node.id {
// Determine whether a quorum has promised, and the
// best previously-accepted value if there is one.
count := uint(0)
var accepted ProposalID
var acceptedVal AcceptedValue
for _, claims := range node.knows {
if claims.Promise == ourClaims.Promise {
count++
if accepted.precedes(claims.Accepted) {
accepted = claims.Accepted
acceptedVal = claims.AcceptedVal
}
}
}
if count >= node.quorum {
if !accepted.valid() {
acceptedVal.Value = node.pickValue()
acceptedVal.Origin = ourClaims.Promise
}
// We automatically accept our own proposal,
// and that's how we communicate the "accept
// request" to other nodes.
ourClaims.Accepted = ourClaims.Promise
ourClaims.AcceptedVal = acceptedVal
}
}
// The "Accepted" step of Paxos: If the proposal we promised
// on got accepted by some other node, we accept it too.
for _, claims := range node.knows {
if claims.Accepted == ourClaims.Promise {
ourClaims.Accepted = claims.Accepted
ourClaims.AcceptedVal = claims.AcceptedVal
break
}
}
if ourClaims.equals(node.knows[node.id]) {
return false
}
node.knows[node.id] = ourClaims
return true
}
// When we get to pick a value, we use the set of peer names we know
// 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))
i := 0
for id := range node.knows {
val[i] = id.Name
i++
}
return val
}
// Has a consensus been reached, based on the known claims of other nodes?
func (node *Node) Consensus() (bool, AcceptedValue) {
counts := map[ProposalID]uint{}
for _, claims := range node.knows {
if claims.Accepted.valid() {
origin := claims.AcceptedVal.Origin
count := counts[origin] + 1
counts[origin] = count
if count >= node.quorum {
return true, claims.AcceptedVal
}
}
}
return false, AcceptedValue{}
}
func (node *Node) String() string {
if ok, val := node.Consensus(); ok {
return fmt.Sprintf("Consensus reached with size %d", len(val.Value))
} else {
return fmt.Sprintf("Nodes known: %d, Quorum size: %d", len(node.knows), node.quorum)
}
}
+372
View File
@@ -0,0 +1,372 @@
package paxos
import (
"fmt"
"github.com/weaveworks/weave/router"
"math/rand"
"testing"
"time"
)
type TestNode struct {
*Node
// Topology
links []*Link
isolated bool
// The first consensus the Node observed
firstConsensus AcceptedValue
}
type Link struct {
from *TestNode
to *TestNode
converse *Link
// A link is considered ready if it is worthwhile gossipping
// along it (i.e. unless we already gossipped along it and the
// "from" node didn't change since then). If this is <0, then
// the link is not ready. Otherwise, this gives the index in
// the Model's readyLinks slice.
ready int
}
type Model struct {
t *testing.T
r *rand.Rand
quorum uint
nodes []TestNode
readyLinks []*Link
nextID uint
}
func (m *Model) addLink(a, b *TestNode) {
if a == b {
// Never link a node to itself
return
}
ab := Link{from: a, to: b, ready: len(m.readyLinks)}
ba := Link{from: b, to: a, converse: &ab, ready: len(m.readyLinks) + 1}
ab.converse = &ba
a.links = append(a.links, &ab)
b.links = append(b.links, &ba)
m.readyLinks = append(m.readyLinks, &ab, &ba)
}
func (m *Model) linkExists(a, b *TestNode) bool {
for _, l := range a.links {
if l.to == b {
return true
}
}
return false
}
type TestParams struct {
// Number of nodes
nodeCount uint
// Probability that two nodes are connected.
connectedProb float32
// Probability that some node will re-propose at each
// step. Setting this too high makes it likely that we'll fail
// to converge.
reproposeProb float32
// Probability that some node will be isolated at each step.
isolateProb float32
// Probability that some node willbe restarted at each step.
restartProb float32
}
// Make a network of nodes with random topology
func makeRandomModel(params *TestParams, r *rand.Rand, t *testing.T) *Model {
m := Model{
t: t,
r: r,
quorum: params.nodeCount/2 + 1,
nodes: make([]TestNode, params.nodeCount),
readyLinks: []*Link{},
nextID: params.nodeCount + 1,
}
for i := range m.nodes {
m.nodes[i].Node = NewNode(router.PeerName(i/2+1),
router.PeerUID(r.Int63()), m.quorum)
m.nodes[i].Propose()
}
for i := 1; i < len(m.nodes); i++ {
// was node i connected to the other nodes yet?
connected := false
for j := 0; j < i; j++ {
if r.Float32() < params.connectedProb {
connected = true
m.addLink(&m.nodes[i], &m.nodes[j])
}
}
if !connected {
// node i must be connected into the graph
// somewhere. So if we didn't connect it
// already, this is a last resort.
m.addLink(&m.nodes[i], &m.nodes[r.Intn(i)])
}
}
return &m
}
// Mark a link as unready
func (m *Model) unreadyLink(link *Link) {
i := link.ready
if i >= 0 {
m.readyLinks[i] = m.readyLinks[len(m.readyLinks)-1]
m.readyLinks[i].ready = i
m.readyLinks = m.readyLinks[:len(m.readyLinks)-1]
link.ready = -1
}
}
// Mark a link as ready
func (m *Model) readyLink(link *Link) {
if link.ready < 0 {
link.ready = len(m.readyLinks)
m.readyLinks = append(m.readyLinks, link)
}
}
// Mark all the outgoing links from a node as ready
func (m *Model) nodeChanged(node *TestNode) {
for _, l := range node.links {
m.readyLink(l)
}
}
// Isolate a node
func (m *Model) isolateNode(node *TestNode) {
node.isolated = true
for _, l := range node.links {
if l.ready >= 0 {
m.unreadyLink(l)
m.unreadyLink(l.converse)
}
}
// Isolating a node could partition the network. We don't
// want to test such a case (because it could prevent
// consensus). Checking for partitions would be more code
// than its worth, so just add some links to prevent the
// possibility of partitions.
for i := 1; i < len(node.links); i++ {
m.addLink(node.links[i-1].to, node.links[i].to)
}
}
// Restart a node
func (m *Model) restart(node *TestNode) {
node.Node = NewNode(router.PeerName(m.nextID),
router.PeerUID(m.r.Int63()), m.quorum)
m.nextID++
node.Propose()
// The node is now ignorant, so we need to mark the links into
// the node as ready.
for _, l := range node.links {
if !l.to.isolated {
m.readyLink(l.converse)
}
}
// If a consensus was just accepted due to this node accepting
// it, without other nodes hearing of it, and we then restart
// this node, then a different consensus can occur later on.
// If a tree falls with no one to hear it, does it make a
// sound?
node.firstConsensus = AcceptedValue{}
}
func (m *Model) pickNode() *TestNode {
for {
node := &m.nodes[m.r.Intn(len(m.nodes))]
if !node.isolated {
return node
}
}
}
func (m *Model) simulate(params *TestParams) bool {
nodesLeft := uint(len(m.nodes))
restarts := uint(0)
for step := 0; step < 1000000; step++ {
if len(m.readyLinks) == 0 {
// Everything has converged. This can be
// because consensus was reached, or because
// consensus became impossible, e.g. everyone
// promised on a particular proposal, but then
// the proposer restarted or was isolated. So
// we detect the latter cases and force a new
// proposal
for i := range m.nodes {
ok, _ := m.nodes[i].Consensus()
if ok {
return true
}
}
node := m.pickNode()
node.Propose()
m.nodeChanged(node)
}
// Pick a ready link at random
i := m.r.Intn(len(m.readyLinks))
link := m.readyLinks[i]
if link.ready != i {
m.t.Fatal("Link in readyLinks was not ready")
}
// gossip across link
node := link.to
if node.Update(link.from.GossipState()) {
node.Think()
if !node.firstConsensus.Origin.valid() {
ok, val := node.Consensus()
if ok {
node.firstConsensus = val
}
}
m.nodeChanged(node)
}
m.unreadyLink(link)
// Re-propose?
if m.r.Float32() < params.reproposeProb {
node := m.pickNode()
node.Propose()
m.nodeChanged(node)
}
// Isolate?
if nodesLeft > m.quorum && m.r.Float32() < params.isolateProb {
m.isolateNode(m.pickNode())
nodesLeft--
// We isolated a node, so get another node to
// re-propose. In reality the lack of
// consensus would be detected via a timeout
node := m.pickNode()
node.Propose()
m.nodeChanged(node)
}
// Restart?
if restarts < m.quorum && m.r.Float32() < params.restartProb {
restarts++
node := m.pickNode()
m.restart(node)
m.nodeChanged(node)
}
}
return false
}
func (m *Model) dump() {
for i := range m.nodes {
node := &m.nodes[i]
fmt.Println(node.id)
for n, claims := range node.knows {
fmt.Printf("\t%d %v\n", n, claims)
}
}
}
// Validate the final model state
func (m *Model) validate() {
var origin ProposalID
for i := range m.nodes {
ok, val := m.nodes[i].Consensus()
if !ok {
//m.dump()
m.t.Fatal("Node doesn't know about consensus")
}
firstConsensus := m.nodes[i].firstConsensus
if firstConsensus.Origin.valid() &&
firstConsensus.Origin != val.Origin {
//m.dump()
m.t.Fatal("Consensus mismatch")
}
if i == 0 {
origin = val.Origin
} else if val.Origin != origin {
m.t.Fatal("Node disagrees about consensus")
}
}
}
func TestSingleNode(t *testing.T) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Test the single node case
params := &TestParams{
nodeCount: 1,
connectedProb: 0,
reproposeProb: 0,
isolateProb: 0,
restartProb: 0,
}
m := makeRandomModel(params, r, t)
if !m.simulate(params) {
m.t.Fatal("Failed to converge")
}
m.validate()
}
func TestPaxos(t *testing.T) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
params := &TestParams{
nodeCount: 10,
connectedProb: 0.5,
reproposeProb: 0.01,
isolateProb: 0.01,
// Restarts cause failures due to the case where 1)
// the gossip ordering is such that there is an
// effective partition from the start, with a quorum
// on one side and one node less than a quorum on the
// other; 2) the quorum reaches a consensus; 3) a node
// is restarted, and at the same time jumps the
// partition, so the side that was now just less than
// a quorum becomes a quorum; 4) the new quorum
// reaches a contradictory consensus.
restartProb: 0,
}
for i := 0; i < 1000; i++ {
m := makeRandomModel(params, r, t)
if !m.simulate(params) {
m.t.Fatal("Failed to converge")
}
m.validate()
}
}
+102
View File
@@ -0,0 +1,102 @@
package ring
import (
"sort"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/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
Version uint32 // Version of this range
Free address.Offset // Number of free IPs in this range
}
func (e *entry) Equal(e2 *entry) bool {
return e.Token == e2.Token && e.Peer == e2.Peer &&
e.Version == e2.Version
}
func (e *entry) update(peername router.PeerName, free address.Offset) {
e.Peer = peername
e.Version++
e.Free = free
}
// For compatibility with sort.Interface
type entries []*entry
func (es entries) Len() int { return len(es) }
func (es entries) Less(i, j int) bool { return es[i].Token < es[j].Token }
func (es entries) Swap(i, j int) { panic("Should never be swapping entries!") }
func (es entries) entry(i int) *entry {
i = i % len(es)
if i < 0 {
i += len(es)
}
return es[i]
}
func (es *entries) insert(e entry) {
i := sort.Search(len(*es), func(j int) bool {
return (*es)[j].Token >= e.Token
})
if i < len(*es) && (*es)[i].Token == e.Token {
panic("Trying to insert an existing token!")
}
*es = append(*es, &entry{})
copy((*es)[i+1:], (*es)[i:])
(*es)[i] = &e
}
func (es entries) get(token address.Address) (*entry, bool) {
i := sort.Search(len(es), func(j int) bool {
return es[j].Token >= token
})
if i < len(es) && es[i].Token == token {
return es[i], true
}
return nil, false
}
func (es *entries) remove(i int) {
*es = (*es)[:i+copy((*es)[i:], (*es)[i+1:])]
}
// Is token between entries at i and j?
// NB i and j can overflow and will wrap
// NBB this function does not work very well if there is only one
// token on the ring; luckily an accurate answer is not needed
// by the call sites in this case.
func (es entries) between(token address.Address, i, j int) bool {
common.Assert(i < j)
first := es.entry(i)
second := es.entry(j)
switch {
case first.Token == second.Token:
// This implies there is only one token
// on the ring (i < j and i.token == j.token)
// In which case everything is between, expect
// this one token
return token != first.Token
case first.Token < second.Token:
return first.Token <= token && token < second.Token
case second.Token < first.Token:
return first.Token <= token || token < second.Token
}
panic("Should never get here - switch covers all possibilities.")
}
+31
View File
@@ -0,0 +1,31 @@
package ring
/* Exported variables for monitoring and management */
import (
"expvar"
"strconv"
)
var (
expRingSize = expvar.NewMap("ipam.ringSize")
expRingEntries = expvar.NewMap("ipam.ringEntries")
)
type _int int
func (i _int) String() string {
return strconv.Itoa(int(i))
}
type _uint32 uint32
func (i _uint32) String() string {
return strconv.FormatUint(uint64(i), 10)
}
func (r *Ring) updateExportedVariables() {
ringName := r.Start.String()
expRingSize.Set(ringName, _uint32(r.End-r.Start))
expRingEntries.Set(ringName, _int(len(r.Entries)))
}
+520
View File
@@ -0,0 +1,520 @@
/*
Package ring implements a simple ring CRDT.
*/
package ring
import (
"bytes"
"errors"
"fmt"
"io"
"math/rand"
"sort"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/router"
)
// Ring represents the ring itself
type Ring struct {
Start, End address.Address // [min, max) tokens in this ring. Due to wrapping, min == max (effectively)
Peer router.PeerName // name of peer owning this ring instance
Entries entries // list of entries sorted by token
}
func (r *Ring) assertInvariants() {
err := r.checkInvariants()
if err != nil {
panic(err.Error())
}
}
// Errors returned by Merge
var (
ErrNotSorted = errors.New("Ring not sorted")
ErrTokenRepeated = errors.New("Token appears twice in ring")
ErrTokenOutOfRange = errors.New("Token is out of range")
ErrDifferentSubnets = errors.New("IP Allocator with different subnet detected")
ErrNewerVersion = errors.New("Received new version for entry I own!")
ErrInvalidEntry = errors.New("Received invalid state update!")
ErrEntryInMyRange = errors.New("Received new entry in my range!")
ErrNoFreeSpace = errors.New("No free space found!")
ErrInvalidTimeout = errors.New("dt must be greater than 0")
ErrNotFound = errors.New("No entries for peer found")
)
func (r *Ring) checkInvariants() error {
if !sort.IsSorted(r.Entries) {
return ErrNotSorted
}
// Check no token appears twice
// We know it's sorted...
for i := 1; i < len(r.Entries); i++ {
if r.Entries[i-1].Token == r.Entries[i].Token {
return ErrTokenRepeated
}
}
if len(r.Entries) == 0 {
return nil
}
// Check tokens are in range
if r.Entries.entry(0).Token < r.Start {
return ErrTokenOutOfRange
}
if r.Entries.entry(-1).Token >= r.End {
return ErrTokenOutOfRange
}
// Check all the freespaces are in range
for i, entry := range r.Entries {
next := r.Entries.entry(i + 1)
distance := r.distance(entry.Token, next.Token)
if entry.Free > distance {
return fmt.Errorf("Entry %s-%s reporting too much free space: %d > %d", entry.Token, next.Token, entry.Free, distance)
}
}
return nil
}
// New creates an empty ring belonging to peer.
func New(start, end address.Address, peer router.PeerName) *Ring {
common.Assert(start < end)
ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0)}
ring.updateExportedVariables()
return ring
}
// TotalRemoteFree returns the approximate number of free IPs
// on other hosts.
func (r *Ring) TotalRemoteFree() address.Offset {
result := address.Offset(0)
for _, entry := range r.Entries {
if entry.Peer != r.Peer {
result += entry.Free
}
}
return result
}
// Returns the distance between two tokens on this ring, dealing
// with ranges which cross the origin
func (r *Ring) distance(start, end address.Address) address.Offset {
if end > start {
return address.Offset(end - start)
}
return address.Offset((r.End - start) + (end - r.Start))
}
// GrantRangeToHost modifies the ring such that range [start, end)
// is assigned to peer. This may insert up to two new tokens.
// Preconditions:
// - start < end
// - [start, end) must be owned by the calling peer
func (r *Ring) GrantRangeToHost(start, end address.Address, peer router.PeerName) {
//fmt.Printf("%s GrantRangeToHost [%v,%v) -> %s\n", r.Peer, start, end, peer)
r.assertInvariants()
defer r.assertInvariants()
defer r.updateExportedVariables()
// ----------------- Start of Checks -----------------
common.Assert(start < end)
common.Assert(r.Start <= start && start < r.End)
common.Assert(r.Start < end && end <= r.End)
common.Assert(len(r.Entries) > 0)
// Look for the left-most entry greater than start, then go one previous
// to get the right-most entry less than or equal to start
preceedingPos := sort.Search(len(r.Entries), func(j int) bool {
return r.Entries[j].Token > start
})
preceedingPos--
// Check all tokens up to end are owned by us
for pos := preceedingPos; pos < len(r.Entries) && r.Entries.entry(pos).Token < end; pos++ {
common.Assert(r.Entries.entry(pos).Peer == r.Peer)
}
// ----------------- End of Checks -----------------
// Free space at start is max(length of range, distance to next token)
startFree := r.distance(start, r.Entries.entry(preceedingPos+1).Token)
if length := r.distance(start, end); startFree > length {
startFree = length
}
// Is there already a token at start, update it
if previousEntry := r.Entries.entry(preceedingPos); previousEntry.Token == start {
previousEntry.update(peer, startFree)
} else {
// Otherwise, these isn't a token here, insert a new one.
r.Entries.insert(entry{Token: start, Peer: peer, Free: startFree})
preceedingPos++
// Reset free space on previous entry, which we own.
previousEntry.update(r.Peer, r.distance(previousEntry.Token, start))
}
// Give all intervening tokens to the other peer
pos := preceedingPos + 1
for ; pos < len(r.Entries) && r.Entries.entry(pos).Token < end; pos++ {
entry := r.Entries.entry(pos)
entry.update(peer, address.Min(entry.Free, r.distance(entry.Token, end)))
}
// There is never an entry with a token of r.End, as the end of
// the ring is exclusive.
if end == r.End {
end = r.Start
}
// If there is a token equal to the end of the range, we don't need to do anything further
if _, found := r.Entries.get(end); found {
return
}
// If not, we need to insert a token such that we claim this bit on the end.
endFree := r.distance(end, r.Entries.entry(pos).Token)
r.Entries.insert(entry{Token: end, Peer: r.Peer, Free: endFree})
}
// Merge the given ring into this ring and return any new ranges added
func (r *Ring) Merge(gossip Ring) error {
r.assertInvariants()
defer r.assertInvariants()
defer r.updateExportedVariables()
// Don't panic when checking the gossiped in ring.
// In this case just return any error found.
if err := gossip.checkInvariants(); err != nil {
return err
}
if r.Start != gossip.Start || r.End != gossip.End {
return ErrDifferentSubnets
}
// Now merge their ring with yours, in a temporary ring.
var result entries
addToResult := func(e entry) { result = append(result, &e) }
var mine, theirs *entry
var previousOwner *router.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) {
mine, theirs = r.Entries[i], gossip.Entries[j]
switch {
case mine.Token < theirs.Token:
addToResult(*mine)
previousOwner = &mine.Peer
i++
case mine.Token > theirs.Token:
// insert, checking that a range owned by us hasn't been split
if previousOwner != nil && *previousOwner == r.Peer && theirs.Peer != r.Peer {
return ErrEntryInMyRange
}
addToResult(*theirs)
previousOwner = nil
j++
case mine.Token == theirs.Token:
// merge
switch {
case mine.Version >= theirs.Version:
if mine.Version == theirs.Version && !mine.Equal(theirs) {
common.Debug.Printf("Error merging entries at %s - %v != %v\n", mine.Token, mine, theirs)
return ErrInvalidEntry
}
addToResult(*mine)
previousOwner = &mine.Peer
case mine.Version < theirs.Version:
if mine.Peer == r.Peer { // We shouldn't receive updates to our own tokens
return ErrNewerVersion
}
addToResult(*theirs)
previousOwner = nil
}
i++
j++
}
}
// At this point, either i is at the end of r or j is at the end
// of gossip, so copy over the remaining entries.
for ; i < len(r.Entries); i++ {
mine = r.Entries[i]
addToResult(*mine)
}
for ; j < len(gossip.Entries); j++ {
theirs = gossip.Entries[j]
if previousOwner != nil && *previousOwner == r.Peer && theirs.Peer != r.Peer {
return ErrEntryInMyRange
}
addToResult(*theirs)
previousOwner = nil
}
r.Entries = result
return nil
}
// Empty returns true if the ring has no entries
func (r *Ring) Empty() bool {
return len(r.Entries) == 0
}
// Given a slice of ranges which are all in the right order except
// possibly the last one spans zero, fix that up and return the slice
func (r *Ring) splitRangesOverZero(ranges []address.Range) []address.Range {
if len(ranges) == 0 {
return ranges
}
lastRange := ranges[len(ranges)-1]
// if end token == start (ie last) entry on ring, we want to actually use r.End
if lastRange.End == r.Start {
ranges[len(ranges)-1].End = r.End
} else if lastRange.End <= lastRange.Start {
// We wrapped; want to split around 0
// First shuffle everything up as we want results to be sorted
ranges = append(ranges, address.Range{})
copy(ranges[1:], ranges[:len(ranges)-1])
ranges[0] = address.Range{Start: r.Start, End: lastRange.End}
ranges[len(ranges)-1].End = r.End
}
return ranges
}
// OwnedRanges returns slice of Ranges, ordered by IP, indicating which
// ranges are owned by this peer. Will split ranges which
// span 0 in the ring.
func (r *Ring) OwnedRanges() (result []address.Range) {
r.assertInvariants()
for i, entry := range r.Entries {
if entry.Peer == r.Peer {
nextEntry := r.Entries.entry(i + 1)
result = append(result, address.Range{Start: entry.Token, End: nextEntry.Token})
}
}
return r.splitRangesOverZero(result)
}
// ClaimForPeers claims the entire ring for the array of peers passed
// in. Only works for empty rings.
func (r *Ring) ClaimForPeers(peers []router.PeerName) {
common.Assert(r.Empty())
defer r.assertInvariants()
defer r.updateExportedVariables()
totalSize := r.distance(r.Start, r.End)
share := totalSize/address.Offset(len(peers)) + 1
remainder := totalSize % address.Offset(len(peers))
pos := r.Start
for i, peer := range peers {
if address.Offset(i) == remainder {
share--
if share == 0 {
break
}
}
if e, found := r.Entries.get(pos); found {
e.update(peer, share)
} else {
r.Entries.insert(entry{Token: pos, Peer: peer, Free: share})
}
pos += address.Address(share)
}
common.Assert(pos == r.End)
}
func (r *Ring) ClaimItAll() {
r.ClaimForPeers([]router.PeerName{r.Peer})
}
func (r *Ring) FprintWithNicknames(w io.Writer, m map[router.PeerName]string) {
for _, entry := range r.Entries {
nickname, found := m[entry.Peer]
if found {
nickname = fmt.Sprintf(" (%s)", nickname)
}
fmt.Fprintf(w, "\n %s -> %s%s (v%d)", entry.Token,
entry.Peer, nickname, entry.Version)
}
}
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))
return buffer.String()
}
// ReportFree is used by the allocator to tell the ring
// how many free ips are in a given range, so that ChoosePeerToAskForSpace
// can make more intelligent decisions.
func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
r.assertInvariants()
defer r.assertInvariants()
defer r.updateExportedVariables()
common.Assert(!r.Empty())
entries := r.Entries
// As OwnedRanges splits around the origin, we need to
// detect that here and fix up freespace
if free, found := freespace[r.Start]; found && entries.entry(0).Token != r.Start {
lastToken := entries.entry(-1).Token
prevFree, found := freespace[lastToken]
common.Assert(found)
freespace[lastToken] = prevFree + free
delete(freespace, r.Start)
}
for start, free := range freespace {
// Look for entry
i := sort.Search(len(entries), func(j int) bool {
return entries[j].Token >= start
})
// Are you trying to report free on space I don't own?
common.Assert(i < len(entries) && entries[i].Token == start && entries[i].Peer == r.Peer)
// Check we're not reporting more space than the range
entry, next := entries.entry(i), entries.entry(i+1)
maxSize := r.distance(entry.Token, next.Token)
common.Assert(free <= maxSize)
if entries[i].Free == free {
return
}
entries[i].Free = free
entries[i].Version++
}
}
// ChoosePeerToAskForSpace chooses a weighted-random peer to ask
// for space.
func (r *Ring) ChoosePeerToAskForSpace() (result router.PeerName, err error) {
var (
sum address.Offset
totalSpacePerPeer = make(map[router.PeerName]address.Offset) // Compute total free space per peer
)
// iterate through tokens
for _, entry := range r.Entries {
// Ignore ranges with no free space
if entry.Free <= 0 {
continue
}
// Don't talk to yourself
if entry.Peer == r.Peer {
continue
}
totalSpacePerPeer[entry.Peer] += entry.Free
sum += entry.Free
}
if sum == 0 {
err = ErrNoFreeSpace
return
}
// Pick random peer, weighted by total free space
rn := rand.Int63n(int64(sum))
for peername, space := range totalSpacePerPeer {
rn -= int64(space)
if rn < 0 {
return peername, nil
}
}
panic("Should never reach this point")
}
func (r *Ring) PickPeerForTransfer() router.PeerName {
for _, entry := range r.Entries {
if entry.Peer != r.Peer {
return entry.Peer
}
}
return router.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) (error, []address.Range) {
r.assertInvariants()
defer r.assertInvariants()
defer r.updateExportedVariables()
var newRanges []address.Range
found := false
for i, entry := range r.Entries {
if entry.Peer == from {
found = true
entry.Peer = to
entry.Version++
newRanges = append(newRanges, address.Range{Start: entry.Token, End: r.Entries.entry(i + 1).Token})
}
}
if !found {
return ErrNotFound, nil
}
return nil, r.splitRangesOverZero(newRanges)
}
// Contains returns true if addr is in this ring
func (r *Ring) Contains(addr address.Address) bool {
return addr >= r.Start && addr < r.End
}
// Owner returns the peername which owns the range containing addr
func (r *Ring) Owner(token address.Address) router.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
}
// Look for the right-most entry, less than or equal to token
preceedingEntry := sort.Search(len(r.Entries), func(j int) bool {
return r.Entries[j].Token > token
})
preceedingEntry--
entry := r.Entries.entry(preceedingEntry)
return entry.Peer
}
// Get the set of PeerNames mentioned in the ring
func (r *Ring) PeerNames() map[router.PeerName]struct{} {
res := make(map[router.PeerName]struct{})
for _, entry := range r.Entries {
res[entry.Peer] = struct{}{}
}
return res
}
+724
View File
@@ -0,0 +1,724 @@
package ring
import (
"bytes"
"fmt"
"math/rand"
"sort"
"testing"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/router"
wt "github.com/weaveworks/weave/testing"
)
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")
start, end = ParseIP("10.0.0.0"), ParseIP("10.0.0.255")
dot10, dot245 = ParseIP("10.0.0.10"), ParseIP("10.0.0.245")
dot250 = ParseIP("10.0.0.250")
middle = ParseIP("10.0.0.128")
)
func ParseIP(s string) address.Address {
addr, _ := address.ParseIP(s)
return addr
}
func TestInvariants(t *testing.T) {
ring := New(start, end, peer1name)
// Check ring is sorted
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot10, Peer: peer2name}}
wt.AssertTrue(t, ring.checkInvariants() == ErrNotSorted, "Expected error")
// Check tokens don't appear twice
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot245, Peer: peer2name}}
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenRepeated, "Expected error")
// Check tokens are in bounds
ring = New(dot10, dot245, peer1name)
ring.Entries = []*entry{{Token: start, Peer: peer1name}}
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
ring.Entries = []*entry{{Token: end, Peer: peer1name}}
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
}
func TestInsert(t *testing.T) {
ring := New(start, end, peer1name)
ring.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
wt.AssertPanic(t, func() {
ring.Entries.insert(entry{Token: start, Peer: peer1name})
})
ring.Entries.entry(0).Free = 0
ring.Entries.insert(entry{Token: dot245, Peer: peer1name})
ring2 := New(start, end, peer1name)
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot245, Peer: peer1name}}
wt.AssertEquals(t, ring, ring2)
ring.Entries.insert(entry{Token: dot10, Peer: peer1name})
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot10, Peer: peer1name}, {Token: dot245, Peer: peer1name}}
wt.AssertEquals(t, ring, ring2)
}
func TestBetween(t *testing.T) {
ring1 := New(start, end, peer1name)
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
// First off, in a ring where everything is owned by the peer
// between should return true for everything
for i := 1; i <= 255; i++ {
ip := ParseIP(fmt.Sprintf("10.0.0.%d", i))
wt.AssertTrue(t, ring1.Entries.between(ip, 0, 1), "between should be true!")
}
// Now, construct a ring with entries at +10 and -10
// And check the correct behaviour
ring1.Entries = []*entry{{Token: dot10, Peer: peer1name}, {Token: dot245, Peer: peer2name}}
ring1.assertInvariants()
for i := 10; i <= 244; i++ {
ipStr := fmt.Sprintf("10.0.0.%d", i)
ip := ParseIP(ipStr)
wt.AssertTrue(t, ring1.Entries.between(ip, 0, 1),
fmt.Sprintf("Between should be true for %s!", ipStr))
wt.AssertFalse(t, ring1.Entries.between(ip, 1, 2),
fmt.Sprintf("Between should be false for %s!", ipStr))
}
for i := 0; i <= 9; i++ {
ipStr := fmt.Sprintf("10.0.0.%d", i)
ip := ParseIP(ipStr)
wt.AssertFalse(t, ring1.Entries.between(ip, 0, 1),
fmt.Sprintf("Between should be false for %s!", ipStr))
wt.AssertTrue(t, ring1.Entries.between(ip, 1, 2),
fmt.Sprintf("Between should be true for %s!", ipStr))
}
for i := 245; i <= 255; i++ {
ipStr := fmt.Sprintf("10.0.0.%d", i)
ip := ParseIP(ipStr)
wt.AssertFalse(t, ring1.Entries.between(ip, 0, 1),
fmt.Sprintf("Between should be false for %s!", ipStr))
wt.AssertTrue(t, ring1.Entries.between(ip, 1, 2),
fmt.Sprintf("Between should be true for %s!", ipStr))
}
}
func TestGrantSimple(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
// Claim everything for peer1
ring1.ClaimItAll()
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 255}})
// Now grant everything to peer2
ring1.GrantRangeToHost(start, end, peer2name)
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}}
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
// Now spint back to peer 1
ring2.GrantRangeToHost(dot10, end, peer1name)
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 10, Version: 2},
{Token: dot10, Peer: peer1name, Free: 245}}
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
// And spint back to peer 2 again
ring1.GrantRangeToHost(dot245, end, peer2name)
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
{Token: dot10, Peer: peer1name, Free: 235, Version: 1},
{Token: dot245, Peer: peer2name, Free: 10}})
// Grant range spanning a live token
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 10, Version: 2},
{Token: dot10, Peer: peer1name, Free: 235}, {Token: dot245, Peer: peer1name, Free: 10}}
ring1.GrantRangeToHost(dot10, end, peer2name)
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
{Token: dot10, Peer: peer2name, Free: 235, Version: 1},
{Token: dot245, Peer: peer2name, Free: 10, Version: 1}})
}
func TestGrantSplit(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
// Claim everything for peer1
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
ring2.Merge(*ring1)
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
// Now grant a split range to peer2
ring1.GrantRangeToHost(dot10, dot245, peer2name)
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
{Token: dot10, Peer: peer2name, Free: 235},
{Token: dot245, Peer: peer1name, Free: 10}})
ring1.assertInvariants()
// Grant range spanning a live token, and inserting a new token
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 10, Version: 2},
{Token: dot10, Peer: peer1name, Free: 118}, {Token: middle, Peer: peer1name, Free: 127}}
ring1.GrantRangeToHost(dot10, dot245, peer2name)
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
{Token: dot10, Peer: peer2name, Free: 118, Version: 1},
{Token: middle, Peer: peer2name, Free: 117, Version: 1},
{Token: dot245, Peer: peer1name, Free: 10, Version: 0}})
ring1.assertInvariants()
}
func TestMergeSimple(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
// Claim everything for peer1
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
wt.AssertSuccess(t, ring2.Merge(*ring1))
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 128, Version: 1},
{Token: middle, Peer: peer2name, Free: 127}})
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
// Now to two different operations on either side,
// check we can Merge again
ring1.GrantRangeToHost(start, middle, peer2name)
ring2.GrantRangeToHost(middle, end, peer1name)
wt.AssertSuccess(t, ring2.Merge(*ring1))
wt.AssertSuccess(t, ring1.Merge(*ring2))
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127, Version: 1}})
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
}
func TestMergeErrors(t *testing.T) {
// Cannot Merge in an invalid ring
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}, {Token: start, Peer: peer2name}}
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrNotSorted, "Expected ErrNotSorted")
// Should Merge two rings for different ranges
ring2 = New(start, middle, peer2name)
ring2.Entries = []*entry{}
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrDifferentSubnets, "Expected ErrDifferentSubnets")
// Cannot Merge newer version of entry I own
ring2 = New(start, end, peer2name)
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Version: 1}}
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrNewerVersion, "Expected ErrNewerVersion")
// Cannot Merge two entries with same version but different hosts
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
ring2.Entries = []*entry{{Token: start, Peer: peer2name}}
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrInvalidEntry, "Expected ErrInvalidEntry")
// Cannot Merge an entry into a range I own
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrEntryInMyRange, "Expected ErrEntryInMyRange")
}
func TestMergeMore(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
assertRing := func(ring *Ring, entries entries) {
wt.AssertEquals(t, ring.Entries, entries)
}
assertRing(ring1, []*entry{})
assertRing(ring2, []*entry{})
// Claim everything for peer1
ring1.ClaimItAll()
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
assertRing(ring2, []*entry{})
// Check the Merge sends it to the other ring
wt.AssertSuccess(t, ring2.Merge(*ring1))
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
// Give everything to peer2
ring1.GrantRangeToHost(start, end, peer2name)
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
wt.AssertSuccess(t, ring2.Merge(*ring1))
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
// And carve off some space
ring2.GrantRangeToHost(middle, end, peer1name)
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127}})
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
// And Merge back
wt.AssertSuccess(t, ring1.Merge(*ring2))
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127}})
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127}})
// This should be a no-op
wt.AssertSuccess(t, ring2.Merge(*ring1))
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127}})
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127}})
}
func TestMergeSplit(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
// Claim everything for peer2
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255}}
wt.AssertSuccess(t, ring2.Merge(*ring1))
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
// Now grant a split range to peer1
ring2.GrantRangeToHost(dot10, dot245, peer1name)
wt.AssertEquals(t, ring2.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
{Token: dot10, Peer: peer1name, Free: 235},
{Token: dot245, Peer: peer2name, Free: 10}})
wt.AssertSuccess(t, ring1.Merge(*ring2))
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
{Token: dot10, Peer: peer1name, Free: 235},
{Token: dot245, Peer: peer2name, Free: 10}})
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
}
func TestMergeSplit2(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
// Claim everything for peer2
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 250}, {Token: dot250, Peer: peer2name, Free: 5}}
wt.AssertSuccess(t, ring2.Merge(*ring1))
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
// Now grant a split range to peer1
ring2.GrantRangeToHost(dot10, dot245, peer1name)
wt.AssertEquals(t, ring2.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
{Token: dot10, Peer: peer1name, Free: 235},
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}})
wt.AssertSuccess(t, ring1.Merge(*ring2))
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
{Token: dot10, Peer: peer1name, Free: 235},
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}})
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
}
// A simple test, very similar to above, but using the marshalling to byte[]s
func TestGossip(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
assertRing := func(ring *Ring, entries entries) {
wt.AssertEquals(t, ring.Entries, entries)
}
assertRing(ring1, []*entry{})
assertRing(ring2, []*entry{})
// Claim everything for peer1
ring1.ClaimItAll()
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
assertRing(ring2, []*entry{})
// Check the Merge sends it to the other ring
wt.AssertSuccess(t, ring2.Merge(*ring1))
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
}
func TestFindFree(t *testing.T) {
ring1 := New(start, end, peer1name)
_, err := ring1.ChoosePeerToAskForSpace()
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
_, err = ring1.ChoosePeerToAskForSpace()
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
// We shouldn't return outselves
ring1.ReportFree(map[address.Address]address.Offset{start: 10})
_, err = ring1.ChoosePeerToAskForSpace()
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 1},
{Token: start, Peer: peer1name, Free: 1}}
_, err = ring1.ChoosePeerToAskForSpace()
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
// We should return others
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
peer, err := ring1.ChoosePeerToAskForSpace()
wt.AssertSuccess(t, err)
wt.AssertEquals(t, peer, peer2name)
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1},
{Token: start, Peer: peer2name, Free: 1}}
peer, err = ring1.ChoosePeerToAskForSpace()
wt.AssertSuccess(t, err)
wt.AssertEquals(t, peer, peer2name)
}
func TestReportFree(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
wt.AssertSuccess(t, ring2.Merge(*ring1))
freespace := make(map[address.Address]address.Offset)
for _, r := range ring2.OwnedRanges() {
freespace[r.Start] = 0
}
ring2.ReportFree(freespace)
}
func TestMisc(t *testing.T) {
ring := New(start, end, peer1name)
wt.AssertTrue(t, ring.Empty(), "empty")
ring.ClaimItAll()
println(ring.String())
}
func TestEmptyGossip(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
ring1.ClaimItAll()
// This used to panic, and it shouldn't
wt.AssertSuccess(t, ring1.Merge(*ring2))
}
func TestMergeOldMessage(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
ring1.ClaimItAll()
wt.AssertSuccess(t, ring2.Merge(*ring1))
ring1.GrantRangeToHost(middle, end, peer1name)
wt.AssertSuccess(t, ring1.Merge(*ring2))
}
func TestSplitRangeAtBeginning(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
ring1.ClaimItAll()
wt.AssertSuccess(t, ring2.Merge(*ring1))
ring1.GrantRangeToHost(start, middle, peer2name)
wt.AssertSuccess(t, ring2.Merge(*ring1))
}
func TestOwnedRange(t *testing.T) {
ring1 := New(start, end, peer1name)
ring1.ClaimItAll()
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{Start: start, End: end}})
ring1.GrantRangeToHost(middle, end, peer2name)
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{Start: start, End: middle}})
ring2 := New(start, end, peer2name)
ring2.Merge(*ring1)
wt.AssertEquals(t, ring2.OwnedRanges(), []address.Range{{Start: middle, End: end}})
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
wt.AssertEquals(t, ring2.OwnedRanges(),
[]address.Range{{Start: start, End: middle}, {Start: middle, End: end}})
ring2.Entries = []*entry{{Token: dot10, Peer: peer2name}, {Token: middle, Peer: peer2name}}
wt.AssertEquals(t, ring2.OwnedRanges(),
[]address.Range{{Start: start, End: dot10}, {Start: dot10, End: middle},
{Start: middle, End: end}})
}
func TestTransfer(t *testing.T) {
// First test just checks if we can grant some range to a host, when we transfer it, we get it back
ring1 := New(start, end, peer1name)
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
ring1.Transfer(peer2name, peer1name)
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{start, middle}, {middle, end}})
// Second test is what happens when a token exists at the end of a range but is transferred
// - does it get resurrected correctly?
ring1 = New(start, end, peer1name)
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
ring1.Transfer(peer2name, peer1name)
ring1.GrantRangeToHost(dot10, middle, peer2name)
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{start, dot10}, {middle, end}})
}
func TestOwner(t *testing.T) {
ring1 := New(start, end, peer1name)
wt.AssertTrue(t, ring1.Contains(start), "start should be in ring")
wt.AssertFalse(t, ring1.Contains(end), "end should not be in ring")
wt.AssertEquals(t, ring1.Owner(start), router.UnknownPeerName)
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
wt.AssertEquals(t, ring1.Owner(start), peer1name)
wt.AssertEquals(t, ring1.Owner(middle), peer2name)
wt.AssertPanic(t, func() {
ring1.Owner(end)
})
}
type addressSlice []address.Address
func (s addressSlice) Len() int { return len(s) }
func (s addressSlice) Less(i, j int) bool { return s[i] < s[j] }
func (s addressSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func TestFuzzRing(t *testing.T) {
var (
numPeers = 25
iterations = 1000
)
peers := make([]router.PeerName, numPeers)
for i := 0; i < numPeers; i++ {
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:00:02:00", i))
peers[i] = peer
}
// Make a valid, random ring
makeGoodRandomRing := func() *Ring {
addressSpace := end - start
numTokens := rand.Intn(int(addressSpace))
tokenMap := make(map[address.Address]bool)
for i := 0; i < numTokens; i++ {
tokenMap[address.Address(rand.Intn(int(addressSpace)))] = true
}
var tokens []address.Address
for token := range tokenMap {
tokens = append(tokens, token)
}
sort.Sort(addressSlice(tokens))
peer := peers[rand.Intn(len(peers))]
ring := New(start, end, peer)
for _, token := range tokens {
peer = peers[rand.Intn(len(peers))]
ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
}
ring.assertInvariants()
return ring
}
for i := 0; i < iterations; i++ {
// make 2 random rings
ring1 := makeGoodRandomRing()
ring2 := makeGoodRandomRing()
// Merge them - this might fail, we don't care
// We just want to make sure it doesn't panic
ring1.Merge(*ring2)
// Check whats left still passes assertions
ring1.assertInvariants()
ring2.assertInvariants()
}
// Make an invalid, random ring
makeBadRandomRing := func() *Ring {
addressSpace := end - start
numTokens := rand.Intn(int(addressSpace))
tokens := make([]address.Address, numTokens)
for i := 0; i < numTokens; i++ {
tokens[i] = address.Address(rand.Intn(int(addressSpace)))
}
peer := peers[rand.Intn(len(peers))]
ring := New(start, end, peer)
for _, token := range tokens {
peer = peers[rand.Intn(len(peers))]
ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
}
return ring
}
for i := 0; i < iterations; i++ {
// make 2 random rings
ring1 := makeGoodRandomRing()
ring2 := makeBadRandomRing()
// Merge them - this might fail, we don't care
// We just want to make sure it doesn't panic
ring1.Merge(*ring2)
// Check whats left still passes assertions
ring1.assertInvariants()
}
}
func TestFuzzRingHard(t *testing.T) {
//common.InitDefaultLogging(true)
var (
numPeers = 100
iterations = 3000
peers []router.PeerName
rings []*Ring
nextPeerID = 0
)
addPeer := func() {
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
common.Debug.Printf("%s: Adding peer", peer)
nextPeerID++
peers = append(peers, peer)
rings = append(rings, New(start, end, peer))
}
for i := 0; i < numPeers; i++ {
addPeer()
}
rings[0].ClaimItAll()
randomPeer := func(exclude int) (int, router.PeerName, *Ring) {
var peerIndex int
if exclude >= 0 {
peerIndex = rand.Intn(len(peers) - 1)
if peerIndex == exclude {
peerIndex++
}
} else {
peerIndex = rand.Intn(len(peers))
}
return peerIndex, peers[peerIndex], rings[peerIndex]
}
// Keep a map of index -> ranges, as these are a little expensive to
// calculate for every ring on every iteration.
var theRanges = make(map[int][]address.Range)
theRanges[0] = rings[0].OwnedRanges()
addOrRmPeer := func() {
if len(peers) < numPeers {
addPeer()
return
}
peerIndex, peername, _ := randomPeer(-1)
// Remove peer from our state
peers = append(peers[:peerIndex], peers[peerIndex+1:]...)
rings = append(rings[:peerIndex], rings[peerIndex+1:]...)
theRanges = make(map[int][]address.Range)
// Transfer the space for this peer on another peer, but not this one
_, otherPeername, otherRing := randomPeer(peerIndex)
// We need to be in a ~converged ring to rmpeer
for _, ring := range rings {
wt.AssertSuccess(t, otherRing.Merge(*ring))
}
common.Debug.Printf("%s: transferring from peer %s", otherPeername, peername)
otherRing.Transfer(peername, peername)
// And now tell everyone about the transfer - rmpeer is
// not partition safe
for i, ring := range rings {
wt.AssertSuccess(t, ring.Merge(*otherRing))
theRanges[i] = ring.OwnedRanges()
}
}
doGrantOrGossip := func() {
var ringsWithRanges = make([]int, 0, len(rings))
for index, ranges := range theRanges {
if len(ranges) > 0 {
ringsWithRanges = append(ringsWithRanges, index)
}
}
if len(ringsWithRanges) > 0 {
// Produce a random split in a random owned range, given to a random peer
indexWithRanges := ringsWithRanges[rand.Intn(len(ringsWithRanges))]
ownedRanges := theRanges[indexWithRanges]
ring := rings[indexWithRanges]
rangeToSplit := ownedRanges[rand.Intn(len(ownedRanges))]
size := address.Subtract(rangeToSplit.End, rangeToSplit.Start)
ipInRange := address.Add(rangeToSplit.Start, address.Offset(rand.Intn(int(size))))
_, peerToGiveTo, _ := randomPeer(-1)
common.Debug.Printf("%s: Granting [%v, %v) to %s", ring.Peer, ipInRange, rangeToSplit.End, peerToGiveTo)
ring.GrantRangeToHost(ipInRange, rangeToSplit.End, peerToGiveTo)
// Now 'gossip' this to a random host (note, note could be same host as above)
otherIndex, _, otherRing := randomPeer(-1)
common.Debug.Printf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
wt.AssertSuccess(t, otherRing.Merge(*ring))
theRanges[indexWithRanges] = ring.OwnedRanges()
theRanges[otherIndex] = otherRing.OwnedRanges()
return
}
// No rings think they own anything (as gossip might be behind)
// We're going to pick a random host (which has entries) and gossip
// it to a random host (which may or may not have entries).
var ringsWithEntries = make([]*Ring, 0, len(rings))
for _, ring := range rings {
if len(ring.Entries) > 0 {
ringsWithEntries = append(ringsWithEntries, ring)
}
}
ring1 := ringsWithEntries[rand.Intn(len(ringsWithEntries))]
ring2index, _, ring2 := randomPeer(-1)
common.Debug.Printf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
wt.AssertSuccess(t, ring2.Merge(*ring1))
theRanges[ring2index] = ring2.OwnedRanges()
}
for i := 0; i < iterations; i++ {
// about 1 in 10 times, rmpeer or add host
n := rand.Intn(10)
switch {
case n < 1:
addOrRmPeer()
default:
doGrantOrGossip()
}
}
}
func (es entries) String() string {
var buffer bytes.Buffer
fmt.Fprintf(&buffer, "[")
for i, entry := range es {
fmt.Fprintf(&buffer, "%+v", *entry)
if i+1 < len(es) {
fmt.Fprintf(&buffer, " ")
}
}
fmt.Fprintf(&buffer, "]")
return buffer.String()
}
+248
View File
@@ -0,0 +1,248 @@
package space
import (
"bytes"
"fmt"
"sort"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
)
type Addr address.Address
type Space struct {
// ours and free represent a set of addresses as a sorted
// sequences of ranges. Even elements give the inclusive
// starting points of ranges, and odd elements give the
// exclusive ending points. Ranges in an array do not
// overlap, and neighbouring ranges are always coalesced if
// possible, so the arrays consist of sorted Addrs without
// repetition.
ours []address.Address
free []address.Address
}
func New() *Space {
return &Space{}
}
func (s *Space) Add(start address.Address, size address.Offset) {
s.free = add(s.free, start, address.Add(start, size))
}
// Clear removes all spaces from this space set. Used during node shutdown.
func (s *Space) Clear() {
s.free = s.free[:0]
s.ours = s.ours[:0]
}
func (s *Space) Allocate() (bool, address.Address) {
if len(s.free) == 0 {
return false, 0
} else {
res := s.free[0]
s.ours = add(s.ours, res, res+1)
s.free = subtract(s.free, res, res+1)
return true, res
}
}
func (s *Space) Claim(addr address.Address) error {
if !contains(s.free, addr) {
return fmt.Errorf("Address %v is not free to claim", addr)
}
s.ours = add(s.ours, addr, addr+1)
s.free = subtract(s.free, addr, addr+1)
return nil
}
func (s *Space) NumFreeAddresses() address.Offset {
res := address.Offset(0)
for i := 0; i < len(s.free); i += 2 {
res += address.Subtract(s.free[i+1], s.free[i])
}
return res
}
func (s *Space) NumFreeAddressesInRange(start, end address.Address) address.Offset {
res := address.Offset(0)
for i := 0; i < len(s.free); i += 2 {
s, e := s.free[i], s.free[i+1]
if s < start {
s = start
}
if e > end {
e = end
}
if s >= e {
continue
}
res += address.Subtract(e, s)
}
return res
}
func (s *Space) Free(addr address.Address) error {
if !contains(s.ours, addr) {
return fmt.Errorf("Address %v is not ours", addr)
}
if contains(s.free, addr) {
return fmt.Errorf("Address %v is already free", addr)
}
s.ours = subtract(s.ours, addr, addr+1)
s.free = add(s.free, addr, addr+1)
return nil
}
func (s *Space) biggestFreeRange() (int, address.Offset) {
pos := -1
biggest := address.Offset(0)
for i := 0; i < len(s.free); i += 2 {
size := address.Subtract(s.free[i+1], s.free[i])
if size >= biggest {
pos = i
biggest = size
}
}
return pos, biggest
}
func (s *Space) Donate() (address.Address, address.Offset, bool) {
if len(s.free) == 0 {
return 0, 0, false
}
pos, biggest := s.biggestFreeRange()
// Donate half of that biggest free range, rounding up so
// that the donation can't be empty
end := s.free[pos+1]
start := end - address.Address((biggest+1)/2)
s.ours = subtract(s.ours, start, end)
s.free = subtract(s.free, start, end)
return start, address.Subtract(end, start), true
}
func firstGreater(a []address.Address, x address.Address) int {
return sort.Search(len(a), func(i int) bool { return a[i] > x })
}
func firstGreaterOrEq(a []address.Address, x address.Address) int {
return sort.Search(len(a), func(i int) bool { return a[i] >= x })
}
// Do the ranges contain the given address?
func contains(addrs []address.Address, addr address.Address) bool {
return firstGreater(addrs, addr)&1 != 0
}
// Take the union of the range [start, end) with the ranges in the array
func add(addrs []address.Address, start address.Address, end address.Address) []address.Address {
return addSub(addrs, start, end, 0)
}
// Subtract the range [start, end) from the ranges in the array
func subtract(addrs []address.Address, start address.Address, end address.Address) []address.Address {
return addSub(addrs, start, end, 1)
}
func addSub(addrs []address.Address, start address.Address, end address.Address, sense int) []address.Address {
start_pos := firstGreaterOrEq(addrs, start)
end_pos := firstGreater(addrs[start_pos:], end) + start_pos
// Boundaries up to start_pos are unaffected
res := make([]address.Address, start_pos, len(addrs)+2)
copy(res, addrs)
// Include start and end as new boundaries if they lie
// outside/inside existing ranges (according to sense).
if start_pos&1 == sense {
res = append(res, start)
}
if end_pos&1 == sense {
res = append(res, end)
}
// Boundaries after end_pos are unaffected
return append(res, addrs[end_pos:]...)
}
func (s *Space) String() string {
var buf bytes.Buffer
if len(s.ours) > 0 {
fmt.Fprint(&buf, "owned:")
for i := 0; i < len(s.ours); i += 2 {
fmt.Fprintf(&buf, " %s+%d ", s.ours[i], s.ours[i+1]-s.ours[i])
}
}
if len(s.free) > 0 {
fmt.Fprintf(&buf, "free:")
for i := 0; i < len(s.free); i += 2 {
fmt.Fprintf(&buf, " %s+%d ", s.free[i], s.free[i+1]-s.free[i])
}
}
if len(s.ours) == 0 && len(s.free) == 0 {
fmt.Fprintf(&buf, "No address ranges owned")
}
return buf.String()
}
type addressSlice []address.Address
func (p addressSlice) Len() int { return len(p) }
func (p addressSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p addressSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (s *Space) assertInvariants() {
common.Assert(sort.IsSorted(addressSlice(s.ours)))
common.Assert(sort.IsSorted(addressSlice(s.free)))
}
// Return a slice representing everything we own, whether it is free or not
func (s *Space) everything() []address.Address {
a := make([]address.Address, len(s.ours))
copy(a, s.ours)
for i := 0; i < len(s.free); i += 2 {
a = add(a, s.free[i], s.free[i+1])
}
return a
}
// OwnedRanges returns slice of Ranges, ordered by IP, gluing together
// contiguous sequences of owned and free addresses
func (s *Space) OwnedRanges() []address.Range {
everything := s.everything()
result := make([]address.Range, len(everything)/2)
for i := 0; i < len(everything); i += 2 {
result[i/2] = address.Range{Start: everything[i], End: everything[i+1]}
}
return result
}
// Create a Space that has free space in all the supplied Ranges.
func (s *Space) AddRanges(ranges []address.Range) {
for _, r := range ranges {
s.free = add(s.free, r.Start, r.End)
}
}
// Taking ranges to be a set of all space we should own, add in any excess as free space
func (s *Space) UpdateRanges(ranges []address.Range) {
new := []address.Address{}
for _, r := range ranges {
new = add(new, r.Start, r.End)
}
current := s.everything()
for i := 0; i < len(current); i += 2 {
new = subtract(new, current[i], current[i+1])
}
for i := 0; i < len(new); i += 2 {
s.free = add(s.free, new[i], new[i+1])
}
}
+249
View File
@@ -0,0 +1,249 @@
package space
import (
"reflect"
"testing"
"github.com/weaveworks/weave/ipam/address"
wt "github.com/weaveworks/weave/testing"
)
func makeSpace(start address.Address, size address.Offset) *Space {
s := New()
s.Add(start, size)
return s
}
func ip(s string) address.Address {
addr, _ := address.ParseIP(s)
return addr
}
func TestLowlevel(t *testing.T) {
a := []address.Address{}
a = add(a, 100, 200)
wt.AssertEquals(t, a, []address.Address{100, 200})
wt.AssertTrue(t, !contains(a, 99), "")
wt.AssertTrue(t, contains(a, 100), "")
wt.AssertTrue(t, contains(a, 199), "")
wt.AssertTrue(t, !contains(a, 200), "")
a = add(a, 700, 800)
wt.AssertEquals(t, a, []address.Address{100, 200, 700, 800})
a = add(a, 300, 400)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 400, 700, 800})
a = add(a, 400, 500)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 700, 800})
a = add(a, 600, 700)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 600, 800})
a = add(a, 500, 600)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 800})
a = subtract(a, 500, 600)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 600, 800})
a = subtract(a, 600, 700)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 700, 800})
a = subtract(a, 400, 500)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 400, 700, 800})
a = subtract(a, 300, 400)
wt.AssertEquals(t, a, []address.Address{100, 200, 700, 800})
a = subtract(a, 700, 800)
wt.AssertEquals(t, a, []address.Address{100, 200})
a = subtract(a, 100, 200)
wt.AssertEquals(t, a, []address.Address{})
s := New()
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(0))
ok, got := s.Allocate()
wt.AssertFalse(t, ok, "allocate in empty space should fail")
s.Add(100, 100)
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(100))
ok, got = s.Allocate()
wt.AssertTrue(t, ok && got == 100, "allocate")
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(99))
wt.AssertNoErr(t, s.Claim(150))
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(98))
wt.AssertNoErr(t, s.Free(100))
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(99))
wt.AssertErrorInterface(t, s.Free(0), (*error)(nil), "free not allocated")
wt.AssertErrorInterface(t, s.Free(100), (*error)(nil), "double free")
start, size, ok := s.Donate()
wt.AssertTrue(t, ok && start == 125 && size == 25, "donate")
// test Donate when addresses are scarce
s = New()
start, size, ok = s.Donate()
wt.AssertTrue(t, !ok, "donate on empty space should fail")
s.Add(0, 3)
wt.AssertNoErr(t, s.Claim(0))
wt.AssertNoErr(t, s.Claim(2))
start, size, ok = s.Donate()
wt.AssertTrue(t, ok && start == 1 && size == 1, "donate")
start, size, ok = s.Donate()
wt.AssertTrue(t, !ok, "donate should fail")
}
func TestSpaceAllocate(t *testing.T) {
const (
testAddr1 = "10.0.3.4"
testAddrx = "10.0.3.19"
testAddry = "10.0.9.19"
containerID = "deadbeef"
)
space1 := makeSpace(ip(testAddr1), 20)
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(20))
space1.assertInvariants()
_, addr1 := space1.Allocate()
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(19))
space1.assertInvariants()
_, addr2 := space1.Allocate()
wt.AssertFalse(t, addr2.String() == testAddr1, "address")
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(18))
wt.AssertEquals(t, space1.NumFreeAddressesInRange(ip(testAddr1), ip(testAddrx)), address.Offset(13))
wt.AssertEquals(t, space1.NumFreeAddressesInRange(ip(testAddr1), ip(testAddry)), address.Offset(18))
space1.assertInvariants()
space1.Free(addr2)
space1.assertInvariants()
wt.AssertErrorInterface(t, space1.Free(addr2), (*error)(nil), "double free")
wt.AssertErrorInterface(t, space1.Free(ip(testAddrx)), (*error)(nil), "address not allocated")
wt.AssertErrorInterface(t, space1.Free(ip(testAddry)), (*error)(nil), "wrong out of range")
space1.assertInvariants()
}
func TestSpaceFree(t *testing.T) {
const (
testAddr1 = "10.0.3.4"
testAddrx = "10.0.3.19"
testAddry = "10.0.9.19"
containerID = "deadbeef"
)
space := makeSpace(ip(testAddr1), 20)
// Check we are prepared to give up the entire space
pos, size := space.biggestFreeRange()
wt.AssertTrue(t, space.free[pos] == ip(testAddr1) && size == 20, "Wrong space")
for i := 0; i < 20; i++ {
ok, _ := space.Allocate()
wt.AssertTrue(t, ok, "Failed to get address")
}
// Check we are full
ok, _ := space.Allocate()
wt.AssertTrue(t, !ok, "Should have failed to get address")
_, size, ok = space.Donate()
wt.AssertTrue(t, size == 0, "Wrong space")
// Free in the middle
wt.AssertSuccess(t, space.Free(ip("10.0.3.13")))
pos, size = space.biggestFreeRange()
wt.AssertTrue(t, space.free[pos] == ip("10.0.3.13") && size == 1, "Wrong space")
// Free one at the end
wt.AssertSuccess(t, space.Free(ip("10.0.3.23")))
pos, size = space.biggestFreeRange()
wt.AssertTrue(t, space.free[pos] == ip("10.0.3.23") && size == 1, "Wrong space")
// Now free a few at the end
wt.AssertSuccess(t, space.Free(ip("10.0.3.22")))
wt.AssertSuccess(t, space.Free(ip("10.0.3.21")))
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(4))
// Now get the biggest free space; should be 3.21
pos, size = space.biggestFreeRange()
wt.AssertTrue(t, space.free[pos] == ip("10.0.3.21") && size == 3, "Wrong space")
// Now free a few in the middle
wt.AssertSuccess(t, space.Free(ip("10.0.3.12")))
wt.AssertSuccess(t, space.Free(ip("10.0.3.11")))
wt.AssertSuccess(t, space.Free(ip("10.0.3.10")))
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(7))
// Now get the biggest free space; should be 3.21
pos, size = space.biggestFreeRange()
wt.AssertTrue(t, space.free[pos] == ip("10.0.3.10") && size == 4, "Wrong space")
wt.AssertEquals(t, space.OwnedRanges(), []address.Range{address.Range{Start: ip("10.0.3.4"), End: ip("10.0.3.24")}})
}
func (s1 *Space) Equal(s2 *Space) bool {
return reflect.DeepEqual(s1.ours, s2.ours) && reflect.DeepEqual(s1.free, s2.free)
}
func TestDonateSimple(t *testing.T) {
const (
testAddr1 = "10.0.1.0"
testAddr2 = "10.0.1.32"
)
var (
ipAddr1 = ip(testAddr1)
)
ps1 := makeSpace(ipAddr1, 48)
// Empty space set should split in two and give me the second half
start, numGivenUp, ok := ps1.Donate()
wt.AssertTrue(t, ok, "Donate result")
wt.AssertEqualString(t, start.String(), "10.0.1.24", "Invalid start")
wt.AssertEquals(t, numGivenUp, address.Offset(24))
wt.AssertEquals(t, ps1.NumFreeAddresses(), address.Offset(24))
// Now check we can give the rest up.
count := 0 // count to avoid infinite loop
for ; count < 1000; count++ {
_, size, ok := ps1.Donate()
if !ok {
break
}
numGivenUp += size
}
wt.AssertEquals(t, ps1.NumFreeAddresses(), address.Offset(0))
wt.AssertEquals(t, numGivenUp, address.Offset(48))
}
func TestDonateHard(t *testing.T) {
//common.InitDefaultLogging(true)
var (
start = ip("10.0.1.0")
size address.Offset = 48
)
// Fill a fresh space
spaceset := makeSpace(start, size)
for i := address.Offset(0); i < size; i++ {
ok, _ := spaceset.Allocate()
wt.AssertTrue(t, ok, "Failed to get IP!")
}
wt.AssertEquals(t, spaceset.NumFreeAddresses(), address.Offset(0))
// Now free all but the last address
// this will force us to split the free list
for i := address.Offset(0); i < size-1; i++ {
wt.AssertSuccess(t, spaceset.Free(address.Add(start, i)))
}
// Now split
newRange, numGivenUp, ok := spaceset.Donate()
wt.AssertTrue(t, ok, "GiveUpSpace result")
wt.AssertEquals(t, newRange, ip("10.0.1.23"))
wt.AssertEquals(t, numGivenUp, address.Offset(24))
wt.AssertEquals(t, spaceset.NumFreeAddresses(), address.Offset(23))
//Space set should now have 2 spaces
expected := New()
expected.Add(start, 23)
expected.ours = add(nil, ip("10.0.1.47"), ip("10.0.1.48"))
wt.AssertEquals(t, spaceset, expected)
}
+286
View File
@@ -0,0 +1,286 @@
package ipam
import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/router"
wt "github.com/weaveworks/weave/testing"
)
type mockMessage struct {
dst router.PeerName
msgType byte
buf []byte
}
func (m *mockMessage) String() string {
return fmt.Sprintf("-> %s [%x]", m.dst, m.buf)
}
func toStringArray(messages []mockMessage) []string {
out := make([]string, len(messages))
for i := range out {
out[i] = messages[i].String()
}
return out
}
type mockGossipComms struct {
t *testing.T
name string
messages []mockMessage
}
func (m *mockGossipComms) String() string {
return fmt.Sprintf("[mockGossipComms %s]", m.name)
}
// Note: this style of verification, using equalByteBuffer, requires
// 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 {
buf := []byte{}
if len(m.messages) == 0 {
m.Fatalf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf)
} else if msg := m.messages[0]; msg.dst != router.UnknownPeerName {
m.Fatalf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst)
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf) {
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf)
} else {
// Swallow this message
m.messages = m.messages[1:]
}
return nil
}
func equalByteBuffer(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
func (m *mockGossipComms) Fatalf(format string, args ...interface{}) {
// this sometimes hangs: wt.Fatalf(m.t, args...)
panic(fmt.Sprintf(format, args...))
}
func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
if len(m.messages) == 0 {
m.Fatalf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf)
} else if msg := m.messages[0]; msg.dst == router.UnknownPeerName {
m.Fatalf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName)
} else if msg.dst != dstPeerName {
m.Fatalf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName)
} else if buf[0] != msg.msgType {
m.Fatalf("%s: Expected Gossip message of type %d but got type %d", m.name, msg.msgType, buf[0])
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf[1:]) {
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf[1:])
} else {
// Swallow this message
m.messages = m.messages[1:]
}
return nil
}
func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
m := alloc.gossip.(*mockGossipComms)
dstPeerName, _ := router.PeerNameFromString(dst)
m.messages = append(m.messages, mockMessage{dstPeerName, msgType, buf})
}
func ExpectBroadcastMessage(alloc *Allocator, buf []byte) {
m := alloc.gossip.(*mockGossipComms)
m.messages = append(m.messages, mockMessage{router.UnknownPeerName, 0, buf})
}
func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
for _, alloc := range allocs {
m := alloc.gossip.(*mockGossipComms)
if len(m.messages) > 0 {
wt.Fatalf(m.t, "%s: Gossip message(s) not sent as expected: \n%x", m.name, m.messages)
}
}
}
func makeAllocator(name string, cidr string, quorum uint) *Allocator {
peername, err := router.PeerNameFromString(name)
if err != nil {
panic(err)
}
alloc, err := NewAllocator(peername, router.PeerUID(rand.Int63()),
"nick-"+name, cidr, quorum)
if err != nil {
panic(err)
}
return alloc
}
func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, quorum uint) *Allocator {
alloc := makeAllocator(name, universeCIDR, quorum)
gossip := &mockGossipComms{t: t, name: name}
alloc.SetInterfaces(gossip)
alloc.Start()
return alloc
}
func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
peers := []router.PeerName{alloc.ourName}
for _, alloc2 := range allocs {
peers = append(peers, alloc2.ourName)
}
alloc.ring.ClaimForPeers(normalizeConsensus(peers))
alloc.space.AddRanges(alloc.ring.OwnedRanges())
}
// Check whether or not something was sent on a channel
func AssertSent(t *testing.T, ch <-chan bool) {
timeout := time.After(10 * time.Second)
select {
case <-ch:
// This case is ok
case <-timeout:
wt.Fatalf(t, "Nothing sent on channel")
}
}
func AssertNothingSent(t *testing.T, ch <-chan bool) {
select {
case val := <-ch:
wt.Fatalf(t, "Unexpected value on channel: %t", val)
default:
// no message received
}
}
func AssertNothingSentErr(t *testing.T, ch <-chan error) {
select {
case val := <-ch:
wt.Fatalf(t, "Unexpected value on channel: %t", val)
default:
// no message received
}
}
// Router to convey gossip from one gossiper to another, for testing
type gossipMessage struct {
isUnicast bool
sender *router.PeerName
buf []byte
exitChan chan bool
}
type TestGossipRouter struct {
gossipChans map[router.PeerName]chan gossipMessage
loss float32 // 0.0 means no loss
}
func (grouter *TestGossipRouter) GossipBroadcast(update router.GossipData) error {
for _, gossipChan := range grouter.gossipChans {
select {
case gossipChan <- gossipMessage{buf: update.(*ipamGossipData).alloc.encode()}:
default: // drop the message if we cannot send it
}
}
return nil
}
func (grouter *TestGossipRouter) removePeer(peer router.PeerName) {
gossipChan := grouter.gossipChans[peer]
resultChan := make(chan bool)
gossipChan <- gossipMessage{exitChan: resultChan}
<-resultChan
delete(grouter.gossipChans, peer)
}
type TestGossipRouterClient struct {
router *TestGossipRouter
sender router.PeerName
}
func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router.Gossiper) router.Gossip {
gossipChan := make(chan gossipMessage, 100)
go func() {
gossipTimer := time.Tick(10 * time.Second)
for {
select {
case message := <-gossipChan:
if message.exitChan != nil {
message.exitChan <- true
return
}
if rand.Float32() > (1.0 - grouter.loss) {
continue
}
if message.isUnicast {
err := gossiper.OnGossipUnicast(*message.sender, message.buf)
if err != nil {
panic(fmt.Sprintf("Error doing gossip unicast to %s: %s", sender, err))
}
} else {
_, err := gossiper.OnGossipBroadcast(message.buf)
if err != nil {
panic(fmt.Sprintf("Error doing gossip broadcast to %s: %s", sender, err))
}
}
case <-gossipTimer:
grouter.GossipBroadcast(gossiper.Gossip())
}
}
}()
grouter.gossipChans[sender] = gossipChan
return TestGossipRouterClient{grouter, sender}
}
func (client TestGossipRouterClient) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
select {
case client.router.gossipChans[dstPeerName] <- gossipMessage{isUnicast: true, sender: &client.sender, buf: buf}:
default: // drop the message if we cannot send it
common.Error.Printf("Dropping message")
}
return nil
}
func (client TestGossipRouterClient) GossipBroadcast(update router.GossipData) error {
return client.router.GossipBroadcast(update)
}
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, TestGossipRouter) {
gossipRouter := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
allocs := make([]*Allocator, size)
for i := 0; i < size; i++ {
alloc := makeAllocator(fmt.Sprintf("%02d:00:00:02:00:00", i),
cidr, uint(size/2+1))
alloc.SetInterfaces(gossipRouter.connect(alloc.ourName, alloc))
alloc.Start()
allocs[i] = alloc
}
gossipRouter.GossipBroadcast(allocs[size-1].Gossip())
time.Sleep(1000 * time.Millisecond)
return allocs, gossipRouter
}
func stopNetworkOfAllocators(allocs []*Allocator) {
for _, alloc := range allocs {
alloc.Stop()
}
}
+125
View File
@@ -0,0 +1,125 @@
package nameserver
import (
"bytes"
"fmt"
"net"
)
// A zone record
// Note: we will try to keep all the zone records information is concentrated here. Maybe not all queries will
// use things like priorities or weights, but we do not want to create a hierarchy for dealing with all
// possible queries...
type ZoneRecord interface {
Name() string // The name for this IP
IP() net.IP // The IP (v4) address
Priority() int // The priority
Weight() int // The weight
TTL() int // TTL
}
type ZoneLookup interface {
// Lookup for a name
LookupName(name string) ([]ZoneRecord, error)
// Lookup for an address
LookupInaddr(inaddr string) ([]ZoneRecord, error)
}
type ZoneObserver interface {
// Observe anything that affects a particular name in the zone
ObserveName(name string, observer ZoneRecordObserver) error
// Observe anything that affects a particular IP in the zone
ObserveInaddr(inaddr string, observer ZoneRecordObserver) error
}
type ZoneRecordObserver func()
/////////////////////////////////////////////////////////////
// A basic record struct (satisfying the ZoneRecord interface)
type Record struct {
name string
ip net.IP
priority int
weight int
ttl int
}
func (r Record) Name() string { return r.name }
func (r Record) IP() net.IP { return r.ip }
func (r Record) Priority() int { return r.priority }
func (r Record) Weight() int { return r.weight }
func (r Record) TTL() int { return r.ttl }
func (r Record) String() string {
var buf bytes.Buffer
if len(r.Name()) > 0 {
fmt.Fprintf(&buf, "%s", r.Name())
}
if !r.IP().IsUnspecified() {
fmt.Fprintf(&buf, "[%s]", r.IP())
}
if r.Priority() > 0 {
fmt.Fprintf(&buf, "/P:%d", r.Priority())
}
if r.Weight() > 0 {
fmt.Fprintf(&buf, "/W:%d", r.Weight())
}
if r.TTL() > 0 {
fmt.Fprintf(&buf, "/TTL:%d", r.TTL())
}
return buf.String()
}
/////////////////////////////////////////////////////////////
func newParseError(reason string, addr string) *net.ParseError {
return &net.ParseError{Type: reason, Text: addr}
}
// simple IPv4 type that can be used as a key in a map (in contrast with net.IP), used for sets of IPs, etc...
type IPv4 [4]byte
func (ip IPv4) toNetIP() net.IP { return net.IPv4(ip[0], ip[1], ip[2], ip[3]) }
func (ip IPv4) String() string { return ip.toNetIP().String() }
func ipToIPv4(nip net.IP) IPv4 { ip := nip.To4(); return IPv4([4]byte{ip[0], ip[1], ip[2], ip[3]}) }
// Parse an address (eg, "10.13.12.11") and return the corresponding IPv4 (eg, "10.13.12.11")
func addrToIPv4(addr string) (IPv4, error) {
ip := net.ParseIP(addr)
if ip == nil {
return IPv4{}, newParseError("IP address", addr)
}
return ipToIPv4(ip), nil
}
// Parse a reverse address (eg, "11.12.13.10.in-addr.arpa.") and return the corresponding IPv4 (eg, "10.13.12.11")
func raddrToIPv4(addr string) (IPv4, error) {
l := len(addr)
if l < rdnsDomainLen+1 {
return IPv4{}, newParseError("too short reverse IP address", addr)
}
suffixLen := l - rdnsDomainLen
suffix := addr[suffixLen+1:]
if suffix != RDNSDomain {
return IPv4{}, newParseError("suffix of reverse IP address", addr)
}
ipStr := addr[:suffixLen]
revIP := net.ParseIP(ipStr)
if revIP == nil {
return IPv4{}, newParseError("reverse IP address", addr)
}
revIP4 := revIP.To4()
return IPv4([4]byte{revIP4[3], revIP4[2], revIP4[1], revIP4[0]}), nil
}
func raddrToIP(addr string) (net.IP, error) {
r, err := raddrToIPv4(addr)
if err != nil {
return net.IP{}, err
}
return r.toNetIP(), nil
}
+31
View File
@@ -0,0 +1,31 @@
package nameserver
import (
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
)
func TestAddrs(t *testing.T) {
InitDefaultLogging(testing.Verbose())
Info.Println("TestAddrs starting")
ip, err := addrToIPv4("10.13.12.11")
wt.AssertNoErr(t, err)
wt.AssertTrue(t, net.ParseIP("10.13.12.11").Equal(ip.toNetIP()), "IP")
ip, err = raddrToIPv4("11.12.13.10.in-addr.arpa.")
wt.AssertNoErr(t, err)
wt.AssertTrue(t, net.ParseIP("10.13.12.11").Equal(ip.toNetIP()), "IP")
// some malformed addresses
ip, err = addrToIPv4("10.13.12")
wt.AssertTrue(t, err != nil, "when parsing malformed address")
ip, err = addrToIPv4("10.13.AA.12")
wt.AssertTrue(t, err != nil, "when parsing malformed address")
ip, err = raddrToIPv4("11.12.13.10.in-axxx.arpa.")
wt.AssertTrue(t, err != nil, "when parsing malformed address")
ip, err = raddrToIPv4("11.12.13.10.in-addr")
wt.AssertTrue(t, err != nil, "when parsing malformed address")
}
+94 -16
View File
@@ -1,14 +1,18 @@
package nameserver
import (
"bytes"
"container/heap"
"errors"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"fmt"
"math"
"math/rand"
"sync"
"time"
"github.com/benbjohnson/clock"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
)
var (
@@ -31,6 +35,11 @@ const (
stResolved entryStatus = iota // resolved
)
var statusToString = map[entryStatus]string{
stPending: "pending",
stResolved: "resolved",
}
const (
CacheNoLocalReplies uint8 = 1 << iota // not found in local network (stored in the cache so we skip another local lookup or some time)
)
@@ -137,9 +146,12 @@ func (e *cacheEntry) setReply(reply *dns.Msg, ttl int, flags uint8, now time.Tim
prevValidUntil = e.validUntil
}
e.Status = stResolved
e.Flags = flags
e.putTime = now
// make sure we do not overwrite noLocalReplies entries
if flags&CacheNoLocalReplies != 0 {
if e.Flags&CacheNoLocalReplies != 0 {
return false
}
}
if ttl != nullTTL {
e.validUntil = now.Add(time.Second * time.Duration(ttl))
@@ -153,8 +165,14 @@ func (e *cacheEntry) setReply(reply *dns.Msg, ttl int, flags uint8, now time.Tim
}
}
e.validUntil = now.Add(time.Second * time.Duration(minTTL))
} else {
Warning.Printf("[cache] no valid TTL could be calculated")
}
e.Status = stResolved
e.Flags = flags
e.putTime = now
if reply != nil {
e.reply = *reply
e.ReplyLen = reply.Len()
@@ -163,6 +181,19 @@ func (e *cacheEntry) setReply(reply *dns.Msg, ttl int, flags uint8, now time.Tim
return (prevValidUntil != e.validUntil)
}
func (e *cacheEntry) String() string {
var buf bytes.Buffer
q := e.question
fmt.Fprintf(&buf, "'%s'[%s]: ", q.Name, dns.TypeToString[q.Qtype])
if e.Flags&CacheNoLocalReplies != 0 {
fmt.Fprintf(&buf, "neg-local")
} else {
fmt.Fprintf(&buf, "%s", statusToString[e.Status])
}
fmt.Fprintf(&buf, "(%d bytes)", e.ReplyLen)
return buf.String()
}
//////////////////////////////////////////////////////////////////////////////////////
// An entriesPtrHeap is a min-heap of cache entries.
@@ -196,26 +227,49 @@ func (h *entriesPtrsHeap) Pop() interface{} {
//////////////////////////////////////////////////////////////////////////////////////
// The cache interface
type ZoneCache interface {
// Get from the cache
Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error)
// Put in the cache
Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int
// Remove from the cache
Remove(question *dns.Question)
// Purge all expired entries from the cache
Purge()
// Remove all entries
Clear()
// Return the cache length
Len() int
// Return the max capacity
Capacity() int
}
type cacheKey dns.Question
type entries map[cacheKey]*cacheEntry
// Cache is a thread-safe fixed capacity LRU cache.
type Cache struct {
Capacity int
capacity int
entries entries
entriesH entriesPtrsHeap // len(entriesH) <= len(entries), as pending entries can be in entries but not in entriesH
clock clock.Clock
lock sync.RWMutex
}
// NewCache creates a cache of the given capacity
func NewCache(capacity int) (*Cache, error) {
func NewCache(capacity int, clk clock.Clock) (*Cache, error) {
if capacity <= 0 {
return nil, errInvalidCapacity
}
c := &Cache{
Capacity: capacity,
capacity: capacity,
entries: make(entries, capacity),
clock: clk,
}
if c.clock == nil {
c.clock = clock.New()
}
heap.Init(&c.entriesH)
@@ -227,15 +281,16 @@ func (c *Cache) Clear() {
c.lock.Lock()
defer c.lock.Unlock()
c.entries = make(entries, c.Capacity)
c.entries = make(entries, c.capacity)
heap.Init(&c.entriesH)
}
// Purge removes the old elements in the cache
func (c *Cache) Purge(now time.Time) {
func (c *Cache) Purge() {
c.lock.Lock()
defer c.lock.Unlock()
now := c.clock.Now()
for i, entry := range c.entriesH {
if entry.hasExpired(now) {
heap.Remove(&c.entriesH, i)
@@ -247,10 +302,12 @@ func (c *Cache) Purge(now time.Time) {
}
// Add adds a reply to the cache.
func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8, now time.Time) int {
// When `ttl` is equal to `nullTTL`, the cache entry will be valid until the closest TTL in the `reply`
func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int {
c.lock.Lock()
defer c.lock.Unlock()
now := c.clock.Now()
question := request.Question[0]
key := cacheKey(question)
ent, found := c.entries[key]
@@ -261,7 +318,7 @@ func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8, now
}
} else {
// If we will add a new item and the capacity has been exceeded, make some room...
if len(c.entriesH) >= c.Capacity {
if len(c.entriesH) >= c.capacity {
lowestEntry := heap.Pop(&c.entriesH).(*cacheEntry)
delete(c.entries, cacheKey(lowestEntry.question))
}
@@ -274,12 +331,12 @@ func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8, now
}
// Look up for a question's reply from the cache.
// If no reply is stored in the cache, it returns a `nil` reply and no error. The caller can then `Wait()`
// for another goroutine `Put`ing a reply in the cache.
func (c *Cache) Get(request *dns.Msg, maxLen int, now time.Time) (reply *dns.Msg, err error) {
// If no reply is stored in the cache, it returns a `nil` reply and no error.
func (c *Cache) Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error) {
c.lock.Lock()
defer c.lock.Unlock()
now := c.clock.Now()
question := request.Question[0]
key := cacheKey(question)
if ent, found := c.entries[key]; found {
@@ -307,6 +364,7 @@ func (c *Cache) Remove(question *dns.Question) {
key := cacheKey(*question)
if entry, found := c.entries[key]; found {
Debug.Printf("[cache] removing %s-response for '%s'", dns.TypeToString[question.Qtype], question.Name)
if entry.index > 0 {
heap.Remove(&c.entriesH, entry.index)
}
@@ -321,3 +379,23 @@ func (c *Cache) Len() int {
return len(c.entries)
}
// Return the max capacity
func (c *Cache) Capacity() int {
c.lock.RLock()
defer c.lock.RUnlock()
return c.capacity
}
// Return the max capacity
func (c *Cache) String() string {
c.lock.RLock()
defer c.lock.RUnlock()
var buf bytes.Buffer
for _, entry := range c.entries {
fmt.Fprintf(&buf, "%s\n", entry)
}
return buf.String()
}
+68 -46
View File
@@ -2,39 +2,44 @@ package nameserver
import (
"fmt"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
"time"
"github.com/benbjohnson/clock"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
)
// Check that the cache keeps its intended capacity constant
func TestCacheLength(t *testing.T) {
InitDefaultLogging(true)
InitDefaultLogging(testing.Verbose())
Info.Println("TestCacheLength starting")
const cacheLen = 128
l, err := NewCache(cacheLen)
l, err := NewCache(cacheLen, nil)
wt.AssertNoErr(t, err)
insTime := time.Now()
t.Logf("Inserting 256 questions in the cache at '%s', with TTL from 0 to 255", insTime)
for i := 0; i < cacheLen*2; i++ {
name := fmt.Sprintf("name%d", i)
questionMsg := new(dns.Msg)
questionMsg.SetQuestion(fmt.Sprintf("name%d", i), dns.TypeA)
questionMsg.SetQuestion(name, dns.TypeA)
questionMsg.RecursionDesired = true
question := &questionMsg.Question[0]
ip := net.ParseIP(fmt.Sprintf("10.0.1.%d", i))
ips := []net.IP{ip}
reply := makeAddressReply(questionMsg, question, ips)
records := []ZoneRecord{Record{name, ip, 0, 0, 0}}
reply := makeAddressReply(questionMsg, question, records)
reply.Answer[0].Header().Ttl = uint32(i)
l.Put(questionMsg, reply, 0, 0, insTime)
l.Put(questionMsg, reply, 0, 0)
}
wt.AssertEqualInt(t, l.Len(), cacheLen, "cache length")
@@ -50,13 +55,15 @@ func TestCacheLength(t *testing.T) {
// Check that the cache entries are ok
func TestCacheEntries(t *testing.T) {
InitDefaultLogging(true)
InitDefaultLogging(testing.Verbose())
Info.Println("TestCacheEntries starting")
Info.Println("Checking cache consistency")
const cacheLen = 128
clk := clock.NewMock()
l, err := NewCache(cacheLen)
l, err := NewCache(cacheLen, clk)
wt.AssertNoErr(t, err)
questionMsg := new(dns.Msg)
@@ -66,14 +73,14 @@ func TestCacheEntries(t *testing.T) {
question := &questionMsg.Question[0]
t.Logf("Trying to get a name")
resp, err := l.Get(questionMsg, minUDPSize, time.Now())
resp, err := l.Get(questionMsg, minUDPSize)
wt.AssertNoErr(t, err)
if resp != nil {
t.Logf("Got\n%s", resp)
t.Fatalf("ERROR: Did not expect a reponse from Get() yet")
}
t.Logf("Trying to get it again")
resp, err = l.Get(questionMsg, minUDPSize, time.Now())
resp, err = l.Get(questionMsg, minUDPSize)
wt.AssertNoErr(t, err)
if resp != nil {
t.Logf("Got\n%s", resp)
@@ -81,21 +88,21 @@ func TestCacheEntries(t *testing.T) {
}
t.Logf("Inserting the reply")
reply1 := makeAddressReply(questionMsg, question, []net.IP{net.ParseIP("10.0.1.1")})
l.Put(questionMsg, reply1, nullTTL, 0, time.Now())
records := []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.1"), 0, 0, 0}}
reply1 := makeAddressReply(questionMsg, question, records)
l.Put(questionMsg, reply1, nullTTL, 0)
timeGet1 := time.Now()
t.Logf("Checking we can Get() the reply now")
resp, err = l.Get(questionMsg, minUDPSize, timeGet1)
resp, err = l.Get(questionMsg, minUDPSize)
wt.AssertNoErr(t, err)
wt.AssertTrue(t, resp != nil, "reponse from Get()")
t.Logf("Received '%s'", resp.Answer[0])
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
ttlGet1 := resp.Answer[0].Header().Ttl
timeGet2 := timeGet1.Add(time.Duration(1) * time.Second)
clk.Add(time.Duration(1) * time.Second)
t.Logf("Checking that a second Get(), after 1 second, gets the same result, but with reduced TTL")
resp, err = l.Get(questionMsg, minUDPSize, timeGet2)
resp, err = l.Get(questionMsg, minUDPSize)
wt.AssertNoErr(t, err)
wt.AssertTrue(t, resp != nil, "reponse from a second Get()")
t.Logf("Received '%s'", resp.Answer[0])
@@ -103,9 +110,9 @@ func TestCacheEntries(t *testing.T) {
ttlGet2 := resp.Answer[0].Header().Ttl
wt.AssertEqualInt(t, int(ttlGet1-ttlGet2), 1, "TTL difference")
timeGet3 := timeGet1.Add(time.Duration(localTTL) * time.Second)
clk.Add(time.Duration(localTTL) * time.Second)
t.Logf("Checking that a third Get(), after %d second, gets no result", localTTL)
resp, err = l.Get(questionMsg, minUDPSize, timeGet3)
resp, err = l.Get(questionMsg, minUDPSize)
wt.AssertNoErr(t, err)
if resp != nil {
t.Logf("Got\n%s", resp)
@@ -113,28 +120,30 @@ func TestCacheEntries(t *testing.T) {
}
t.Logf("Checking that an Remove() results in Get() returning nothing")
replyTemp := makeAddressReply(questionMsg, question, []net.IP{net.ParseIP("10.0.9.9")})
l.Put(questionMsg, replyTemp, nullTTL, 0, time.Now())
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}}
replyTemp := makeAddressReply(questionMsg, question, records)
l.Put(questionMsg, replyTemp, nullTTL, 0)
lenBefore := l.Len()
l.Remove(question)
wt.AssertEqualInt(t, l.Len(), lenBefore-1, "cache length")
l.Remove(question) // do it again: should have no effect...
wt.AssertEqualInt(t, l.Len(), lenBefore-1, "cache length")
resp, err = l.Get(questionMsg, minUDPSize, timeGet1)
resp, err = l.Get(questionMsg, minUDPSize)
wt.AssertNoErr(t, err)
wt.AssertTrue(t, resp == nil, "reponse from the Get() after a Remove()")
t.Logf("Inserting a two replies for the same query")
timePut2 := time.Now()
reply2 := makeAddressReply(questionMsg, question, []net.IP{net.ParseIP("10.0.1.2")})
l.Put(questionMsg, reply2, nullTTL, 0, timePut2)
timePut3 := timePut2.Add(time.Duration(1) * time.Second)
reply3 := makeAddressReply(questionMsg, question, []net.IP{net.ParseIP("10.0.1.3")})
l.Put(questionMsg, reply3, nullTTL, 0, timePut3)
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.2"), 0, 0, 0}}
reply2 := makeAddressReply(questionMsg, question, records)
l.Put(questionMsg, reply2, nullTTL, 0)
clk.Add(time.Duration(1) * time.Second)
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.3"), 0, 0, 0}}
reply3 := makeAddressReply(questionMsg, question, records)
l.Put(questionMsg, reply3, nullTTL, 0)
t.Logf("Checking we get the last one...")
resp, err = l.Get(questionMsg, minUDPSize, timePut3)
resp, err = l.Get(questionMsg, minUDPSize)
wt.AssertNoErr(t, err)
wt.AssertTrue(t, resp != nil, "reponse from the Get()")
t.Logf("Received '%s'", resp.Answer[0])
@@ -142,7 +151,8 @@ func TestCacheEntries(t *testing.T) {
wt.AssertEqualString(t, resp.Answer[0].(*dns.A).A.String(), "10.0.1.3", "IP address")
wt.AssertEqualInt(t, int(resp.Answer[0].Header().Ttl), int(localTTL), "TTL")
resp, err = l.Get(questionMsg, minUDPSize, timePut3.Add(time.Duration(localTTL-1)*time.Second))
clk.Add(time.Duration(localTTL-1) * time.Second)
resp, err = l.Get(questionMsg, minUDPSize)
wt.AssertNoErr(t, err)
wt.AssertTrue(t, resp != nil, "reponse from the Get()")
t.Logf("Received '%s'", resp.Answer[0])
@@ -152,7 +162,8 @@ func TestCacheEntries(t *testing.T) {
t.Logf("Checking we get empty replies when they are expired...")
lenBefore = l.Len()
resp, err = l.Get(questionMsg, minUDPSize, timePut3.Add(time.Duration(localTTL)*time.Second))
clk.Add(time.Duration(localTTL) * time.Second)
resp, err = l.Get(questionMsg, minUDPSize)
wt.AssertNoErr(t, err)
if resp != nil {
t.Logf("Got\n%s", resp.Answer[0])
@@ -166,15 +177,16 @@ func TestCacheEntries(t *testing.T) {
question2 := &questionMsg2.Question[0]
t.Logf("Trying to Get() a name")
resp, err = l.Get(questionMsg2, minUDPSize, time.Now())
resp, err = l.Get(questionMsg2, minUDPSize)
wt.AssertNoErr(t, err)
wt.AssertNil(t, resp, "reponse from Get() yet")
t.Logf("Checking that an Remove() between Get() and Put() does not break things")
replyTemp2 := makeAddressReply(questionMsg2, question2, []net.IP{net.ParseIP("10.0.9.9")})
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}}
replyTemp2 := makeAddressReply(questionMsg2, question2, records)
l.Remove(question2)
l.Put(questionMsg2, replyTemp2, nullTTL, 0, time.Now())
resp, err = l.Get(questionMsg2, minUDPSize, time.Now())
l.Put(questionMsg2, replyTemp2, nullTTL, 0)
resp, err = l.Get(questionMsg2, minUDPSize)
wt.AssertNoErr(t, err)
wt.AssertNotNil(t, resp, "reponse from Get()")
@@ -184,23 +196,33 @@ func TestCacheEntries(t *testing.T) {
question3 := &questionMsg3.Question[0]
t.Logf("Checking that a entry with CacheNoLocalReplies return an error")
timePut3 = time.Now()
l.Put(questionMsg3, nil, nullTTL, CacheNoLocalReplies, timePut3)
resp, err = l.Get(questionMsg3, minUDPSize, timePut3)
l.Put(questionMsg3, nil, negLocalTTL, CacheNoLocalReplies)
resp, err = l.Get(questionMsg3, minUDPSize)
wt.AssertNil(t, resp, "Get() response with CacheNoLocalReplies")
wt.AssertNotNil(t, err, "Get() error with CacheNoLocalReplies")
wt.AssertTrue(t, err == errNoLocalReplies, "Get() error with CacheNoLocalReplies")
timeExpiredGet3 := timePut3.Add(time.Second * time.Duration(negLocalTTL+1))
t.Logf("Checking that we get an expired response after %f seconds", timeExpiredGet3.Sub(timePut3).Seconds())
resp, err = l.Get(questionMsg3, minUDPSize, timeExpiredGet3)
t.Logf("Checking that more Put's do not make the negative TTL longer")
clk.Add(time.Duration(negLocalTTL/2) * time.Second)
l.Put(questionMsg3, nil, negLocalTTL, CacheNoLocalReplies)
clk.Add(time.Duration(1) * time.Second)
l.Put(questionMsg3, nil, negLocalTTL, CacheNoLocalReplies)
clk.Add(time.Duration(1) * time.Second)
l.Put(questionMsg3, nil, negLocalTTL, CacheNoLocalReplies)
resp, err = l.Get(questionMsg3, minUDPSize)
wt.AssertNil(t, resp, "Get() response with CacheNoLocalReplies")
wt.AssertTrue(t, err == errNoLocalReplies, "Get() error with CacheNoLocalReplies")
clk.Add(time.Duration(negLocalTTL/2) * time.Second)
t.Logf("Checking that we get an expired response after %f seconds", negLocalTTL)
resp, err = l.Get(questionMsg3, minUDPSize)
wt.AssertNil(t, resp, "expired Get() response with CacheNoLocalReplies")
wt.AssertNil(t, err, "expired Get() error with CacheNoLocalReplies")
l.Remove(question3)
t.Logf("Checking that Put&Get with CacheNoLocalReplies with a Remove in the middle returns nothing")
l.Put(questionMsg3, nil, nullTTL, CacheNoLocalReplies, time.Now())
l.Put(questionMsg3, nil, nullTTL, CacheNoLocalReplies)
l.Remove(question3)
resp, err = l.Get(questionMsg3, minUDPSize, time.Now())
resp, err = l.Get(questionMsg3, minUDPSize)
wt.AssertNil(t, resp, "Get() reponse with CacheNoLocalReplies")
wt.AssertNil(t, err, "Get() error with CacheNoLocalReplies")
}
+10 -16
View File
@@ -2,7 +2,6 @@ package nameserver
import (
"github.com/miekg/dns"
"net"
)
const (
@@ -34,20 +33,23 @@ func makeTruncatedReply(r *dns.Msg) *dns.Msg {
return reply
}
func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []net.IP) *dns.Msg {
func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []ZoneRecord) *dns.Msg {
answers := make([]dns.RR, len(addrs))
header := makeHeader(r, q)
count := 0
for _, addr := range addrs {
ip := addr.IP()
ip4 := ip.To4()
switch q.Qtype {
case dns.TypeA:
if ip4 := addr.To4(); ip4 != nil {
answers[count] = &dns.A{Hdr: *header, A: addr}
if ip4 != nil {
answers[count] = &dns.A{Hdr: *header, A: ip}
count++
}
case dns.TypeAAAA:
if ip4 := addr.To4(); ip4 == nil {
answers[count] = &dns.AAAA{Hdr: *header, AAAA: addr}
if ip4 == nil {
answers[count] = &dns.AAAA{Hdr: *header, AAAA: ip}
count++
}
}
@@ -55,11 +57,11 @@ func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []net.IP) *dns.Msg {
return makeReply(r, answers[:count])
}
func makePTRReply(r *dns.Msg, q *dns.Question, names []string) *dns.Msg {
func makePTRReply(r *dns.Msg, q *dns.Question, names []ZoneRecord) *dns.Msg {
answers := make([]dns.RR, len(names))
header := makeHeader(r, q)
for i, name := range names {
answers[i] = &dns.PTR{Hdr: *header, Ptr: name}
answers[i] = &dns.PTR{Hdr: *header, Ptr: name.Name()}
}
return makeReply(r, answers)
}
@@ -72,14 +74,6 @@ func makeDNSFailResponse(r *dns.Msg) *dns.Msg {
return m
}
func makeDNSNotImplResponse(r *dns.Msg) *dns.Msg {
m := new(dns.Msg)
m.SetReply(r)
m.RecursionAvailable = true
m.Rcode = dns.RcodeNotImplemented
return m
}
// get the maximum UDP-reply length
func getMaxReplyLen(r *dns.Msg, proto dnsProtocol) int {
maxLen := minUDPSize
+9 -6
View File
@@ -27,7 +27,8 @@ func TestHttp(t *testing.T) {
dockerIP = "9.8.7.6"
)
var zone = new(ZoneDb)
zone := NewZoneDb(DefaultLocalDomain)
port := rand.Intn(10000) + 32768
fmt.Println("Http test on port", port)
go ListenHTTP("", nil, testDomain, zone, port)
@@ -43,11 +44,11 @@ func TestHttp(t *testing.T) {
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Check that the address is now there.
ip, _, _ := net.ParseCIDR(testAddr1)
foundIP, err := zone.LookupName(successTestName)
wt.AssertNoErr(t, err)
ip, _, _ := net.ParseCIDR(testAddr1)
if !foundIP.Equal(ip) {
t.Fatal("Unexpected result for", successTestName, foundIP)
if !foundIP[0].IP().Equal(ip) {
t.Fatalf("Unexpected result for %s: received %s, expected %s", successTestName, foundIP, ip)
}
// Adding exactly the same address should be OK
@@ -70,7 +71,8 @@ func TestHttp(t *testing.T) {
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Check that the address is still resolvable.
_, err = zone.LookupName(successTestName)
x, err := zone.LookupName(successTestName)
t.Logf("Got %s", x)
wt.AssertNoErr(t, err)
// Delete the address record mentioning the other container
@@ -79,7 +81,8 @@ func TestHttp(t *testing.T) {
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Check that the address is gone
_, err = zone.LookupName(successTestName)
x, err = zone.LookupName(successTestName)
t.Logf("Got %s", x)
wt.AssertErrorType(t, err, (*LookupError)(nil), "fully-removed address")
// Would like to shut down the http server at the end of this test
+90 -26
View File
@@ -1,6 +1,8 @@
package nameserver
import (
"bytes"
"fmt"
"github.com/miekg/dns"
"math"
"net"
@@ -27,14 +29,53 @@ var (
)
type Response struct {
Name string
Addr net.IP
Err error
name string
addr net.IP
ttl int
err error
}
func (r Response) Name() string { return r.name }
func (r Response) IP() net.IP { return r.addr }
func (r Response) Priority() int { return 0 }
func (r Response) Weight() int { return 0 }
func (r Response) TTL() int { return r.ttl }
func (r Response) Equal(r2 *Response) bool {
if r.name != r2.name {
return false
}
if !r.addr.Equal(r2.addr) {
return false
}
if r.err != r2.err {
return false
}
return true
}
func (r Response) String() string {
var buf bytes.Buffer
if r.err != nil {
fmt.Fprintf(&buf, "%s", r.err)
} else {
if len(r.Name()) > 0 {
fmt.Fprintf(&buf, "%s", r.Name())
}
if !r.IP().IsUnspecified() {
fmt.Fprintf(&buf, "[%s]", r.IP())
}
if r.ttl > 0 {
fmt.Fprintf(&buf, "(TTL:%d)", r.TTL())
}
}
return buf.String()
}
type responseInfo struct {
timeout time.Time // if no answer by this time, give up
ch chan<- *Response
timeout time.Time // if no answer by this time, give up
insistent bool // insistent queries are not removed on the first reply
ch chan<- *Response
}
// Represents one query that we have sent for one name.
@@ -55,6 +96,7 @@ type MDNSClient struct {
addr *net.UDPAddr
inflight map[string]*inflightQuery
actionChan chan<- MDNSAction
running bool
}
type mDNSQueryInfo struct {
@@ -64,17 +106,21 @@ type mDNSQueryInfo struct {
}
func NewMDNSClient() (*MDNSClient, error) {
if conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}); err != nil {
return nil, err
} else {
return &MDNSClient{
conn: conn,
addr: ipv4Addr,
inflight: make(map[string]*inflightQuery)}, nil
}
return &MDNSClient{
addr: ipv4Addr,
running: false,
inflight: make(map[string]*inflightQuery)}, nil
}
func (c *MDNSClient) Start(ifi *net.Interface) error {
func (c *MDNSClient) Start(ifi *net.Interface) (err error) {
if c.running {
return nil
}
if c.conn, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}); err != nil {
return err
}
multicast, err := LinkLocalMulticastListener(ifi)
if err != nil {
return err
@@ -93,7 +139,13 @@ func (c *MDNSClient) Start(ifi *net.Interface) error {
actionChan := make(chan MDNSAction, MailboxSize)
c.actionChan = actionChan
go c.actorLoop(actionChan)
return nil
}
func (c *MDNSClient) Stop() error {
if c.running {
c.actionChan <- nil
}
return nil
}
@@ -106,7 +158,7 @@ func LinkLocalMulticastListener(ifi *net.Interface) (net.PacketConn, error) {
type MDNSAction func()
// Async
func (c *MDNSClient) SendQuery(name string, querytype uint16, responseCh chan<- *Response) {
func (c *MDNSClient) SendQuery(name string, querytype uint16, insistent bool, responseCh chan<- *Response) {
c.actionChan <- func() {
query, found := c.inflight[name]
if !found {
@@ -115,7 +167,7 @@ func (c *MDNSClient) SendQuery(name string, querytype uint16, responseCh chan<-
m.RecursionDesired = false
buf, err := m.Pack()
if err != nil {
responseCh <- &Response{Err: err}
responseCh <- &Response{err: err}
close(responseCh)
return
}
@@ -124,15 +176,16 @@ func (c *MDNSClient) SendQuery(name string, querytype uint16, responseCh chan<-
id: m.Id,
}
if _, err = c.conn.WriteTo(buf, c.addr); err != nil {
responseCh <- &Response{Err: err}
responseCh <- &Response{err: err}
close(responseCh)
return
}
c.inflight[name] = query
}
info := &responseInfo{
ch: responseCh,
timeout: time.Now().Add(mDNSTimeout),
ch: responseCh,
timeout: time.Now().Add(mDNSTimeout),
insistent: insistent,
}
// Invariant on responseInfos: they are in ascending order of
// timeout. Since we use a fixed interval from Now(), this
@@ -151,20 +204,31 @@ func (c *MDNSClient) ResponseCallback(r *dns.Msg) {
switch rr := answer.(type) {
case *dns.A:
name = rr.Hdr.Name
res = &Response{Addr: rr.A}
res = &Response{name: name, addr: rr.A, ttl: int(rr.Hdr.Ttl)}
case *dns.PTR:
name = rr.Hdr.Name
res = &Response{Name: rr.Ptr}
raddr, _ := raddrToIP(name)
res = &Response{name: rr.Ptr, addr: raddr, ttl: int(rr.Hdr.Ttl)}
default:
return
}
if query, found := c.inflight[name]; found {
var newResponseInfos []*responseInfo
for _, resp := range query.responseInfos {
resp.ch <- res
close(resp.ch)
// insistent queries are not removed on the first reply, but on the timeout
if resp.insistent {
newResponseInfos = append(newResponseInfos, resp)
} else {
close(resp.ch)
}
}
if len(newResponseInfos) == 0 {
delete(c.inflight, name)
} else {
query.responseInfos = newResponseInfos
}
delete(c.inflight, name)
} else {
// We've received a response that didn't match a query
// Do we want to cache it?
@@ -207,13 +271,13 @@ func (c *MDNSClient) checkInFlightQueries() time.Duration {
func (c *MDNSClient) actorLoop(actionChan <-chan MDNSAction) {
timer := time.NewTimer(MaxDuration)
run := func() { timer.Reset(c.checkInFlightQueries()) }
terminate := false
for !terminate {
c.running = true
for c.running {
select {
case action := <-actionChan:
if action == nil {
c.listener.Shutdown()
terminate = true
c.running = false
} else {
action()
run()
+16 -14
View File
@@ -76,6 +76,8 @@ func RunLocalMulticastServer() (*dns.Server, error) {
}
func setup(t *testing.T) (*MDNSClient, *dns.Server, error) {
InitDefaultLogging(testing.Verbose())
server, err := RunLocalMulticastServer()
if err != nil {
t.Fatalf("Unable to run test server: %s. No default multicast interface?", err)
@@ -108,9 +110,9 @@ func (c *testContext) checkResponse(t *testing.T, channelOk bool, resp *Response
c.channel = nil
return
}
wt.AssertNoErr(t, resp.Err)
log.Printf("Got address response %s addr %s", resp.Name, resp.Addr)
c.receivedAddr = resp.Addr
wt.AssertNoErr(t, resp.err)
log.Printf("Got address response %s addr %s", resp.Name(), resp.IP())
c.receivedAddr = resp.IP()
c.receivedCount++
}
@@ -123,7 +125,7 @@ func TestSimpleQuery(t *testing.T) {
context := newTestContext()
// First, a test we expect to succeed
mdnsClient.SendQuery(successTestName, dns.TypeA, context.channel)
mdnsClient.SendQuery(successTestName, dns.TypeA, false, context.channel)
for resp := range context.channel {
context.checkResponse(t, true, resp)
}
@@ -134,7 +136,7 @@ func TestSimpleQuery(t *testing.T) {
// Now, a test we expect to time out with no responses
context = newTestContext()
mdnsClient.SendQuery("test2.weave.", dns.TypeA, context.channel)
mdnsClient.SendQuery("test2.weave.", dns.TypeA, false, context.channel)
for resp := range context.channel {
context.checkResponse(t, true, resp)
}
@@ -153,8 +155,8 @@ func TestParallelQuery(t *testing.T) {
context1 := newTestContext()
context2 := newTestContext()
go mdnsClient.SendQuery(successTestName, dns.TypeA, context1.channel)
go mdnsClient.SendQuery(successTestName, dns.TypeA, context2.channel)
go mdnsClient.SendQuery(successTestName, dns.TypeA, false, context1.channel)
go mdnsClient.SendQuery(successTestName, dns.TypeA, false, context2.channel)
timeout := time.After(2 * time.Second)
outerloop:
for context1.channel != nil || context2.channel != nil {
@@ -178,18 +180,18 @@ func TestAsLookup(t *testing.T) {
defer mdnsClient.Shutdown()
defer server.Shutdown()
ip, err := mdnsClient.LookupName(successTestName)
ips, err := mdnsClient.LookupName(successTestName)
wt.AssertNoErr(t, err)
if !testAddr.Equal(ip) {
t.Fatalf("Returned address incorrect %s", ip)
if !testAddr.Equal(ips[0].IP()) {
t.Fatalf("Returned address incorrect %s", ips)
}
ip, err = mdnsClient.LookupName("foo.example.com.")
ips, err = mdnsClient.LookupName("foo.example.com.")
wt.AssertErrorType(t, err, (*LookupError)(nil), "unknown hostname")
name, err := mdnsClient.LookupInaddr(testInAddr)
names, err := mdnsClient.LookupInaddr(testInAddr)
wt.AssertNoErr(t, err)
if !(successTestName == name) {
t.Fatalf("Expected name %s, got %s", successTestName, name)
if !(successTestName == names[0].Name()) {
t.Fatalf("Expected name %s, got %s", successTestName, names)
}
}
+54 -22
View File
@@ -3,37 +3,69 @@ package nameserver
import (
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"net"
)
func mdnsLookup(client *MDNSClient, name string, qtype uint16) (*Response, error) {
func mdnsLookup(client *MDNSClient, name string, qtype uint16, insistent bool) ([]ZoneRecord, error) {
var responses []*Response
channel := make(chan *Response)
client.SendQuery(name, qtype, channel)
client.SendQuery(name, qtype, insistent, channel)
for resp := range channel {
if err := resp.Err; err != nil {
Debug.Printf("[mdns] Error for query type %s name %s: %s",
dns.TypeToString[qtype], name, err)
if err := resp.err; err != nil {
Debug.Printf("[mdns] Error for query type %s name %s: %s", dns.TypeToString[qtype], name, err)
return nil, err
}
Debug.Printf("[mdns] Got response name for query type %s name %s: name %s, addr %s",
dns.TypeToString[qtype], name, resp.Name, resp.Addr)
return resp, nil
Debug.Printf("[mdns] Got response name for %s-query about name '%s': name '%s', addr '%s'",
dns.TypeToString[qtype], name, resp.name, resp.addr)
if !insistent {
return []ZoneRecord{resp}, nil // non-background queries return the first response...
}
// add the response, avoiding duplicates
// TODO: replace this linear search by something better... (not a big deal as we don't expect long responses)
duplicate := false
for _, r := range responses {
if r.Equal(resp) {
duplicate = true
break
}
}
if !duplicate {
responses = append(responses, resp)
}
}
return nil, LookupError(name)
if len(responses) == 0 {
return nil, LookupError(name)
}
// convert []Response to []ZoneRecord
res := make([]ZoneRecord, len(responses))
for i, rr := range responses {
res[i] = ZoneRecord(rr)
}
return res, nil
}
func (client *MDNSClient) LookupName(name string) (net.IP, error) {
if r, e := mdnsLookup(client, name, dns.TypeA); r != nil {
return r.Addr, nil
} else {
return nil, e
}
// Perform a lookup for a name, returning either an IP address or an error
func (client *MDNSClient) LookupName(name string) ([]ZoneRecord, error) {
return mdnsLookup(client, name, dns.TypeA, false)
}
func (client *MDNSClient) LookupInaddr(inaddr string) (string, error) {
if r, e := mdnsLookup(client, inaddr, dns.TypePTR); r != nil {
return r.Name, nil
} else {
return "", e
}
// Perform a lookup for an IP address, returning either a name or an error
func (client *MDNSClient) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
return mdnsLookup(client, inaddr, dns.TypePTR, false)
}
// Perform lookup for a name, returning either a list of IP addresses or an error
// Insistent queries will wait up to `mDNSTimeout` milliseconds for responses
func (client *MDNSClient) InsistentLookupName(name string) ([]ZoneRecord, error) {
return mdnsLookup(client, name, dns.TypeA, true)
}
// Perform an insistent lookup for an IP address, returning either a list of names or an error
// Insistent queries will wait up to `mDNSTimeout` milliseconds for responses
func (client *MDNSClient) InsistentLookupInaddr(inaddr string) ([]ZoneRecord, error) {
return mdnsLookup(client, inaddr, dns.TypePTR, true)
}
+40 -21
View File
@@ -11,15 +11,13 @@ type MDNSServer struct {
sendconn *net.UDPConn
srv *dns.Server
zone Zone
running bool
}
// Create a new mDNS server
// Nothing will be done (including port bindings) until you `Start()` the server
func NewMDNSServer(zone Zone) (*MDNSServer, error) {
// This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work
sendconn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
return nil, err
}
return &MDNSServer{sendconn: sendconn, zone: zone}, nil
return &MDNSServer{zone: zone}, nil
}
// Return true if testaddr is a UDP address with IP matching my local i/f
@@ -36,7 +34,19 @@ func (s *MDNSServer) addrIsLocal(testaddr net.Addr) bool {
return false
}
func (s *MDNSServer) Start(ifi *net.Interface, localDomain string) error {
// Start the mDNS server
func (s *MDNSServer) Start(ifi *net.Interface) (err error) {
// skip double initialization
if s.running {
return nil
}
// This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work
s.sendconn, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
return err
}
conn, err := LinkLocalMulticastListener(ifi)
if err != nil {
return err
@@ -52,23 +62,23 @@ func (s *MDNSServer) Start(ifi *net.Interface, localDomain string) error {
}
handleLocal := s.makeHandler(dns.TypeA,
func(zone Lookup, r *dns.Msg, q *dns.Question) *dns.Msg {
if ip, err := zone.LookupName(q.Name); err == nil {
return makeAddressReply(r, q, []net.IP{ip})
func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg {
if ips, err := zone.LookupName(q.Name); err == nil {
return makeAddressReply(r, q, ips)
}
return nil
})
handleReverse := s.makeHandler(dns.TypePTR,
func(zone Lookup, r *dns.Msg, q *dns.Question) *dns.Msg {
if name, err := zone.LookupInaddr(q.Name); err == nil {
return makePTRReply(r, q, []string{name})
func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg {
if names, err := zone.LookupInaddr(q.Name); err == nil {
return makePTRReply(r, q, names)
}
return nil
})
mux := dns.NewServeMux()
mux.HandleFunc(localDomain, handleLocal)
mux.HandleFunc(s.zone.Domain(), handleLocal)
mux.HandleFunc(RDNSDomain, handleReverse)
s.srv = &dns.Server{
@@ -77,17 +87,24 @@ func (s *MDNSServer) Start(ifi *net.Interface, localDomain string) error {
Handler: mux,
}
go s.srv.ActivateAndServe()
s.running = true
return err
}
// Stop the mDNS server
func (s *MDNSServer) Stop() error {
s.running = false
return s.srv.Shutdown()
}
type LookupFunc func(Lookup, *dns.Msg, *dns.Question) *dns.Msg
func (s *MDNSServer) Zone() Zone {
return s.zone
}
type LookupFunc func(ZoneLookup, *dns.Msg, *dns.Question) *dns.Msg
func (s *MDNSServer) makeHandler(qtype uint16, lookup LookupFunc) dns.HandlerFunc {
return func(_ dns.ResponseWriter, r *dns.Msg) {
return func(rw dns.ResponseWriter, r *dns.Msg) {
// Handle only questions, ignore answers. We might also ignore
// questions that arise locally (i.e., that come from an IP we
// think is local), but in the interest of avoiding
@@ -95,18 +112,20 @@ func (s *MDNSServer) makeHandler(qtype uint16, lookup LookupFunc) dns.HandlerFun
// assumption that the client wouldn't ask if it already knew
// the answer, and if it does ask, it'll be happy to get an
// answer.
// TODO: use any Answer in the query for adding records in the Zone database...
if len(r.Answer) == 0 && len(r.Question) > 0 {
q := &r.Question[0]
if q.Qtype == qtype {
Debug.Printf("[mdns msgid %d] srv: trying to answer to mDNS query '%s'", r.MsgHdr.Id, q.Name)
if m := lookup(s.zone, r, q); m != nil {
Debug.Printf("[mdns msgid %d] Found local answer to mDNS query %s",
r.MsgHdr.Id, q.Name)
Debug.Printf("[mdns msgid %d] srv: found local answer to mDNS query '%s'", r.MsgHdr.Id, q.Name)
if err := s.sendResponse(m); err != nil {
Warning.Printf("[mdns msgid %d] Error writing to %v",
r.MsgHdr.Id, s.sendconn)
Warning.Printf("[mdns msgid %d] srv: error writing mDNS response to %v", r.MsgHdr.Id, s.sendconn)
} else {
Debug.Printf("[mdns msgid %d] srv: response sent: %d answers", r.MsgHdr.Id, len(m.Answer))
}
} else {
Debug.Printf("[mdns msgid %d] No local answer for mDNS query %s",
Debug.Printf("[mdns msgid %d] srv: no local answer for answering mDNS query '%s'",
r.MsgHdr.Id, q.Name)
}
}
+63 -75
View File
@@ -2,82 +2,51 @@ package nameserver
import (
"github.com/miekg/dns"
"github.com/weaveworks/weave/common"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"log"
"net"
"testing"
"time"
)
var (
containerID = "deadbeef"
testName = "test.weave.local."
testAddr1 = "10.2.2.1/24"
testInAddr1 = "1.2.2.10.in-addr.arpa."
)
func sendQuery(name string, querytype uint16) error {
m := new(dns.Msg)
m.SetQuestion(name, querytype)
m.RecursionDesired = false
buf, err := m.Pack()
if err != nil {
return err
}
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
return err
}
_, err = conn.WriteTo(buf, ipv4Addr)
return err
}
func TestServerSimpleQuery(t *testing.T) {
// The ff can be handy for debugging (obvs)
common.InitDefaultLogging(true)
var (
testRecord1 = Record{"test.weave.local.", net.ParseIP("10.20.20.10"), 0, 0, 0}
testRecord2 = Record{"test.weave.local.", net.ParseIP("10.20.20.20"), 0, 0, 0}
testInAddr1 = "10.20.20.10.in-addr.arpa."
)
log.Println("TestServerSimpleQuery starting")
var zone = new(ZoneDb)
ip, _, _ := net.ParseCIDR(testAddr1)
zone.AddRecord(containerID, testName, ip)
InitDefaultLogging(testing.Verbose())
Info.Println("TestServerSimpleQuery starting")
var receivedAddr net.IP
var receivedName string
var recvChan chan interface{}
mzone := newMockedZoneWithRecords([]ZoneRecord{testRecord1, testRecord2})
mdnsServer, err := NewMDNSServer(mzone)
wt.AssertNoErr(t, err)
err = mdnsServer.Start(nil)
wt.AssertNoErr(t, err)
defer mdnsServer.Stop()
var receivedAddrs []net.IP
receivedName := ""
recvChan := make(chan interface{})
receivedCount := 0
reset := func() {
receivedAddr = nil
receivedName = ""
receivedCount = 0
recvChan = make(chan interface{})
}
wait := func() {
select {
case <-recvChan:
return
case <-time.After(100 * time.Millisecond):
return
}
}
// Implement a minimal listener for responses
multicast, err := LinkLocalMulticastListener(nil)
if err != nil {
t.Fatalf("Unable to run test server: %s. No default multicast interface?", err)
}
wt.AssertNoErr(t, err)
handleMDNS := func(w dns.ResponseWriter, r *dns.Msg) {
// Only handle responses here
if len(r.Answer) > 0 {
t.Logf("Received %d answer(s)", len(r.Answer))
for _, answer := range r.Answer {
switch rr := answer.(type) {
case *dns.A:
receivedAddr = rr.A
t.Logf("... A:\n%+v", rr)
receivedAddrs = append(receivedAddrs, rr.A)
receivedCount++
case *dns.PTR:
t.Logf("... PTR:\n%+v", rr)
receivedName = rr.Ptr
receivedCount++
}
@@ -86,6 +55,33 @@ func TestServerSimpleQuery(t *testing.T) {
}
}
sendQuery := func(name string, querytype uint16) {
receivedAddrs = make([]net.IP, 0)
receivedName = ""
receivedCount = 0
recvChan = make(chan interface{})
m := new(dns.Msg)
m.SetQuestion(name, querytype)
m.RecursionDesired = false
buf, err := m.Pack()
wt.AssertNoErr(t, err)
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
wt.AssertNoErr(t, err)
Debug.Printf("Sending UDP packet to %s", ipv4Addr)
_, err = conn.WriteTo(buf, ipv4Addr)
wt.AssertNoErr(t, err)
Debug.Printf("Waiting for response")
select {
case <-recvChan:
return
case <-time.After(100 * time.Millisecond):
Debug.Printf("Timeout while waiting for response")
return
}
}
listener := &dns.Server{
Unsafe: true,
PacketConn: multicast,
@@ -94,39 +90,31 @@ func TestServerSimpleQuery(t *testing.T) {
go listener.ActivateAndServe()
defer listener.Shutdown()
mdnsServer, err := NewMDNSServer(zone)
wt.AssertNoErr(t, err)
err = mdnsServer.Start(nil, DefaultLocalDomain)
wt.AssertNoErr(t, err)
time.Sleep(100 * time.Millisecond) // Allow for server to get going
reset()
sendQuery(testName, dns.TypeA)
wait()
if receivedCount != 1 {
t.Fatalf("Unexpected result count %d for %s", receivedCount, testName)
Debug.Printf("Checking that we get 2 IPs fo name '%s' [A]", testRecord1.Name())
sendQuery(testRecord1.Name(), dns.TypeA)
if receivedCount != 2 {
t.Fatalf("Unexpected result count %d for %s", receivedCount, testRecord1.Name())
}
if !receivedAddr.Equal(ip) {
t.Fatalf("Unexpected result %s for %s", receivedAddr, testName)
if !(receivedAddrs[0].Equal(testRecord1.IP()) || receivedAddrs[0].Equal(testRecord2.IP())) {
t.Fatalf("Unexpected result %s for %s", receivedAddrs, testRecord1.Name())
}
if !(receivedAddrs[1].Equal(testRecord1.IP()) || receivedAddrs[1].Equal(testRecord2.IP())) {
t.Fatalf("Unexpected result %s for %s", receivedAddrs, testRecord1.Name())
}
reset()
Debug.Printf("Checking that 'testfail.weave.' [A] gets no answers")
sendQuery("testfail.weave.", dns.TypeA)
wait()
if receivedCount != 0 {
t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
}
reset()
Debug.Printf("Checking that '%s' [PTR] gets one name", testInAddr1)
sendQuery(testInAddr1, dns.TypePTR)
wait()
if receivedCount != 1 {
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
} else if !(testName == receivedName) {
t.Fatalf("Expected answer %s to query for %s, got %s", testName, testInAddr1, receivedName)
} else if !(testRecord1.Name() == receivedName) {
t.Fatalf("Expected answer %s to query for %s, got %s", testRecord1.Name(), testInAddr1, receivedName)
}
}
+91
View File
@@ -0,0 +1,91 @@
package nameserver
import (
. "github.com/weaveworks/weave/common"
"net"
"sync"
)
const (
testSocketTimeout = 100 // in millisecs
)
// Warn about some methods that some day should be implemented...
func notImplWarn() { Warning.Printf("Mocked method. Not implemented.") }
// A mocked Zone that always returns the same records
// * it does not send/receive any mDNS query
type mockedZoneWithRecords struct {
sync.RWMutex
records []ZoneRecord
// Statistics
NumLookupsName int
NumLookupsInaddr int
}
func newMockedZoneWithRecords(zr []ZoneRecord) *mockedZoneWithRecords {
return &mockedZoneWithRecords{records: zr}
}
func (mz *mockedZoneWithRecords) Domain() string { return DefaultLocalDomain }
func (mz *mockedZoneWithRecords) LookupName(name string) ([]ZoneRecord, error) {
Debug.Printf("[mocked zone]: LookupName: returning records %s", mz.records)
mz.Lock()
defer mz.Unlock()
mz.NumLookupsName++
var res []ZoneRecord
for _, r := range mz.records {
if r.Name() == name {
res = append(res, r)
}
}
return res, nil
}
func (mz *mockedZoneWithRecords) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
Debug.Printf("[mocked zone]: LookupInaddr: returning records %s", mz.records)
mz.Lock()
defer mz.Unlock()
mz.NumLookupsInaddr++
var res []ZoneRecord
for _, r := range mz.records {
revIP, err := raddrToIP(inaddr)
if err != nil {
return nil, newParseError("lookup address", inaddr)
}
if r.IP().Equal(revIP) {
res = append(res, r)
}
}
return res, nil
}
func (mz *mockedZoneWithRecords) DomainLookupName(name string) ([]ZoneRecord, error) {
return mz.LookupName(name)
}
func (mz *mockedZoneWithRecords) DomainLookupInaddr(inaddr string) ([]ZoneRecord, error) {
return mz.LookupInaddr(inaddr)
}
// the following methods are not currently needed...
func (mz *mockedZoneWithRecords) AddRecord(ident string, name string, ip net.IP) error {
notImplWarn()
return nil
}
func (mz *mockedZoneWithRecords) DeleteRecord(ident string, ip net.IP) error {
notImplWarn()
return nil
}
func (mz *mockedZoneWithRecords) DeleteRecordsFor(ident string) error { notImplWarn(); return nil }
func (mz *mockedZoneWithRecords) Status() string { notImplWarn(); return "nothing" }
func (mz *mockedZoneWithRecords) ObserveName(name string, observer ZoneRecordObserver) error {
notImplWarn()
return nil
}
func (mz *mockedZoneWithRecords) ObserveInaddr(inaddr string, observer ZoneRecordObserver) error {
notImplWarn()
return nil
}
+48 -74
View File
@@ -7,7 +7,6 @@ import (
. "github.com/weaveworks/weave/common"
"net"
"sync"
"time"
)
const (
@@ -130,7 +129,7 @@ func NewDNSServer(config DNSServerConfig, zone Zone, iface *net.Interface) (s *D
cacheLen = config.CacheLen
}
Debug.Printf("[dns] Initializing cache: %d entries", config.CacheLen)
s.cache, err = NewCache(cacheLen)
s.cache, err = NewCache(cacheLen, nil)
if err != nil {
return
}
@@ -156,7 +155,7 @@ func (s *DNSServer) Start() error {
Info.Printf("Using mDNS on %v", s.Iface)
err := s.mdnsCli.Start(s.Iface)
CheckFatal(err)
err = s.mdnsSrv.Start(s.Iface, s.Domain)
err = s.mdnsSrv.Start(s.Iface)
CheckFatal(err)
s.listenersWg.Add(2)
@@ -215,75 +214,53 @@ func (s *DNSServer) Stop() error {
}
func (s *DNSServer) queryHandler(proto dnsProtocol) dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
now := time.Now()
q := r.Question[0]
maxLen := getMaxReplyLen(r, proto)
lookups := []Lookup{s.Zone, s.mdnsCli}
Debug.Printf("Query: %+v", q)
reply, err := s.cache.Get(r, maxLen, time.Now())
zoneLookup := func(lookup ZoneLookup, q *dns.Question, r *dns.Msg) (*dns.Msg, []ZoneRecord, error) {
ips, err := lookup.LookupName(q.Name)
if err != nil {
if err == errNoLocalReplies {
Debug.Printf("[dns msgid %d] Cached 'no local replies' - skipping local lookup", r.MsgHdr.Id)
lookups = []Lookup{s.Zone}
} else {
Debug.Printf("[dns msgid %d] Error from cache: %s", r.MsgHdr.Id, err)
w.WriteMsg(makeDNSFailResponse(r))
return
}
}
if reply != nil {
Debug.Printf("[dns msgid %d] Returning reply from cache: %s/%d answers",
r.MsgHdr.Id, dns.RcodeToString[reply.MsgHdr.Rcode], len(reply.Answer))
w.WriteMsg(reply)
return
return nil, nil, err
}
return makeAddressReply(r, q, ips), ips, nil
}
// catch unsupported queries
if q.Qtype != dns.TypeA {
Debug.Printf("[dns msgid %d] Unsuported query type %s", r.MsgHdr.Id, dns.TypeToString[q.Qtype])
m := makeDNSNotImplResponse(r)
s.cache.Put(r, m, negLocalTTL, 0, now)
w.WriteMsg(m)
return
}
for _, lookup := range lookups {
if ip, err := lookup.LookupName(q.Name); err == nil {
m := makeAddressReply(r, &q, []net.IP{ip})
m.Authoritative = true
Debug.Printf("[dns msgid %d] Caching response for %s-query for \"%s\": %s [code:%s]",
m.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name, ip, dns.RcodeToString[m.Rcode])
s.cache.Put(r, m, nullTTL, 0, now)
w.WriteMsg(m)
return
}
now = time.Now()
}
Info.Printf("[dns msgid %d] No results for type %s query %s [caching no-local]",
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
s.cache.Put(r, nil, negLocalTTL, CacheNoLocalReplies, now)
fallback := func(w dns.ResponseWriter, r *dns.Msg) {
w.WriteMsg(makeDNSFailResponse(r))
}
return s.commonQueryHandler(proto, "Query", dns.TypeA, zoneLookup, fallback)
}
func (s *DNSServer) rdnsHandler(proto dnsProtocol) dns.HandlerFunc {
fallback := s.notUsHandler(proto)
return func(w dns.ResponseWriter, r *dns.Msg) {
now := time.Now()
q := r.Question[0]
lookups := []Lookup{s.Zone, s.mdnsCli}
maxLen := getMaxReplyLen(r, proto)
zoneLookup := func(lookup ZoneLookup, q *dns.Question, r *dns.Msg) (*dns.Msg, []ZoneRecord, error) {
names, err := lookup.LookupInaddr(q.Name)
if err != nil {
return nil, nil, err
}
return makePTRReply(r, q, names), names, nil
}
Debug.Printf("Reverse query: %+v", q)
reply, err := s.cache.Get(r, maxLen, time.Now())
notUsHandler := s.notUsHandler(proto)
fallback := func(w dns.ResponseWriter, r *dns.Msg) {
Info.Printf("[dns msgid %d] -> sending to fallback server", r.MsgHdr.Id)
notUsHandler(w, r)
}
return s.commonQueryHandler(proto, "Reverse query", dns.TypePTR, zoneLookup, fallback)
}
func (s *DNSServer) commonQueryHandler(proto dnsProtocol, kind string, qtype uint16,
zoneLookup func(ZoneLookup, *dns.Question, *dns.Msg) (*dns.Msg, []ZoneRecord, error), fallback dns.HandlerFunc) dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
q := r.Question[0]
Debug.Printf("[dns msgid %d] %s: %+v", r.MsgHdr.Id, kind, q)
maxLen := getMaxReplyLen(r, proto)
lookups := []ZoneLookup{s.Zone, s.mdnsCli}
reply, err := s.cache.Get(r, maxLen)
if err != nil {
if err == errNoLocalReplies {
Debug.Printf("[dns msgid %d] Cached 'no local replies' - skipping local lookup", r.MsgHdr.Id)
lookups = []Lookup{s.Zone}
lookups = []ZoneLookup{s.Zone}
} else {
Debug.Printf("[dns msgid %d] Error from cache: %s", r.MsgHdr.Id, err)
w.WriteMsg(makeDNSFailResponse(r))
@@ -298,32 +275,29 @@ func (s *DNSServer) rdnsHandler(proto dnsProtocol) dns.HandlerFunc {
}
// catch unsupported queries
if q.Qtype != dns.TypePTR {
Warning.Printf("[dns msgid %d] Unexpected reverse query type %s: %+v",
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q)
m := makeDNSNotImplResponse(r)
s.cache.Put(r, m, negLocalTTL, 0, now)
if q.Qtype != qtype {
Debug.Printf("[dns msgid %d] Unsupported query type %s", r.MsgHdr.Id, dns.TypeToString[q.Qtype])
m := makeDNSFailResponse(r)
s.cache.Put(r, m, negLocalTTL, 0)
w.WriteMsg(m)
return
}
for _, lookup := range lookups {
if name, err := lookup.LookupInaddr(q.Name); err == nil {
m := makePTRReply(r, &q, []string{name})
if m, answers, err := zoneLookup(lookup, &q, r); err == nil {
m.Authoritative = true
Debug.Printf("[dns msgid %d] Caching response for %s-query for \"%s\": %s [code:%s]",
m.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name, name, dns.RcodeToString[m.Rcode])
s.cache.Put(r, m, nullTTL, 0, now)
Debug.Printf("[dns msgid %d] Caching response for type %s query for '%s': %s [code:%s]",
m.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name, answers, dns.RcodeToString[m.Rcode])
s.cache.Put(r, m, nullTTL, 0)
w.WriteMsg(m)
return
}
now = time.Now()
}
Info.Printf("[dns msgid %d] No results for %s-query about '%s' [caching no-local] -> sending to fallback server",
Info.Printf("[dns msgid %d] No results for type %s query for '%s' [caching no-local]",
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
s.cache.Put(r, nil, negLocalTTL, CacheNoLocalReplies, now)
s.cache.Put(r, nil, negLocalTTL, CacheNoLocalReplies)
fallback(w, r)
}
}
+7 -6
View File
@@ -36,6 +36,7 @@ func setupForTest(t *testing.T) {
func TestUDPDNSServer(t *testing.T) {
setupForTest(t)
const (
containerID = "foobar"
successTestName = "test1.weave.local."
failTestName = "test2.weave.local."
nonLocalName = "weave.works."
@@ -44,7 +45,7 @@ func TestUDPDNSServer(t *testing.T) {
testCIDR1 := testAddr1 + "/24"
InitDefaultLogging(true)
var zone = new(ZoneDb)
var zone = NewZoneDb(DefaultLocalDomain)
ip, _, _ := net.ParseCIDR(testCIDR1)
zone.AddRecord(containerID, successTestName, ip)
@@ -100,8 +101,8 @@ func TestUDPDNSServer(t *testing.T) {
assertExchange(t, testRDNSfail, dns.TypePTR, 0, 0, dns.RcodeNameError)
// This should fail because we don't handle MX records
assertExchange(t, successTestName, dns.TypeMX, 0, 0, dns.RcodeNotImplemented)
// This should fail because we don't have information about MX records
assertExchange(t, successTestName, dns.TypeMX, 0, 0, dns.RcodeNameError)
// This non-local query for an MX record should succeed by being
// passed on to the fallback server
@@ -125,14 +126,14 @@ func TestTCPDNSServer(t *testing.T) {
dnsAddr := fmt.Sprintf("localhost:%d", testPort)
InitDefaultLogging(true)
var zone = new(ZoneDb)
var zone = NewZoneDb(DefaultLocalDomain)
// generate a list of `numAnswers` IP addresses
var addrs []net.IP
var addrs []ZoneRecord
bs := make([]byte, 4)
for i := 0; i < numAnswers; i++ {
binary.LittleEndian.PutUint32(bs, uint32(i))
addrs = append(addrs, net.IPv4(bs[0], bs[1], bs[2], bs[3]))
addrs = append(addrs, Record{"", net.IPv4(bs[0], bs[1], bs[2], bs[3]), 0, 0, 0})
}
// handler for the fallback server: it will just return a very long response
+26 -19
View File
@@ -16,19 +16,15 @@ const (
// +1 to also exclude a dot
var rdnsDomainLen = len(RDNSDomain) + 1
type Lookup interface {
LookupName(name string) (net.IP, error)
LookupInaddr(inaddr string) (string, error)
}
type Zone interface {
AddRecord(ident string, name string, ip net.IP) error
DeleteRecord(ident string, ip net.IP) error
DeleteRecordsFor(ident string) error
Lookup
Domain() string
ZoneLookup
}
type Record struct {
type dbRecord struct {
Ident string
Name string
IP net.IP
@@ -37,8 +33,9 @@ type Record struct {
// Very simple data structure for now, with linear searching.
// TODO: make more sophisticated to improve performance.
type ZoneDb struct {
mx sync.RWMutex
recs []Record
mx sync.RWMutex
recs []dbRecord
domain string
}
type LookupError string
@@ -54,7 +51,17 @@ func (dup DuplicateError) Error() string {
return "Tried to add a duplicate entry"
}
func (zone *ZoneDb) indexOf(match func(Record) bool) int {
func NewZoneDb(domain string) *ZoneDb {
return &ZoneDb{
domain: domain,
}
}
func (zone *ZoneDb) Domain() string {
return zone.domain
}
func (zone *ZoneDb) indexOf(match func(dbRecord) bool) int {
for i, r := range zone.recs {
if match(r) {
return i
@@ -73,18 +80,18 @@ func (zone *ZoneDb) String() string {
return buf.String()
}
func (zone *ZoneDb) LookupName(name string) (net.IP, error) {
func (zone *ZoneDb) LookupName(name string) ([]ZoneRecord, error) {
zone.mx.RLock()
defer zone.mx.RUnlock()
for _, r := range zone.recs {
if r.Name == name {
return r.IP, nil
return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
}
}
return nil, LookupError(name)
}
func (zone *ZoneDb) LookupInaddr(inaddr string) (string, error) {
func (zone *ZoneDb) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
if revIP := net.ParseIP(inaddr[:len(inaddr)-rdnsDomainLen]); revIP != nil {
revIP4 := revIP.To4()
ip := []byte{revIP4[3], revIP4[2], revIP4[1], revIP4[0]}
@@ -93,31 +100,31 @@ func (zone *ZoneDb) LookupInaddr(inaddr string) (string, error) {
defer zone.mx.RUnlock()
for _, r := range zone.recs {
if r.IP.Equal(ip) {
return r.Name, nil
return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
}
}
return "", LookupError(inaddr)
return nil, LookupError(inaddr)
}
Warning.Printf("[zonedb] Asked to reverse lookup %s", inaddr)
return "", LookupError(inaddr)
return nil, LookupError(inaddr)
}
func (zone *ZoneDb) AddRecord(ident string, name string, ip net.IP) error {
zone.mx.Lock()
defer zone.mx.Unlock()
fqdn := dns.Fqdn(name)
pred := func(r Record) bool { return r.Name == fqdn && r.IP.Equal(ip) && r.Ident == ident }
pred := func(r dbRecord) bool { return r.Name == fqdn && r.IP.Equal(ip) && r.Ident == ident }
if index := zone.indexOf(pred); index != -1 {
return DuplicateError{}
}
zone.recs = append(zone.recs, Record{ident, fqdn, ip})
zone.recs = append(zone.recs, dbRecord{ident, fqdn, ip})
return nil
}
func (zone *ZoneDb) DeleteRecord(ident string, ip net.IP) error {
zone.mx.Lock()
defer zone.mx.Unlock()
pred := func(r Record) bool { return r.Ident == ident && r.IP.Equal(ip) }
pred := func(r dbRecord) bool { return r.Ident == ident && r.IP.Equal(ip) }
if index := zone.indexOf(pred); index != -1 {
zone.recs = append(zone.recs[:index], zone.recs[index+1:]...)
return nil
+4 -4
View File
@@ -14,7 +14,7 @@ func TestZone(t *testing.T) {
testAddr1 = "10.2.2.1/24"
)
var zone = new(ZoneDb)
var zone = NewZoneDb(DefaultLocalDomain)
ip, _, _ := net.ParseCIDR(testAddr1)
err := zone.AddRecord(containerID, successTestName, ip)
@@ -30,7 +30,7 @@ func TestZone(t *testing.T) {
foundIP, err := zone.LookupName(successTestName)
wt.AssertNoErr(t, err)
if !foundIP.Equal(ip) {
if !foundIP[0].IP().Equal(ip) {
t.Fatal("Unexpected result for", successTestName, foundIP)
}
@@ -38,7 +38,7 @@ func TestZone(t *testing.T) {
foundName, err := zone.LookupInaddr("1.2.2.10.in-addr.arpa.")
wt.AssertNoErr(t, err)
if foundName != successTestName {
if foundName[0].Name() != successTestName {
t.Fatal("Unexpected result for", ip, foundName)
}
@@ -72,7 +72,7 @@ func TestDeleteFor(t *testing.T) {
addr1 = "10.2.2.3/24"
addr2 = "10.2.7.8/24"
)
zone := new(ZoneDb)
zone := NewZoneDb(DefaultLocalDomain)
for _, addr := range []string{addr1, addr2} {
ip, _, _ := net.ParseCIDR(addr)
err := zone.AddRecord(id, name, ip)
+170
View File
@@ -0,0 +1,170 @@
package proxy
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
type client struct {
interceptor
Dial func() (net.Conn, error)
}
func newClient(dial func() (net.Conn, error), i interceptor) *client {
return &client{
interceptor: i,
Dial: dial,
}
}
func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := c.InterceptRequest(r); err != nil {
_, isNoSuchContainer := err.(*docker.NoSuchContainer)
switch {
case isNoSuchContainer:
http.Error(w, err.Error(), http.StatusNotFound)
case err == docker.ErrNoSuchImage:
http.Error(w, err.Error(), http.StatusNotFound)
default:
http.Error(w, err.Error(), http.StatusInternalServerError)
Warning.Print("Error intercepting request: ", err)
}
return
}
conn, err := c.Dial()
if err != nil {
http.Error(w, "Could not connect to target", http.StatusInternalServerError)
Warning.Print(err)
return
}
client := httputil.NewClientConn(conn, nil)
defer client.Close()
resp, err := client.Do(r)
if err != nil && err != httputil.ErrPersistEOF {
http.Error(w, fmt.Sprintf("Could not make request to target: %v", err), http.StatusInternalServerError)
Warning.Print("Error forwarding request: ", err)
return
}
err = c.InterceptResponse(resp)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
Warning.Print("Error intercepting response: ", err)
return
}
hdr := w.Header()
for k, vs := range resp.Header {
for _, v := range vs {
hdr.Add(k, v)
}
}
Debug.Printf("Response from target: %s %v", resp.Status, w.Header())
if resp.Header.Get("Content-Type") == "application/vnd.docker.raw-stream" {
doRawStream(w, resp, client)
} else if resp.TransferEncoding != nil && resp.TransferEncoding[0] == "chunked" {
doChunkedResponse(w, resp, client)
} else {
w.WriteHeader(resp.StatusCode)
if _, err := io.Copy(w, resp.Body); err != nil {
Warning.Print(err)
}
}
}
func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
down, downBuf, up, rem, err := hijack(w, client)
if err != nil {
http.Error(w, "Unable to hijack connection for raw stream mode", http.StatusInternalServerError)
return
}
defer down.Close()
defer up.Close()
if _, err := down.Write([]byte("HTTP/1.1 200 OK\n")); err != nil {
Warning.Print(err)
return
}
if err := resp.Header.Write(down); err != nil {
Warning.Print(err)
return
}
if _, err := down.Write([]byte("\n")); err != nil {
Warning.Print(err)
return
}
upDone := make(chan struct{})
downDone := make(chan struct{})
go copyStream(down, io.MultiReader(rem, up), upDone)
go copyStream(up, downBuf, downDone)
<-upDone
<-downDone
}
func copyStream(dst io.Writer, src io.Reader, done chan struct{}) {
defer close(done)
if _, err := io.Copy(dst, src); err != nil {
Warning.Print(err)
}
if c, ok := dst.(interface {
CloseWrite() error
}); ok {
if err := c.CloseWrite(); err != nil {
Warning.Printf("Error closing connection: %s", err)
}
}
}
func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
// Because we can't go back to request/response after we
// hijack the connection, we need to close it and make the
// client open another.
w.Header().Add("Connection", "close")
w.WriteHeader(resp.StatusCode)
down, _, up, rem, err := hijack(w, client)
if err != nil {
http.Error(w, "Unable to hijack response stream for chunked response", http.StatusInternalServerError)
return
}
defer up.Close()
defer down.Close()
// Copy the chunked response body to downstream,
// stopping at the end of the chunked section.
rawResponseBody := io.MultiReader(rem, up)
if _, err := io.Copy(ioutil.Discard, httputil.NewChunkedReader(io.TeeReader(rawResponseBody, down))); err != nil {
http.Error(w, "Error copying chunked response body", http.StatusInternalServerError)
return
}
resp.Trailer.Write(down)
// a chunked response ends with a CRLF
down.Write([]byte("\r\n"))
}
func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn, downBuf *bufio.ReadWriter, up net.Conn, rem io.Reader, err error) {
hj, ok := w.(http.Hijacker)
if !ok {
err = errors.New("Unable to cast to Hijack")
return
}
down, downBuf, err = hj.Hijack()
if err != nil {
return
}
up, rem = client.Hijack()
return
}
+64
View File
@@ -0,0 +1,64 @@
package proxy
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os/exec"
"regexp"
"strings"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
var (
containerIDRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/([^/]*)/.*")
weaveWaitEntrypoint = []string{"/home/weavewait/weavewait"}
)
func callWeave(args ...string) ([]byte, error) {
args = append([]string{"--local"}, args...)
Debug.Print("Calling weave", args)
cmd := exec.Command("./weave", args...)
cmd.Env = []string{"PROCFS=/hostproc", "PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}
out, err := cmd.CombinedOutput()
return out, err
}
func weaveCIDRsFromConfig(config *docker.Config) ([]string, bool) {
for _, e := range config.Env {
if strings.HasPrefix(e, "WEAVE_CIDR=") {
return strings.Fields(e[11:]), true
}
}
return nil, false
}
func marshalRequestBody(r *http.Request, body interface{}) error {
newBody, err := json.Marshal(body)
if err != nil {
return err
}
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
r.ContentLength = int64(len(newBody))
return nil
}
func inspectContainerInPath(client *docker.Client, path string) (*docker.Container, error) {
subs := containerIDRegexp.FindStringSubmatch(path)
if subs == nil {
err := fmt.Errorf("No container id found in request with path %s", path)
Warning.Println(err)
return nil, err
}
containerID := subs[2]
container, err := client.InspectContainer(containerID)
if err != nil {
Warning.Printf("Error inspecting container %s: %v", containerID, err)
}
return container, err
}
+108
View File
@@ -0,0 +1,108 @@
package proxy
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
type createContainerInterceptor struct {
client *docker.Client
withDNS bool
dockerBridgeIP string
withIPAM bool
}
type createContainerRequestBody struct {
*docker.Config
HostConfig *docker.HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
}
func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
container := createContainerRequestBody{}
if err := json.Unmarshal(body, &container); err != nil {
return err
}
if cidrs, ok := weaveCIDRsFromConfig(container.Config); ok || i.withIPAM {
Info.Printf("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " "))
if container.HostConfig == nil {
container.HostConfig = &docker.HostConfig{}
}
container.HostConfig.VolumesFrom = append(container.HostConfig.VolumesFrom, "weaveproxy")
if container.Config == nil {
container.Config = &docker.Config{}
}
if err := i.setWeaveWaitEntrypoint(container.Config); err != nil {
return err
}
if err := i.setWeaveDNS(&container); err != nil {
return err
}
}
newBody, err := json.Marshal(container)
if err != nil {
return err
}
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
r.ContentLength = int64(len(newBody))
return nil
}
func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error {
if len(container.Entrypoint) == 0 {
image, err := i.client.InspectImage(container.Image)
if err != nil {
return err
}
if len(container.Cmd) == 0 {
container.Cmd = image.Config.Cmd
}
if container.Entrypoint == nil {
container.Entrypoint = image.Config.Entrypoint
}
}
container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...)
return nil
}
func configHasEntrypoint(c *docker.Config) bool {
return c != nil && len(c.Entrypoint) > 0
}
func configHasCmd(c *docker.Config) bool {
return c != nil && len(c.Cmd) > 0
}
func (i *createContainerInterceptor) setWeaveDNS(container *createContainerRequestBody) error {
if !i.withDNS {
return nil
}
container.HostConfig.DNS = append(container.HostConfig.DNS, i.dockerBridgeIP)
if len(container.HostConfig.DNSSearch) == 0 {
container.HostConfig.DNSSearch = []string{"."}
}
return nil
}
func (i *createContainerInterceptor) InterceptResponse(r *http.Response) error {
return nil
}
+55
View File
@@ -0,0 +1,55 @@
package proxy
import (
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
type createExecInterceptor struct {
client *docker.Client
withIPAM bool
}
type createExecRequestBody struct {
*docker.Config
HostConfig *docker.HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
}
func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
options := docker.CreateExecOptions{}
if err := json.Unmarshal(body, &options); err != nil {
return err
}
container, err := inspectContainerInPath(i.client, r.URL.Path)
if err != nil {
return err
}
if cidrs, ok := weaveCIDRsFromConfig(container.Config); ok || i.withIPAM {
Info.Printf("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " "))
options.Cmd = append(weaveWaitEntrypoint, options.Cmd...)
}
if err := marshalRequestBody(r, options); err != nil {
return err
}
return nil
}
func (i *createExecInterceptor) InterceptResponse(r *http.Response) error {
return nil
}
+19
View File
@@ -0,0 +1,19 @@
package proxy
import "net/http"
type interceptor interface {
InterceptRequest(*http.Request) error
InterceptResponse(*http.Response) error
}
type nullInterceptor struct {
}
func (i nullInterceptor) InterceptRequest(r *http.Request) error {
return nil
}
func (i nullInterceptor) InterceptResponse(r *http.Response) error {
return nil
}
+85
View File
@@ -0,0 +1,85 @@
package proxy
import (
"net"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
var (
containerCreateRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/create$")
containerStartRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/[^/]*/(re)?start$")
execCreateRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/[^/]*/exec$")
)
type Proxy struct {
Dial func() (net.Conn, error)
client *docker.Client
withDNS bool
dockerBridgeIP string
withIPAM bool
}
func NewProxy(targetURL string, withDNS, withIPAM bool) (*Proxy, error) {
u, err := url.Parse(targetURL)
if err != nil {
return nil, err
}
var dockerBridgeIP []byte
if withDNS {
dockerBridgeIP, err = callWeave("docker-bridge-ip")
if err != nil {
return nil, err
}
}
client, err := docker.NewClient(targetURL)
if err != nil {
return nil, err
}
targetAddr := ""
switch u.Scheme {
case "tcp":
targetAddr = u.Host
case "unix":
targetAddr = u.Path
}
return &Proxy{
Dial: func() (net.Conn, error) {
return net.Dial(u.Scheme, targetAddr)
},
client: client,
withDNS: withDNS,
dockerBridgeIP: string(dockerBridgeIP),
withIPAM: withIPAM,
}, nil
}
func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Info.Printf("%s %s", r.Method, r.URL)
path := r.URL.Path
switch {
case containerCreateRegexp.MatchString(path):
proxy.serveWithInterceptor(&createContainerInterceptor{proxy.client, proxy.withDNS, proxy.dockerBridgeIP, proxy.withIPAM}, w, r)
case containerStartRegexp.MatchString(path):
proxy.serveWithInterceptor(&startContainerInterceptor{proxy.client, proxy.withDNS, proxy.withIPAM}, w, r)
case execCreateRegexp.MatchString(path):
proxy.serveWithInterceptor(&createExecInterceptor{proxy.client, proxy.withIPAM}, w, r)
case strings.HasPrefix(path, "/weave"):
w.WriteHeader(http.StatusOK)
default:
proxy.serveWithInterceptor(&nullInterceptor{}, w, r)
}
}
func (proxy *Proxy) serveWithInterceptor(i interceptor, w http.ResponseWriter, r *http.Request) {
newClient(proxy.Dial, i).ServeHTTP(w, r)
}
+43
View File
@@ -0,0 +1,43 @@
package proxy
import (
"errors"
"net/http"
"strings"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
type startContainerInterceptor struct {
client *docker.Client
withDNS bool
withIPAM bool
}
func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error {
return nil
}
func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
container, err := inspectContainerInPath(i.client, r.Request.URL.Path)
if err != nil {
return err
}
cidrs, ok := weaveCIDRsFromConfig(container.Config)
if !ok && !i.withIPAM {
Debug.Print("No Weave CIDR, ignoring")
return nil
}
Info.Printf("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
args := []string{"attach"}
args = append(args, cidrs...)
args = append(args, container.ID)
if output, err := callWeave(args...); err != nil {
i.client.KillContainer(docker.KillContainerOptions{ID: container.ID})
Warning.Printf("Attaching container %s to weave network failed: %s", container.ID, string(output))
return errors.New(string(output))
}
return nil
}
+14 -27
View File
@@ -82,17 +82,17 @@ func (conn *RemoteConnection) BreakTie(Connection) ConnectionTieBreak { return T
func (conn *RemoteConnection) Shutdown(error) {}
func (conn *RemoteConnection) Log(args ...interface{}) {
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote.FullName())), args...)...)
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
}
func (conn *RemoteConnection) String() string {
from := "<nil>"
if conn.local != nil {
from = conn.local.FullName()
from = conn.local.String()
}
to := "<nil>"
if conn.remote != nil {
to = conn.remote.FullName()
to = conn.remote.String()
}
return fmt.Sprint("Connection ", from, "->", to)
}
@@ -196,14 +196,13 @@ func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
// 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() {
if err == nil {
conn.sendAction(nil)
} else {
conn.sendAction(func() error { return err })
}
}()
go func() { conn.sendAction(func() error { return err }) }()
}
// Async
@@ -231,7 +230,7 @@ func (conn *LocalConnection) ReceivedHeartbeat(remoteUDPAddr *net.UDPAddr, connU
if oldRemoteUDPAddr == nil {
return conn.sendFastHeartbeats()
} else if oldRemoteUDPAddr.String() != remoteUDPAddr.String() {
log.Println("Peer", conn.remote.FullName(), "moved from", old, "to", remoteUDPAddr)
log.Println("Peer", conn.remote, "moved from", old, "to", remoteUDPAddr)
}
return nil
})
@@ -379,9 +378,6 @@ func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err
for err == nil {
select {
case action := <-actionChan:
if action == nil {
break
}
err = action()
case <-conn.heartbeatTCP.C:
err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat)
@@ -398,13 +394,10 @@ func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err
}
func (conn *LocalConnection) shutdown(err error) {
switch {
case err != nil && conn.remote == nil:
if conn.remote == nil {
log.Printf("->[%s] connection shutting down due to error during handshake: %v\n", conn.remoteTCPAddr, err)
case err != nil:
} else {
conn.Log("connection shutting down due to error:", err)
default:
conn.Log("connection shutting down")
}
if conn.TCPConn != nil {
@@ -412,7 +405,7 @@ func (conn *LocalConnection) shutdown(err error) {
}
if conn.remote != nil {
conn.remote.DecrementLocalRefCount()
conn.Router.Peers.Dereference(conn.remote)
conn.Router.Ourself.DeleteConnection(conn)
}
@@ -442,11 +435,10 @@ func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
}
func (conn *LocalConnection) receiveTCP(decoder *gob.Decoder) {
defer conn.Decryptor.Shutdown()
usingPassword := conn.SessionKey != nil
var receiver TCPReceiver
if usingPassword {
receiver = NewEncryptedTCPReceiver(conn)
receiver = NewEncryptedTCPReceiver(conn.SessionKey, conn.outbound)
} else {
receiver = NewSimpleTCPReceiver()
}
@@ -489,11 +481,6 @@ func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte)
nil)
case ProtocolFragmentationReceived:
conn.setStackFrag(true)
case ProtocolNonce:
if conn.SessionKey == nil {
return fmt.Errorf("unexpected nonce on unencrypted connection")
}
conn.Decryptor.ReceiveNonce(payload)
case ProtocolPMTUVerified:
conn.pmtuVerified(int(binary.BigEndian.Uint16(payload)))
case ProtocolGossipUnicast, ProtocolGossipBroadcast, ProtocolGossip:
+12 -6
View File
@@ -206,8 +206,13 @@ func (cm *ConnectionMaker) ourConnections() (PeerNameSet, map[string]struct{}, m
func (cm *ConnectionMaker) addPeerTargets(ourConnectedPeers PeerNameSet, addTarget func(string)) {
cm.peers.ForEach(func(peer *Peer) {
for conn := range peer.Connections() {
otherPeer := conn.Remote().Name
if peer == cm.ourself.Peer {
return
}
// Modifying peer.connections requires a write lock on Peers,
// and since we are holding a read lock (due to the ForEach),
// access without locking the peer is safe.
for otherPeer, conn := range peer.connections {
if otherPeer == cm.ourself.Name {
continue
}
@@ -215,12 +220,13 @@ func (cm *ConnectionMaker) addPeerTargets(ourConnectedPeers PeerNameSet, addTarg
continue
}
address := conn.RemoteTCPAddr()
// Try both port of connection and standard port. Don't
// use remote side of inbound connection.
if conn.Outbound() {
addTarget(address)
}
if ip, _, err := net.SplitHostPort(address); err == nil {
} 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
// weave port instead.
addTarget(fmt.Sprintf("%s:%d", ip, cm.port))
}
}
+1 -2
View File
@@ -13,7 +13,6 @@ const (
DefaultPMTU = 65535
MaxUDPPacketSize = 65536
ChannelSize = 16
UDPNonceSendAt = 8192
FragTestSize = 60001
PMTUDiscoverySize = 60000
TCPHeartbeat = 30 * time.Second
@@ -23,7 +22,7 @@ const (
PMTUVerifyAttempts = 8
PMTUVerifyTimeout = 10 * time.Millisecond // gets doubled with every attempt
MaxDuration = time.Duration(math.MaxInt64)
MaxMissedHeartbeats = 3
MaxMissedHeartbeats = 6
HeartbeatTimeout = MaxMissedHeartbeats * SlowHeartbeat
)
+167 -303
View File
@@ -1,7 +1,6 @@
package router
import (
"bytes"
"code.google.com/p/go-bit/bit"
"code.google.com/p/go.crypto/nacl/box"
"code.google.com/p/go.crypto/nacl/secretbox"
@@ -10,7 +9,6 @@ import (
"encoding/binary"
"encoding/gob"
"fmt"
"log"
"sync"
)
@@ -27,85 +25,13 @@ func FormSessionKey(remotePublicKey, localPrivateKey *[32]byte, secretKey []byte
return &sessionKey
}
func GenerateRandomNonce() ([24]byte, error) {
var nonce [24]byte
n, err := rand.Read(nonce[:])
if err != nil {
return nonce, err
}
if n != 24 {
return nonce, fmt.Errorf("Did not read enough - wanted 24, got %v", n)
}
return nonce, nil
}
func EncryptPrefixNonce(plaintxt []byte, nonce *[24]byte, secret *[32]byte) []byte {
buf := make([]byte, 24, 24+len(plaintxt)+secretbox.Overhead)
copy(buf, nonce[:])
// Seal *appends* to buf
return secretbox.Seal(buf, plaintxt, nonce, secret)
}
func DecryptPrefixNonce(ciphertxt []byte, secret *[32]byte) ([]byte, bool) {
if len(ciphertxt) < secretbox.Overhead+24 {
return nil, false
}
// There is no way to nicely convert from a slice to an
// array. So have to used the following loop.
var nonce [24]byte
for idx, e := range ciphertxt[0:24] {
nonce[idx] = e
}
ciphertxt = ciphertxt[24:]
return secretbox.Open(nil, ciphertxt, &nonce, secret)
}
func SetNonceLow15Bits(nonce *[24]byte, offset uint16) {
// ensure top bit of offset is 0
offset = offset & ((1 << 15) - 1)
// grab top bit of nonce[22:24] (and clear out lower bits)
nonceBits := binary.BigEndian.Uint16(nonce[22:24]) & (1 << 15)
// Big endian => the MSB is stored at the *lowest* address. So
// that top bit in nonce[22] should stay as the top bit in
// nonce[22]
binary.BigEndian.PutUint16(nonce[22:24], nonceBits+offset)
}
// Nonce encoding/decoding
func EncodeNonce(df bool) (*[24]byte, []byte, error) {
nonce, err := GenerateRandomNonce()
if err != nil {
return nil, []byte{}, err
}
// wipe out lowest 15 bits, but encode the df right at the bottom
flags := uint16(0)
if df {
flags = flags | 1
}
SetNonceLow15Bits(&nonce, flags)
// NB: need to make a copy since callers may modify the array
return &nonce, Concat(nonce[:]), nil
}
func DecodeNonce(msg []byte) (bool, *[24]byte) {
flags := uint16(msg[23])
df := 0 != (flags & 1)
nonce := [24]byte{}
// upper bound is exclusive so this avoids copying the flags
for idx, elem := range msg[:23] {
nonce[idx] = elem
}
return df, &nonce
}
// Frame Encryptors
type Encryptor interface {
FrameOverhead() int
PacketOverhead() int
IsEmpty() bool
Bytes() []byte
Bytes() ([]byte, error)
AppendFrame(src []byte, dst []byte, frame []byte)
TotalLen() int
}
@@ -119,14 +45,12 @@ type NonEncryptor struct {
type NaClEncryptor struct {
NonEncryptor
buf []byte
offset uint16
nonce *[24]byte
nonceChan chan *[24]byte
flags uint16
prefixLen int
conn *LocalConnection
df bool
buf []byte
prefixLen int
sessionKey *[32]byte
nonce [24]byte
seqNo uint64
df bool
}
func NewNonEncryptor(prefix []byte) *NonEncryptor {
@@ -151,11 +75,11 @@ func (ne *NonEncryptor) IsEmpty() bool {
return ne.buffered == ne.prefixLen
}
func (ne *NonEncryptor) Bytes() []byte {
func (ne *NonEncryptor) Bytes() ([]byte, error) {
buf := ne.buf[:ne.buffered]
ne.buffered = ne.prefixLen
ne.bufTail = ne.buf[ne.prefixLen:]
return buf
return buf, nil
}
func (ne *NonEncryptor) AppendFrame(src []byte, dst []byte, frame []byte) {
@@ -175,66 +99,47 @@ func (ne *NonEncryptor) TotalLen() int {
return ne.buffered
}
func NewNaClEncryptor(prefix []byte, conn *LocalConnection, df bool) *NaClEncryptor {
func NewNaClEncryptor(prefix []byte, sessionKey *[32]byte, outbound bool, df bool) *NaClEncryptor {
buf := make([]byte, MaxUDPPacketSize)
prefixLen := copy(buf, prefix)
flags := uint16(0)
if df {
flags = flags | (1 << 15)
}
return &NaClEncryptor{
ne := &NaClEncryptor{
NonEncryptor: *NewNonEncryptor([]byte{}),
buf: buf,
offset: 0,
nonce: nil,
nonceChan: make(chan *[24]byte, ChannelSize),
flags: flags,
prefixLen: prefixLen,
conn: conn,
sessionKey: sessionKey,
df: df}
if outbound {
ne.nonce[0] |= (1 << 7)
}
return ne
}
func (ne *NaClEncryptor) Bytes() []byte {
plaintext := ne.NonEncryptor.Bytes()
offsetFlags := ne.offset | ne.flags
func (ne *NaClEncryptor) Bytes() ([]byte, error) {
plaintext, err := ne.NonEncryptor.Bytes()
if err != nil {
return nil, err
}
// We carry the DF flag in the (unencrypted portion of the)
// payload, rather than just extracting it from the packet headers
// at the receiving end, since we do not trust routers not to mess
// with headers. As we have different decryptors for non-DF and
// DF, that would result in hard to track down packet drops due to
// crypto errors.
seqNoAndDF := ne.seqNo
if ne.df {
seqNoAndDF |= (1 << 63)
}
ciphertext := ne.buf
binary.BigEndian.PutUint16(ciphertext[ne.prefixLen:], offsetFlags)
nonce := ne.nonce
if nonce == nil {
freshNonce, encodedNonce, err := EncodeNonce(ne.df)
if err != nil {
ne.conn.Shutdown(err)
return []byte{}
}
ne.conn.SendProtocolMsg(ProtocolMsg{ProtocolNonce, encodedNonce})
ne.nonce = freshNonce
nonce = freshNonce
}
offset := ne.offset
SetNonceLow15Bits(nonce, offset)
binary.BigEndian.PutUint64(ciphertext[ne.prefixLen:], seqNoAndDF)
binary.BigEndian.PutUint64(ne.nonce[16:24], seqNoAndDF)
// Seal *appends* to ciphertext
ciphertext = secretbox.Seal(ciphertext[:ne.prefixLen+2], plaintext, nonce, ne.conn.SessionKey)
offset = (offset + 1) & ((1 << 15) - 1)
if offset == 0 {
// need a new nonce please
ne.nonce = <-ne.nonceChan
} else if offset == 1<<14 { // half way through range, send new nonce
nonce, encodedNonce, err := EncodeNonce(ne.df)
if err != nil {
ne.conn.Shutdown(err)
return []byte{}
}
ne.nonceChan <- nonce
ne.conn.SendProtocolMsg(ProtocolMsg{ProtocolNonce, encodedNonce})
}
ne.offset = offset
return ciphertext
ciphertext = secretbox.Seal(ciphertext[:ne.prefixLen+8], plaintext, &ne.nonce, ne.sessionKey)
ne.seqNo++
return ciphertext, nil
}
func (ne *NaClEncryptor) PacketOverhead() int {
return ne.prefixLen + 2 + secretbox.Overhead + ne.NonEncryptor.PacketOverhead()
return ne.prefixLen + 8 + secretbox.Overhead + ne.NonEncryptor.PacketOverhead()
}
func (ne *NaClEncryptor) TotalLen() int {
@@ -243,39 +148,43 @@ func (ne *NaClEncryptor) TotalLen() int {
// Frame Decryptors
type FrameConsumer func(conn *LocalConnection, src []byte, dst []byte, frame []byte)
type FrameConsumer func(src []byte, dst []byte, frame []byte)
type Decryptor interface {
IterateFrames([]byte, FrameConsumer) error
ReceiveNonce([]byte)
Shutdown()
}
type NonDecryptor struct {
conn *LocalConnection
}
type NaClDecryptor struct {
NonDecryptor
sessionKey *[32]byte
instance *NaClDecryptorInstance
instanceDF *NaClDecryptorInstance
}
type NaClDecryptorInstance struct {
nonce *[24]byte
previousNonce *[24]byte
nonce [24]byte
currentWindow uint64
usedOffsets *bit.Set
previousUsedOffsets *bit.Set
highestOffsetSeen uint16
nonceChan chan *[24]byte
}
func NewNaClDecryptorInstance(outbound bool) *NaClDecryptorInstance {
di := &NaClDecryptorInstance{usedOffsets: bit.New()}
if !outbound {
di.nonce[0] |= (1 << 7)
}
return di
}
type PacketDecodingError struct {
Desc string
}
func NewNonDecryptor(conn *LocalConnection) *NonDecryptor {
return &NonDecryptor{conn: conn}
func NewNonDecryptor() *NonDecryptor {
return &NonDecryptor{}
}
func (nd *NonDecryptor) IterateFrames(packet []byte, consumer FrameConsumer) error {
@@ -291,7 +200,7 @@ func (nd *NonDecryptor) IterateFrames(packet []byte, consumer FrameConsumer) err
}
frame := packet[:length]
packet = packet[length:]
consumer(nd.conn, srcNameByte, dstNameByte, frame)
consumer(srcNameByte, dstNameByte, frame)
}
if len(packet) > 0 {
return PacketDecodingError{Desc: fmt.Sprintf("%d octets of trailing garbage", len(packet))}
@@ -299,127 +208,125 @@ func (nd *NonDecryptor) IterateFrames(packet []byte, consumer FrameConsumer) err
return nil
}
func (nd *NonDecryptor) Shutdown() {
}
func (nd *NonDecryptor) ReceiveNonce(msg []byte) {
log.Println("Received Nonce on non-encrypted channel. Ignoring.")
}
func NewNaClDecryptor(conn *LocalConnection) *NaClDecryptor {
func NewNaClDecryptor(sessionKey *[32]byte, outbound bool) *NaClDecryptor {
return &NaClDecryptor{
NonDecryptor: *NewNonDecryptor(conn),
instance: &NaClDecryptorInstance{
usedOffsets: bit.New(),
nonceChan: make(chan *[24]byte, ChannelSize)},
instanceDF: &NaClDecryptorInstance{
usedOffsets: bit.New(),
nonceChan: make(chan *[24]byte, ChannelSize)}}
}
func (nd *NaClDecryptor) Shutdown() {
close(nd.instance.nonceChan)
close(nd.instanceDF.nonceChan)
}
func (nd *NaClDecryptor) ReceiveNonce(msg []byte) {
df, nonce := DecodeNonce(msg)
if df {
nd.instanceDF.nonceChan <- nonce
} else {
nd.instance.nonceChan <- nonce
}
NonDecryptor: *NewNonDecryptor(),
sessionKey: sessionKey,
instance: NewNaClDecryptorInstance(outbound),
instanceDF: NewNaClDecryptorInstance(outbound)}
}
func (nd *NaClDecryptor) IterateFrames(packet []byte, consumer FrameConsumer) error {
buf, err := nd.decrypt(packet)
if err != nil {
return PacketDecodingError{Desc: fmt.Sprint("decryption failed; ", err)}
if len(packet) < 8 {
return PacketDecodingError{Desc: fmt.Sprintf("encrypted UDP packet too short; expected length >= 8, got %d", len(packet))}
}
buf, success := nd.decrypt(packet)
if !success {
return PacketDecodingError{Desc: fmt.Sprint("UDP packet decryption failed")}
}
return nd.NonDecryptor.IterateFrames(buf, consumer)
}
func (nd *NaClDecryptor) decrypt(buf []byte) ([]byte, error) {
offset := binary.BigEndian.Uint16(buf[:2])
df := (offset & (1 << 15)) != 0
offsetNoFlags := offset & ((1 << 15) - 1)
func (nd *NaClDecryptor) decrypt(buf []byte) ([]byte, bool) {
seqNoAndDF := binary.BigEndian.Uint64(buf[:8])
df := (seqNoAndDF & (1 << 63)) != 0
seqNo := seqNoAndDF & ((1 << 63) - 1)
var di *NaClDecryptorInstance
if df {
di = nd.instanceDF
} else {
di = nd.instance
}
nonce, usedOffsets, err := di.advanceState(offsetNoFlags)
if err != nil {
return nil, err
}
offsetNoFlagsInt := int(offsetNoFlags)
if usedOffsets.Contains(offsetNoFlagsInt) {
return nil, fmt.Errorf("Suspected replay attack detected when decrypting UDP packet")
}
SetNonceLow15Bits(nonce, offsetNoFlags)
result, success := secretbox.Open(nil, buf[2:], nonce, nd.conn.SessionKey)
binary.BigEndian.PutUint64(di.nonce[16:24], seqNoAndDF)
result, success := secretbox.Open(nil, buf[8:], &di.nonce, nd.sessionKey)
if !success {
return nil, fmt.Errorf("Unable to decrypt UDP packet")
return nil, false
}
usedOffsets.Add(offsetNoFlagsInt)
return result, nil
// Drop duplicates. We do this *after* decryption since we must
// not advance our state unless decryption succeeded. Doing so
// would open an easy attack vector where an adversary could
// inject a packet with a sequence number of (1 << 63) - 1,
// causing all subsequent genuine packets to get dropped.
offset, usedOffsets := di.advanceState(seqNo)
if usedOffsets == nil || usedOffsets.Contains(offset) {
// We have detected a possible replay attack, but it is
// possible we may have just received a very old packet, or
// duplication may have occurred in the network. So let's just
// drop the packet silently.
return nil, true
}
usedOffsets.Add(offset)
return result, success
}
func (di *NaClDecryptorInstance) advanceState(offsetNoFlags uint16) (*[24]byte, *bit.Set, error) {
var ok bool
if di.nonce == nil {
if offsetNoFlags > (1 << 13) {
// offset is already beyond the first quarter and it's the
// first thing we've seen?! I don't think so.
return nil, nil, fmt.Errorf("Unexpected offset when decrypting UDP packet")
}
di.nonce, ok = <-di.nonceChan
if !ok {
return nil, nil, fmt.Errorf("Nonce chan closed")
}
di.highestOffsetSeen = offsetNoFlags
} else {
highestOffsetSeen := di.highestOffsetSeen
switch {
case offsetNoFlags < (1<<13) && highestOffsetSeen > ((1<<14)+(1<<13)) &&
(highestOffsetSeen-offsetNoFlags) > ((1<<14)+(1<<13)):
// offset is in the first quarter, highestOffsetSeen is in
// the top quarter and under a quarter behind us. We
// interpret this as we need to move to the next nonce
di.previousUsedOffsets = di.usedOffsets
di.usedOffsets = bit.New()
di.previousNonce = di.nonce
di.nonce, ok = <-di.nonceChan
if !ok {
return nil, nil, fmt.Errorf("Nonce chan closed")
}
di.highestOffsetSeen = offsetNoFlags
case offsetNoFlags > highestOffsetSeen &&
(offsetNoFlags-highestOffsetSeen) < (1<<13):
// offset is under a quarter above highestOffsetSeen. This
// is ok - maybe some packet loss
di.highestOffsetSeen = offsetNoFlags
case offsetNoFlags <= highestOffsetSeen &&
(highestOffsetSeen-offsetNoFlags) < (1<<13):
// offset is within a quarter of the highest we've
// seen. This is ok - just assuming some out-of-order
// delivery.
case highestOffsetSeen < (1<<13) && offsetNoFlags > ((1<<14)+(1<<13)) &&
(offsetNoFlags-highestOffsetSeen) > ((1<<14)+(1<<13)):
// offset is in the last quarter, highestOffsetSeen is in
// the first quarter, and offset is under a quarter behind
// us. This is ok - as above, just some out of order. But
// here it means we're dealing with the previous nonce
return di.previousNonce, di.previousUsedOffsets, nil
default:
return nil, nil, fmt.Errorf("Unexpected offset when decrypting UDP packet")
}
// We record seen message sequence numbers in a sliding window of
// 2*WindowSize which slides in WindowSize increments. This allows us
// to process out-of-order delivery within the window, while
// accurately discarding duplicates. By contrast any messages with
// sequence numbers below the window are discarded as potential
// duplicates.
//
// There are two sets, corresponding to the lower and upper half of
// the window. We slide the window s.t. that 2nd set always contains
// the highest seen sequence number. We do this regardless of how far
// ahead of the current window that sequence number might be, so we
// can cope with large gaps resulting from packet loss.
const (
WindowSize = 20 // bits
)
func (di *NaClDecryptorInstance) advanceState(seqNo uint64) (int, *bit.Set) {
var (
offset = int(seqNo & ((1 << WindowSize) - 1))
window = seqNo >> WindowSize
)
switch delta := int64(window - di.currentWindow); {
case delta < -1:
return offset, nil
case delta == -1:
return offset, di.previousUsedOffsets
default:
return offset, di.usedOffsets
case delta == +1:
di.currentWindow = window
di.previousUsedOffsets = di.usedOffsets
di.usedOffsets = bit.New()
return offset, di.usedOffsets
case delta > +1:
di.currentWindow = window
di.previousUsedOffsets = bit.New()
di.usedOffsets = bit.New()
return offset, di.usedOffsets
}
return di.nonce, di.usedOffsets, nil
}
// TCP Senders
// 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 remaining 127 bits are zero. The
// polarity is needed so that the two ends of a connection do not use
// the same nonces. 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)
}
return s
}
func (s *TCPCryptoState) advance() {
s.seqNo++
binary.BigEndian.PutUint64(s.nonce[16:24], s.seqNo)
}
type TCPSender interface {
Send([]byte) error
@@ -430,17 +337,9 @@ type SimpleTCPSender struct {
}
type EncryptedTCPSender struct {
SimpleTCPSender
sync.RWMutex
outerEncoder *gob.Encoder
innerEncoder *gob.Encoder
buffer *bytes.Buffer
conn *LocalConnection
msgCount int
}
type EncryptedTCPMessage struct {
Number int
Body []byte
state *TCPCryptoState
}
func NewSimpleTCPSender(encoder *gob.Encoder) *SimpleTCPSender {
@@ -451,37 +350,20 @@ func (sender *SimpleTCPSender) Send(msg []byte) error {
return sender.encoder.Encode(msg)
}
func NewEncryptedTCPSender(encoder *gob.Encoder, conn *LocalConnection) *EncryptedTCPSender {
buffer := new(bytes.Buffer)
func NewEncryptedTCPSender(encoder *gob.Encoder, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender {
return &EncryptedTCPSender{
outerEncoder: encoder,
innerEncoder: gob.NewEncoder(buffer),
buffer: buffer,
conn: conn,
msgCount: 0}
SimpleTCPSender: *NewSimpleTCPSender(encoder),
state: NewTCPCryptoState(sessionKey, outbound)}
}
func (sender *EncryptedTCPSender) Send(msg []byte) error {
nonce, err := GenerateRandomNonce()
if err != nil {
return err
}
sender.Lock()
defer sender.Unlock()
wrappedMsg := EncryptedTCPMessage{Number: sender.msgCount, Body: msg}
buffer := sender.buffer
buffer.Reset()
err = sender.innerEncoder.Encode(wrappedMsg)
if err != nil {
return err
}
sender.msgCount = sender.msgCount + 1
return sender.outerEncoder.Encode(
EncryptPrefixNonce(buffer.Bytes(), &nonce, sender.conn.SessionKey))
encodedMsg := secretbox.Seal(nil, msg, &sender.state.nonce, sender.state.sessionKey)
sender.state.advance()
return sender.SimpleTCPSender.Send(encodedMsg)
}
// TCP Receivers
type TCPReceiver interface {
Decode([]byte) ([]byte, error)
}
@@ -490,10 +372,8 @@ type SimpleTCPReceiver struct {
}
type EncryptedTCPReceiver struct {
conn *LocalConnection
decoder *gob.Decoder
buffer *bytes.Buffer
msgCount int
SimpleTCPReceiver
state *TCPCryptoState
}
func NewSimpleTCPReceiver() *SimpleTCPReceiver {
@@ -504,33 +384,17 @@ func (receiver *SimpleTCPReceiver) Decode(msg []byte) ([]byte, error) {
return msg, nil
}
func NewEncryptedTCPReceiver(conn *LocalConnection) *EncryptedTCPReceiver {
buffer := new(bytes.Buffer)
func NewEncryptedTCPReceiver(sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver {
return &EncryptedTCPReceiver{
conn: conn,
decoder: gob.NewDecoder(buffer),
buffer: buffer,
msgCount: 0}
SimpleTCPReceiver: *NewSimpleTCPReceiver(),
state: NewTCPCryptoState(sessionKey, !outbound)}
}
func (receiver *EncryptedTCPReceiver) Decode(msg []byte) ([]byte, error) {
plaintext, success := DecryptPrefixNonce(msg, receiver.conn.SessionKey)
decodedMsg, success := secretbox.Open(nil, msg, &receiver.state.nonce, receiver.state.sessionKey)
if !success {
return msg, fmt.Errorf("Unable to decrypt TCP msg")
return nil, fmt.Errorf("Unable to decrypt TCP msg")
}
receiver.buffer.Reset()
_, err := receiver.buffer.Write(plaintext)
if err != nil {
return msg, err
}
wrappedMsg := new(EncryptedTCPMessage)
err = receiver.decoder.Decode(wrappedMsg)
if err != nil {
return msg, err
}
if wrappedMsg.Number != receiver.msgCount {
return msg, fmt.Errorf("Received TCP message with wrong sequence number; possible replay attack")
}
receiver.msgCount = receiver.msgCount + 1
return wrappedMsg.Body, nil
receiver.state.advance()
return decodedMsg, nil
}
+7 -3
View File
@@ -30,8 +30,8 @@ func (conn *LocalConnection) ensureForwarders() error {
usingPassword := conn.SessionKey != nil
var encryptor, encryptorDF Encryptor
if usingPassword {
encryptor = NewNaClEncryptor(conn.local.NameByte, conn, false)
encryptorDF = NewNaClEncryptor(conn.local.NameByte, conn, true)
encryptor = NewNaClEncryptor(conn.local.NameByte, conn.SessionKey, conn.outbound, false)
encryptorDF = NewNaClEncryptor(conn.local.NameByte, conn.SessionKey, conn.outbound, true)
} else {
encryptor = NewNonEncryptor(conn.local.NameByte)
encryptorDF = NewNonEncryptor(conn.local.NameByte)
@@ -287,7 +287,11 @@ func (fwd *Forwarder) appendFrame(frame *ForwardedFrame) bool {
}
func (fwd *Forwarder) flush() {
err := fwd.processSendError(fwd.udpSender.Send(fwd.enc.Bytes()))
msg, err := fwd.enc.Bytes()
if err != nil {
fwd.conn.Shutdown(err)
}
err = fwd.processSendError(fwd.udpSender.Send(msg))
if err != nil && PosixError(err) != syscall.ENOBUFS {
fwd.conn.Shutdown(err)
}
+6 -2
View File
@@ -107,13 +107,17 @@ func (router *Router) NewGossip(channelName string, g Gossiper) Gossip {
func (router *Router) SendAllGossip() {
for _, channel := range router.GossipChannels {
channel.Send(router.Ourself.Name, channel.gossiper.Gossip())
if gossip := channel.gossiper.Gossip(); gossip != nil {
channel.Send(router.Ourself.Name, gossip)
}
}
}
func (router *Router) SendAllGossipDown(conn Connection) {
for _, channel := range router.GossipChannels {
channel.SendDown(conn, channel.gossiper.Gossip())
if gossip := channel.gossiper.Gossip(); gossip != nil {
channel.SendDown(conn, channel.gossiper.Gossip())
}
}
}
+4 -3
View File
@@ -55,8 +55,8 @@ func (router *Router) DeleteTestChannelConnection(r *Router) {
fromPeer, _ := r.Peers.Fetch(fromName)
toPeer, _ := router.Peers.Fetch(toName)
fromPeer.DecrementLocalRefCount()
toPeer.DecrementLocalRefCount()
r.Peers.Dereference(fromPeer)
router.Peers.Dereference(toPeer)
conn, _ := router.Ourself.ConnectionTo(toName)
router.Ourself.handleDeleteConnection(conn)
@@ -79,7 +79,8 @@ func (router *Router) tp(routers ...*Router) *Peer {
p := NewPeer(r.Ourself.Peer.Name, "", r.Ourself.Peer.UID, r.Ourself.Peer.version)
connections[r.Ourself.Peer.Name] = newMockConnection(peer, p)
}
peer.SetVersionAndConnections(router.Ourself.Peer.version, connections)
peer.version = router.Ourself.Peer.version
peer.connections = connections
return peer
}
+6 -6
View File
@@ -75,10 +75,10 @@ func (conn *LocalConnection) handshake(enc *gob.Encoder, dec *gob.Decoder, accep
return fmt.Errorf("Found unknown remote name: %s at %s", name, conn.remoteTCPAddr)
}
}
if existingConn, found := conn.local.ConnectionTo(name); found && existingConn.Established() {
return fmt.Errorf("Already have connection to %s at %s", existingConn.Remote().FullName(), existingConn.RemoteTCPAddr())
if existingConn, found := conn.Router.Ourself.ConnectionTo(name); found && existingConn.Established() {
return fmt.Errorf("Already have connection to %s at %s", existingConn.Remote(), existingConn.RemoteTCPAddr())
}
uid, err := strconv.ParseUint(uidStr, 10, 64)
uid, err := ParsePeerUID(uidStr)
if err != nil {
return err
}
@@ -102,14 +102,14 @@ func (conn *LocalConnection) handshake(enc *gob.Encoder, dec *gob.Decoder, accep
remotePublic[idx] = elem
}
conn.SessionKey = FormSessionKey(&remotePublic, private, conn.Router.Password)
conn.tcpSender = NewEncryptedTCPSender(enc, conn)
conn.Decryptor = NewNaClDecryptor(conn)
conn.tcpSender = NewEncryptedTCPSender(enc, conn.SessionKey, conn.outbound)
conn.Decryptor = NewNaClDecryptor(conn.SessionKey, conn.outbound)
} else {
if rpErr == nil {
return fmt.Errorf("Remote network is encrypted. Password required.")
}
conn.tcpSender = NewSimpleTCPSender(enc)
conn.Decryptor = NewNonDecryptor(conn)
conn.Decryptor = NewNonDecryptor()
}
return conn.setRemote(NewPeer(name, nickNameStr, uid, 0))
+25 -18
View File
@@ -6,7 +6,7 @@ import (
"time"
)
func (router *Router) GenerateStatusJSON(version, encryption string) ([]byte, error) {
func (router *Router) StatusJSON(version, encryption string) ([]byte, error) {
return json.Marshal(struct {
Version string
Encryption string
@@ -35,8 +35,30 @@ func (cache *MacCache) MarshalJSON() ([]byte, error) {
}
func (peers *Peers) MarshalJSON() ([]byte, error) {
var ps []*Peer
peers.ForEach(func(peer *Peer) { ps = append(ps, peer) })
type p struct {
Name string
NickName string
UID PeerUID
Version uint64
Connections []Connection
}
var ps []*p
peers.ForEach(func(peer *Peer) {
var connections []Connection
if peer == peers.ourself.Peer {
for conn := range peers.ourself.Connections() {
connections = append(connections, conn)
}
} else {
// Modifying peer.connections requires a write lock on
// Peers, and since we are holding a read lock (due to the
// ForEach), access without locking the peer is safe.
for _, conn := range peer.connections {
connections = append(connections, conn)
}
}
ps = append(ps, &p{peer.Name.String(), peer.NickName, peer.UID, peer.version, connections})
})
return json.Marshal(ps)
}
@@ -63,21 +85,6 @@ func (routes *Routes) MarshalJSON() ([]byte, error) {
return json.Marshal(r)
}
func (peer *Peer) MarshalJSON() ([]byte, error) {
conns := peer.Connections()
connections := make([]Connection, 0, len(conns))
for conn := range conns {
connections = append(connections, conn)
}
return json.Marshal(struct {
Name string
NickName string
UID uint64
Version uint64
Connections []Connection
}{peer.Name.String(), peer.NickName, peer.UID, peer.version, connections})
}
func (conn *RemoteConnection) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Name string
+56 -3
View File
@@ -4,10 +4,12 @@ import (
"fmt"
"log"
"net"
"sync"
"time"
)
type LocalPeer struct {
sync.RWMutex
*Peer
router *Router
actionChan chan<- LocalPeerAction
@@ -38,7 +40,7 @@ func (peer *LocalPeer) Relay(srcPeer, dstPeer *Peer, df bool, frame []byte, dec
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.FullName())
log.Println("Received packet for unknown destination:", dstPeer)
return nil
}
conn, found := peer.ConnectionTo(relayPeerName)
@@ -75,6 +77,29 @@ func (peer *LocalPeer) RelayBroadcast(srcPeer *Peer, df bool, frame []byte, dec
}
}
func (peer *LocalPeer) Info() string {
peer.RLock()
defer peer.RUnlock()
return peer.Peer.Info()
}
func (peer *LocalPeer) Connections() ConnectionSet {
connections := make(ConnectionSet)
peer.RLock()
defer peer.RUnlock()
for _, conn := range peer.connections {
connections[conn] = void
}
return connections
}
func (peer *LocalPeer) ConnectionTo(name PeerName) (Connection, bool) {
peer.RLock()
defer peer.RUnlock()
conn, found := peer.connections[name]
return conn, found // yes, you really can't inline that. FFS.
}
func (peer *LocalPeer) ConnectionsTo(names []PeerName) []Connection {
conns := make([]Connection, 0, len(names))
peer.RLock()
@@ -162,7 +187,7 @@ func (peer *LocalPeer) handleAddConnection(conn Connection) error {
log.Fatal("Attempt made to add connection to peer with unknown remote peer")
}
toName := conn.Remote().Name
dupErr := fmt.Errorf("Multiple connections to %s added to %s", conn.Remote().FullName(), peer.FullName())
dupErr := fmt.Errorf("Multiple connections to %s added to %s", conn.Remote(), peer.String())
// deliberately non symmetrical
if dupConn, found := peer.connections[toName]; found {
if dupConn == conn {
@@ -228,6 +253,8 @@ func (peer *LocalPeer) handleDeleteConnection(conn Connection) {
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)...))
@@ -235,8 +262,34 @@ func (peer *LocalPeer) broadcastPeerUpdate(peers ...*Peer) {
func (peer *LocalPeer) checkConnectionLimit() error {
limit := peer.router.ConnLimit
if 0 != limit && peer.ConnectionCount() >= limit {
if 0 != limit && peer.connectionCount() >= limit {
return fmt.Errorf("Connection limit reached (%v)", limit)
}
return nil
}
func (peer *LocalPeer) addConnection(conn Connection) {
peer.Lock()
defer peer.Unlock()
peer.connections[conn.Remote().Name] = conn
peer.version++
}
func (peer *LocalPeer) deleteConnection(conn Connection) {
peer.Lock()
defer peer.Unlock()
delete(peer.connections, conn.Remote().Name)
peer.version++
}
func (peer *LocalPeer) connectionEstablished(conn Connection) {
peer.Lock()
defer peer.Unlock()
peer.version++
}
func (peer *LocalPeer) connectionCount() int {
peer.RLock()
defer peer.RUnlock()
return len(peer.connections)
}
+1 -1
View File
@@ -89,7 +89,7 @@ func (cache *MacCache) String() string {
cache.RLock()
defer cache.RUnlock()
for key, entry := range cache.table {
fmt.Fprintf(&buf, "%v -> %s (%v)\n", intmac(key), entry.peer.FullName(), entry.lastSeen)
fmt.Fprintf(&buf, "%v -> %s (%v)\n", intmac(key), entry.peer, entry.lastSeen)
}
return buf.String()
}
+2 -2
View File
@@ -14,7 +14,7 @@ import (
func (peers *Peers) AddTestConnection(p *Peer) {
toPeer := NewPeer(p.Name, "", p.UID, 0)
peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
conn := newMockConnection(peers.ourself, toPeer)
conn := newMockConnection(peers.ourself.Peer, toPeer)
peers.ourself.addConnection(conn)
peers.ourself.connectionEstablished(conn)
}
@@ -33,7 +33,7 @@ func (peers *Peers) AddTestRemoteConnection(p1, p2 *Peer) {
func (peers *Peers) DeleteTestConnection(p *Peer) {
toName := p.Name
toPeer, _ := peers.Fetch(toName)
toPeer.DecrementLocalRefCount()
peers.Dereference(toPeer)
conn, _ := peers.ourself.ConnectionTo(toName)
peers.ourself.deleteConnection(conn)
}
+11
View File
@@ -2,6 +2,7 @@ package router
import (
"code.google.com/p/gopacket/pcap"
"fmt"
)
type PcapIO struct {
@@ -44,6 +45,16 @@ func newPcapIO(ifName string, promisc bool, snaplen int, bufSz int) (handle *Pca
return
}
if err = inactive.SetImmediateMode(true); err != nil {
// If gopacket is compiled against an older pcap.h that
// doesn't have pcap_set_immediate_mode, it supplies a dummy
// definition that always returns PCAP_ERROR. That becomes
// "Generic error", which is not very helpful. The real
// pcap_set_immediate_mode never returns PCAP_ERROR, so this
// turns it into a more informative message.
if fmt.Sprint(err) == "Generic error" {
err = fmt.Errorf("compiled against an old version of libpcap; please compile against libpcap-1.5.0 or later")
}
return
}
if err = inactive.SetBufferSize(bufSz); err != nil {
+37 -110
View File
@@ -3,25 +3,31 @@ package router
import (
"fmt"
"sort"
"sync"
"strconv"
)
type PeerUID uint64
func ParsePeerUID(s string) (PeerUID, error) {
uid, err := strconv.ParseUint(s, 10, 64)
return PeerUID(uid), err
}
type Peer struct {
sync.RWMutex
Name PeerName
NameByte []byte
NickName string
UID uint64
UID PeerUID
version uint64
localRefCount uint64
localRefCount uint64 // maintained by Peers
connections map[PeerName]Connection
}
type ConnectionSet map[Connection]struct{}
func NewPeer(name PeerName, nickName string, uid uint64, version uint64) *Peer {
func NewPeer(name PeerName, nickName string, uid PeerUID, version uint64) *Peer {
if uid == 0 {
uid = randUint64()
uid = PeerUID(randUint64())
}
return &Peer{
Name: name,
@@ -33,87 +39,11 @@ func NewPeer(name PeerName, nickName string, uid uint64, version uint64) *Peer {
}
func (peer *Peer) String() string {
peer.RLock()
defer peer.RUnlock()
return fmt.Sprint(peer.FullName(), " (v", peer.version, ") (UID ", peer.UID, ")")
}
func (peer *Peer) FullName() string {
return fmt.Sprint(peer.Name, "(", peer.NickName, ")")
}
func (peer *Peer) Version() uint64 {
peer.RLock()
defer peer.RUnlock()
return peer.version
}
func (peer *Peer) IncrementLocalRefCount() {
peer.Lock()
defer peer.Unlock()
peer.localRefCount++
}
func (peer *Peer) DecrementLocalRefCount() {
peer.Lock()
defer peer.Unlock()
peer.localRefCount--
}
func (peer *Peer) IsLocallyReferenced() bool {
peer.RLock()
defer peer.RUnlock()
return peer.localRefCount != 0
}
func (peer *Peer) ConnectionCount() int {
peer.RLock()
defer peer.RUnlock()
return len(peer.connections)
}
func (peer *Peer) ConnectionTo(name PeerName) (Connection, bool) {
peer.RLock()
defer peer.RUnlock()
conn, found := peer.connections[name]
return conn, found // yes, you really can't inline that. FFS.
}
func (peer *Peer) Connections() ConnectionSet {
connections := make(ConnectionSet)
peer.RLock()
defer peer.RUnlock()
for _, conn := range peer.connections {
connections[conn] = void
}
return connections
}
func (peer *Peer) SetVersionAndConnections(version uint64, connections map[PeerName]Connection) {
peer.Lock()
defer peer.Unlock()
peer.version = version
peer.connections = connections
}
func (peer *Peer) addConnection(conn Connection) {
peer.Lock()
defer peer.Unlock()
peer.connections[conn.Remote().Name] = conn
peer.version++
}
func (peer *Peer) deleteConnection(conn Connection) {
peer.Lock()
defer peer.Unlock()
delete(peer.connections, conn.Remote().Name)
peer.version++
}
func (peer *Peer) connectionEstablished(conn Connection) {
peer.Lock()
defer peer.Unlock()
peer.version++
func (peer *Peer) Info() string {
return fmt.Sprint(peer.String(), " (v", peer.version, ") (UID ", peer.UID, ")")
}
// Calculate the routing table from this peer to all peers reachable
@@ -137,13 +67,9 @@ func (peer *Peer) connectionEstablished(conn Connection) {
// reaches that peer. The boolean return indicates whether that has
// happened.
//
// We acquire read locks on peers as we encounter them during the
// traversal. This prevents the connectivity graph from changing
// underneath us in ways that would invalidate the result. Thus the
// answer returned may be out of date, but never inconsistent.
// 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) {
peer.RLock()
defer peer.RUnlock()
routes := make(map[PeerName]PeerName)
routes[peer.Name] = UnknownPeerName
nextWorklist := []*Peer{peer}
@@ -155,21 +81,10 @@ func (peer *Peer) Routes(stopAt *Peer, establishedAndSymmetric bool) (bool, map[
if curPeer == stopAt {
return true, routes
}
curName := curPeer.Name
for remoteName, conn := range curPeer.connections {
if establishedAndSymmetric && !conn.Established() {
continue
}
if _, found := routes[remoteName]; found {
continue
}
remote := conn.Remote()
remote.RLock()
// DANGER holding rlock on remote, possibly taking
// rlock on remoteConn
if remoteConn, found := remote.connections[curName]; !establishedAndSymmetric || (found && remoteConn.Established()) {
defer remote.RUnlock()
nextWorklist = append(nextWorklist, remote)
curPeer.ForEachConnectedPeer(establishedAndSymmetric, routes,
func(remotePeer *Peer) {
nextWorklist = append(nextWorklist, remotePeer)
remoteName := remotePeer.Name
// We now know how to get to remoteName: the same
// way we get to curPeer. Except, if curPeer is
// the starting peer in which case we know we can
@@ -177,13 +92,25 @@ func (peer *Peer) Routes(stopAt *Peer, establishedAndSymmetric bool) (bool, map[
if curPeer == peer {
routes[remoteName] = remoteName
} else {
routes[remoteName] = routes[curName]
routes[remoteName] = routes[curPeer.Name]
}
} else {
remote.RUnlock()
}
}
})
}
}
return false, routes
}
func (peer *Peer) ForEachConnectedPeer(establishedAndSymmetric bool, exclude map[PeerName]PeerName, f func(*Peer)) {
for remoteName, conn := range peer.connections {
if establishedAndSymmetric && !conn.Established() {
continue
}
if _, found := exclude[remoteName]; found {
continue
}
remotePeer := conn.Remote()
if remoteConn, found := remotePeer.connections[peer.Name]; !establishedAndSymmetric || (found && remoteConn.Established()) {
f(remotePeer)
}
}
}
+1
View File
@@ -2,6 +2,7 @@
// Let peer names be sha256 hashes...of anything (as long as it's
// unique).
package router
import (
+1
View File
@@ -14,6 +14,7 @@
// Note that we do not mandate *what* MAC should be used as the peer
// name. In particular it doesn't actually have to be the MAC of, say,
// the network interface the peer is sniffing on.
package router
import (
+104 -118
View File
@@ -10,7 +10,7 @@ import (
type Peers struct {
sync.RWMutex
ourself *Peer
ourself *LocalPeer
table map[PeerName]*Peer
onGC func(*Peer)
}
@@ -25,7 +25,21 @@ type NameCollisionError struct {
type PeerNameSet map[PeerName]struct{}
func NewPeers(ourself *Peer, onGC func(*Peer)) *Peers {
type PeerSummary struct {
NameByte []byte
NickName string
UID PeerUID
Version uint64
}
type ConnectionSummary struct {
NameByte []byte
RemoteTCPAddr string
Outbound bool
Established bool
}
func NewPeers(ourself *LocalPeer, onGC func(*Peer)) *Peers {
return &Peers{
ourself: ourself,
table: make(map[PeerName]*Peer),
@@ -33,20 +47,17 @@ func NewPeers(ourself *Peer, onGC func(*Peer)) *Peers {
}
func (peers *Peers) FetchWithDefault(peer *Peer) *Peer {
peers.RLock()
res, found := peers.fetchAlias(peer)
peers.RUnlock()
if found {
return res
}
peers.Lock()
defer peers.Unlock()
res, found = peers.fetchAlias(peer)
if found {
return res
if existingPeer, found := peers.table[peer.Name]; found {
if existingPeer.UID != peer.UID {
return nil
}
existingPeer.localRefCount++
return existingPeer
}
peers.table[peer.Name] = peer
peer.IncrementLocalRefCount()
peer.localRefCount++
return peer
}
@@ -57,6 +68,12 @@ func (peers *Peers) Fetch(name PeerName) (*Peer, bool) {
return peer, found // GRRR, why can't I inline this!?
}
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()
@@ -111,19 +128,21 @@ func (peers *Peers) Names() PeerNameSet {
}
func (peers *Peers) EncodePeers(names PeerNameSet) []byte {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
peers.RLock()
defer peers.RUnlock()
peerList := make([]*Peer, 0, len(names))
for name := range names {
if peer, found := peers.table[name]; found {
if peer == peers.ourself.Peer {
peers.ourself.Peer.Encode(enc)
} else {
peer.Encode(enc)
}
peerList = append(peerList, peer)
}
}
peers.RUnlock() // release lock so we don't hold it while encoding
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
for _, peer := range peerList {
peer.encode(enc)
}
return buf.Bytes()
}
@@ -135,35 +154,39 @@ func (peers *Peers) GarbageCollect() []*Peer {
func (peers *Peers) String() string {
var buf bytes.Buffer
printConnection := func(conn Connection) {
established := ""
if !conn.Established() {
established = " (unestablished)"
}
fmt.Fprintf(&buf, " -> %s [%v%s]\n", conn.Remote(), conn.RemoteTCPAddr(), established)
}
peers.ForEach(func(peer *Peer) {
fmt.Fprint(&buf, peer, "\n")
for conn := range peer.Connections() {
established := ""
if !conn.Established() {
established = " (unestablished)"
if peer == peers.ourself.Peer {
fmt.Fprintln(&buf, peers.ourself.Info())
for conn := range peers.ourself.Connections() {
printConnection(conn)
}
} else {
fmt.Fprintln(&buf, peer.Info())
// Modifying peer.connections requires a write lock on
// Peers, and since we are holding a read lock (due to the
// ForEach), access without locking the peer is safe.
for _, conn := range peer.connections {
printConnection(conn)
}
fmt.Fprintf(&buf, " -> %s [%v%s]\n", conn.Remote().FullName(), conn.RemoteTCPAddr(), established)
}
})
return buf.String()
}
func (peers *Peers) fetchAlias(peer *Peer) (*Peer, bool) {
if existingPeer, found := peers.table[peer.Name]; found {
if existingPeer.UID != peer.UID {
return nil, true
}
existingPeer.IncrementLocalRefCount()
return existingPeer, true
}
return nil, false
}
func (peers *Peers) garbageCollect() []*Peer {
removed := []*Peer{}
peers.ourself.RLock()
_, reached := peers.ourself.Routes(nil, false)
peers.ourself.RUnlock()
for name, peer := range peers.table {
if _, found := reached[peer.Name]; !found && !peer.IsLocallyReferenced() {
if _, found := reached[peer.Name]; !found && peer.localRefCount == 0 {
delete(peers.table, name)
peers.onGC(peer)
removed = append(removed, peer)
@@ -180,27 +203,27 @@ func setFromPeersMap(peers map[PeerName]*Peer) PeerNameSet {
return names
}
func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, decodedUpdate []*Peer, decodedConns [][]byte, err error) {
func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, decodedUpdate []*Peer, decodedConns [][]ConnectionSummary, err error) {
newPeers = make(map[PeerName]*Peer)
decodedUpdate = []*Peer{}
decodedConns = [][]byte{}
decodedConns = [][]ConnectionSummary{}
updateBuf := new(bytes.Buffer)
updateBuf.Write(update)
decoder := gob.NewDecoder(updateBuf)
for {
nameByte, nickName, uid, version, connsBuf, decErr := decodePeerNoConns(decoder)
peerSummary, connSummaries, decErr := decodePeer(decoder)
if decErr == io.EOF {
break
} else if decErr != nil {
err = decErr
return
}
name := PeerNameFromBin(nameByte)
newPeer := NewPeer(name, nickName, uid, version)
name := PeerNameFromBin(peerSummary.NameByte)
newPeer := NewPeer(name, peerSummary.NickName, peerSummary.UID, peerSummary.Version)
decodedUpdate = append(decodedUpdate, newPeer)
decodedConns = append(decodedConns, connsBuf)
decodedConns = append(decodedConns, connSummaries)
existingPeer, found := peers.table[name]
if !found {
newPeers[name] = newPeer
@@ -210,38 +233,33 @@ func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, de
}
}
for _, connsBuf := range decodedConns {
decErr := connsIterator(connsBuf, func(remoteNameByte []byte, _ string, _, _ bool) {
remoteName := PeerNameFromBin(remoteNameByte)
for _, connSummaries := range decodedConns {
for _, connSummary := range connSummaries {
remoteName := PeerNameFromBin(connSummary.NameByte)
if _, found := newPeers[remoteName]; found {
return
continue
}
if _, found := peers.table[remoteName]; found {
return
continue
}
// Update refers to a peer which we have no knowledge
// of. Thus we can't apply the update. Abort.
err = UnknownPeerError{remoteName}
})
if decErr != nil && decErr != io.EOF {
err = decErr
}
if err != nil {
return
}
}
return
}
func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]byte) map[PeerName]*Peer {
func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]ConnectionSummary) map[PeerName]*Peer {
newUpdate := make(map[PeerName]*Peer)
for idx, newPeer := range decodedUpdate {
connsBuf := decodedConns[idx]
connSummaries := decodedConns[idx]
name := newPeer.Name
// guaranteed to find peer in the peers.table
peer := peers.table[name]
if peer != newPeer &&
(peer == peers.ourself || peer.Version() >= newPeer.Version()) {
(peer == peers.ourself.Peer || peer.version >= newPeer.version) {
// Nobody but us updates us. And if we know more about a
// peer than what's in the the update, we ignore the
// latter.
@@ -257,89 +275,57 @@ func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]byte) ma
// for an update would be someone else calling
// router.Peers.ApplyUpdate. But ApplyUpdate takes the Lock on
// the router.Peers, so there can be no race here.
conns := readConnsMap(peer, connsBuf, peers.table)
peer.SetVersionAndConnections(newPeer.Version(), conns)
peer.version = newPeer.version
peer.connections = makeConnsMap(peer, connSummaries, peers.table)
newUpdate[name] = peer
}
return newUpdate
}
func (peer *Peer) encode(enc *gob.Encoder) {
peer.RLock()
defer peer.RUnlock()
func (peer *Peer) Encode(enc *gob.Encoder) {
checkFatal(enc.Encode(PeerSummary{
peer.NameByte,
peer.NickName,
peer.UID,
peer.version}))
checkFatal(enc.Encode(peer.NameByte))
checkFatal(enc.Encode(peer.NickName))
checkFatal(enc.Encode(peer.UID))
checkFatal(enc.Encode(peer.version))
connsBuf := new(bytes.Buffer)
connsEnc := gob.NewEncoder(connsBuf)
connSummaries := []ConnectionSummary{}
for _, conn := range peer.connections {
checkFatal(connsEnc.Encode(conn.Remote().NameByte))
checkFatal(connsEnc.Encode(conn.RemoteTCPAddr()))
checkFatal(connsEnc.Encode(conn.Outbound()))
// DANGER holding rlock on peer, going to take rlock on conn
checkFatal(connsEnc.Encode(conn.Established()))
connSummaries = append(connSummaries, ConnectionSummary{
conn.Remote().NameByte,
conn.RemoteTCPAddr(),
conn.Outbound(),
// DANGER holding rlock on peer, going to take rlock on conn
conn.Established(),
})
}
checkFatal(enc.Encode(connsBuf.Bytes()))
checkFatal(enc.Encode(connSummaries))
}
func decodePeerNoConns(dec *gob.Decoder) (nameByte []byte, nickName string, uid uint64, version uint64, conns []byte, err error) {
if err = dec.Decode(&nameByte); err != nil {
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(&nickName); err != nil {
return
}
if err = dec.Decode(&uid); err != nil {
return
}
if err = dec.Decode(&version); err != nil {
return
}
if err = dec.Decode(&conns); err != nil {
if err = dec.Decode(&connSummaries); err != nil {
return
}
return
}
func connsIterator(input []byte, fun func([]byte, string, bool, bool)) error {
buf := new(bytes.Buffer)
buf.Write(input)
dec := gob.NewDecoder(buf)
for {
var nameByte []byte
if err := dec.Decode(&nameByte); err != nil {
return err
}
var foundAt string
if err := dec.Decode(&foundAt); err != nil {
return err
}
var outbound bool
if err := dec.Decode(&outbound); err != nil {
return err
}
var established bool
if err := dec.Decode(&established); err != nil {
return err
}
fun(nameByte, foundAt, outbound, established)
}
}
func readConnsMap(peer *Peer, buf []byte, table map[PeerName]*Peer) map[PeerName]Connection {
func makeConnsMap(peer *Peer, connSummaries []ConnectionSummary, table map[PeerName]*Peer) map[PeerName]Connection {
conns := make(map[PeerName]Connection)
if err := connsIterator(buf, func(nameByte []byte, remoteTCPAddr string, outbound bool, established bool) {
name := PeerNameFromBin(nameByte)
for _, connSummary := range connSummaries {
name := PeerNameFromBin(connSummary.NameByte)
remotePeer := table[name]
conn := NewRemoteConnection(peer, remotePeer, remoteTCPAddr, outbound, established)
conn := NewRemoteConnection(peer, remotePeer, connSummary.RemoteTCPAddr, connSummary.Outbound, connSummary.Established)
conns[name] = conn
}); err != io.EOF {
// this should never happen since we've already successfully
// decoded the same data in decodeUpdate
checkFatal(err)
}
return conns
}
+5 -5
View File
@@ -17,10 +17,10 @@ import (
// - non-gc of peers that are only referenced locally
func newNode(name PeerName) (*Peer, *Peers) {
peer := NewPeer(name, "", 0, 0)
peer := NewLocalPeer(name, "", nil)
peers := NewPeers(peer, func(*Peer) {})
peers.FetchWithDefault(peer)
return peer, peers
peers.FetchWithDefault(peer.Peer)
return peer.Peer, peers
}
// Check that ApplyUpdate copies the whole topology from peers
@@ -29,7 +29,7 @@ func checkApplyUpdate(t *testing.T, peers *Peers) {
// We need a new node outside of the network, with a connection
// into it.
_, testBedPeers := newNode(dummyName)
testBedPeers.AddTestConnection(peers.ourself)
testBedPeers.AddTestConnection(peers.ourself.Peer)
testBedPeers.ApplyUpdate(peers.EncodePeers(peers.Names()))
checkTopologyPeers(t, true, testBedPeers.allPeersExcept(dummyName), peers.allPeers()...)
@@ -52,7 +52,7 @@ func TestPeersEncoding(t *testing.T) {
case 0:
from, to := rand.Intn(numNodes), rand.Intn(numNodes)
if from != to {
if _, found := peer[from].ConnectionTo(peer[to].Name); !found {
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])
+1 -2
View File
@@ -2,7 +2,7 @@ package router
const (
Protocol = "weave"
ProtocolVersion = 15
ProtocolVersion = 17
)
type ProtocolTag byte
@@ -12,7 +12,6 @@ const (
ProtocolConnectionEstablished
ProtocolFragmentationReceived
ProtocolStartFragmentationTest
ProtocolNonce
ProtocolPMTUVerified
ProtocolGossip
ProtocolGossipUnicast
+14 -9
View File
@@ -56,17 +56,17 @@ type PacketSourceSink interface {
func NewRouter(config RouterConfig, name PeerName, nickName string) *Router {
router := &Router{RouterConfig: config, GossipChannels: make(map[uint32]*GossipChannel)}
onMacExpiry := func(mac net.HardwareAddr, peer *Peer) {
log.Println("Expired MAC", mac, "at", peer.FullName())
log.Println("Expired MAC", mac, "at", peer)
}
onPeerGC := func(peer *Peer) {
router.Macs.Delete(peer)
log.Println("Removed unreachable peer", peer.FullName())
log.Println("Removed unreachable peer", peer)
}
router.Ourself = NewLocalPeer(name, nickName, router)
router.Macs = NewMacCache(macMaxAge, onMacExpiry)
router.Peers = NewPeers(router.Ourself.Peer, onPeerGC)
router.Peers = NewPeers(router.Ourself, onPeerGC)
router.Peers.FetchWithDefault(router.Ourself.Peer)
router.Routes = NewRoutes(router.Ourself.Peer, router.Peers)
router.Routes = NewRoutes(router.Ourself, router.Peers)
router.ConnectionMaker = NewConnectionMaker(router.Ourself, router.Peers, router.Port)
router.TopologyGossip = router.NewGossip("topology", router)
return router
@@ -94,13 +94,18 @@ func (router *Router) Start() {
}
}
func (router *Router) Stop() error {
// TODO: perform graceful shutdown...
return nil
}
func (router *Router) UsingPassword() bool {
return router.Password != nil
}
func (router *Router) Status() string {
var buf bytes.Buffer
fmt.Fprintln(&buf, "Our name is", router.Ourself.FullName())
fmt.Fprintln(&buf, "Our name is", router.Ourself)
fmt.Fprintln(&buf, "Sniffing traffic on", router.Iface)
fmt.Fprintf(&buf, "MACs:\n%s", router.Macs)
fmt.Fprintf(&buf, "Peers:\n%s", router.Peers)
@@ -250,7 +255,7 @@ func (router *Router) udpReader(conn *net.UDPConn, po PacketSink) {
if !ok {
continue
}
if err := relayConn.Decryptor.IterateFrames(packet, router.handleUDPPacketFunc(dec, sender, po)); err != nil {
if err := relayConn.Decryptor.IterateFrames(packet, router.handleUDPPacketFunc(relayConn, dec, sender, po)); err != nil {
// Errors during UDP packet decoding / processing are
// non-fatal. One common cause is that we receive and
// attempt to decrypt a "stray" packet. This can actually
@@ -267,8 +272,8 @@ func (router *Router) udpReader(conn *net.UDPConn, po PacketSink) {
}
}
func (router *Router) handleUDPPacketFunc(dec *EthernetDecoder, sender *net.UDPAddr, po PacketSink) FrameConsumer {
return func(relayConn *LocalConnection, srcNameByte, dstNameByte []byte, frame []byte) {
func (router *Router) handleUDPPacketFunc(relayConn *LocalConnection, dec *EthernetDecoder, sender *net.UDPAddr, po PacketSink) FrameConsumer {
return func(srcNameByte, dstNameByte []byte, frame []byte) {
srcPeer, found := router.Peers.Fetch(PeerNameFromBin(srcNameByte))
if !found {
return
@@ -323,7 +328,7 @@ func (router *Router) handleUDPPacketFunc(dec *EthernetDecoder, sender *net.UDPA
dstMac := dec.eth.DstMAC
if router.Macs.Enter(srcMac, srcPeer) {
log.Println("Discovered remote MAC", srcMac, "at", srcPeer.FullName())
log.Println("Discovered remote MAC", srcMac, "at", srcPeer)
}
if po != nil {
router.LogFrame("Injecting", frame, &dec.eth)
+12 -23
View File
@@ -9,7 +9,7 @@ import (
type Routes struct {
sync.RWMutex
ourself *Peer
ourself *LocalPeer
peers *Peers
unicast map[PeerName]PeerName
unicastAll map[PeerName]PeerName // [1]
@@ -21,7 +21,7 @@ type Routes struct {
// symmetric ones
}
func NewRoutes(ourself *Peer, peers *Peers) *Routes {
func NewRoutes(ourself *LocalPeer, peers *Peers) *Routes {
routes := &Routes{
ourself: ourself,
peers: peers,
@@ -166,12 +166,17 @@ func (routes *Routes) run(recalculate <-chan *struct{}, wait <-chan chan struct{
}
func (routes *Routes) calculate() {
routes.peers.RLock()
routes.ourself.RLock()
var (
unicast = routes.calculateUnicast(true)
unicastAll = routes.calculateUnicast(false)
broadcast = routes.calculateBroadcast(true)
broadcastAll = routes.calculateBroadcast(false)
)
routes.ourself.RUnlock()
routes.peers.RUnlock()
routes.Lock()
routes.unicast = unicast
routes.unicastAll = unicastAll
@@ -214,29 +219,13 @@ func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) map[PeerNam
// 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)
ourself := routes.ourself
ourConnections := ourself.Connections()
routes.peers.ForEach(func(peer *Peer) {
for _, peer := range routes.peers.table {
hops := []PeerName{}
if found, reached := peer.Routes(ourself, establishedAndSymmetric); found {
// This is rather similar to the inner loop on
// peer.Routes(...); the main difference is in the
// locking.
for conn := range ourConnections {
if establishedAndSymmetric && !conn.Established() {
continue
}
remoteName := conn.Remote().Name
if _, found := reached[remoteName]; found {
continue
}
if remoteConn, found := conn.Remote().ConnectionTo(ourself.Name); !establishedAndSymmetric || (found && remoteConn.Established()) {
hops = append(hops, remoteName)
}
}
if found, reached := peer.Routes(routes.ourself.Peer, establishedAndSymmetric); found {
routes.ourself.ForEachConnectedPeer(establishedAndSymmetric, reached,
func(remotePeer *Peer) { hops = append(hops, remotePeer.Name) })
}
broadcast[peer.Name] = hops
})
}
return broadcast
}
+3
View File
@@ -0,0 +1,3 @@
source 'https://rubygems.org'
gem 'jekyll'
+6
View File
@@ -2,3 +2,9 @@ markdown: redcarpet
redcarpet:
extensions: ['fenced_code_blocks', 'smart']
exclude:
- 'Gemfile'
- 'Gemfile.lock'
- 'params.json'
- 'build-and-publish.sh'
+1 -1
View File
@@ -23,7 +23,7 @@ example above, the 'launch' command would be run like this:
and the 'run' command like this:
host1# C=$(boot2docker ssh "sudo ./weave run 10.2.1.1/24 -t -i ubuntu")
host1$ C=$(boot2docker ssh "sudo ./weave run 10.2.1.1/24 -t -i ubuntu")
For more information about how to access services running in
Boot2Docker from the host or other machines (i.e. outside of the weave
+36
View File
@@ -0,0 +1,36 @@
#!/bin/sh -ex
cd $(dirname $0)/
## Use GCS until we set up Route53/CloudFront/S3
BRANCH=${TRAVIS_BRANCH:-`git rev-parse --abbrev-ref @`}
COMMIT=${TRAVIS_COMMIT:-`git rev-parse @`}
OUTPUT="weave/${BRANCH}/${COMMIT}"
if [ -n "${TRAVIS_TAG}" ]; then
## Travis will run separate build for commit and tag pushes,
OUTPUT="weave/${TRAVIS_TAG}"
fi
if [ "${BRANCH}" = "latest_release_doc_updates" ]; then
OUTPUT="weave/latest_release"
fi
export BRANCH COMMIT OUTPUT
bundle install --path=.bundle
bundle exec jekyll build --verbose
gsutil -m rsync -r -d _site "gs://docs.weave.works/${OUTPUT}"
echo "Published at http://docs.weave.works/${OUTPUT}"
if [ -z "${TRAVIS_TAG}" -a ! "${BRANCH}" = "latest_release_doc_updates" ]; then
echo "<meta http-equiv=\"refresh\" content=\"0; url=http://docs.weave.works/${OUTPUT}\" />" \
| gsutil \
-h "Content-Type:text/html" \
-h "Cache-Control:private, max-age=0, no-transform" \
cp -a "public-read" - "gs://docs.weave.works/weave/${BRANCH}/index.html"
gsutil -m rsync -r -d -p gs://docs.weave.works/${OUTPUT} gs://docs.weave.works/weave/${BRANCH}/head
fi
+18
View File
@@ -162,6 +162,24 @@ vm$ sudo docker ps
and so on.
If you are looking to just do a build and not run anything on this VM,
you can do so with
```bash
$ vagrant ssh -c 'make -C src/github.com/weaveworks/weave'
```
you should then find container snapshot tarballs in the top-level
directory. You can use these snapshots with `docker load` against a
different host, e.g.
```bash
$ export DOCKER_HOST=tcp://<HOST:PORT>
$ docker load < weave.tar
$ docker load < weavedns.tar
$ docker load < weaveexec.tar
```
You can provide extra Vagrant configuration by putting a file
`Vagrant.local` in the same place as `Vagrantfile`; for instance, to
forward additional ports.
+51 -35
View File
@@ -22,7 +22,9 @@ example](https://github.com/weaveworks/weave#example):
* [Dynamic topologies](#dynamic-topologies)
* [Container mobility](#container-mobility)
* [Fault tolerance](#fault-tolerance)
* [DNS](#dns)
* [Automatic IP address management](#ipam)
* [Automatic discovery with WeaveDNS](#dns)
* [Starting containers with Docker](#proxy)
### <a name="virtual-ethernet-switch"></a>Virtual Ethernet Switch
@@ -63,13 +65,13 @@ To accomplish that, we assign each application a different subnet. So,
in the above example, if we wanted to add another application similar
to, but isolated from, our first, we'd launch the containers with...
host1# D=$(weave run 10.2.2.1/24 -t -i ubuntu)
host2# D=$(weave run 10.2.2.2/24 -t -i ubuntu)
host1$ D=$(weave run 10.2.2.1/24 -t -i ubuntu)
host2$ D=$(weave run 10.2.2.2/24 -t -i ubuntu)
A quick 'ping' test in the containers confirms that they can talk to
each other but not the containers of our first application...
host1# docker attach $D
host1$ docker attach $D
root@da50502598d5:/# ping -c 1 -q 10.2.2.2
PING 10.2.2.2 (10.2.2.2): 48 data bytes
@@ -93,7 +95,7 @@ well-known technique from the 'on metal' days to containers.
If desired, a container can be attached to multiple subnets when it is
started:
host1# weave run 10.2.2.1/24 10.2.3.1/24 -t -i ubuntu
host1$ weave run 10.2.2.1/24 10.2.3.1/24 -t -i ubuntu
NB: By default docker permits communication between containers on the
same host, via their docker-assigned IP addresses. For complete
@@ -114,30 +116,30 @@ situations, weave allows an existing, running container to be attached
to the weave network. To illustrate, we can achieve the same effect as
the first example with
host1# C=$(docker run -d -t -i ubuntu)
host1# weave attach 10.2.1.1/24 $C
host1$ C=$(docker run -d -t -i ubuntu)
host1$ weave attach 10.2.1.1/24 $C
There is a matching `weave detach` command:
host1# weave detach 10.2.1.1/24 $C
host1$ weave detach 10.2.1.1/24 $C
You can detach a container from one application network and attach it
to another:
host1# weave detach 10.2.1.1/24 $C
host1# weave attach 10.2.2.1/24 $C
host1$ weave detach 10.2.1.1/24 $C
host1$ weave attach 10.2.2.1/24 $C
or attach a container to multiple application networks, effectively
sharing it between applications:
host1# weave attach 10.2.1.1/24 $C
host1# weave attach 10.2.2.1/24 $C
host1$ weave attach 10.2.1.1/24 $C
host1$ weave attach 10.2.2.1/24 $C
Finally, multiple addresses can be attached or detached with a single
invocation:
host1# weave attach 10.2.1.1/24 10.2.2.1/24 10.2.3.1/24 $C
host1# weave detach 10.2.1.1/24 10.2.2.1/24 10.2.3.1/24 $C
host1$ weave attach 10.2.1.1/24 10.2.2.1/24 10.2.3.1/24 $C
host1$ weave detach 10.2.1.1/24 10.2.2.1/24 10.2.3.1/24 $C
### <a name="security"></a>Security
@@ -145,12 +147,12 @@ In order to connect containers across untrusted networks, weave peers
can be told to encrypt traffic by supplying a `-password` option or
`WEAVE_PASSWORD` environment variable when launching weave, e.g.
host1# weave launch -password wEaVe
host1$ weave launch -password wEaVe
or
host1# export WEAVE_PASSWORD=wEaVe
host1# weave launch
host1$ export WEAVE_PASSWORD=wEaVe
host1$ weave launch
_NOTE: The command line option takes precedence over the environment
variable._
@@ -169,31 +171,31 @@ anywhere.
Let's say that in our example we want `$HOST2` to have access to the
application containers. On `$HOST2` we run
host2# weave expose 10.2.1.102/24
host2$ weave expose 10.2.1.102/24
choosing an unused IP address in the application subnet. (There is a
corresponding 'hide' command to revert this step.)
Now
host2# ping 10.2.1.2
host2$ ping 10.2.1.2
will work. And, more interestingly,
host2# ping 10.2.1.1
host2$ ping 10.2.1.1
will work too, which is talking to a container that resides on `$HOST1`.
Multiple subnet addresses can be exposed or hidden with a single
invocation:
host2# weave expose 10.2.1.102/24 10.2.2.102/24
host2# weave hide 10.2.1.102/24 10.2.2.102/24
host2$ weave expose 10.2.1.102/24 10.2.2.102/24
host2$ weave hide 10.2.1.102/24 10.2.2.102/24
Finally, exposed addresses can be added to weaveDNS by supplying a
hostname:
fully-qualified domain name:
host2# weave expose 10.2.1.102/24 -h exposed.weave.local
host2$ weave expose 10.2.1.102/24 -h exposed.weave.local
### <a name="service-export"></a>Service export
@@ -208,12 +210,12 @@ a container on `$HOST1`, accessible to the outside world via `$HOST2`.
First we need to expose the application network to `$HOST2`, as
explained [above](#host-network-integration), i.e.
host2# weave expose 10.2.1.102/24
host2$ weave expose 10.2.1.102/24
Then we add a NAT rule to route from the outside world to the
destination container service.
host2# iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 2211 \
host2$ iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 2211 \
-j DNAT --to-destination 10.2.1.1:4422
Here we are assuming that the "outside world" is connecting to `$HOST2`
@@ -248,19 +250,19 @@ First we need to expose the application network to the host, as
explained [above](#host-network-integration), this time on `$HOST1`,
i.e.
host1# weave expose 10.2.1.101/24
host1$ weave expose 10.2.1.101/24
Then we add a NAT rule to route from the above IP to the destination
service.
host1# iptables -t nat -A PREROUTING -p tcp -d 10.2.1.101 --dport 3322 \
host1$ iptables -t nat -A PREROUTING -p tcp -d 10.2.1.101 --dport 3322 \
-j DNAT --to-destination $HOST3:2211
This allows any application container to reach the service by
connecting to 10.2.1.101:3322. So if `$HOST3` is indeed running a netcat
service on port 2211, e.g.
host3# nc -lk -p 2211
host3$ nc -lk -p 2211
then we can connect to it from our application container on `$HOST2` with
@@ -295,13 +297,13 @@ In our example above, the netcat service on `$HOST3` is imported into
weave via `$HOST1`. We can export it on `$HOST2` by first exposing the
application network with
host2# weave expose 10.2.1.102/24
host2$ weave expose 10.2.1.102/24
and then adding a NAT rule which routes traffic from the `$HOST2`
network (i.e. anything which can connect to `$HOST2`) to the service
endpoint in the weave network
host2# iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 4433 \
host2$ iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 4433 \
-j DNAT --to-destination 10.2.1.101:3322
Now any host on the same network as `$HOST2` can access the service with
@@ -390,9 +392,23 @@ restarted in that event, and indeed may not even experience a
temporary connectivity failure if the weave container is restarted
quickly enough.
### <a name="dns"></a>DNS
### <a name="ipam"></a>Automatic IP Address Management
Weave can automatically assign unique IP addresses to each container
across the network. The `run`, `start`, `attach`, `expose` and `hide`
commands will then fetch an address automatically if none is
specified. See the [IPAM](ipam.html) documentation for details on how
to enable and use this feature.
### <a name="dns"></a>Automatic discovery with WeaveDNS
WeaveDNS is a distributed DNS service for weave networks, enabling
containers to address each other by name rather than IP address. A
preview release of weaveDNS is described [on our
blog](http://weaveblog.com/2014/11/04/have-you-met-weavedns/).
containers to address each other by name rather than IP address. Find
out more about WeaveDNS from its [documentation](weavedns.html).
### <a name="proxy"></a>Starting containers with Docker
Instead of the `weave` command-line utility, you may prefer to use the
standard Docker command-line interface, or the Docker remote API, for
starting weave-enabled containers. This can be accomplished with the
[Weave Proxy](proxy.html).
+85 -98
View File
@@ -5,6 +5,14 @@ layout: default
## How does it work?
* [Overview](#overview)
* [Encapsulation](#encapsulation)
* [Topology](#topology)
* [Crypto](#crypto)
* [Further reading](#further-reading)
### <a name="overview"></a>Overview
A weave network consists of a number of 'peers' - weave routers
residing on different hosts. Each peer has a name, which tends to
remain the same over restarts, a human friendly nickname for use in
@@ -102,30 +110,34 @@ MAC discovery.
The topology information captures which peers are connected to which
other peers. Weave peers communicate their knowledge of the topology
(and changes to it) to their neighbours, who then pass this
information on to their own neighbours, and so on, until the entire
network knows about any change.
(and changes to it) to others, so that all peers learn about the
entire topology. This communication occurs over the TCP links between
peers, using a) spanning-tree based broadcast mechanism, and b) a
neighour gossip mechanism.
Topology is communicated over the TCP links between peers, using a
Gossip mechanism. Topology messages are sent by a peer...
Topology messages are sent by a peer...
- when a connection has been added; the entire topology is sent to the
remote peer, and an incremental update, containing information on
just the two peers at the ends of the connection, is sent to all
neighbours,
- when a connection has been added; if the remote peer appears to be
new to the network, the entire topology is sent to it, and an
incremental update, containing information on just the two peers at
the ends of the connection, is broadcast,
- when a connection has been marked as 'established', indicating that
the remote peer can receive UDP traffic from the peer; an update
containing just information about the local peer is sent to all
neighbours,
containing just information about the local peer is broadcast,
- when a connection has been torn down; an update containing just
information about the local peer is sent to all neighbours,
- periodically, on a timer, in case someone has missed an update.
information about the local peer is broadcast,
- periodically, on a timer, the entire topology is "gossiped" to a
subset of neighbours, based on a topology-sensitive random
distribution. This is done in case some of the aforementioned
broadcasts do not reach all peers, due to rapid changes in the
topology causing broadcast routing tables to become outdated.
The receiver of a topology update merges that update with its own
topology model, adding peers hitherto unknown to it, and updating
peers for which the update contains a more recent version than known
to it. If there were any such new/updated peers, then an improved
update containing them is sent out on all connections.
to it. If there were any such new/updated peers, and the topology
update was received over gossip (rather than broadcast), then an
improved update containing them is gossiped.
If the update mentions a peer that the receiver does not know, then
the entire update is ignored.
@@ -206,8 +218,8 @@ no longer has any connections within the network, it drops all
knowledge of that second peer.
#### Out-of-date topology
The peer-to-peer gossiping of updates is not instantaneous, so it is
very possible for a node elsewhere in the network to have an
The propagation of topology changes to all peers is not instantaneous,
so it is very possible for a node elsewhere in the network to have an
out-of-date view.
If the destination peer for a packet is still reachable, then
@@ -235,6 +247,16 @@ quite difficult to use NaCl incorrectly. Contrast this with libraries
such as OpenSSL where the library and its APIs are vast in size,
poorly documented, and easily used wrongly.
There are some similarities between weave's crypto and
[TLS](https://tools.ietf.org/html/rfc4346). We do not need to cater
for multiple cipher suites, certificate exchange and other
requirements emanating from X509, and a number of other features. This
simplifies the protocol and implementation considerably. On the other
hand, we need to support UDP transports, and while there are
extensions to TLS such as [DTLS](https://tools.ietf.org/html/rfc4347)
which can operate over UDP, these are not widely implemented and
deployed.
#### Establishing the Ephemeral Session Key
For every connection between peers, a fresh public/private key pair is
@@ -268,25 +290,23 @@ between two peers.
#### TCP
TCP connection are only used to exchange topology information between
peers, via a message-based protocol. The router generates a fresh
192-bit random nonce for every message to be sent, and prepends the
nonce to the encrypted message, as is normal in NaCl, so that the
receiver knows the nonce. Encryption of each message is carried out
using NaCl's `secretbox.Seal` function using the ephemeral session
key. Each TCP connection has a monotonically incrementing message
counter, the current value of which is included in the encrypted part
of the message. Given the assumption that TCP is reliable and ordered,
a message received via TCP is only acted upon if the message counter
in the received message is the expected message counter. This prevents
replay attacks on the TCP connection.
peers, via a message-based protocol. Encryption of each message is
carried out by NaCl's `secretbox.Seal` function using the ephemeral
session key and a nonce. The nonce contains the message sequence
number, which is incremented for every message sent, and a bit
indicating the polarity of the connection at the sender ('1' for
outbound). The latter is required by the
[NaCl Security Model](http://nacl.cr.yp.to/box.html) in order to
ensure that the two ends of the connection do not use the same nonces.
As TCP connections do not carry captured traffic, minimising message
size or latency is not a major concern, so the potentially substantial
increase in length of messages by prepending the full nonce, or the
cost of generating a fresh random nonce for each message is not
considered likely to cause problems. The random nonces are created by
the go `crypto/rand` package, which implements a cryptographically
secure pseudorandom number generator.
Decryption of a message at the receiver is carried out by NaCl's
`secretbox.Open` function using the ephemeral session key and a
nonce. The receiver maintains its own message sequence number, which
it increments for every message it decrypted successfully. The nonce
is constructed from that sequence number and the connection
polarity. As a result the receiver will only be able to decrypt a
message if it has the expected sequence number. This prevents replay
attacks.
#### UDP
@@ -297,7 +317,7 @@ follows:
+-----------------------------------+
| Name of sending peer |
+-----------------------------------+
| Nonce offset and flags |
| Message Sequence No and flags |
+-----------------------------------+
| NaCl SecretBox overheads |
+-----------------------------------+ -+
@@ -330,71 +350,38 @@ follows:
This is very similar to the [non-crypto encapsulation](#encapsulation).
All of the frames are encrypted with the same ephemeral session key
and all must be decrypted by the receiving peer. Frames which are to
be forwarded on to some further peer will be re-encrypted with the
relevant ephemeral session keys for the onward connections. Thus all
traffic is fully decrypted on every peer it passes through.
Encryption is again done with the NaCl `secretbox.Seal` function.
All of the frames on a connection are encrypted with the same
ephemeral session key, and a nonce constructed from a message sequence
number, flags and the connection polarity. This is very similar to the
TCP encryption scheme, and encryption is again done with the NaCl
`secretbox.Seal` function. The main difference is that the message
sequence number and flags are transmitted as part of the message,
unencrypted.
The name of the sending peer enables the receiver to identify the peer
who sent this UDP packet, and in turn to determine which ephemeral
session key was used and which nonce, and perform decryption. To avoid
sending a fresh 192-bit nonce with every UDP packet, which would pose
an unacceptable overhead, each UDP packet only carries the lowest 15
bits of the nonce, which is treated as an offset from the "established
nonce". The lifecycle of a nonce for UDP is:
The receiver uses the name of the sending peer to determine which
ephemeral session key and local cryptographic state to use for
decryption. Frames which are to be forwarded on to some further peer
will be re-encrypted with the relevant ephemeral session keys for the
onward connections. Thus all traffic is fully decrypted on every peer
it passes through.
1. A fresh nonce is generated and the most significant 177 bits are
sent to the receiving peer over the TCP connection. These upper most
177 bits are used for the next 2^15 (32768) UDP messages. This is the
"established nonce".
Decryption is once again carried out by NaCl's `secretbox.Open`
function using the ephemeral session key and nonce. The latter is
constructed from the message sequence number and flags that appeared
in the unencrypted portion of the received message, and the connection
polarity.
2. Each UDP message carries the lowest 15 bits as a unique counter. Thus
the lowest 15 bits are combined with (appended to) the uppermost 177
bits to form the unique nonce for that message.
To guard against replay attacks, the receiver maintains some state in
which it remembers the highest message sequence number seen. It could
simply reject messages with lower sequence numbers, but that could
result in excessive message loss when messages are re-ordered. The
receiver therefore additionally maintains a set of received message
sequence numbers in a window below the highest number seen, and only
rejects messages with a sequence number below that window, or
contained in the set. The window spans at least 2^20 message sequence
numbers, and hence any re-ordering between the most recent ~1 million
messages is handled without dropping messages.
3. Once the sending side has sent the 16384'th message on the current
nonce (50% of the way through the available range), it generates a new
nonce (upper 177 bits only) and sends that over the TCP connection,
thus hopefully ensuring it arrives and is ready before it is
needed. The sending side will switch to using the new nonce once the
full 32768 messages of the current nonce have been used. At this
point, the sending side resets the offset to 0.
4. The receiving side must deal with the fact that UDP is unreliable
and unordered. The receiving side keeps track of the highest offset
seen for each established nonce. The condition to switching to the new
nonce it has received via the TCP connection is: the highest offset
seen for the current nonce must be above 24576 (75% of the available
range) _and_ the current message just received must have an offset
less than 8192 (25% of the available range) _and_ the new offset must
be less than 8192 messages *ahead* of the highest seen offset so far
(assuming modulo 32768). If these conditions are met, the highest
offset is set to the offset of the current message and the new nonce
is used. If those conditions are not met, the receiving side continues
with the current nonce (according to the rules below), updating the
highest offset seen as appropriate. Thus in order for the nonce to not
be updated correctly would require a loss of at least 8192 messages.
5. When the receiving side switches to the new nonce, it does not
discard the old nonce. If the highest offset seen is below 8192 (25% of
the range) _and_ the current message offset is above 24576 (75% of the
range), _and_ the current message offset is less than 8192 behind the
highest seen nonce (assuming modulo 32768), then the current message is
decoded with the old nonce.
6. In the remaining cases, the current message is only decoded if its
offset is within 8192 either side of the highest seen offset.
7. To avoid replay attacks, the receiving side keeps a set of which
offsets have been used with the current and previous nonce. If the
offset doesn't exist in the set, and the message can be correctly
decoded, the offset is added to the relevant set (thus we avoid
poisoning attacks). If the offset already exists in the set, or the
message cannot be correctly decoded, the message is not processed
further.
### Further reading
### <a name="further-reading"></a>Further reading
More details on the inner workings of weave can be found in the
[architecture documentation](https://github.com/weaveworks/weave/blob/master/docs/architecture.txt).
+4 -4
View File
@@ -5,12 +5,12 @@ layout: default
# weave documentation
* [Introduction and example (in README)](https://github.com/weaveworks/weave#readme)
* [Introduction, Installation and Example](https://github.com/weaveworks/weave#readme)
* [Getting Started Guides](http://weave.works/guides/)
* [Features](features.html)
* [Automatic IP Address Management](ipam.html)
* [Automatic Discovery with WeaveDNS](weavedns.html)
* [Troubleshooting](troubleshooting.html)
* [Building](building.html)
* [How it works](how-it-works.html)
* [Contact information](https://github.com/weaveworks/weave#contact-us)
* *Installation Notes*
* [Using with boot2docker](boot2docker.html)
* [Using with systemd](systemd.html)
+139
View File
@@ -0,0 +1,139 @@
---
title: Automatic IP Address Management
layout: default
---
# Automatic IP Address Management
Weave can automatically assign unique IP addresses to each container
across the network. To make this work, weave must be told on startup
what range of addresses to allocate from, for example:
host1# weave launch -iprange 10.2.3.0/24
The `run`, `start`, `attach`, `expose` and `hide` commands will then
fetch an address automatically if none is specified, i.e.:
host1# C=$(weave run -ti ubuntu)
You can see which address was allocated with
[`weave ps`](troubleshooting.html#list-attached-containers):
host1# weave ps $C
a7aff7249393 7a:51:d1:09:21:78 10.2.3.1/24
The `-iprange` parameter is given in [CIDR
notation](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing)
format - in this example "/24" means the first 24 bits of the address
form the network address and the allocator is to allocate container
addresses that all start 10.2.3. The ".0" and ".-1" addresses in a
subnet are not used, as required by [RFC
1122](https://tools.ietf.org/html/rfc1122#page-29).
Once you have given a range of addresses to the IP allocator, you must
not use any addresses in that range for anything else. If, in our
example, you subsequently did `weave run 10.2.3.x/24`, you run the
risk that that address will be allocated to another container, which
will make network traffic delivery intermittent or non-existent for
the containers that share the same IP address. No diagnostic message
is output by weave if you break this rule.
Weave will automatically learn when a container has exited
and hence can release its IP address.
Weave shares the IP range across all peers, dynamically according to
their needs. If a group of peers becomes isolated from the rest (a
partition), they can continue to work with the IP ranges they had
before isolation, and can be re-connected to the rest of the network
and carry on. Note that you must specify the same range with
`-iprange` on each host, and you cannot mix weaves started with and
without -iprange.
### Initialisation
Just once, when you start up the whole network, weave needs a majority
of peers to agree in order to avoid isolated groups starting off
inconsistently. Therefore, you must either supply the list of all
peers in the network to `weave launch` or add the `-initpeercount`
flag to specify how many peers there will be. It isn't a problem to
over-estimate by a bit, but if you supply a number that is too small
then multiple independent groups may form.
To illustrate, suppose you have three hosts, accessible to each other
as `$HOST1`, `$HOST2` and `$HOST3`. You can start weave on those three
hosts with these three commands:
host1$ weave launch -iprange 10.3.0.0/16 $HOST2 $HOST3
host2$ weave launch -iprange 10.3.0.0/16 $HOST1 $HOST3
host3$ weave launch -iprange 10.3.0.0/16 $HOST1 $HOST2
Or, if it is not convenient to name all the other hosts at launch
time, you can give the number of peers like this:
host1$ weave launch -iprange 10.3.0.0/16 -initpeercount 3
host2$ weave launch -iprange 10.3.0.0/16 -initpeercount 3 $HOST3
host3$ weave launch -iprange 10.3.0.0/16 -initpeercount 3 $HOST2
### Stopping and removing peers
You may wish to `weave stop` and re-launch to change some config or to
upgrade to a new version; provided the underlying protocol hasn't
changed it will pick up where it left off and learn from peers in the
network which address ranges it was previously using. If, however, you
run `weave reset` this will remove the peer from the network so
if Weave is run again on that node it will start from scratch.
For failed peers, the `weave rmpeer` command can be used to
permanently remove the ranges allocated to said peer. This will allow
other peers to allocate IPs in the ranges previously owner by the rm'd
peer, and as such should be used with extreme caution - if the rm'd
peer had transferred some range of IP addresses to another peer but
this is not known to the whole network, or if it later rejoins
the Weave network, the same IP address may be allocated twice.
Assuming we had started the three peers in the example earlier, and
host3 has caught fire, we can go to one of the other hosts and run:
host1$ weave rmpeer host3
Weave will take all the IP address ranges owned by host3 and transfer
them to be owned by host1. The name "host3" is resolved via the
'nickname' feature of weave, which defaults to the local host
name. Alternatively, one can supply a peer name as shown in `weave
status`.
## <a name="troubleshooting"></a>Troubleshooting
The command
weave status
reports on the current status of the weave router and IP allocator:
````
weave router git-8f675f15c0b5
...
Allocator subnet 10.2.1.0/24
Free IPs: ~98.0%, 62 local, ~189 remote
Owned Ranges:
10.2.1.1 -> 96:e9:e2:2e:2d:bc (host1) (v3)
10.2.1.128 -> ea:84:25:9b:31:2e (host2) (v3)
10.2.1.192 -> ea:6c:21:09:cf:f0 (host3) (v9)
````
The first section covers the router; see the [troubleshooting
guide](troubleshooting.html#status-report) for full details.
The 'Allocator' section, which is only present if weave has been
started with the `-iprange` option, summarises the overall position and
lists which address ranges have been assigned to which peer. Each
range begins at the address shown and ends just before the next
address, or wraps around at the end of the subnet. The 'v' number
denotes how many times that entry has been updated.
The 'Free IPs' information may be out of date with respect to changes
happening elsewhere in the network.
+108
View File
@@ -0,0 +1,108 @@
---
title: Weave Proxy
layout: default
---
# Proxy
Instead of the `weave` command-line utility, you may prefer to use the
standard [Docker command-line
interface](https://docs.docker.com/reference/commandline/cli/), or the
[Docker remote
API](https://docs.docker.com/reference/api/docker_remote_api/). The
weave proxy sits between the `docker` command or API and the Docker
daemon, so that it can automatically attach containers to the weave
network.
Using the proxy brings some additional benefits over `weave run`. The
proxy ensures that the weave network interface is available before
starting a container's application process. Furthermore, containers
can be started in foreground mode, and can be automatically removed
(with the ususal `--rm`).
## Setup
Start the proxy with
host1$ weave launch-proxy
By default, the proxy will connect to docker at
`unix:///var/run/docker.sock`, and listen on port 12375. However, we
can adjust the connection to docker via the `-H` argument. All docker
commands can be run via the proxy, so it is safe to globally adjust
your `DOCKER_HOST`.
host1$ export DOCKER_HOST=tcp://host1:12375
host1$ docker ps
## Usage
When containers are created via the weave proxy, their entrypoint will
be modified to wait for the weave network interface to become
available. When they are started via the weave proxy, any IP addresses
and networks specified in the `WEAVE_CIDR` environment variable will
be attached. We can create and start a container via the weave proxy
with
host1$ docker run -e WEAVE_CIDR=10.2.1.1/24 -ti ubuntu /bin/sh
or, equivalently with
host1$ docker create -e WEAVE_CIDR=10.2.1.1/24 -ti ubuntu /bin/sh
5ef831df61d50a1a49272357155a976595e7268e590f0a2c75693337b14e1382
host1$ docker start 5ef831df61d50a1a49272357155a976595e7268e590f0a2c75693337b14e1382
Multiple IP addresses and networks can be supplied in the `WEAVE_CIDR`
variable by space-separating them, as in
`WEAVE_CIDR="10.2.1.1/24 10.2.2.1/24"`.
## Usage with IPAM
To automatically assign a unique IP address to a container, weave must
be told on startup what range of addresses to allocate from. For
example:
host1# weave launch -iprange 10.2.3.0/24
host1$ weave launch-proxy
host1$ export DOCKER_HOST=tcp://host1:12375
With this done, we can automatically assign an address to a container
by providing a blank `WEAVE_CIDR` value, as in
host1$ docker run -e WEAVE_CIDR= -ti ubuntu /bin/sh
Alternatively, to enable automatic allocation of all containers
without a `WEAVE_CIDR`, we can launch the proxy with the `--with-ipam`
option. For example:
host1$ weave launch-proxy --with-ipam
host1$ export DOCKER_HOST=tcp://host1:12375
Now, containers will always be automatically assigned an IP address
and attached to the weave network when we start them. For example:
host1$ docker run -ti ubuntu /bin/sh
More details on IPAM can be found in the [IPAM
documentation](ipam.html).
## Usage with WeaveDNS
Containers started via the proxy can be automatically configured to
use WeaveDNS for name resolution. To accomplish this we need to launch
the proxy with the `--with-dns` option
host1$ weave launch
host1$ weave launch-dns 10.2.254.1/24
host1$ weave launch-proxy --with-dns
With this done, any containers connected via IPAM or `WEAVE_CIDR=...`,
which are launched through the proxy will use weaveDNS for name
resolution. WeaveDNS is used in addition to any dns servers specified
via the `--dns` option. More details on weaveDNS can be found in the
[weaveDNS documentation](weavedns.html).
## Limitations
* The proxy does not currently support TLS.
* If you have a firewall, you will need to make sure port 12375 is open.
+3 -3
View File
@@ -15,7 +15,7 @@ normally place it in `/etc/systemd/system/weave.service`.
[Unit]
Description=Weave Network
Documentation=http://weaveworks.github.io/weave/
Documentation=http://docs.weave.works/weave/latest_release/
Requires=docker.service
After=docker.service
[Service]
@@ -65,6 +65,6 @@ context with the commands shown below. You will need to have the
sudo restorecon /usr/local/bin/weave
[readme]: https://github.com/weaveworks/weave/blob/master/README.md#installation
[connect]: http://weaveworks.github.io/weave/features.html#dynamic-topologies
[connect]: features.html#dynamic-topologies
[systemd]: http://www.freedesktop.org/wiki/Software/systemd/
[encryption]: http://weaveworks.github.io/weave/how-it-works.html#crypto
[encryption]: how-it-works.html#crypto
+6 -2
View File
@@ -21,8 +21,8 @@ Check the weave container logs with
A reasonable amount of information, and all errors, get logged there.
The log verbosity can be increased by supplying the `-debug` flag when
launching weave. Be warned, this will log information on a per-packet
basis, so can produce a lot of output.
launching weave. To log information on a per-packet basis use
`-pktdebug` - be warned, this can produce a lot of output.
Another useful debugging technique is to attach standard packet
capture and analysis tools, such as tcpdump and wireshark, to the
@@ -126,6 +126,10 @@ not currently connected to. Each line contains some information about
what went wrong the last time; whether it is attempting to connect or
is waiting for a while before connecting again.
There may also be further sections for
[IP allocator](ipam.html#troubleshooting) and
[weaveDNS](weavedns.html#troubleshooting).
### <a name="list-attached-containers"></a>List attached containers
weave ps
@@ -1,3 +1,8 @@
---
title: WeaveDNS Design Notes
layout: default
---
# Weave DNS (service discovery) design
The model is that each host has a service that is notified of
+126 -91
View File
@@ -1,3 +1,8 @@
---
title: Automatic Discovery with WeaveDNS
layout: default
---
# Weave DNS server
The Weave DNS server answers name queries in a Weave network. This
@@ -6,13 +11,24 @@ them hostnames and tell other containers to connect to those names.
Unlike Docker 'links', this requires no code changes and works across
hosts.
## Using weaveDNS
* [Using weaveDNS](#usage)
* [How it works](#how-it-works)
* [Adding and removing extra DNS entries](#add-remove)
* [Hot-swapping service containers](#hot-swapping)
* [Retaining DNS entries when containers stop](#retain-stopped)
* [Configuring the domain search path](#domain-search-path)
* [Using a different local domain](#local-domain)
* [Using weaveDNS without `weave run`](#without-run)
* [Troubleshooting](#troubleshooting)
* [Present limitations](#limitations)
## <a name="usage"></a>Using weaveDNS
WeaveDNS is deployed as a set of containers that communicate with each
other over the weave network. One such container needs to be started
on every weave host, by invoking the weave script command
`launch-dns`. Application containers are then instructed to use
WeaveDNS as their nameserver by supplying the `--with-dns` option when
weaveDNS as their nameserver by supplying the `--with-dns` option when
starting them. Giving any container a hostname in the `.weave.local`
domain registers it in weaveDNS. For example:
@@ -23,20 +39,41 @@ $ weave run 10.2.1.25/24 -ti -h pingme.weave.local ubuntu
$ shell1=$(weave run --with-dns 10.2.1.26/24 -ti -h ubuntu.weave.local ubuntu)
$ docker attach $shell1
# ping pingme
root@ubuntu:/# ping pingme
...
```
Note that it is permissible to register multiple containers with the
same name. weaveDNS picks one address to return when asked for the
name. Since weaveDNS removes any container that dies, this is a simple
way to implement redundancy. In the current implementation it does
not attempt to do load-balancing.
Each weaveDNS container started with `launch-dns` needs to be given
its own, unique, IP address, in a subnet that is a) common to all
weaveDNS containers, b) disjoint from the application subnets, and c)
not in use on any of the hosts. In our example the weaveDNS address is
in subnet 10.2.254.0/24 and the application containers are in
subnet 10.2.1.0/24.
not in use on any of the hosts.
In our example the weaveDNS address is in subnet 10.2.254.0/24 and the
application containers are in subnet 10.2.1.0/24. So, to launch and
use weaveDNS on a second host we would run:
```bash
host2$ weave launch $HOST1
host2$ weave launch-dns 10.2.254.2/24
host2$ shell2=$(weave run --with-dns 10.2.1.36/24 -ti -h ubuntu2.weave.local ubuntu)
host2$ docker attach $shell2
root@ubuntu2:/# ping pingme
...
```
Notice the different IP address in the 10.2.254.0/24 subnet we gave to
`weave launch-dns`, compared to what we supplied on the first host.
WeaveDNS containers can be stopped with `stop-dns`.
## How it works
## <a name="how-it-works"></a>How it works
The weaveDNS container running on every host acts as the nameserver
for containers on that host. It is told about hostnames for local
@@ -52,7 +89,62 @@ When weaveDNS is queried for a name in a domain other than
`.weave.local`, it queries the host's configured nameserver,
which is the standard behaviour for Docker containers.
## Domain search paths
So that containers can connect to a stable and always routable IP
address, weaveDNS publishes its port 53 to the Docker bridge device,
which is assumed to be `docker0`. Some configurations may use a
different Docker bridge device. To supply a different bridge device,
use the environment variable `DOCKER_BRIDGE`, e.g.,
```bash
$ sudo DOCKER_BRIDGE=someother weave launch-dns 10.2.254.1/24
```
In the event that weaveDNS is launched in this way, it's important that
other calls to `weave` also specify the bridge device:
```bash
$ sudo DOCKER_BRIDGE=someother weave run --with-dns ...
```
## <a name="add-remove"></a>Adding and removing extra DNS entries
If you want to give the container a name in DNS *other* than its
hostname, you can register it using the `dns-add` command. For example:
```bash
$ shell2=(weave run 10.2.1.27/24 -ti ubuntu)
$ weave dns-add 10.2.1.28 $shell2 -h pingme2.weave.local
```
You can also use `dns-add` to add the container's configured hostname
and domain, simply by omitting `-h <fqdn>`.
The inverse operation can be carried out using the `dns-remove` command:
```bash
$ weave dns-remove 10.2.1.27 $shell2
```
## <a name="hot-swapping"></a>Hot-swapping service containers
If you would like to deploy a new version of a service, keep the old
one running because it has active connections but make all new
requests go to the new version, then you can simply start the new
server container and then [remove](#add-remove) the entry for the old
server container. Later, when all connections to the old server have
terminated, stop the container as normal.
## <a name="retain-stopped"></a>Retaining DNS entries when containers stop
By default, weaveDNS watches docker events and removes entries for any
containers that die. You can tell it not to, by adding `--watch=false`
to the container args:
```bash
$ weave launch-dns 10.2.254.1/24 --watch=false
```
## <a name="domain-search-path"></a>Configuring the domain search paths
If you don't supply a domain search path (with `--dns-search=`),
`weave run ...` tells a container to look for "bare" hostnames, like
@@ -73,31 +165,32 @@ weave run --with-dns 10.2.1.4/24 -ti \
--dns-search=weave.local ubuntu
```
## Doing things more manually
## <a name="local-domain"></a>Using a different local domain
If you use the `--with-dns` option, `weave run` automatically supplies
the DNS server address to the new container. And both `weave run` and
`weave attach` register the hostname of the given container against
the given weave network IP address.
In some circumstances, you may not want to use the `weave`
command. You can still take advantage of a running weaveDNS, with some
extra manual steps.
### Using a different docker bridge
So that containers can connect to a stable and always routable IP
address, weaveDNS publishes its port 53 to the Docker bridge device,
which is assumed to be `docker0`.
Some configurations may use a different Docker bridge device. To
supply a different bridge device, use the environment variable
`DOCKER_BRIDGE`, e.g.,
By default, weaveDNS uses `weave.local.` as the domain for names on the
Weave network. In general users do not need to change this domain, but you
can force weaveDNS to use a different domain by launching it
with the `--domain` argument. For example,
```bash
$ sudo DOCKER_BRIDGE=someother weave launch-dns 10.2.254.1/24
$ weave launch-dns 10.2.254.1/24 --domain="mycompany.local."
```
The local domain should end with `local.`, since these names are
link-local as per [RFC6762](https://tools.ietf.org/html/rfc6762),
(though this is not strictly necessary).
## <a name="without-run"></a>Using weaveDNS without `weave run`
When weaveDNS is running, both `weave run` and `weave attach` register
the hostname of the given container against the given weave network IP
address. And if you use the `--with-dns` option, `weave run`
automatically supplies the DNS server address to the new container.
In some circumstances, you may not want to use the `weave run` command
to start containers. You can still take advantage of a running
weaveDNS, with some extra manual steps.
### Supplying the DNS server
If you want to start containers with `docker run` rather than `weave
@@ -134,65 +227,7 @@ that you can use unqualified hostnames. Use `--dns-search=.` to make
the resolver use the container's domain, or e.g.,
`--dns-search=weave.local` to make it look in `weave.local`.
### Using a different local domain
By default, WeaveDNS uses `weave.local.` as the domain for names on the
Weave network. In general users do not need to change this domain, but you
can force WeaveDNS to use a different domain by launching it
with the `--domain` argument. For example,
```bash
$ weave launch-dns 10.2.254.1/24 --domain="mycompany.local."
```
The local domain should end with `local.`, since these names are
link-local as per [RFC6762](https://tools.ietf.org/html/rfc6762),
(though this is not strictly neccessary).
### Adding containers to DNS
If DNS is started after you've attached a container to the weave
network, or you want to give the container a name in DNS *other* than
its hostname, you can register it using the HTTP API:
```bash
$ docker start $shell2
$ curl -X PUT "http://$dns_ip:6785/name/$shell2/10.2.1.27" -d fqdn=shell2.weave.local
```
### Registering multiple containers with the same name
This is supported; weaveDNS picks one address to return when you ask
for the name. Since weaveDNS removes any container that dies, this is
a simple way to implement redundancy. In the current implementation
it does not attempt to do load-balancing.
### Replacing one container with another at the same name
If you would like to deploy a new version of a service, keep the old one running because it has active connections but make all new requests go to the new version, then you can simply start the new server container and then [unregister](https://github.com/weaveworks/weave/tree/master/weavedns#unregistering) the old one from DNS. And finally, when all connections to the old server have terminated, stop the container as normal.
### Not watching docker events
By default, weaveDNS watches docker events and removes entries for any
containers that die. You can tell it not to, by adding `--watch=false`
to the container args:
```bash
$ weave launch-dns 10.2.254.1/24 --watch=false
```
### Unregistering
You can manually delete entries for a host, by poking weaveDNS's HTTP
API with e.g., `curl`:
```bash
$ docker stop $shell2
$ dns_ip=$(docker inspect --format='{{ .NetworkSettings.IPAddress }}' weavedns)
$ curl -X DELETE "http://$dns_ip:6785/name/$shell2/10.2.1.27"
```
## Troubleshooting
## <a name="troubleshooting"></a>Troubleshooting
The command
@@ -216,8 +251,8 @@ e8d85b1dcdb1 10.2.1.27 waff.weave.local.
1ab0e8b17c39 10.2.1.29 pong.weave.local.
````
The first section covers the router; see the troubleshooting guide in
the main documentation for more detail.
The first section covers the router; see the [troubleshooting
guide](troubleshooting.html#status-report) for more detail.
The second section is pertinent to weaveDNS, and includes:
@@ -227,7 +262,7 @@ The second section is pertinent to weaveDNS, and includes:
* The fallback DNS which will be used to resolve non local names
* The names known to the local weaveDNS server. Each entry comprises the container ID, IP address and its fully qualified domain name
## Present limitations
## <a name="limitations"></a>Present limitations
* The server will not know about restarted containers, but if you
re-attach a restarted container to the weave network, it will be
@@ -237,4 +272,4 @@ The second section is pertinent to weaveDNS, and includes:
hostnames with the subnets.
* We use UDP multicast to find out about remote names (from weaveDNS
servers on other hosts); this likely won't scale well beyond a
certain point T.B.D., so we'll have to come up with another scheme.
certain point T.B.D.
+11 -12
View File
@@ -4,21 +4,20 @@
C1=10.2.1.4
C2=10.2.1.7
UNIVERSE=10.2.2.0/24
start_suite "Ping over cross-host weave network"
start_suite "Ping over cross-host weave network (with and without IPAM)"
for HOST in $HOST1 $HOST2; do
weave_on $HOST stop || true
weave_on $HOST stop-dns || true
docker_on $HOST rm -f c1 c2 || true
done
weave_on $HOST1 launch -iprange $UNIVERSE
weave_on $HOST2 launch -iprange $UNIVERSE $HOST1
weave_on $HOST1 launch
weave_on $HOST2 launch $HOST1
start_container $HOST1 $C1/24 --name=c1
start_container $HOST2 $C2/24 --name=c2
assert_raises "exec_on $HOST1 c1 $PING $C2"
weave_on $HOST2 run $C2/24 -t --name=c2 ubuntu
weave_on $HOST1 run $C1/24 -t --name=c1 ubuntu
ok=$(docker -H tcp://$HOST1:2375 exec c1 sh -c "ping -q -c 4 $C2 >&2 && echo ok")
assert "echo $ok" "ok"
start_container $HOST1 --name=c3
start_container $HOST2 --name=c4
C4=$(container_ip $HOST2 c4)
assert_raises "exec_on $HOST1 c3 $PING $C4"
end_suite
+3 -10
View File
@@ -7,18 +7,11 @@ C2=10.2.1.7
start_suite "Ping over encrypted cross-host weave network"
for HOST in $HOST1 $HOST2; do
weave_on $HOST stop || true
weave_on $HOST stop-dns || true
docker_on $HOST rm -f c1 c2 || true
done
weave_on $HOST1 launch -password notverysecure
weave_on $HOST2 launch -password notverysecure $HOST1
weave_on $HOST2 run $C2/24 -t --name=c2 ubuntu
weave_on $HOST1 run $C1/24 -t --name=c1 ubuntu
ok=$(docker -H tcp://$HOST1:2375 exec c1 sh -c "ping -q -c 4 $C2 >&2 && echo ok")
assert "echo $ok" "ok"
start_container $HOST1 $C1/24 --name=c1
start_container $HOST2 $C2/24 --name=c2
assert_raises "exec_on $HOST1 c1 $PING $C2"
end_suite
+81
View File
@@ -0,0 +1,81 @@
#! /bin/bash
. ./config.sh
C1=10.2.1.34
C2=10.2.1.37
C3=10.2.2.34
C4=10.2.2.37
EXP=10.2.2.101
UNIVERSE=10.2.3.0/24
weave_on1() {
assert_raises "weave_on $HOST1 $@"
}
run_on1() {
assert_raises "run_on $HOST1 $@"
}
exec_on1() {
assert_raises "exec_on $HOST1 $@"
}
# Containers in the same subnet should be able to talk; different subnet not.
check_container_connectivity() {
exec_on1 "c1 $PING $C2"
exec_on1 "c3 $PING $C4"
exec_on1 "c5 $PING $C6"
exec_on1 "c1 sh -c \"! $PING $C3\""
exec_on1 "c3 sh -c \"! $PING $C5\""
exec_on1 "c5 sh -c \"! $PING $C1\""
}
start_suite "exposing weave network to host"
# expose/hide with a CIDR work prior to launching weave
run_on1 "! $PING $EXP"
weave_on1 "expose $EXP/24"
run_on1 " $PING $EXP"
weave_on1 "hide $EXP/24"
run_on1 "! $PING $EXP"
weave_on $HOST1 launch -iprange $UNIVERSE
start_container $HOST1 $C1/24 --name=c1
start_container $HOST1 $C2/24 --name=c2
start_container $HOST1 $C3/24 --name=c3
start_container $HOST1 $C4/24 --name=c4
start_container $HOST1 --name=c5
start_container $HOST1 --name=c6
C5=$(container_ip $HOST1 c5)
C6=$(container_ip $HOST1 c6)
# absence of host connectivity by default
run_on1 "! $PING $C3"
run_on1 "! $PING $C5"
check_container_connectivity
# host connectivity after 'expose', and idempotence of 'expose'
weave_on1 "expose $EXP/24"
weave_on1 "expose $EXP/24"
run_on1 "! $PING $C1"
run_on1 " $PING $C3"
run_on1 "! $PING $C5"
weave_on1 "expose"
weave_on1 "expose"
run_on1 "! $PING $C1"
run_on1 " $PING $C3"
run_on1 " $PING $C5"
check_container_connectivity
# no host connectivity after 'hide', and idempotence of 'hide'
weave_on1 "hide $EXP/24"
weave_on1 "hide $EXP/24"
run_on1 "! $PING $C3"
run_on1 " $PING $C5"
weave_on1 "hide"
weave_on1 "hide"
run_on1 "! $PING $C5"
end_suite
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
. ./config.sh
start_suite "Run weave with --local"
run_on $HOST1 sudo ./weave --local reset
run_on $HOST1 sudo ./weave --local launch -iprange 10.2.5.0/24
assert_raises "docker_on $HOST1 ps | grep weave"
run_on $HOST1 sudo ./weave --local run 10.2.6.5/24 -ti --name=c1 $SMALL_IMAGE /bin/sh
assert_raises "exec_on $HOST1 c1 $CHECK_ETHWE_UP"
run_on $HOST1 sudo ./weave --local run -ti --name=c2 $SMALL_IMAGE /bin/sh
assert_raises "exec_on $HOST1 c2 $CHECK_ETHWE_UP"
end_suite
+5 -10
View File
@@ -4,22 +4,17 @@
C1=10.2.0.78
C2=10.2.0.34
NAME=seetwo.weave.local
start_suite "Resolve names on a single host"
weave_on $HOST1 stop || true
weave_on $HOST1 stop-dns || true
weave_on $HOST1 launch-dns 10.2.254.1/24
docker_on $HOST1 rm -f c1 c2 || true
start_container $HOST1 $C2/24 --name=c2 -h $NAME
start_container_with_dns $HOST1 $C1/24 --name=c1
weave_on $HOST1 run $C2/24 -t --name=c2 -h seetwo.weave.local ubuntu
weave_on $HOST1 run --with-dns $C1/24 -t --name=c1 aanand/docker-dnsutils /bin/sh
assert_dns_record $HOST1 c1 $NAME $C2
ok=$(docker -H tcp://$HOST1:2375 exec c1 sh -c "dig +short seetwo.weave.local")
assert "echo $ok" "$C2"
ok=$(docker -H tcp://$HOST1:2375 exec c1 sh -c "dig +short -x $C2")
assert "echo $ok" "seetwo.weave.local."
assert_raises "exec_on $HOST1 c1 dig MX $NAME | grep -q \"status: NXDOMAIN\""
end_suite
+17 -20
View File
@@ -4,31 +4,28 @@
C1=10.2.3.78
C2=10.2.3.34
UNIVERSE=10.2.4.0/24
NAME2=seetwo.weave.local
NAME4=seefour.weave.local
start_suite "Resolve names across hosts"
start_suite "Resolve names across hosts (with and without IPAM)"
for host in $HOST1 $HOST2; do
weave_on $host stop || true
weave_on $host stop-dns || true
docker_on $host rm -f c1 c2 || true
done
weave_on $HOST1 launch -iprange $UNIVERSE
weave_on $HOST2 launch -iprange $UNIVERSE $HOST1
weave_on $HOST1 launch
weave_on $HOST2 launch $HOST1
weave_on $HOST1 launch-dns 10.2.254.1/24
weave_on $HOST2 launch-dns 10.2.254.2/24
weave_on $HOST1 launch-dns 10.2.254.1/24 -debug
weave_on $HOST2 launch-dns 10.2.254.2/24 -debug
start_container $HOST2 $C2/24 --name=c2 -h $NAME2
start_container $HOST2 --name=c4 -h $NAME4
start_container_with_dns $HOST1 $C1/24 --name=c1
start_container_with_dns $HOST1 --name=c3
C4=$(container_ip $HOST2 c4)
weave_on $HOST2 run $C2/24 -t --name=c2 -h seetwo.weave.local ubuntu
weave_on $HOST1 run --with-dns $C1/24 --name=c1 -t aanand/docker-dnsutils /bin/sh
assert_dns_record $HOST1 c1 $NAME2 $C2
assert_dns_record $HOST1 c3 $NAME4 $C4
ok=$(docker -H tcp://$HOST1:2375 exec c1 dig +short seetwo.weave.local)
assert "echo $ok" "$C2"
ok=$(docker -H tcp://$HOST1:2375 exec c1 dig +short -x $C2)
assert "echo $ok" "seetwo.weave.local."
ok=$(docker -H tcp://$HOST1:2375 exec c1 dig +short -x 8.8.8.8)
assert "test -n \"$ok\" && echo pass" "pass"
assert_raises "exec_on $HOST1 c1 getent hosts 8.8.8.8 | grep google"
assert_raises "exec_on $HOST1 c3 getent hosts 8.8.8.8 | grep google"
end_suite
+6 -12
View File
@@ -4,22 +4,16 @@
C1=10.2.56.34
C2=10.2.54.91
DOMAIN=foo.bar
NAME=seetwo.$DOMAIN
start_suite "Resolve names in custom domain"
weave_on $HOST1 stop || true
weave_on $HOST1 stop-dns || true
weave_on $HOST1 launch-dns 10.2.254.1/24 --domain foo.bar.
weave_on $HOST1 launch-dns 10.2.254.1/24 --domain $DOMAIN.
docker_on $HOST1 rm -f c1 c2 || true
start_container $HOST1 $C2/24 --name=c2 -h $NAME
start_container_with_dns $HOST1 $C1/24 --name=c1
weave_on $HOST1 run $C2/24 -t --name=c2 -h seetwo.foo.bar ubuntu
weave_on $HOST1 run --with-dns $C1/24 -t --name=c1 aanand/docker-dnsutils /bin/sh
ok=$(docker -H tcp://$HOST1:2375 exec c1 sh -c "dig +short seetwo.foo.bar.")
assert "echo $ok" "$C2"
ok=$(docker -H tcp://$HOST1:2375 exec c1 sh -c "dig +short -x $C2")
assert "echo $ok" "seetwo.foo.bar."
assert_dns_record $HOST1 c1 $NAME $C2
end_suite
+22
View File
@@ -0,0 +1,22 @@
#! /bin/bash
. ./config.sh
C1=10.2.0.78
C2=10.2.0.34
C3=10.2.0.57
DOMAIN=weave.local
NAME=seeone.$DOMAIN
start_suite "Resolve unqualified names"
weave_on $HOST1 launch-dns 10.2.254.1/24
start_container $HOST1 $C1/24 --name=c1 -h $NAME
start_container_with_dns $HOST1 $C2/24 --name=c2 -h seetwo.$DOMAIN
start_container_with_dns $HOST1 $C3/24 --name=c3 --dns-search=$DOMAIN
assert "exec_on $HOST1 c2 getent hosts seeone | tr -s ' '" "$C1 $NAME"
assert "exec_on $HOST1 c3 getent hosts seeone | tr -s ' '" "$C1 $NAME"
end_suite
+29
View File
@@ -0,0 +1,29 @@
#! /bin/bash
. ./config.sh
C1=10.2.0.78
C2=10.2.0.34
NAME1=seeone.weave.local
NAME2=seetwo.weave.local
start_suite "Add and remove names on a single host"
weave_on $HOST1 launch-dns 10.2.254.1/24
start_container $HOST1 $C2/24 --name=c2
start_container_with_dns $HOST1 $C1/24 --name=c1
weave_on $HOST1 dns-add $C2 c2 -h $NAME2
assert_dns_record $HOST1 c1 $NAME2 $C2
weave_on $HOST1 dns-add $C1 c1 -h $NAME1
assert "exec_on $HOST1 c1 getent hosts $C1 | tr -s ' '" "$C1 $NAME1"
weave_on $HOST1 dns-remove $C1 c1
assert_raises "exec_on $HOST1 c1 getent hosts $NAME1" 2
end_suite
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more