Compare commits

...
1048 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
Matthias Radestock dd5bfdf742 add changelog entry for 0.10.0 2015-04-21 16:03:01 +01:00
Matthias Radestock cbe73e7645 switch dockerhub user from zettio to weaveworks 2015-04-21 15:37:41 +01:00
Adam Harrison 8b9a2142c9 Include util-linux package in weaveexec container 2015-04-21 15:19:56 +01:00
Matthias Radestock 04d0cd33e6 We now require Docker 1.3.1
see #576.
2015-04-21 14:55:25 +01:00
Alvaro Saurin d49a3fe5ca Fixed wrong argument 2015-04-21 12:26:06 +02:00
Matthias Radestock caded5e605 add link 2015-04-21 07:37:32 +01:00
Matthias Radestock 87011586ae add a section on snapshot releases 2015-04-21 07:35:05 +01:00
Matthias Radestock c7044b0c14 improve troubleshooting instructions
by explaining how to check the version and upgrade
2015-04-21 07:19:13 +01:00
rade 8a8c4bae76 Merge pull request #581 from inercia/weave-573
Added some documentation for the local domain argument

Fixes #573.
2015-04-20 20:15:20 +01:00
Alvaro Saurin 6fd90d45a1 Renamed --localDomain to --domain 2015-04-20 20:14:20 +02:00
Alvaro Saurin cc4ba53d1b Minor improvements in the docs 2015-04-20 20:14:04 +02:00
Alvaro Saurin 084e6d6119 Documented the local domain argument 2015-04-20 17:52:16 +02:00
Adam Harrison 4059ff67e8 Add Vagrantfile.local to git ignore 2015-04-20 16:15:43 +01:00
Adam Harrison 32372fce8c Add Vagrantfile.local hook to smoke test Vagrantfile 2015-04-20 16:13:30 +01:00
Adam Harrison f3ab25a512 Remove unnecessary -i flag from docker exec invocations.
Fixes #579.
2015-04-20 14:35:18 +01:00
Matthias Radestock 16f3a96a67 fix handling of --dns-search
This got broken in b4c00050e0.
2015-04-20 12:58:04 +01:00
Matthias Radestock 1e3d429d58 refactor: better increment 2015-04-20 12:45:57 +01:00
Michael Bridgen 39c925983a Merge branch 'pr575' 2015-04-20 12:10:10 +01:00
Matthias Radestock b4c00050e0 simplify dns-args
it makes more sense to strip off the `--with-dns` at the call site.
`weave dns-args` now does something useful, instead of having to be
invoked as `weave dns-args --with-dns`.
2015-04-20 11:31:12 +01:00
Matthias Radestock 53e145bea7 simplify local portion of weave run
pull all the local `weave run` execution into one place, thus
dispensing with the need to have lots of tests for whether we are
executing in the context of "--local".
Also, we only remote exec dns-args if "--with-dns" is supplied, which
is more efficient in the common case (and also more obvious).

NB: we need to retain the docker version check for `run` since the
specific breakage it guards against does affect the code we execute
locally for `run`.
2015-04-20 11:30:56 +01:00
Matthias Radestock 0a2f341c6b cosmetic: move DOCKERHUB_USER setting
for more logical grouping
2015-04-19 13:19:06 +01:00
Matthias Radestock cefd89b7d8 only set vars that we need for remote execution 2015-04-19 13:19:06 +01:00
Matthias Radestock fbf7612726 only check for command / print usage at remote
This is desirable since we want to do as little as possible locally,
and not duplicate any checks.
2015-04-19 13:19:06 +01:00
Matthias Radestock 7752d6b102 refactor: move "need to run as root" check
to a more obvious place
2015-04-19 13:18:22 +01:00
Matthias Radestock 74f77a71ce cosmetic: move usage() 2015-04-19 11:53:33 +01:00
Matthias Radestock 40cd875763 don't validate CIDRs twice in weave expose 2015-04-19 11:17:42 +01:00
Matthias Radestock 5addf4e7b1 cosmetic: logical grouping of functions in weave script 2015-04-19 09:14:28 +01:00
rade 35201b8865 Merge pull request #572 from dpw/forwarder-df
refactor: separate Forwarder and ForwarderDF
2015-04-18 20:09:51 +01:00
David Wragg dceef04ffa Separate Forwarder and ForwarderDF
Much of the state and code associated with Forwarder only applied in the
DF case.
2015-04-18 10:38:05 +01:00
rade 3974b9282a Merge pull request #570 from awh/fix_multi_cidr_error_messages
Fix multi cidr error messages
2015-04-17 15:34:36 +01:00
Adam Harrison 3943b0231b Improved validation of trailing arguments 2015-04-17 13:54:55 +01:00
rade ac2afdadb6 Merge pull request #571 from tomwilkie/travis
Run linter in travis; fix linter; make nameserver lint free.
2015-04-17 12:58:35 +01:00
Tom Wilkie f735082d95 Fix gofmt errors in nameserver. 2015-04-17 11:42:29 +00:00
Tom Wilkie c187c9b290 Make lint script work; $result has special meaning! 2015-04-17 11:42:19 +00:00
Tom Wilkie eef5559a43 Lint the codebase in travis. 2015-04-17 11:11:52 +00:00
rade 5006839fb8 Merge pull request #569 from bboreham/simplify_parse
Simplify address parsing for populate_dns in weave script
2015-04-17 12:04:38 +01:00
Adam Harrison be05cec1b0 Ensure at least one CIDR is supplied to multi-CIDR commands 2015-04-17 11:38:46 +01:00
Adam Harrison f17cd6ac3b Convert hide to use collect_cidr_args 2015-04-17 11:37:28 +01:00
Bryan Boreham 7c68525e93 Simplify address parsing for populate_dns() 2015-04-17 11:31:31 +01:00
Adam Harrison 2332bc9a94 Require minimum Vagrant version (1.7.0) in smoke tests 2015-04-17 11:07:53 +01:00
Matthias Radestock 803821ab2c we need arping in the vagrant vm, for weave --local to work 2015-04-17 10:22:08 +01:00
Matthias Radestock 1cd31fd090 only invoke create_bridge once in expose/hide 2015-04-16 18:19:15 +01:00
Matthias Radestock 705dda9d3e cosmetic: remove outdated comment 2015-04-16 18:19:00 +01:00
Matthias Radestock 19da213dab refactor: more shell magic, for consistency 2015-04-16 18:00:18 +01:00
Matthias Radestock 779b69c0f6 correct usage for expose+hide
Hangover from #498.
2015-04-16 17:49:17 +01:00
rade 8f4d894a77 Merge pull request #566 from awh/name_exposed_addresses_in_weavedns
Add optional hostname argument to weave expose

Closes #375.
2015-04-16 17:45:47 +01:00
Adam Harrison 4d941338fd Add optional hostname argument to weave expose 2015-04-16 15:19:01 +01:00
rade af51aef33f Merge pull request #551 from rade/weave_port
make weave port configurable via WEAVE_PORT
2015-04-16 14:44:39 +01:00
Bryan Boreham 36b13b704d Merge pull request #565 from rade/515_crypto_glitch_stall
Big improvement in observed behaviour.  LGTM; closes #515
2015-04-16 12:08:02 +01:00
Matthias Radestock a5a91ebb33 do not shut down connections on PacketDecodingError 2015-04-15 21:52:56 +01:00
Matthias Radestock f4ba0f7287 log udp packet processing errors against the connection 2015-04-15 21:10:22 +01:00
Matthias Radestock 57b7276ceb create forwarders before AddConnection, if we can
This is beneficial for reasons explained in the comments. And we used
to do that previously, when initHeartbeats was called before
AddConnection.
2015-04-15 20:55:42 +01:00
Matthias Radestock 9b25815641 don't send heartbeats on new but ultimately invalid connections
It is wasteful and can trip up crypto at the other end. See comment
for why.

To accomplish this we move the responsibility for shutting down the
new, invalid connection from LocalPeer.AddConnection to its caller,
i.e. LocalConnection.run. So rather than always firing up all the
connection machinery and getting told asynchronously by AddConnection
to shut it all down again, we now only fire it up if AddConnection
succeeded.
2015-04-15 20:04:40 +01:00
Matthias Radestock a9963675b6 remove dead code 2015-04-15 16:13:18 +01:00
rade 0ccf19e488 Merge pull request #561 from dpw/random-mac
Make the first byte of the generated bridge MAC more random
2015-04-15 15:43:57 +01:00
David Wragg a1db1e11f5 Make the first byte of the generated bridge MAC more random 2015-04-15 14:19:28 +01:00
rade 2eb13f0020 Merge pull request #558 from awh/weave_ps_includes_exposed_addresses
Include weave exposed addresses in ps output

Closes #385.
2015-04-15 12:01:30 +01:00
Adam Harrison cbbe3cc122 Include weave exposed addresses in ps output 2015-04-15 11:54:05 +01:00
rade ab49eb42fb Merge pull request #555 from rade/478_more_discriminate_connectivity
more discriminate connectivity

Closes #478.
2015-04-15 11:48:23 +01:00
Matthias Radestock 339f85cbcb get rid of Router.NormalisePeerAddr
There was only one call site left and it always gets invoked with an
ip:port, so normalisation is a no-op.

Also remove mysterious comment.
2015-04-15 11:44:48 +01:00
Matthias Radestock 290d5443cc more discriminate connectivity
If a peer was specified on the command line w/o a port, then do not
connect to it if we have inbound connections from that IP.

This eliminates unnecessary (and ultimately failing) connection
attempts in the common scenario of several peers having been told
about each other on their command lines.

We represent a command line peer address with a TCPAddr, which avoids
back and forth conversion to string and extra fields to carry the "did
the original include a port?" information.
2015-04-15 11:42:53 +01:00
rade 9bd2afc6c3 Merge pull request #560 from rade/include_remote_address_in_connection_logging
always include the remote address in connection log entries
tidy up / declutter some log and status output
2015-04-15 11:23:26 +01:00
Matthias Radestock 9478696d1a update example output in docs 2015-04-15 11:17:30 +01:00
Matthias Radestock 5415e3a60a drop "Peer " prefix in Peers section of status
it's redundant and the output looks much better w/o it
2015-04-15 11:09:35 +01:00
Matthias Radestock f0998d6196 declutter status' reconnects section 2015-04-15 10:56:29 +01:00
Matthias Radestock f6def13ea9 change style for connections in status' reconnects section
to match how they are shown in the logs
2015-04-15 10:55:14 +01:00
Matthias Radestock b1fd1334b4 always include the remote address in connection log entries
in addition to the peer name

This is handy for debugging, especially in situations when there are
multiple connections in flight to the same peer.
2015-04-14 22:14:29 +01:00
Matthias Radestock 74b3bf8299 pass on args in multiweave launch <args>... 2015-04-14 19:22:01 +01:00
Matthias Radestock f9ea3f54d1 Merge branch 'bboreham-make-tests-stop'
Closes #557
2015-04-14 18:03:42 +01:00
Bryan Boreham 8c6c14c6d3 Accumulate any errors from go test commands so we can report overall status 2015-04-14 18:03:10 +01:00
Matthias Radestock 31775a8b84 cosmetic 2015-04-14 17:58:13 +01:00
Matthias Radestock 973996184d fix bug 2015-04-14 13:27:10 +01:00
Bryan Boreham d334b903e4 Merge pull request #556 from rade/do_not_duplicate_router_config
LGTM
2015-04-14 12:58:05 +01:00
Matthias Radestock 798bfeb0a7 make the Router include the RouterConfig
rather than copying the bulk of it
2015-04-14 11:39:11 +01:00
rade 9b29d2ecc5 Merge pull request #554 from dpw/main-routerconfig
Pull forward the declaration of the RouterConfig in main()
2015-04-14 10:57:43 +01:00
Matthias Radestock 961801b792 refactor: shrink function by extracting helpers 2015-04-14 06:49:47 +01:00
David Wragg ae00582212 Reintroduce a bufSzMB variable 2015-04-13 22:29:19 +01:00
David Wragg 1213935030 Pull forward the declaration of the RouterConfig in main()
This eliminates a few variables, and the naming awkardness where we
have corresponding but distinct flag and router config variables.
2015-04-13 21:39:15 +01:00
Matthias Radestock c2972b7558 cosmetic: remove dead code 2015-04-13 21:28:07 +01:00
rade 9833747fd9 Merge pull request #553 from dpw/makefile-cleanups
Makefile cleanups
2015-04-13 21:05:32 +01:00
David Wragg 1328f8bec7 Use $(@D) rather than $(shell dirname $@) 2015-04-13 20:50:50 +01:00
David Wragg 433d406d83 Use $(@D) rather than $(shell dirname $@) 2015-04-13 20:39:01 +01:00
David Wragg bc9bb9e4ae tempfile is a deprecated debianism
As noted in its man page.
2015-04-13 20:37:12 +01:00
Matthias Radestock 805005e632 weave status no longer complains about missing weaveDNS 2015-04-13 18:28:52 +01:00
Matthias Radestock 202f536e07 refactor: extract some helpers in order to shrink main() 2015-04-13 16:03:49 +01:00
Matthias Radestock b062facbe9 refactor: inline 2015-04-13 15:56:52 +01:00
rade a80b8eacac Merge pull request #552 from bboreham/status-conn-errors
Allow ConnectionMaker to report the reason the connection broke
2015-04-13 15:46:09 +01:00
Matthias Radestock 8ab14ea1c6 don't crash
this got broken in a93a09dd35
2015-04-13 15:37:44 +01:00
Matthias Radestock 3db4e86564 remove established connections from ConnectionMaker state
This got broken in 6409a8647c
The issue is that for successful connections nothing ever sets
attempting=false, so we need some other means of removing such
entries.
2015-04-13 14:59:32 +01:00
Bryan Boreham 8ad244e0f9 Let ConnectionMaker report the reason the connection broke 2015-04-13 14:35:27 +01:00
Bryan Boreham f8ec56ea79 Move connection shutdown logging into shutdown() 2015-04-13 14:21:11 +01:00
Adam Harrison 7f434d71e1 Merge pull request #532 from inercia/weave-225-fix-2
Fix in WeaveDNS: do not modify cache when getting a reply
2015-04-13 14:20:40 +01:00
Adam Harrison 29b3b91423 Merge pull request #501 from inercia/weave-225-fix-1
Fix for negative TTLs in WeaveDNS
2015-04-13 14:19:47 +01:00
Adam Harrison 01af41c837 Disable Vagrant 1.7 per host insecure key replacement 2015-04-13 11:43:01 +01:00
Adam Harrison fe58a63996 Merge pull request #550 from rade/549_forget_as_given
remember user-supplied peer addresses as given
2015-04-13 10:38:52 +01:00
Matthias Radestock 272e63643c make weave port configurable via WEAVE_PORT
We cannot just leave that to the `-port` parameter since that only
controls the container internal port weave is listening on (and uses
as the default destination port for outbound connections). Also, we
need the port for connection tracking cleanup during `stop` and
`reset`.

So we make setting `WEAVE_PORT` the only documented mechanism for
overriding the port, and let `-port` set the container internal port
only (and the script pays attentiont to that, so it can establish the
correct port mapping).
2015-04-12 22:07:52 +01:00
Matthias Radestock a93a09dd35 remember user-supplied peer addresses as given
rather than post name resolution. This way we can forget them as given
even when name resolution has changed or fails.
2015-04-12 20:44:12 +01:00
Matthias Radestock 1275bb24e5 exclude stale info from ConnectionMaker.String()
This got broken in 7bd0f7dbae.
2015-04-12 12:23:32 +01:00
Matthias Radestock 6409a8647c simplify connection logic slightly
There is no need to remove entries from cm.targets that are in
ourConnectedTargets. None of these targets will be in validTargets
(since addTarget filters anything in ourConnectedTargets), so they
will be removed a few lines further down anyway...

Unless the target is in the 'attempting' state. But that is ok, and in
fact desirable. Any such targets indicate connection attempts that are
in progress, which is something we display in `weave status` and want
to be accurate. Eventually the connection attempt will either succeed
or fail, and the cm.targets entry will be removed on the next run of
connectToTargets.
2015-04-12 12:11:27 +01:00
Matthias Radestock 21102d3615 refactor: extract router configuration
the param list of NewRouter was getting a bit long
2015-04-12 10:46:46 +01:00
Matthias Radestock e89e3adc69 cosmetic: more consistent param/arg/var ordering re Port 2015-04-12 10:45:49 +01:00
Matthias Radestock 065b632f91 significantly reduce time for test dns servers to shut down
by setting the ReadTimeout to 100ms instead of the default 2s.

Closes #547.
2015-04-11 20:52:51 +01:00
Matthias Radestock e0bba10ead better handling of test multicast listener initialisation errors
We handle that error explicitly in all cases and point out the common
cause, namely the absence of a default multicast interface.

Closes #481.
2015-04-11 20:22:41 +01:00
Matthias Radestock bb99b21248 tiny refactor: make use of helper function 2015-04-11 19:09:41 +01:00
Matthias Radestock 85971193a8 simplify check for --local 2015-04-11 17:58:26 +01:00
Matthias Radestock 59ae50eb4e don't rely on docker inspect for determining container death
Docker can take a while to notice & record the death of containers, so
we perform a more direct check instead.

Fixes #522.
2015-04-11 12:24:09 +01:00
Matthias Radestock f433d26d1f fix typo 2015-04-11 12:23:57 +01:00
rade cd81101508 Merge pull request #546 from rade/awh-handle_signals_without_tty
Implement weaveexec signal proxy

Fixes the following issues with remote (i.e. w/o `--local`) execution of the weave script via a container, as per #507:

1. stderr is merged into stdout
2. line termination with CRLF instead of LF
3. ctrl-c is ignored

Fixes #507. Closes #523.
2015-04-11 11:55:46 +01:00
Adam Harrison 338d577fb7 Implement weaveexec signal proxy 2015-04-11 11:50:42 +01:00
Matthias Radestock 39b9034fef there is no need for multiweave to use --local 2015-04-11 00:18:42 +01:00
Matthias Radestock 363814db5c simplify "need to run as root" condition 2015-04-11 00:07:23 +01:00
rade c2042d52f8 Merge pull request #545 from rade/errordeveloper-issue_540
Only check kernel version and ip command in local mode

Fixes #540. Closes #542.
2015-04-10 23:03:03 +01:00
Ilya Dmitrichenko 35d32bd03a Only check kernel version and ip command in local mode (close #540) 2015-04-10 23:01:59 +01:00
Matthias Radestock 8fcd56bc68 Merge branch 'bboreham-543-fix-fieldvalidator'
Fixes #543.
2015-04-10 22:43:26 +01:00
Bryan Boreham d228a4450a Make sure FieldValidator methods act on the object we pass in.
Added a unit test for FieldValidator
2015-04-10 22:42:40 +01:00
Matthias Radestock d19d485d39 refactor: make LocalPeer.Router "private"
nothing outside LocalPeer should access it
2015-04-10 21:56:00 +01:00
David Wragg 7d51f8751b gofmt 2015-04-10 20:55:27 +01:00
rade 551c1d2925 Merge pull request #541 from abligh/support-unix-domain-control-socket
Support using a unix domain socket for the HTTP control interface.
2015-04-10 20:39:11 +01:00
Alex Bligh 9699b4ce1a Support using a unix domain socket for the HTTP control interface.
When running multiple weaver daemons it is convenient for the HTTP
control interface to bind to a UNIX domain socket, to avoid the
need to allocate an additional unique port. This commit adds the
ability to specify
   httpaddr="/path/to/socket"
on the command line.

Signed-off-by: Alex Bligh <alex@alex.org.uk>
2015-04-10 20:18:27 +01:00
rade 3a83570005 Merge pull request #526 from bboreham/extend-ps
Allow container names/ids to be supplied to `weave ps`
2015-04-10 15:57:53 +01:00
Matthias Radestock f88ce6e798 don't mention defaults twice in usage
go's flag package already shows the default values, so we only need to
mention them when they are determined later.
2015-04-10 15:38:11 +01:00
Matthias Radestock 0799d9f70b shorten connection timeout for http interactions with containers
This issue came to light in the context of `weave launch`. When the
weaver container died after we've completed network configuration, it
would take a minute for the script to terminate, due to `curl` being
stuck in a connection attempt.

Fixes #538.
2015-04-10 15:33:11 +01:00
rade db302a0d3d Merge pull request #539 from abligh/make-iface-mandatory
Error if neither -iface or -name is specified
2015-04-10 15:22:07 +01:00
Alex Bligh 24bd7151e2 Error if neither -iface or -name is specified
This prevents a nil pointer dereference if (e.g.) weaver is
invoked with no arguments.

Signed-off-by: Alex Bligh <alex@alex.org.uk>
2015-04-10 15:14:51 +01:00
Bryan Boreham 10bd84a460 Document name-list option to weave ps 2015-04-10 15:08:35 +01:00
rade 0540e41ffd Merge pull request #537 from bboreham/master
Simplify printing
2015-04-10 14:51:30 +01:00
Bryan Boreham d32f328c24 Simplify printing 2015-04-10 14:46:31 +01:00
rade c13980af4b Merge pull request #536 from dpw/535-golang142
Docker needs golang 1.4.2 to build, so we do too

Fixes #535.
2015-04-10 14:10:46 +01:00
rade bd8cb02f17 Merge pull request #534 from abligh/select-ports-on-cli
Add options to set ports on weaver command line
2015-04-10 13:54:41 +01:00
David Wragg 162b056784 Docker needs golang 1.4.2 to build, so we do too 2015-04-10 12:45:27 +01:00
Alex Bligh 12d88eac23 Add options to set ports on weaver command line
This commit adds two command line options, intended to allow multiple
instances of weave to run without container separation between them.

* -port=PORT makes weave bind to port PORT (an integer). Without the
  option, the default value (6783) is used.

* -httpaddr=ADDRESS makes weave bind its control interface to
  a different address. For instance "127.0.0.1:6784" can be used to
  bind to localhost only. An absent address element makes it bind to
  all interfaces. ":6784" is the default. An empty string
  can be used to disable the control interface.

Signed-off-by: Alex Bligh <alex@alex.org.uk>
2015-04-10 12:09:13 +01:00
Matthias Radestock f3d20111d8 eliminate dependency on LocalPeer.Router
(from things outside LocalPeer)
2015-04-10 10:51:40 +01:00
Bryan Boreham 2ba8f34132 Merge pull request #521 from rade/517_lower_topology_periodic_gossip_complexity
LGTM.  Closes #517
2015-04-10 10:08:23 +01:00
David Wragg bb9e26b4ae Merge pull request #528 from 'bboreham/name_change' 2015-04-10 08:03:38 +01:00
rade ff657d1512 Merge pull request #533 from abligh/fix-dockerless-ip-address
Fix dockerless.txt
2015-04-09 21:45:38 +01:00
Alex Bligh 26d9c37dd2 Fix dockerless.txt
The IP address for VM1 is 192.168.122.2/24. 192.168.122.1/24 is virbr0.
2015-04-09 21:11:18 +01:00
Matthias Radestock 12c97b4fc5 topology-sensitive random selection of gossip neighbours 2015-04-09 18:10:57 +01:00
Bryan Boreham 642cc3f00e Change release GitHub user name from zettio to weaveworks 2015-04-09 17:32:36 +01:00
Alvaro Saurin 7811e2b470 Improved log messages 2015-04-09 16:19:05 +02:00
Alvaro Saurin 5d5b101707 Set locally resolved queries as Authoritative 2015-04-09 16:19:05 +02:00
Alvaro Saurin 186481052d Catch unsupported queries by using the cache 2015-04-09 16:18:55 +02:00
Alvaro Saurin 2474adb2c5 Do not modify the TTL stored in the cache when getting a reply from the cache 2015-04-09 16:17:24 +02:00
Alvaro Saurin 6f545a4296 Fix for negative TTLs by using a different lookups set depending on the value stored in the cache. 2015-04-09 16:17:19 +02:00
Bryan Boreham a4fd0b571d Row back on Docker image names until we do a release under the weaveworks name 2015-04-09 11:46:08 +01:00
Matthias Radestock f5362289cf switch to a more efficient GC for gossip senders 2015-04-09 11:44:13 +01:00
Matthias Radestock 40d127d9a4 make connection heartbeating independent of periodic gossip
This was a convenient but rather suspect coupling. In particular, it
will break if the period gossip logic changes s.t. it no longer sends
gossip on all connections every interval.
2015-04-09 11:44:13 +01:00
Bryan Boreham 067ba6d970 Fix all zettio.github.io links too 2015-04-09 11:39:55 +01:00
Bryan Boreham cec1efbcef Merge pull request #530 from rade/529_eliminate_connection_add_remove_race
Very subtle, but it looks ok to me.  Fixes #529
2015-04-09 10:36:27 +01:00
Matthias Radestock e639007b62 eliminate AddConnection/RemoveConnection race
We were invoking AddConnection in a separate goroutine, and this could
result in RemoveConnection getting invoked first, resulting in bogus
entries in our connection map, which would effectively prevent
connectivity to the other peer until the stale entry got removed
through a lost tie break. And if, per chance, the remote end ran into
exactly the same race condition, then connectivity between that peer
and ourself was broken permanently.

Hitting this race condition required a combination of a) no UDP
heartbeat being received for HeartbeatTimeout(30s) - this causes the
actorLoop to terminate and RemoveConnection to be invoked, and b) the
AddConnection invocation getting delayed for at least that long.
2015-04-09 06:34:35 +01:00
Matthias Radestock 56f5c749e1 optimise broadcast route calculation
...by obtaining our connection set just once. This also guards against
potential locking issues, since we no longer obtain the required
lock on our local peer while holding a lock on Peers.

Note that this change means the connection list can be out of date by
the time we enter Peers.ForEach. However, previously it was possible
for the list to change for each peer we are iterating over. So it's
just a different flavour of out-of-dateness.
2015-04-08 21:54:45 +01:00
Matthias Radestock ec30bdaf3d explain kernel settings that need changing 2015-04-08 18:33:39 +01:00
David Wragg 74dae2abb4 Allow multiweave to be run from any directory 2015-04-08 18:16:10 +01:00
Bryan Boreham 8a82f368d0 Update repository name from zettio to weaveworks 2015-04-08 16:35:52 +01:00
Bryan Boreham 13f3439a06 Allow container names to be supplied to 'weave ps' 2015-04-08 15:38:23 +01:00
Tom Wilkie 580ffc4fa4 Repoint Travis and Coveralls badges in readme 2015-04-08 12:42:28 +01:00
Bryan Boreham 86abc6ac2b Merge pull request #516 from rade/514_lower_topology_gossip_complexity
LGTM.  Closes #514
2015-04-07 15:00:39 +01:00
Matthias Radestock f80410bf59 switch to gossip broadcast for topology updates 2015-04-06 14:17:23 +01:00
Matthias Radestock f4416bfadc introduce Routes.EnsureRecalculated
A means to ensure that any preceeding route recalculation requests
have been processed. It's akin to a global promise.
2015-04-06 14:17:23 +01:00
Matthias Radestock 6bc660068f use GossipSender for GossipBroadcast
This makes GossipBroadcast properly async.
2015-04-06 14:17:23 +01:00
Matthias Radestock 8a2828d57d add "representation of received update" to return of Peers.ApplyUpdate 2015-04-06 14:17:23 +01:00
Matthias Radestock a58eb6ba7d make gossip broadcast operate on GossipData 2015-04-06 14:00:51 +01:00
Matthias Radestock 9b9d8a03aa refactor: make Peer.Connections() return a set instead of a slice
This is more flexible since it allows callers to perform efficient
lookups on the result.
2015-04-06 13:57:13 +01:00
rade fe4d8b7767 Merge pull request #518 from rade/canonical_sets
canonical sets
2015-04-06 13:29:03 +01:00
Matthias Radestock e8e9a43abd tired of typing "struct{}{}" 2015-04-06 11:53:51 +01:00
Matthias Radestock 6db12d682e make PeerNameSet a more broadly used type 2015-04-06 11:45:17 +01:00
Matthias Radestock 74b3f8d44e use map[...]struct{} for sets instead of map[...]bool
See https://groups.google.com/d/msg/golang-nuts/lb4xLHq7wug/MhrSLkyS4F8J

We don't really care about the performance aspect of this, but using
struct{}{} values is in some way canonical since it means the
representation carries no redundant information.
2015-04-06 11:35:11 +01:00
Matthias Radestock 6f2f25aca5 don't complain in weave status about weavedns absence/errors
it's tiresome to see this when weavedns isn't running
2015-04-05 18:50:17 +01:00
Matthias Radestock 80aa60b3ab separate multiweave 'launch' and 'connect' phases 2015-04-05 17:04:43 +01:00
Matthias Radestock 624373ace2 simplify 2015-04-05 17:03:18 +01:00
Matthias Radestock 8c1015d2a4 cosmetic: consistent terminology in var names 2015-04-04 22:18:31 +01:00
Matthias Radestock 4ebbdabfa1 never, ever use 10.0 for anything
it has a high probability of conflicting
2015-04-04 22:00:06 +01:00
Matthias Radestock 95c1e226f0 use routes based on tcp overlay connectivity for gossip
i.e. include asymmetric and non-established connections
This is TRTTD since gossip flows over the tcp overlay.
2015-04-04 21:56:30 +01:00
Matthias Radestock 3fba123bc0 comment 2015-04-04 21:14:25 +01:00
Matthias Radestock b272dcf8c1 rename: less 'gossip' in gossip 2015-04-04 20:58:10 +01:00
Matthias Radestock 91ed22a953 refactor(ish): generalise an abstraction
LocalPeer.NextBroadcastHops -> ConnectionsTo
thus separating the computation of who we want to connect to from
obtaining the associated Connections
2015-04-04 20:36:48 +01:00
Matthias Radestock bcd2a0184c small refactor 2015-04-04 20:08:38 +01:00
Matthias Radestock 45fd750318 cosmetic: move some test helpers from test code into main code
since they reach quite deeply into the data structures
2015-04-04 20:02:22 +01:00
Matthias Radestock 0a02d1a926 cosmetic: rename a type 2015-04-04 20:02:01 +01:00
Matthias Radestock ac0788cb5a simplify test router creation 2015-04-04 19:50:19 +01:00
Matthias Radestock 683caef866 tweak comment 2015-04-04 19:46:47 +01:00
Matthias Radestock fa5279ab3e cosmetic 2015-04-04 16:02:01 +01:00
Matthias Radestock 6cda74d49f refactor: inline 2015-04-04 15:36:42 +01:00
Matthias Radestock 582b582b98 fix silly bug 2015-04-03 19:52:28 +01:00
Matthias Radestock fdea7d93f9 refactor: improve abstraction in connection gossip handling
- turn previously free-floating functions into methods on GossipChannel
- move gossip message type dispatch from connection into router
2015-04-03 19:38:01 +01:00
Matthias Radestock 6f398a7459 refactor(ish): raise GossipSender abstraction level slightly 2015-04-03 18:33:59 +01:00
Matthias Radestock 7166d151aa make route calculcation more efficient and properly async
Instead of following the usual actor pattern, we use a 1-place channel
with a non-blocking send. Essentially, if there is already a pending
recalculation request queued up, there is no point enqueuing another
one.
2015-04-03 18:12:03 +01:00
Matthias Radestock acbb911823 Merge remote-tracking branch 'upstream/master' 2015-04-03 10:09:49 +01:00
Matthias Radestock 604d210e55 more flexibility 2015-04-03 10:07:39 +01:00
David Wragg 587a67d701 Comment
It's not blindingly obvious where this 'found' comes from.
2015-04-02 16:37:30 +01:00
Matthias Radestock 5648f87717 oops
broke this in 45740b1419
2015-04-02 13:03:42 +01:00
Matthias Radestock 488c5c5a2d bump default connection limit to 30
I can get 29 weaves running on my laptop, and once all connections are
established, the avg cpu load is ~10%. 30 runs into difficulty because
the concurrent connection establishment does create a very high load,
resulting in some timeouts. But that wouldn't happen when running each
weave in a separate machine (with more CPU resources overall than my
single laptop).

Stop gap solution for #426.
2015-04-02 12:38:45 +01:00
Matthias Radestock a68d444c33 launch multiple test weaves 2015-04-02 10:14:43 +01:00
Matthias Radestock 24f3634254 pass WEAVE_CONTAINER_NAME to weavexec 2015-04-02 10:13:52 +01:00
Matthias Radestock ea3c5c5f90 introduce (undocumented) weave test-launch command
which allows launching of several weave routers on the same machine,
by invoking this multiple times with different WEAVE_CONTAINER_NAMEs.

The routers are not connected to the bridge and do not perform any
capture/injection. So this is mainly intended for testing of
administrative and control plane functionality, rather than routing.
2015-04-02 09:49:05 +01:00
Matthias Radestock 5230daf943 permit overriding of weave container name via WEAVE_CONTAINER_NAME
useful for testing
2015-04-02 09:48:22 +01:00
Matthias Radestock e97a3f88aa allow router to be started w/o capture/inject interface
useful for testing of the control plane
2015-04-02 09:46:29 +01:00
Matthias Radestock e33d2eaf8e cosmetic 2015-04-02 07:10:21 +01:00
rade e66225ca9b Merge pull request #512 from bboreham/stop_not_kill
Use docker stop instead of docker kill
2015-04-01 17:27:54 +01:00
Bryan Boreham 05b7162239 Use docker stop instead of docker kill 2015-04-01 16:58:16 +01:00
rade a152d24892 Merge pull request #511 from bboreham/generalize_updater
Generalise updater.go and move into its own package
2015-04-01 16:57:20 +01:00
Bryan Boreham 68096b0437 Generalize updater.go 2015-04-01 16:48:11 +01:00
rade b5c8258407 Merge pull request #509 from bboreham/master
Wait for the 'status' http call to return something after 'weave launch'

Fixes #500.
2015-04-01 15:24:32 +01:00
Bryan Boreham bacaf41017 Wait for the 'status' http call to return something after 'weave launch' 2015-04-01 15:14:59 +01:00
rade 3dad9fb593 Merge pull request #508 from tomwilkie/coverage
More feedback on test 'harness'
2015-04-01 13:35:51 +01:00
Tom Wilkie db9a0d7528 More feedback on test 'harness' 2015-04-01 12:32:35 +00:00
rade df7836df62 Merge pull request #505 from tomwilkie/coverage
Move away from recursive go test, as it cannot output a merged coverage profile.
2015-04-01 13:23:19 +01:00
Tom Wilkie d28d52da15 Move away from recursive go test, as it cannot output a merged coverage profile. Update said profile to coveralls.io; add badge. 2015-04-01 12:00:06 +00:00
Adam Harrison f2a340772a Revert stty manipulation of CRLF behaviour 2015-04-01 12:52:15 +01:00
rade 254b837a92 Merge pull request #498 from awh/467_multi_cidr_launch
Allow multiple CIDR arguments to run/start/attach/detach/expose/hide

Closes #467.
2015-04-01 10:58:36 +01:00
Adam Harrison 745b8effbb Merge pull request #503 from tomwilkie/travis
Setup Travis CI for Weave.
2015-04-01 09:43:41 +01:00
rade cf9f8499fb Merge pull request #504 from errordeveloper/issue_470
Check if `/proc/<pid>/ns/net` is a symlink

Fixes #470.
2015-03-31 16:22:10 +01:00
Ilya Dmitrichenko 6ce91aafa8 Check if /proc/<pid>/ns/net is a symlink (fix #470) 2015-03-31 16:18:28 +01:00
Tom Wilkie 7969566b27 Setup Travis CI for Weave. Add .travis.yml; add target to makefile which doesn't use docker; add image to readme. 2015-03-31 15:02:35 +00:00
Tom Wilkie 592be90904 Use recursive go test instead of naming directories individually 2015-03-31 13:33:36 +00:00
Adam Harrison 0912cd416d Use consistent subnets in documentation 2015-03-31 12:23:34 +01:00
Adam Harrison aa88dfca11 Tests use distinct subnets 2015-03-31 12:09:22 +01:00
Matthias Radestock 10f37db9da tidy up DNSServer fields
- make Zone, Iface and Upstream public - this is safe and makes sense
  since all are immutable, and the former two are passed to the
  constructor and are thus known anyway, and the latter is derived
  from external information
- remove IfaceName - we can just use Iface instead
- log Upstream on startup
2015-03-31 12:04:31 +01:00
Matthias Radestock de557a3e03 introduce WEAVEDNS_DOCKER_ARGS
same purpuse as WEAVE_DOCKER_ARGS, except for weavedns
This allows us to get rid if the -fallback option since
WEAVEDNS_DOCKER_ARGS="--dns=..." has the same effect.

Closes #487.
2015-03-31 11:50:41 +01:00
Adam Harrison dc49f78588 Expose and hide take multiple addresses 2015-03-31 11:48:51 +01:00
Matthias Radestock b50688b4de act immediately on weave connecting existing addresses
we simply reset the vars controlling backoff for that address
Note that we do not interrupt any current connection attempt, since
that is hard.

Closes #499.
2015-03-31 11:05:11 +01:00
Adam Harrison 94823187ca Fix function comment 2015-03-30 18:03:20 +01:00
Adam Harrison b4f71df731 Fix unrelated script error 2015-03-30 17:50:26 +01:00
Adam Harrison 772e298340 Factor out CIDR arg parsing 2015-03-30 17:49:44 +01:00
Adam Harrison c1089e8447 Update documentation 2015-03-30 17:31:37 +01:00
Adam Harrison 7f54922ada Update documentation 2015-03-30 16:27:01 +01:00
Adam Harrison 1c7ed9e16a Update documentation 2015-03-30 16:08:38 +01:00
Adam Harrison 3026b5d291 Test tidyup 2015-03-30 15:54:54 +01:00
Adam Harrison 74f2cd09bc Use all caps variable names 2015-03-30 14:43:16 +01:00
Adam Harrison f8649eeecc Modify tell_dns helper to take list of addresses 2015-03-30 14:38:57 +01:00
Adam Harrison 2acefd7df3 Disable LF -> CRLF conversion when attached to Docker pseudoterminal 2015-03-30 13:09:11 +01:00
Adam Harrison 37e6647b0b Implement integration tests 2015-03-30 12:58:20 +01:00
David Wragg b00be096f7 Fix returns when iterating over Connections()
d5eb76f32 replaced (*Peer)ForEachConnection into (*Peer)Connections(),
and consequently converted some nested functions into for loops.  But
the "return" statements in those nested functions were left as-was,
and so exited the outer function rather than moving on to the next
iteration.

Fixes #497
2015-03-29 19:10:44 +01:00
Matthias Radestock d708cb7d4e prevent PMTUVerfied handling from blocking
...due to blocking on sending the pmtu to the forwarder, because the
forwarder has terminated.

Fixes #464.
2015-03-29 14:04:28 +01:00
Matthias Radestock fc22f9664e refactor: improve encapsulation of Forwarder 2015-03-29 12:14:32 +01:00
Matthias Radestock 07c11d87a8 oops
I broke this in 1eca5e1097
2015-03-29 12:11:59 +01:00
Matthias Radestock 4eda7e361b put back forwarder channel nil check
since otherwise forward(...) can block indefinitely due to both the
forwarder channel and finshed channel being nil
2015-03-29 10:36:01 +01:00
Matthias Radestock b6448fa196 more idiomatic closing of forwarders, by closing a 'finished' channel
The previous scheme set the reference to main channel to nil and then
drained it. That was racy and hence could lead to deadlock in the
following scenario:

1. multiple senders obtain the main channel from the stored reference
2. main channel reference is set to nil
3. main channel is drained
4. multiple senders send to main channel

So the issue here is that the entire forwarder stopping sequence
occurs between senders obtaining of the reference to the main channel,
and then sending on that channel. If more senders sent than there is
capacity in the channel then this leads to deadlock.
2015-03-28 20:58:42 +00:00
Matthias Radestock 4dbb3d97e0 cosmetic: remove dead code 2015-03-28 20:33:06 +00:00
Matthias Radestock 502b59c703 refactor: rename 2015-03-28 19:52:10 +00:00
Matthias Radestock 9a462f9d87 refactor: simplify NewNaClEncryptor
- rely on default nil values on initialisation
- inline
2015-03-28 19:49:01 +00:00
Matthias Radestock 9992992ee4 refactor: extract helper from NaClDecryptor.decrypt
for easier comprehension
2015-03-28 19:44:04 +00:00
rade 94cf05a897 Merge pull request #496 from dpw/router-cleanups
Router cleanups
2015-03-27 18:38:43 +00:00
David Wragg 78c70023d8 Don't pass the sender UDPAddr through the crypto layer
The crypto layer doesn't need it, and the cost of an additional
closure allocation for each incoming UDP packet is modest.

Also, make the FrameConsumer the last parameter to IterateFrames.
It seems a more natural ordering.
2015-03-27 18:05:00 +00:00
David Wragg 0d30df165f Eliminate UDPPacket
The Name field is not used anywhere, and once that's gone the remaining
pairing of the packet bytes and sender is not worth it.
2015-03-27 18:05:00 +00:00
David Wragg 4a1d3aeb96 Remove frameLen parameter to FrameConsumer
It's the same as len(frame)
2015-03-27 18:05:00 +00:00
David Wragg bc45a363dd Make Encryptor.AppendFrame more consistent with FrameConsumer
Also removes references to Peer type in crypto.go, to keep it agnostic
of higher-level concerns.
2015-03-27 18:05:00 +00:00
David Wragg a670272a37 Remove name parameter from (*Peers)ForEach consumer function
It's part of the Peer anyway.
2015-03-27 18:05:00 +00:00
Adam Harrison ff0e015f84 Update usage 2015-03-27 17:55:46 +00:00
Adam Harrison a8f2ba0e7f Run takes multiple addresses 2015-03-27 17:54:16 +00:00
Adam Harrison a60490fb88 Start takes multiple addresses 2015-03-27 17:47:17 +00:00
Adam Harrison e6af8996e6 Attach and detach take multiple addresses 2015-03-27 16:46:47 +00:00
Matthias Radestock 5617258cca Merge remote-tracking branch 'upstream/master' 2015-03-27 16:33:28 +00:00
Adam Harrison e3c0807a18 Modify attach/detach helpers to take list of addresses 2015-03-27 15:36:10 +00:00
Matthias Radestock a337e981ba refactor: extract helper 2015-03-27 12:38:06 +00:00
Matthias Radestock 0ef717a7a2 refactor: extract helper function 2015-03-27 12:31:25 +00:00
Matthias Radestock d25d295784 refactor: small tweak for consistency 2015-03-27 12:17:28 +00:00
Matthias Radestock 60ba8cb90d refactor: extract helper function 2015-03-27 12:06:22 +00:00
Matthias Radestock 4d8c156915 continuing accumulation after dropping a frame is bad after all
explain why
2015-03-27 11:56:38 +00:00
Matthias Radestock 6ea625f85b fix typo 2015-03-27 11:54:24 +00:00
Matthias Radestock 84a95f2daf refactor: concentrate frame appending code in one place 2015-03-27 11:53:56 +00:00
Matthias Radestock f544fc68de enter frame accumulation even after dropping one
This logically makes more sense, i.e. we keep draining frames from
fwd.ch until we run out.
2015-03-27 11:35:42 +00:00
Matthias Radestock 83724a08c9 refactor: get rid of Forwarder.stop channel
signal termination by sending nil to the main channel instead
2015-03-27 11:32:50 +00:00
Matthias Radestock 157afa0b5d comment 2015-03-27 11:15:25 +00:00
Matthias Radestock 1a39cb163a keep accumulating frames even when dropping some
This logically makes more sense, i.e. we keep draining frames from
fwd.ch until we run out.
2015-03-27 11:05:50 +00:00
Matthias Radestock 1c5fee0a5e refactor: simplify Forwarder.run by extracing a helper function
Also, don't check for failed receive on fwd.ch; it cannot happen and
we weren't doing it everywhere anyway.
2015-03-27 11:03:05 +00:00
Adam Harrison 140ac8939b Merge pull request #491 from tomwilkie/lint
Add lint script, which can also act as an optional presubmit hook.
2015-03-27 10:15:04 +00:00
Matthias Radestock 2d74bb4cfa one more name->name+nickname display conversion 2015-03-27 07:10:32 +00:00
Matthias Radestock 16ddb95adc include peer nick name in JSON output
and make the keys consistent
2015-03-27 06:32:30 +00:00
Matthias Radestock 33520ea230 include peer nickname in display output whenever possible 2015-03-27 05:25:28 +00:00
Matthias Radestock 1eca5e1097 refactor: extract helper func and simplify 2015-03-27 04:49:00 +00:00
rade 245f747745 Merge pull request #488 from awh/486_administrative_peer_removal
Implement 'weave forget' command

Closes #486.
2015-03-27 01:53:16 +00:00
Matthias Radestock 308da993eb refactor: extract some funs for better readability 2015-03-27 01:51:04 +00:00
Matthias Radestock 8cb9e301ee cosmetic: more linting 2015-03-27 01:24:19 +00:00
Matthias Radestock 2500bc210a cosmetic: more linting 2015-03-27 01:21:36 +00:00
Matthias Radestock 1ad950f177 cosmetic: more linting 2015-03-27 01:12:16 +00:00
Matthias Radestock e3ef5433cf cosmetic: linting 2015-03-27 00:43:26 +00:00
Matthias Radestock 2d83fcc398 oops 2015-03-26 18:37:37 +00:00
Matthias Radestock 44a4dd9888 cosmetic: go vet 2015-03-26 18:36:00 +00:00
Tom Wilkie dde179f861 Quote the last filename 2015-03-26 18:19:02 +00:00
Tom Wilkie 9e63b4e8c7 Feedback on lint script 2015-03-26 18:08:30 +00:00
Bryan Boreham 7a4a88aeb9 Remove unused unnamed second item in ranges 2015-03-26 18:04:57 +00:00
Adam Harrison b3de9ab85f Clarify documentation 2015-03-26 16:10:04 +00:00
Tom Wilkie d041bec315 Add lint script, which can also act as an optional presubmit hook. 2015-03-26 15:14:01 +00:00
Adam Harrison 3417608209 Don't reset timer when forgetting an address 2015-03-26 14:54:44 +00:00
Matthias Radestock 15fdcae25a Merge remote-tracking branch 'upstream/master' 2015-03-26 14:22:37 +00:00
Matthias Radestock 45740b1419 refactor: replace buf.WriteString(fmt.S*(...)) with fmt.F*(&buf,...)
shorter and more efficient
2015-03-26 14:20:28 +00:00
rade 0e54ca8c2b Merge pull request #490 from errordeveloper/master
Updates to Vagrantfile
2015-03-26 13:57:32 +00:00
Matthias Radestock e7335f361c refactor: move a func that is only used in testing 2015-03-26 13:53:09 +00:00
Matthias Radestock b83f4c9ee9 gofmt 2015-03-26 13:31:11 +00:00
Matthias Radestock 31efd76797 eliminate potential deadlock/dealy in connection.Shutdown
Treat the no-error case the same as the error case...
- use the standard sendAction to communicate with the actor, which
  ensures that we don't block when the actor has terminated
- run in a goroutine to prevent delaying the caller when the actor's
  channel is backed up.
2015-03-26 13:25:23 +00:00
rade dbe39659c5 Merge pull request #489 from awh/gofmt
gofmt
2015-03-26 12:46:15 +00:00
Ilya Dmitrichenko 96efc16432 Add missing aufs-tools to get rid of dubious log message -
[warning]: couldn't run auplink before unmount: exec: "auplink": executable file not found in $PATH
2015-03-26 11:24:45 +00:00
Ilya Dmitrichenko e37be3da97 Bump version of Go on Vargant dev VM 2015-03-26 11:24:45 +00:00
Adam Harrison efb7c0f1bc gofmt 2015-03-26 10:54:46 +00:00
Adam Harrison dc8e82ea31 Implement weave forget command 2015-03-26 10:48:25 +00:00
Adam Harrison 066d8001dd Merge pull request #482 from rade/475-simplify-actors
simplify actors
2015-03-25 11:58:41 +00:00
Matthias Radestock ee6325d488 get rid of Interaction
it's no longer needed in the closure-based actor method scheme
2015-03-25 05:14:17 +00:00
Matthias Radestock 0e43a30858 convert MDNSClient actor to use closures for methods
We use a 'nil' closure to indicate shutdown.
2015-03-25 05:14:17 +00:00
Matthias Radestock a06797dd95 convert ConnectionMaker actor to use closures for methods 2015-03-25 05:14:17 +00:00
Matthias Radestock f0731f5d52 convert Routes actor to use closures for methods 2015-03-25 05:14:17 +00:00
Matthias Radestock c54b3bcacf convert LocalPeer actor to use closures for methods 2015-03-25 05:14:17 +00:00
Matthias Radestock 48c73598c4 convert the connection actor to use closures
The only slight trickiness is Shutdown. Generally connections shut
down when an error is encountered, but we also need to be able to shut
down connections w/o an error. Rather than making all the actor
methods return a (terminated bool, error) tuple, we signal the
non-error shutdown by sending nil as the action (instead of a
closure).
2015-03-25 05:14:17 +00:00
Michael Bridgen 60f50cb2e6 Merge branch 'pr429' 2015-03-24 17:03:22 +00:00
Michael Bridgen 075d9a007c Merge branch 'weave-225' of https://github.com/inercia/weave into pr429 2015-03-24 16:47:38 +00:00
Matthias Radestock 8c3b161bd3 cosmetic 2015-03-24 14:41:20 +00:00
Matthias Radestock 7bd0f7dbae don't modify state in ConnectionMaker.String
it's just wrong
2015-03-24 14:24:22 +00:00
Alvaro Saurin 9dcec8ca16 basic DNS cache
unit tests
2015-03-24 12:44:01 +01:00
Bryan Boreham 0bcadb1e4e Merge pull request #476 from rade/445-accumulate-gossip
LGTM.  Fixes #445.
2015-03-24 11:43:01 +00:00
rade 70a824ea07 Merge pull request #477 from awh/fix_how_it_works_doc
Add outbound and established flags to connection structure diagram
2015-03-24 11:36:18 +00:00
Adam Harrison d7959d8ca5 Add outbound and established flags to connection structure diagram 2015-03-24 11:33:24 +00:00
Matthias Radestock aa1dd47bd2 accumulate gossip data until it can be sent
Introduce an intermediary - GossipSender - between GossipChannel and
Connection. This accumulates gossip data (now represented by the new
GossipData interface) from the former until it can be passed onto the
latter, allowing the former to proceed when the latter may be blocked
on i/o.

This

1) prevents deadlocks that arise from cycles in the communication
topology

2) improves performance by allowing GossipChannel and its calling code
to proceed when connection i/o is blocked

3) improves performance by accumulating GossipData - the accumulated
data often is considerably more compact than the sum of all the
accumulated bits, and it is transmitted in a single communication
event.

In order to support accumulation, the GossipData interface has a Merge
method. Furthermore, encoding is deferred until the data can actually
be sent, since accumulation would be hard/impossible to encoded
data. This required changes on some interfaces, most notably Gossiper.

The implementation of GossipData for topology gossip is
TopologyGossipData. It carries a reference to Peers and a set of
PeerNames, indexing into Peers and referencing the Peer entries which
have changed. Previously updates were represented as a list of Peer
entries. The indirection via PeerNames allows Encode to omit entries
which have been removed. The change in representation required changes
to the signature of some methods on Peers.

Note that all the above only applies to the "periodic gossip" portion
of the Gossiper API, which topology gossip in fact uses for *all*
gossip. GossipUnicast and GossipBroadcast are unchanged, but could
conceivably receive the same treatment in the future.

Fixes #445.
2015-03-23 23:23:15 +00:00
Michael Bridgen f64876a64b Increment the server port for each test
Occasionally the test servers overlap and the test will fail with
"address already in use".
2015-03-23 12:02:43 +00:00
Matthias Radestock e096b3bcd1 ignore weavexec/weave build artifact 2015-03-21 13:08:00 +00:00
rade 8e65c7127d Merge pull request #388 from squaremo/remote_weaving
Move weave script into tools image with its deps
and thus enable weaving on a remote docker

Closes #312.
2015-03-20 15:17:09 +00:00
Michael Bridgen 520c54f8da Use a fixed (and old) version of the docker client
As noted in #388 comment 76958961, we need the docker client to be as
old as possible while supporting what weave needs to do. At the minute
that means version 1.3.1.

The `apk` package index does not have old versions of docker, so the
most direct way of getting a docker executable is to download the
specific version from https://get.docker.com.
2015-03-20 14:37:24 +00:00
Michael Bridgen 93f16d91ce Run weave run with the local docker
We want to be compatible with whatever docker the user has; `weave
run` is the only command that passes on arbitrary args to docker
(i.e., that doesn't have a fixed set of docker features that it
needs).

To make this workable we still need to do some things on the remote
host: the IP attachment and figuring out the DNS settings (since those
may depend on the bridge IP).
2015-03-20 14:37:24 +00:00
Matthias Radestock 2b5e2db550 cosmetic - move comment to correct place 2015-03-19 17:43:28 +00:00
Matthias Radestock 9bd695a816 restore workingness of smoketests sans vagrant
Fixes #456.
2015-03-19 17:23:29 +00:00
rade 39e7871cf7 Merge pull request #472 from bboreham/master
Add peer nickname to JSON status
2015-03-19 12:15:16 +00:00
Bryan Boreham 3fbb6eaa8d Add peer nickname to JSON output 2015-03-19 11:34:01 +00:00
Michael Bridgen 84ce0663f8 Don't show env entries in command
The form of `docker -e` without an equals sign passes them along from
the environment without the value appearing in the command.
2015-03-19 10:15:06 +00:00
Michael Bridgen 9fd468d743 Use weave remotely in smoke tests instead of via SSH 2015-03-19 10:15:06 +00:00
rade 6b64516ede Merge pull request #465 from awh/237_add_dns_to_weave_status
Include weave dns information in `weave status`

Closes #237.
2015-03-18 22:21:11 +00:00
Adam Harrison fcab4ee239 Truncate container IDs to twelve characters 2015-03-18 17:56:58 +00:00
Adam Harrison 2e7c281b46 Weave status returns success irrespective of whether router or DNS up 2015-03-18 16:46:51 +00:00
Adam Harrison 6c5dbe0d01 Address documentation comments 2015-03-18 14:57:29 +00:00
Adam Harrison 0005715b1a Weave status only returns success if both router and DNS are up 2015-03-18 14:49:48 +00:00
Adam Harrison 66f4fd94e3 Update documentation 2015-03-18 13:39:16 +00:00
Adam Harrison 0aa36cf8d0 Try to get weaveDNS status even if router container is missing 2015-03-18 13:39:08 +00:00
Michael Bridgen 795d0edc73 weavetools -> weaveexec
This is to make a visible break with previous releases, such that a
newly downloaded weave script will definitely work.

Without this change in name, an existing weavetools image that doesn't
include the script might be invoked, resulting in a confusing message
for the user.
2015-03-18 12:29:20 +00:00
Michael Bridgen 959c46b62a Version the weavetools image run remotely 2015-03-18 12:29:20 +00:00
Michael Bridgen b9353cdee4 Mention using weave remotely in readme 2015-03-18 12:29:19 +00:00
Michael Bridgen 019eeab896 Pass environment variables to remote invocations 2015-03-18 12:29:19 +00:00
Michael Bridgen 0c8d9e8c03 Use this form of apk, since the shortcut masks errors
`apk-install` is a script that runs apk then tidies up. The problem is
that it doesn't exit with an error if the install fails, which means
that if some packages are unavailable at the time of building, it will
still all go ahead.
2015-03-18 12:29:19 +00:00
Michael Bridgen 8d12810446 Move weave script into tools image with its deps
This rearrangement means that the `weave` command can be used
remotely; and, as a side-effect, the script that does all the work is
using executables in its own filesystem, rather than invoking them via
docker.

The remote weavetools is given a terminal so it can be interrupted.

With the flag `--local`, the script will assume all its dependencies
are available and run then and there. Without the flag, it will run
via the weavetools image (adding the flag to the invocation).

A choice here is whether to base the tools image on scratch and add
all the deps, or base it on something else. I've used alpine linux,
which itself is based on busybox. This gives us the usual tools (sed
etc.) while keeping the image size small.

Adding the docker binary adds substantially to the image size, but it
cannot always be e.g., bind-mounted. To mitigate this image bloat, we
might build or otherwise get a client-only docker executable, or write
a special-purpose client, or indeed rewrite the driver script and use
a client library.

This also makes the dockerhub user part of the image names
parameterised, to make it easier to test the release process in full.
2015-03-18 12:29:19 +00:00
Adam Harrison d23a6c86c3 Merge branch 'master' into 237_add_dns_to_weave_status
Conflicts:
	nameserver/http.go
2015-03-18 11:58:39 +00:00
rade e15f52b4cb Merge pull request #468 from awh/gorilla_mux
Replace hand parsing and validation of URLs with Gorilla mux
2015-03-18 11:48:14 +00:00
Adam Harrison bae6865e33 Rename URL parameter 2015-03-18 11:45:04 +00:00
Adam Harrison bd7b232663 Replace hand parsing and validation of URLs with Gorilla mux 2015-03-18 10:10:13 +00:00
Adam Harrison 8f675f15c0 Revert Gorilla mux introduction 2015-03-17 17:52:39 +00:00
Adam Harrison 0f492b563e Fix test compilation
Tests weren't compiling due to change of ListenHttp() signature. Fixed
for now by reintroducing domain parameter in addition to server, as this
allows the HTTP tests to pass in a nil server parameter. In the long
term DNSServer should probably become an interface so we can mock it
easily in such a scenario.
2015-03-17 17:42:16 +00:00
Matthias Radestock d5eb76f32e replace Peer.ForEachConnection with Peer.Connections
The former is prone to triggering deadlocks since the lock is held on
the peer during the iteration.
2015-03-17 16:24:19 +00:00
Adam Harrison c36543a247 Include DNS server config in status 2015-03-17 15:41:01 +00:00
Matthias Radestock 08a08be370 cosmetic 2015-03-17 15:11:28 +00:00
Adam Harrison 6f57774d15 Include zone records in status 2015-03-17 15:02:09 +00:00
Adam Harrison 50f6402774 Include DNS version and domain in status 2015-03-17 14:28:26 +00:00
Adam Harrison 9cf7da7277 Modify weave status to include DNS information 2015-03-17 14:03:18 +00:00
Adam Harrison 9ce26e6973 Replace hand parsing and validation of URLs with Gorilla mux 2015-03-17 13:49:26 +00:00
Bryan Boreham 8f139bcb52 Merge pull request #461 from rade/454_send_all_gossip_to_new_peers_only
LGTM.  Fixes #454
2015-03-17 13:47:49 +00:00
Matthias Radestock dbc683f891 send all gossip to new peers only
Weave sends all gossip down new connections. We do this so that new
peers learn about all gossip'ed information quickly, thus enabling
them to process subsequent incremental updates. In particular, for
topology gossip it ensures that the remote peer learns about all other
peers. This ensures that the incremental update, which is sent as a
result of the new connection getting established and which may
reference a number of other peers, can be processed.

So the intent really is to send all gossip to new peers. But what we
actually do is send it down every new connection. Not every new
connection is from a new peer. In fact most connections aren't. Hence
the current approach is wasteful.

To fix this we need a conservative detection of new peers, i.e. it is
preferable to have false positives. This contrasts with existing logic
in the handshake code. The criteria we use is whether our peer has a
unicast route to the other peer. That is only the case when there is a
continuos chain of symmetric, established connections. The
establishment of these connections should have triggered the sending
of all gossip to the other peer. So we don't need to send it ourself.

Fixes #454.
2015-03-16 17:46:01 +00:00
Adam Harrison 8f8719c298 Correct typo in log message 2015-03-16 17:36:56 +00:00
Matthias Radestock a85d447f01 Merge branch 'dpw-441_slow_arp_fixes'
Fixes #441.
2015-03-16 15:33:41 +00:00
Matthias Radestock 2369b43c8d cosmetic: move new funs to 'right' place 2015-03-16 15:33:05 +00:00
Matthias Radestock c77a7032db Merge branch '441_slow_arp_fixes' of https://github.com/dpw/weave into dpw-441_slow_arp_fixes 2015-03-16 15:31:59 +00:00
David Wragg 4dcf77c210 Commentary 2015-03-16 15:12:44 +00:00
David Wragg 39011a5218 Don't clean up the container interface after moving it into the netns
The most likely cause of failure is the container going away, in which.
case we don't need to clean up.  And there are other points of possible
failure in the same category in attach(), so making sure we clean up the
container interface under all conditions is a  broader issue.
2015-03-16 15:12:44 +00:00
David Wragg e844753a21 Don't duplicate ARP cache configuration 2015-03-16 15:12:44 +00:00
David Wragg 78b0a57007 Send an unsolicited ARP response when a container attaches
Using arping, which is part of the iputils package and so seems to
be ubiquitous.
2015-03-16 15:12:44 +00:00
David Wragg 9b62bcb75d Don't need to use sed to get the IP address part of a CIDR 2015-03-16 15:12:44 +00:00
David Wragg 7a8a7010e2 Refactor for uniform handling of CIDRs
Not quite a pure refactoring because it adds a missing
'validate_cidr'.
2015-03-16 15:12:44 +00:00
David Wragg 11af544071 Configure ARP cache to react more quickly to MAC address changes
When an IP address was reused by a new container, other containers
could experience delays of many seconds the first time they tried to
talk to that new container before successful communication would
occur.  This delay came from three sources related to ARP cache
behaviour:

- The kernel would take a while to decide that an ARP mapping was
  stale due to lack of positive evidence (due to the
  base_reachable_time default of 30s)

- Even once an ARP mapping was considered stale, the kernel would wait
  a few seconds before sending an ARP request, in the hope that a
  higher layer (e.g. TCP) would confirm the ARP entry (due to the
  delay_first_probe default of 5s)

- When the kernel sent out an ARP request to update a stale entry,
  initial attempts would unicast the request to the old MAC address
  before resorting to broadcast (due to the ucast_solicit default of
  3)

This change reduces all these values, so that any delays
are a few seconds.

For most workloads, this should not greatly increase the amount
of ARP traffic, because communication via TCP is often sufficient
to confirm ARP entries.  And one unicast ARP request is still sent
just in case.

Unfortunately, there's a fixed-but-only-recently bug in the kernel
that means that changing base_reachable_time can take a while to take
effect:
<https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=4bf6980dd0328530783fd657c776e3719b421d30>.
This means that a container will continue to use a long reachable_time
delay for the first few minutes of its life.
2015-03-16 15:12:27 +00:00
Michael Bridgen 3c1075459f Switch VirtualBox DNS proxying and host resolver off
While those helped with some issues, the proxying is substantially
broken and the smoke tests wouldn't pass.
2015-03-16 15:06:30 +00:00
rade d65d4776da Merge pull request #459 from tomwilkie/master
Typo in sites page for building weave
2015-03-16 15:03:07 +00:00
Tom Wilkie 475a90bcdd Typo in sites page for building weave 2015-03-16 14:45:51 +00:00
David Wragg e8ed9c707d Refactor 2015-03-16 10:46:24 +00:00
Michael Bridgen 9eeec2fa48 Restore use of DNS from Vagrant VMs
These incantations are said to fix DNS on Vagrant VMs running with a
private network.
2015-03-12 14:39:25 +00:00
Bryan Boreham cce6175a5c Merge pull request #453 from rade/452_only_broadcast_on_established
LGTM.  Fixes #452 - those messages go away entirely in my tests.
2015-03-12 11:27:34 +00:00
rade 0d5562a5b6 Merge pull request #451 from bboreham/448_reduce_conns
Avoid many pointless connection attempts

Fixes #448.
2015-03-11 14:04:25 +00:00
Bryan Boreham 8e274328b6 Do try standard port on inbound connections 2015-03-11 14:01:43 +00:00
Bryan Boreham d87e13b999 Re-order Connection fields more consistently 2015-03-11 14:01:07 +00:00
Matthias Radestock 1ceb041bc5 exclude non-established connections from broadcast routing
We already excluded such connections from unicast routing.

Fixes #452.
2015-03-11 13:26:40 +00:00
Bryan Boreham 89fce7f98a Add an 'outbound' field on each connection,
and only attempt new connections to the remote side of outbound connections.
2015-03-11 11:56:47 +00:00
Matthias Radestock 6f89ead9bd log all connection lifecycle events on local peer
There were situations, such as deleting another connection as result
of it losing a tie-break, where lifecycle events of connections on the
local peer weren't logged.

Fixing that entails moving the logging closer to (i.e. right after)
the state change action. This in turn requires promotion of logging to
the Connection interface instead of it LocalConnection. Which in turn
requires a change from 'log' to 'Log', for consistency with other
interface methods.

Fixes #449.
2015-03-10 14:42:18 +00:00
Matthias Radestock 123e8bbb28 reduce likelihood of IP clashes in tests & examples
by using the 10.2 range, which is not one used by docker and also is
less likely than 10.0 to be used by something else.

Fixes #402.
2015-03-09 20:51:26 +00:00
Matthias Radestock abb1c6c781 refactor(ish): push down some error handling in router 2015-03-06 14:51:39 +00:00
rade 0f7f49d3be Merge pull request #433 from dpw/419_broadcast_frametoobigerror
Do not inject ICMP "fragmentation needed" for broadcast packets.

It's a contravention of RFC1122 and also breaks weave routing due to producing packets with broadcast addresses as the source.

Fixes #419.
2015-03-06 14:37:10 +00:00
Michael Bridgen b38af84368 Merge pull request #435 from fintanr/434
Update README with CoreOS install instructions.
Closes #434.
2015-03-06 14:36:15 +00:00
David Wragg 2f7e0b5cad Simplify version printing 2015-03-06 11:15:24 +00:00
Fintan Ryan 911a3492b2 434 Update README with CoreOS install instructions 2015-03-06 10:01:14 +00:00
Michael Bridgen 6404ac30b9 Minor improvements to vagrant VMs
* disable the default Vagrant shared folder, which we don't need
 * install `bc` on the dev VM, since it's needed by the smoke tests
 * expose the dev VM docker on TCP, for convenience
2015-03-05 16:30:50 +00:00
Matthias Radestock a00f67cf06 use correct API for figuring out whether crypto is enabled 2015-03-05 15:51:54 +00:00
David Wragg 5b66daf1e4 Remove some layers of indirection around FrameTooBigError handling 2015-03-05 15:13:30 +00:00
David Wragg e543bff1ba Don't propagate errors from RelayBroadcast
If forwarding of a broadcast frame resulted in a FrameTooBigError, we
would generate an ICMP "fragmentation needed" packet that contravened
RFC1122 on two counts: First, we should not produce a ICMP error
message in response to a broadcast/multicast packet.  Secondly, we
should not produce an IP packet with a broadcast/multicast source IP
address.  (It also gave an ethernet frame with a broadcast source MAC,
which probably contravenes some IEEE spec.)

So when RelayBroadcast gets a FrameTooBigError, it should just drop
the packet.

Futhermore, if we fail to relay a broadcast on one connection, that's
not a good reason to give up entirely - we can proceed to attempt to
relay on the other next hop connections.  Errors other than
FrameTooBigError that propagated upwards were simply logged, so that
logging is moved down into RelayBroadcast.
2015-03-05 15:13:29 +00:00
David Wragg 619ebc20a8 Don't pass password around as a pointer to a slice
A slice can be nil, so there is no reason for extra indirection to
represent the lack of a password.
2015-03-05 14:19:54 +00:00
David Wragg cda6b8d582 Fix a few deviations from go fmt 2015-03-05 13:56:21 +00:00
Matthias Radestock 6c82f781cd trim some logging from tests 2015-03-02 17:02:10 +00:00
Matthias Radestock 6d69ac5532 refactor and tweak dns tests
- extract helper function
- use "lots and lots of MX records" instead of "lots of TXT records"
  for testing large response
- calculate on the fly the number of records we need to generate a
  large response
- don't hardcode "weave.works"
2015-03-02 16:56:10 +00:00
Michael Bridgen b76e97ac24 Merge branch 'pr394'
Closes #366, again.
2015-02-27 17:02:01 +00:00
Michael Bridgen 1d929263ac Default the localDomain argument from the CLI
Otherwise, the HTTP listener can be given an empty string, and refuse
to record any hosts.
2015-02-27 16:58:47 +00:00
Matthias Radestock 5327c41c82 Allow build to be interrupted.
This was missed in #387.
2015-02-27 16:45:24 +00:00
Bryan Boreham e22b470c3e Add the change from #395, which got lost
`-t` flag creates a tty for the command, so we can interrupt it with Control-C
2015-02-27 16:39:53 +00:00
Michael Bridgen 48ef098518 Merge branch 'pr394': Add localDomain option to weavedns
Improve the DNS server configuration mechanism. The new configuration
system includes the ability to specify a different domain name other
than ".weave.local".
2015-02-27 15:30:49 +00:00
Michael Bridgen 3137e4acdd Smoke test for custom localDomain 2015-02-27 15:29:59 +00:00
Michael Bridgen 35a8a390e7 Merge branch 'weave-366' of https://github.com/inercia/weave into pr394 2015-02-27 15:10:23 +00:00
Bryan Boreham fcb36458c8 Fix breakage from #387 2015-02-27 15:08:14 +00:00
rade be3415b64f Merge pull request #428 from bboreham/415_direct_tcpSend
Remove deadlock in sending protocol messages

Closes #415.
2015-02-27 15:03:38 +00:00
Bryan Boreham 6437caddf2 Reworded comment 2015-02-27 15:02:30 +00:00
Bryan Boreham 139758256b Make clear that tcp-send methods are not handlers and may be called outside of queryLoop 2015-02-27 14:52:47 +00:00
Alvaro Saurin 4682f7ed95 zettio/weave#366 - removed reference to DNSServerConfig in the DNSServer struct 2015-02-27 15:40:32 +01:00
Bryan Boreham 846321afeb Merge pull request #427 from fl0yd/patch-1
LGTM
2015-02-27 14:37:19 +00:00
fl0yd 49cb2758a4 Corrected a misspelling in readme 2015-02-27 08:34:24 -06:00
Bryan Boreham 657125fd54 Send to tcpSender on the calling thread, so the Actor channel doesn't get clogged up 2015-02-27 14:12:32 +00:00
Alvaro Saurin 007873a402 zettio/weave#366 - keep the DNSServerConfig inmutable 2015-02-27 12:22:08 +01:00
Alvaro Saurin 0bc3e333f8 zettio/weave#366 - minor: fixed a typo and removed some superfluous code 2015-02-26 10:51:48 +01:00
Alvaro Saurin 403520f4b6 zettio/weave#366 - move more initialization code to the constructor 2015-02-26 10:51:48 +01:00
Alvaro Saurin bbd69170f9 zettio/weave#366 - improved configuration for th DNSServer, where we can specify the local domain name among other things... (missing some unit tests for this customized local domain) 2015-02-26 10:51:48 +01:00
rade d1400bc233 Merge pull request #407 from awh/369_name_weave_peers
nicknames for weave peers

Closes #369.
2015-02-25 18:10:36 +00:00
rade 47ef9ff85e Merge pull request #413 from awh/373_detect_udp_connectivity_breakage
Tear down connections on prolonged loss of UDP heartbeat.

Closes #373.
2015-02-25 18:09:05 +00:00
Matthias Radestock 62c6242840 remove dead code 2015-02-25 17:13:52 +00:00
rade eafe17255b Merge pull request #421 from bboreham/418_copy_connections
Avoid some deadlocks

Fixes #418
2015-02-25 16:23:05 +00:00
Bryan Boreham 607b528973 Copy connections before sending, to avoid a deadlock 2015-02-25 16:11:58 +00:00
Michael Bridgen 0350c55a21 Merge branch 'pr359' 2015-02-25 15:43:19 +00:00
rade d9cdfff35d Merge pull request #387 from dpw/build_in_container
Containerized weave build
2015-02-25 15:35:35 +00:00
David Wragg f46746c75f Fix a mention of the exported images being in /var/tmp
In fact, remove the mention of the exported images being in the container
altogether. The images are in the host docker daemon, so it is easier to
export them again than to go fishing around with 'docker cp'.
2015-02-25 15:08:26 +00:00
Michael Bridgen 0e4b33b103 Merge branch 'json_status' of https://github.com/bboreham/weave into pr359 2015-02-25 14:46:33 +00:00
David Wragg 3d5e76d0e8 Always use sudo for docker commands in instructions 2015-02-25 14:29:48 +00:00
David Wragg a869e89c7b Fix incorrect markdown link syntax 2015-02-25 14:20:49 +00:00
David Wragg cf2b7bc673 Remove 'make prerequisites' and update docs accordingly 2015-02-25 14:15:07 +00:00
Adam Harrison 25c0cfd0c4 Actually use empty string as test nickname 2015-02-25 12:10:56 +00:00
Adam Harrison 72c9ae7a5c Use empty string as test nickname 2015-02-24 22:25:46 +00:00
Adam Harrison 326a5d19b3 Transpose script arguments to match declaration order 2015-02-24 22:23:51 +00:00
Adam Harrison cd168f54b2 Removed obsolete constant 2015-02-24 19:42:18 +00:00
Adam Harrison d031e8d246 Merge branch 'master' into 373_detect_udp_connectivity_breakage
Conflicts:
	router/connection.go
2015-02-24 18:53:39 +00:00
Adam Harrison 7618ae033c Simplify to single heartbeat timer 2015-02-24 18:31:18 +00:00
Michael Bridgen c74ffe6369 Merge branch 'pr392' 2015-02-24 18:28:08 +00:00
Adam Harrison 4e9e183e0f Simplify argument parsing 2015-02-24 17:59:40 +00:00
Adam Harrison 34f7b5cbd0 Address documentation comments 2015-02-24 16:53:38 +00:00
Adam Harrison 3cf2179c67 Fix formatting 2015-02-24 16:52:44 +00:00
Adam Harrison 5e54637ffa Remove reference to containers from argument help 2015-02-24 16:52:22 +00:00
Michael Bridgen 540f84af3d Merge branch 'weave-362' of https://github.com/inercia/weave into pr392 2015-02-24 16:43:56 +00:00
David Wragg 438e57853b Leave the docker image .tar files in the weave directory
Making things into /var/tmp is a bit odd, and this works better in
the context of the containerized build.
2015-02-24 14:38:59 +00:00
Matthias Radestock d23df19997 reword 2015-02-24 14:31:57 +00:00
rade b53a2c772c Merge pull request #414 from bboreham/411_closed_connections
Add 'finished' chan to stop callers blocking on closed connection
2015-02-24 11:23:36 +00:00
Bryan Boreham 78d2a3e466 Add 'finished' chan to stop callers blocking on closed connection 2015-02-24 10:48:52 +00:00
Adam Harrison 0a0ed7a871 Tear down connections on prolonged loss of UDP heartbeat 2015-02-23 15:29:11 +00:00
Alvaro Saurin c575023681 zettio/weave#362 - command line arg for using a specific fallback DNS server 2015-02-23 13:17:54 +01:00
Alvaro Saurin 419138ec6a zettio/weave#362 - use a WaitGroup for the TCP/UDP goroutines: otherwise, WeaveDNS server just quits 2015-02-23 13:17:54 +01:00
Alvaro Saurin ea1c3e8219 zettio/weave#362 - fix: = instead of := 2015-02-23 13:17:54 +01:00
Alvaro Saurin f8b3c86498 zettio/weave#362 - properly test the TCP-retry mechanism by creating fallback servers that return truncated responses with UDP but not with TCP 2015-02-23 13:17:54 +01:00
Alvaro Saurin 3de924fa2f zettio/weave#362 - moved Check*() to common 2015-02-23 13:17:54 +01:00
Alvaro Saurin 5dd475b2e5 zettio/weave#362 - fix DNSServer initialization
removed a couple of goroutines spawns...
2015-02-23 13:17:54 +01:00
Alvaro Saurin ef72d65aa4 zettio/weave#362 - use for fallback queries the same protocol clients have used for querying us. 2015-02-23 13:17:54 +01:00
Alvaro Saurin 9c7ffd9b81 zettio/weave#362 - improve the unit tests for the TCP server by checking if we get all the responses with TCP when a previous UDP query resulted in a truncated response. 2015-02-23 13:17:54 +01:00
Alvaro Saurin bf88ed8d96 zettio/weave#362 - unit test for TCP requests to our DNS server 2015-02-23 13:17:54 +01:00
Alvaro Saurin c81769cc9a zettio/weave#362 - implement a graceful shutdown 2015-02-23 13:17:54 +01:00
Alvaro Saurin c1f9c8bc10 zettio/weave#362 - new constructor for creating a DNS server with a given config
fix unit tests by using the new constructor
2015-02-23 13:17:54 +01:00
Alvaro Saurin 3be5600383 zettio/weave#362 - reorganize the DNS server code, so it looks like the mDNS server, with a proper class with Start() and so...
Listen on TCP on port 53 by default
2015-02-23 13:17:54 +01:00
rade e485e68946 Merge pull request #409 from achanda/patch-2
Ignore vagrant directory
2015-02-22 08:55:41 +00:00
Abhishek Chanda d930f5c54a Ignore vagrant directory 2015-02-21 23:21:43 -08:00
Adam Harrison 1b92f60034 Add nickname feature to documentation 2015-02-20 15:04:58 +00:00
Adam Harrison 244af11701 Rename hostname to nickname
Accept -nickname parameter to weave script
2015-02-19 17:09:08 +00:00
Adam Harrison 8f6a525bde Bump protocol version due to introduction of hostname field 2015-02-19 14:42:26 +00:00
Adam Harrison 1707244725 Include remote hostname for connections in router status 2015-02-19 13:34:01 +00:00
David Wragg 4ea67bbab9 Elaborate comments 2015-02-19 12:50:40 +00:00
Adam Harrison f517b947e6 Add hostname to peers 2015-02-19 12:29:37 +00:00
Bryan Boreham 2817638ef9 Merge pull request #396 from achanda/patch-2
LGTM.
2015-02-17 12:39:16 +00:00
Abhishek Chanda ed2d0d56a5 Enable code coverage reports
This will make the test print code coverage report once done in each directory
2015-02-14 16:30:32 -08:00
Michael Bridgen a6f1435444 Only fetch the assert script if it's not present 2015-02-11 14:36:00 -08:00
David Wragg cd06a29d6c Update build docs
I'm not sure about using the name 'zettio/weave-build' for the image
here.  There's no intent to upload it to dockerhub, so including the
user name 'zettio' seems wrong.  On the other hand, I'm not sure what
the meaning of an unslashed image name is supposed to be.  Probably it
doesn't matter what a private non-pushed image is called, as long as
it doesn't clash with something.
2015-02-10 16:45:56 +00:00
David Wragg b1ac63cae6 A Dockerfile to facilitate building in a container 2015-02-10 16:45:53 +00:00
David Wragg 949afd9c20 Add some sudos to make tools/build.sh work when not root 2015-02-10 16:27:10 +00:00
David Wragg 3c093d7534 Make build.sh show its progress 2015-02-10 16:27:10 +00:00
David Wragg a31b115b06 Add 'build' makefile target 2015-02-10 16:27:06 +00:00
David Wragg 23233851db Support non-containerized WEAVETOOLS_EXES build 2015-02-10 16:05:54 +00:00
Matthias Radestock 9f9949b80c update copyright notices (year & entity) 2015-02-06 23:14:38 +00:00
Bryan Boreham 410972906b Add version and encryption status into the JSON output 2015-02-06 17:35:39 +00:00
Matthias Radestock 94c7b60983 Merge remote-tracking branch 'upstream/master' 2015-02-04 16:51:15 +00:00
Matthias Radestock e2818f883c refactor: move and simplify handshake logic 2015-02-04 16:49:57 +00:00
Michael Bridgen 59cd5a13da Merge branch 'pr361' 2015-02-04 16:35:29 +00:00
Michael Bridgen 1f9e321097 Merge branch '360_dns_buffer' of https://github.com/bboreham/weave into pr361 2015-02-04 16:35:14 +00:00
Matthias Radestock f01d9ce092 explain why create-bridge is undocumented 2015-02-04 16:00:35 +00:00
Bryan Boreham 04a7aa1405 Use buffer size 4096 on advice from a DNS expert 2015-02-04 14:58:21 +00:00
Matthias Radestock 2f5b586e68 typo 2015-02-03 15:14:14 +00:00
Matthias Radestock 9c3d509b10 cosmetics
suggested by linter
2015-02-03 14:46:35 +00:00
Matthias Radestock b42766fb81 'docker tag' with '-f' so we can easily re-do failed releases 2015-02-03 14:20:04 +00:00
Matthias Radestock af2206e156 add 0.9.0 release change log 2015-02-03 13:19:43 +00:00
Bryan Boreham e325c35c10 Merge pull request #381 from rade/380_prevent_premature_peer_topology_gc
LGTM.  Fixes #380
2015-02-03 10:39:08 +00:00
Bryan Boreham e600816d5f Add UID and version into JSON for each Peer 2015-02-02 14:41:49 +00:00
Bryan Boreham 42dfd5142e Remove Access-Control hack 2015-02-02 14:36:56 +00:00
Bryan Boreham 458ef12da6 Use a larger buffer than the default, so that more queries will succeed 2015-02-02 13:26:44 +00:00
Bryan Boreham e5a08a505d Change DNS fallback test to use a locally-run server to generate long responses reliably 2015-02-02 13:24:37 +00:00
Bryan Boreham 9ae3f4c692 Only read /etc/resolv.conf once 2015-02-02 13:16:05 +00:00
Matthias Radestock b722f9d9dd Merge branch 'master' into 380_prevent_premature_peer_topology_gc 2015-02-01 19:26:00 +00:00
Matthias Radestock 08c34e4bb9 refactor: rename a method 2015-02-01 19:25:31 +00:00
Matthias Radestock ed65023021 only include established connections in route calculation
except for reachability analysis in gc
2015-02-01 19:13:51 +00:00
Matthias Radestock 7834057b2e cosmetic: remove duplicate comment 2015-02-01 17:52:07 +00:00
Matthias Radestock f25696c245 prevent premature gc of peers from topology
Topology gossip can be received on a connection before that connection
has been marked as 'established'. If we do not include unestablished
connections in topology gossip, then parts of the topology can be
removed by gc, since they are deemed unreachable, which can cause
subsequent incremental topology gossips to be rejected due to them
containing references to peers that have been removed by the gc.

Including unestablished connections in topology gossip requires:

- removing the check for 'established' connections in the encoding of
  peers for topology gossip,
- adding the 'established' flag to the internal representation of
  remote connections,
- adding the 'established' flag to to encoding of connections in
  topology gossip,
- treating the *addition* of a connection as an update to the local
  peer.

The latter entails

- bumping the peer version on connection addition (as well as on
  marking 'established' as previously),
- bumping the peer version *unconditionally* on connection deletion
  (rather than only when the connection was 'established', as
  previously),
- moving the initial sending of complete topology on the connection,
  and the broadcasting of a topology update containing the two peers
  at the ends of the connection, from the connection 'established'
  logic to the connection addition logic,
- broadcasting just an topology update for the local peer when a
  connection gets marked as 'established',
- broadcasting a topology update for the local peer *unconditionally*
  when a connection is removed (rather than only doing this when the
  connection was 'established', as previously).
2015-02-01 16:52:39 +00:00
Matthias Radestock b43e5debb1 gofmt 2015-02-01 16:50:43 +00:00
Matthias Radestock 2d135f3da0 prevent premature peer gc
Caused by a race between adding the connection to our map and
receiving a topology gossip on it.

We remove the race by making AddConnection synchronous and invoking it
prior to entering the tcp receive loop.

Fixes #367.
2015-01-31 22:29:07 +00:00
Matthias Radestock 5892cae5d0 set the hostname on the test VMs
which makes it easier to see where you are when ssh'ed in
2015-01-31 17:12:20 +00:00
Matthias Radestock 5967d5de20 make it easier to set up more than 2 VMs for testing 2015-01-31 16:55:45 +00:00
Matthias Radestock 8f18df7a43 cosmetic: improve comment 2015-01-31 11:38:29 +00:00
Matthias Radestock 2dee5abccc cosmetic 2015-01-31 11:35:46 +00:00
Matthias Radestock 65d0c8b4ce log unknown peer received in topology gossip 2015-01-31 11:31:45 +00:00
Matthias Radestock de45a85162 tweak error handling in topology update application
- simplify
- abort early
2015-01-31 10:35:36 +00:00
rade 99770c564b Merge pull request #377 from sttts/expose-hide-without-ethtool
Also avoid calling ethtool in expose and hide
2015-01-29 23:26:30 +00:00
Matthias Radestock bb134154a8 banish non-posix sed regexps
Fixes #371.
2015-01-29 21:39:11 +00:00
Bryan Boreham d354f4bea4 Merge pull request #376 from rade/372_udp_connectivity_check
LGTM. Fixes #372
2015-01-29 13:24:21 +00:00
Matthias Radestock c1dd2e3a09 tear down connections that have no (initial) udp connectivity
Prior to the introduction of gossip, a tcp connection would not see
regular communication until it had been marked as 'established',
because the topology communication scheme would periodically send
'FetchAll' only after that. So in the absence of udp connectivity we'd
time out. The timeout could of course occur for other reasons, namely
disrupted tcp connectivity, but the combination of a timeout and 'not
established' pointed to a lack of udp connectivity as a likely
cause. So that's what we checked for.

In the new gossip-based topology communication, however, gossip (of
anything, not just topology) occurs as soon as we have tcp
connectivity. Therefore we will no longer encounter a timeout when
just udp connectivity is disrupted. Which makes our check invalid.

So instead we introduce a one-shot timer, created on handshake
completion and cancelled when we mark the connection as
'established'. When the timer fires, and the connection still hasn't
been established, we shut down the connection with an error indicating
lack of udp connectivity.
2015-01-29 12:41:52 +00:00
Dr. Stefan Schimanski d77e3a4c22 Also avoid calling ethtool in expose and hide
Those two commands are used in the pre-docker phase when you setup your
weave bridge using create-bridge, e.g. inside /etc/network/interfaces.
2015-01-29 13:21:49 +01:00
Matthias Radestock 17f6653cae cosmetic: var name consistency with Routes.calculateBroadcast 2015-01-27 20:53:56 +00:00
Matthias Radestock d88c12caa7 improve peer gc efficiency
Instead of separately calculating routes to every individual peer,
carry out one calculation for routes to all peers. Same as unicast
routes except taking into account asymmetric connections.
2015-01-27 20:33:13 +00:00
Matthias Radestock 6c934f1cd3 cosmetic: reorder some LocalPeer methods to match typical lifecycle 2015-01-27 18:11:42 +00:00
Bryan Boreham ae0e3306d7 Merge pull request #313 from bboreham/add_gossip
Gossip layer to communicate topology between peers

Note that I have squashed over 100 commits from @bboreham and @rade into the three for this PR; you can see the original history at https://github.com/bboreham/weave/compare/add_gossip_full
2015-01-27 15:37:35 +00:00
Bryan Boreham 4647c36a15 Unit tests for gossip-based topology communication 2015-01-27 12:45:40 +00:00
Bryan Boreham 5dc9b085a0 Replace previous topology communication with gossip-based mechanism 2015-01-27 12:45:40 +00:00
Bryan Boreham 18df01a4bf Basic gossip implementation 2015-01-27 12:45:40 +00:00
Bryan Boreham c80bff473f Simplify DeleteTestConnection along the same lines as AddTestConnection 2015-01-26 14:03:32 +00:00
Michael Bridgen 08eb90c938 No longer is local_ip welcome in weaveDNS
(and point at weaveDNS's readme rather than the blog post, which is
out of date)
2015-01-26 11:49:02 +00:00
Matthias Radestock 1efaac62bf Merge remote-tracking branch 'upstream/master' 2015-01-25 12:33:36 +00:00
Matthias Radestock 824286d382 shrink "improved" topology update
When an update contains an older version of a peer we know about,
there is no point in including our better version in the "improved"
update, since we will have told our neighbours about that already and
any race conditions are taken care off by the periodic sending of the
complete topology.
2015-01-25 12:27:45 +00:00
Matthias Radestock af6e5c3438 move more of the topology docs into their own section
and some cosmetics
2015-01-25 12:21:02 +00:00
Matthias Radestock fc85e12556 push more of the topology docs into the topology section
plus some cosmetics
2015-01-25 12:17:50 +00:00
Matthias Radestock 13fc0bf720 add some tests for Peers
extracted from #313
2015-01-24 10:56:44 +00:00
Matthias Radestock b137f3b639 oops 2015-01-24 10:37:03 +00:00
Matthias Radestock 55f55a59bc refactor: promote connection lifecycle methods from LocalPeer to Peer
They are sufficiently generic, and having them on Peer is useful for
testing.
2015-01-23 20:04:09 +00:00
Matthias Radestock 1dde9bfe0b cosmetic 2015-01-23 16:58:59 +00:00
Matthias Radestock 0c02e0ac12 Merge remote-tracking branch 'upstream/master' 2015-01-23 16:27:10 +00:00
Matthias Radestock 4cb306e714 refactor: eliminate dependency of Peers on MacCache 2015-01-23 16:24:41 +00:00
rade 1926bc7b71 Merge pull request #357 from errordeveloper/split-create-bridge-command
Add create-bridge command
2015-01-22 18:37:00 +00:00
Ilya Dmitrichenko dfd7d0fe54 Add create-bridge command 2015-01-22 18:04:22 +00:00
Matthias Radestock 86c5e22fa8 another assert 2015-01-22 16:43:07 +00:00
Matthias Radestock 3baa3c09a4 revamp error handling on tcp connection receive loop
- treat crypto decoding errors as errors instead of warnings
- introduce helper for handling protocol messages
- put conn.Shutdown in one place
2015-01-22 16:21:48 +00:00
Matthias Radestock 6253be6ecf cosmetic: nuke stray whitespace 2015-01-22 14:49:00 +00:00
Matthias Radestock 8af2a1c25d refactor: make tie breaking part of the Connection interface
and thus eliminate all knowledge of LocalConnection from the
LocalPeer.handle* methods
2015-01-21 21:44:22 +00:00
Matthias Radestock 99525ebf85 refactor: LocalConnection.CheckFatal be gone!
use Shutdown instead, which is in the Connection interface, thus
further reducing dependency on LocalConnection.
2015-01-21 21:23:59 +00:00
Bryan Boreham c7af02776f Obtain status info in JSON format 2015-01-21 21:03:16 +00:00
Matthias Radestock 9a27299562 cosmetic: consistent method order 2015-01-21 20:55:51 +00:00
Matthias Radestock fe07a41dc3 refactor: make LocalConnection uid 'private'
which makes sense now that we've abstracted tie breaking
2015-01-21 20:46:45 +00:00
Matthias Radestock 9302105a2e refactor: abstract away connection tie breaking
So we don't need to carry that logic in LocalPeer
2015-01-21 20:42:27 +00:00
Matthias Radestock 6efa794ab4 some testing improvements
- show complete call stack of failures (this does include some of the
  testing machinery, but a) is simple, and b) is guaranteed to not
  miss important stack frames)
- add helper to run test with a timeout
- add AssertEqualuint64
2015-01-21 18:45:37 +00:00
Bryan Boreham b7b5fd3dd2 Merge pull request #356 from rade/forgive_protocol_decoding_errors
LGTM.
2015-01-21 17:54:07 +00:00
Matthias Radestock 04f9fab78b don't ignore EOF when decoding peer connections
Even when  there are no connections,  we still expect the  decoding to
succeed.
2015-01-21 17:44:54 +00:00
Matthias Radestock f5a9f07026 forgive all protocol decoding errors
log an error and close the connection, instead of blowing up
2015-01-21 15:01:43 +00:00
Matthias Radestock 8eed483c6e set permissons on 110_encryption_test.sh same as everything else 2015-01-21 14:18:17 +00:00
Bryan Boreham bdb8b4625b Merge pull request #355 from rade/protocol_sender
LGTM.
2015-01-21 11:48:54 +00:00
Matthias Radestock c6f5e7ec94 introduce ProtocolSender interface 2015-01-20 23:41:42 +00:00
Matthias Radestock 76e44afdea cosmetic 2015-01-20 23:36:02 +00:00
Matthias Radestock ac95d5ab29 rename and lift ProtocolMsg into API 2015-01-20 23:23:59 +00:00
Matthias Radestock d089900632 fix crypto 2015-01-20 23:22:44 +00:00
Matthias Radestock 1181a0cb33 better APIs for sending protocol messages
no longer tagging requiring the caller to construct them
2015-01-20 22:31:16 +00:00
Matthias Radestock 644697977b refactor: make protocol message tags a separate type 2015-01-20 21:51:18 +00:00
Matthias Radestock a1ef2def2b refactor: extract pdu statics into separate file 2015-01-20 21:41:08 +00:00
Matthias Radestock 9daf753d8a cosmetic: remove types.go 2015-01-20 21:21:58 +00:00
Matthias Radestock cdf8e906d1 cosmetic: move Interaction from types.go to utils.go 2015-01-20 21:21:32 +00:00
Matthias Radestock 44c8533632 cosmetic: move packet i/o types from types.go into router.go 2015-01-20 21:20:28 +00:00
Matthias Radestock e82ed423c1 cosmetic: move ForwardedFrame type from types.go to forwarder.go 2015-01-20 21:15:50 +00:00
Matthias Radestock 2102fc8529 cosmetic: move UDPPacket type from types.go to router.go 2015-01-20 21:14:45 +00:00
Matthias Radestock e171088fac cosmetic: remove unused type 2015-01-20 21:02:48 +00:00
Matthias Radestock 62da72e26d cosmetic: move a bunch of error types to where they belong 2015-01-20 21:01:50 +00:00
Matthias Radestock c6b68dd2bd cosmetic: move Router type from types.go to router.go 2015-01-20 20:49:23 +00:00
Matthias Radestock 74563c6e3a cosmetic: move connection types from types.go to connection.go 2015-01-20 20:44:35 +00:00
Matthias Radestock a649d45085 cosmetic: move Forwarder type from types.go to forwarder.go 2015-01-20 20:41:13 +00:00
Matthias Radestock 1bc27c303e cosmetic: move EthernetDecoder type from types.go to ethernet_decoder.go 2015-01-20 20:35:36 +00:00
Matthias Radestock f5dc851ebe cosmetic: move udp sender types from types.go to udp_sender.go 2015-01-20 20:30:25 +00:00
Matthias Radestock be4dca5bf7 cosmetic: move crypto types from types.go into crypto.go 2015-01-20 20:27:59 +00:00
Matthias Radestock 5dd62b2ecf bound check on protocol message receipt
and some refactoring to extract protocol message payload in common place
2015-01-20 20:06:29 +00:00
Matthias Radestock 1b3d65476c refactor: simplify 2015-01-20 11:51:01 +00:00
Matthias Radestock 1a31748e40 refactor: extract some helpers useful for testing 2015-01-19 21:25:28 +00:00
Matthias Radestock c7701a20b5 refactor: if->switch 2015-01-19 17:01:20 +00:00
Bryan Boreham 404c7a1ad4 When printing call-stacks from SIGQUIT, trim the buffer to remove garbage at the end 2015-01-19 16:54:40 +00:00
Matthias Radestock 24bd0a9a75 refactor: separate MacCache creation from activation 2015-01-19 16:45:06 +00:00
Matthias Radestock 1a85990bdc cosmetic: move a comment 2015-01-19 14:51:59 +00:00
Matthias Radestock b95d5ee182 refactor: split out a helper function 2015-01-19 14:02:22 +00:00
Matthias Radestock 48c8f9ea01 refactor: separate object creation from activation 2015-01-19 13:23:25 +00:00
Matthias Radestock ebe48f5e58 improve weavetools build
- use the more idiomatic `configure --disable-shared && make` instead
  of `configure && make LDFLAGS=-static`. This only works for
  libtool-based builds, so not ethtool.
- check that we really have built static executables
2015-01-18 18:27:30 +00:00
Matthias Radestock 719efd0ac4 eliminate dependency on 'curl' on host
...by bundling it in the weavetools image

Closes #351.
2015-01-18 17:57:21 +00:00
Matthias Radestock 1b229fcc30 gofmt 2015-01-17 19:20:48 +00:00
Matthias Radestock d69854d9c5 cosmetic: turn some if-then-else into 'switch' 2015-01-17 19:19:47 +00:00
Matthias Radestock 198de416ab Merge remote-tracking branch 'upstream/master' 2015-01-17 19:07:59 +00:00
Matthias Radestock 247ebaaf19 ignore heartbeats from the 'wrong' connection
The router may receive heartbeat packets that originated from (the
other end of) a different connection with the remote peer than the
current one. If we don't detect that, we may erroneously tell the
remote peer to mark the connection as 'established'. Which can lead to
a crash.

To enable detection, we include the connection UID - which is the same
at both ends - in the heartbeat frame. That way we can simply ignore
heartbeats with a different connection UID than the current connection
to the remote peer.

Previously heartbeat frames were zero length. In order to include the
64 bit connection UID we could just send 8 octets instead. However,
that would potentially trip up the Ethernet decoder at the other end,
and in any case we might as well make heartbeats fit the format of
other special frames, namely having an all-zero Ethernet header. This
in fact results in a simplification of the special frame handling,
since heartbeats are no longer *extra* special.

Fixes #352.
2015-01-17 18:45:19 +00:00
Bryan Boreham 89ea4f9f80 Changed Twitter handle to @weaveworks 2015-01-17 17:57:18 +00:00
Matthias Radestock a3e9556e75 optimisation: create heartbeat frame only once per connection 2015-01-17 17:29:54 +00:00
Matthias Radestock aceaa66903 refactor(ish) of special frame processing in udp frame handler
- pull all special frame handling in one place
- generalise the condition for detecting special frames
- add some explanation
2015-01-17 16:48:00 +00:00
Matthias Radestock c6898de4c0 refactor: compare Peer pointers instead of names
That is what we do pretty much everywhere else and it is safe since
there are never two Peers with the same name.
2015-01-16 23:10:03 +00:00
Matthias Radestock 4829d411af tiny refactor
don't extract srcMac until we need it
2015-01-16 21:44:57 +00:00
Matthias Radestock c6a684a741 fix some typos 2015-01-16 17:24:03 +00:00
Matthias Radestock a7ed1f0e9c less willing 2015-01-16 16:06:09 +00:00
Matthias Radestock 1890662f65 FetchAll is no longer sent explicitly on ConnectionEstablished
though it is in fact still sent straight away as part of initialsing
the FetchAll timer
2015-01-16 16:02:59 +00:00
Matthias Radestock 45e9d7f17f Merge branch 'bboreham-master' 2015-01-16 16:02:11 +00:00
Matthias Radestock 79b532e4c5 Merge branch 'master' of https://github.com/bboreham/weave into bboreham-master 2015-01-16 16:01:36 +00:00
Matthias Radestock 95e5ebdb5e tweaks to handling of ConnectionEstablished
1) stop the fast heartbeater asap; reducing the liklihood of getting
an extraneous beat
2) uniformely deal with 'avoiding initial wait for timer ticks'; this
entails sending a FetchAll immediately
3) because of (2) there is now no point initiating a FetchAll from the
tcp reader
2015-01-16 15:54:48 +00:00
Bryan Boreham 18184017ec Minor re-wording after review comments 2015-01-16 15:00:46 +00:00
Bryan Boreham bddd0b5ea4 Update FetchAll description to reflect changes in #349 2015-01-16 13:25:03 +00:00
Bryan Boreham 65fd2450ae Added description of topology communication 2015-01-16 12:24:05 +00:00
Matthias Radestock 49f61c8d4c correct comment
This should have been part of #349.
2015-01-15 20:12:04 +00:00
Bryan Boreham 36cb3c77b5 Merge pull request #349 from rade/342_unidirectional_udp
LGTM.  Fixes #342
2015-01-15 16:49:29 +00:00
Bryan Boreham 92043a3dbc Merge pull request #348 from squaremo/issue346_fallback_for_rdns
LGTM.  Closes #346.
2015-01-15 15:31:49 +00:00
Matthias Radestock 9428b7c116 report 'unestablished' in status 2015-01-15 15:10:13 +00:00
Matthias Radestock f2d6b5fdb0 update docs 2015-01-15 14:58:24 +00:00
Matthias Radestock 27c9e70ea9 better handling of unidirectional udp connectivity
Currently we cannot distinguish bi-directional and uni-directional
connectivity between peers. This is what currently happens for a
connection from A to B...

A: remoteUDPAddr=X
B: remoteUDPAddr=nil
A: send fast heartbeats over UDP
B: receive heartbeat; remoteUDPAddr is nil
   remoteUDPAddr=Y
   send 'established' over TCP
   established=true
   send slow heartbeats over UDP
A: receive 'established' over TCP
   established=true
   send slow heartbeats over UDP

If udp packets make it through from A to B but not B to A then both
ends mark the connection as 'established'. Conversely, if udp packets
make it through from B to A but not A to B then the connection is not
marked as 'established' at either end.

In the new scheme, this is what happens...

A: receivedHeartbeat=false, remoteUDPAddr=X
B: receivedHeartbeat=false, remoteUDPAddr=nil
A: send fast heartbeats over UDP
B: receive heartbeat; receivedHeartbeat is false
   remoteUDPAddr=Y
   receivedHeartbeat=true
   send 'established' over TCP
   send fast heartbeats over UDP
A: receive 'established' over TCP (*)
   established=true
   send slow heartbeats over UDP
A: receive heartbeat; receivedHeartbeat is false (*)
   receivedHeartbeat=true
   send 'established' over TCP
B: receive 'established' over TCP
   established=true
   send slow heartbeats over UDP

(*) either of these may occur first

Now, if udp packets make it through from A to B not not B to A then A
marks the connection as 'established' but B doesn't. Conversely, if
udp packets make it through from B to A but not A to B then B marks
the connection as 'established' but A doesn't.
2015-01-15 14:16:16 +00:00
Matthias Radestock 956707ca86 cosmetic 2015-01-14 18:44:18 +00:00
Matthias Radestock 873c95a602 tiny refactor 2015-01-14 18:37:50 +00:00
Matthias Radestock 3765d23825 refactor: move udp sender code into separate file 2015-01-14 17:51:01 +00:00
Matthias Radestock d000beb621 refactor: make LocalConnection.handshake do (only) what it says 2015-01-14 17:36:45 +00:00
Matthias Radestock 9cf5fc5331 refactor: make LocalConnection.queryLoop do what it says 2015-01-14 17:08:38 +00:00
Michael Bridgen 5c3fe86be7 Include testing non-weave reverse queries in DNS smoke tests
Pro-tip: Do not put weaveDNS on the same subnet as the fallback DNS
server.
2015-01-14 17:02:17 +00:00
Michael Bridgen db9bae4162 Use the fallback for reverse lookups too
I considered a couple of different factorings, that is

 * Implementing the fallback as a Lookup
 * Having a server struct that held the resolv.conf and methods for
   handling queries

but ultimately, this is simple and works.
2015-01-14 16:44:12 +00:00
Michael Bridgen fd90212db4 Get the format string right 2015-01-14 13:37:10 +00:00
Bryan Boreham 609585400b Merge pull request #345 from squaremo/better_dns_smoke_tests
LGTM
2015-01-14 10:38:31 +00:00
Bryan Boreham d9b6d5c5f9 Merge pull request #344 from squaremo/issue318_proxy_all_dns
LGTM
2015-01-13 20:50:27 +00:00
Matthias Radestock 72da7cd5d6 refactor: remove superfluous check
which is already performed by the caller
2015-01-13 18:56:20 +00:00
Bryan Boreham ee6f92cd24 Merge pull request #343 from rade/340_improve_connection_logging
LGTM.  Closes #340 - I tested with UDP blocked and got desired log.
2015-01-13 16:53:03 +00:00
Matthias Radestock 0befe997c1 oops 2015-01-13 16:38:58 +00:00
Matthias Radestock cc8fad47dd more connection logging consistency 2015-01-13 16:30:13 +00:00
Michael Bridgen 437b9d0ba2 Add tests for resolving names across hosts
It's more precise to use dig rather than ping. The stock ubuntu image
doesn't come with dig (in package dnsutils), but luckily Aanand made
an image which is exactly ubuntu+dnsutils.
2015-01-13 16:08:07 +00:00
Michael Bridgen 57b8039678 Smoke test for DNS fallback 2015-01-13 15:51:09 +00:00
Matthias Radestock 1235441556 improve connection logging
1) log more lifecycle events
  - acceptance
  - handshake completion
  - added & removed (from our connection map)
The latter is a bit verbose but may help us track down the cause for
going to introduce here, then this would fall in the 'debug' category.

2) better error logging
  - log error as part of "shutting down" log event rather than separately
  - indicate "blocked UDP connectivity" as a possible cause when
    getting a timeout on a not-fully-established connection.
The rationale for the latter is that inbound connections get
marked as established when first receiving a heartbeat frame over
UDP, which are the only frames that get transmitted prior to the
connection being fully established. Note that outbound
connections get marked as established when receiving the
ConnectionEstablished message over TCP, which is sent by the peer
when it first receives a heartbeat. See #342.

Closes #340.
2015-01-13 15:47:23 +00:00
Matthias Radestock 937d9c3ef8 rely un weavetools for testing
...rather than installing ethtool and conntrack

Forgot to do this as part of #309.
2015-01-13 12:46:27 +00:00
Matthias Radestock 1e4d7e4462 refactor: remove superfluous var
conn.shutdown was only set/accessed in handleShutdown, which in turn
is only called on termination of queryLoop and hence multiple
invocations are impossible.
2015-01-12 18:39:25 +00:00
Matthias Radestock aed95326f3 better name for test suite 2015-01-12 18:29:52 +00:00
Michael Bridgen ff45bcf945 Change Lookup method names
The Local was redundant (and inaccurate in the case of the mDNS client
implementation), and otherwise they are too nondescript.
2015-01-12 17:42:23 +00:00
Michael Bridgen 5f642d390c Pass queries for non-local names on to the configured nameserver 2015-01-12 17:12:15 +00:00
Michael Bridgen c76e213f76 Fix up common/testing confusions 2015-01-12 16:33:51 +00:00
Bryan Boreham dc55448c7d Added a smoke-test with a password 2015-01-12 12:49:37 +00:00
rade 344eadfd8e Merge pull request #336 from squaremo/reform_dns_logging
Reform logging in weaveDNS

Closes #311.
2015-01-12 12:36:36 +00:00
rade 73e1d0c12e Merge pull request #314 from squaremo/issue289_exclude_test
Move testing utils to its own package

Fixes #289.
2015-01-12 11:42:55 +00:00
Michael Bridgen 14f7d83e3e Merge branch '321_cope_with_weavetools_delays' of git://github.com/rade/weave into pr334 2015-01-09 17:40:42 +00:00
Michael Bridgen 382095f291 Merge branch 'pr333' 2015-01-09 16:35:17 +00:00
rade eb0b635bdd Merge pull request #337 from bboreham/master
Remove repeated 'iota' in const lists, since Go knows what you mean
2015-01-09 16:31:11 +00:00
Michael Bridgen a0eccd3971 Merge branch '331_reinstate_make_publish' of git://github.com/rade/weave into pr333 2015-01-09 16:03:22 +00:00
Bryan Boreham 15852dfa42 Remove repeated 'iota' in const lists, since Go knows what you mean 2015-01-09 15:55:35 +00:00
David Wragg ad0b4a5a5a Fix a missing 'the' 2015-01-09 14:21:58 +00:00
David Wragg 67b07ec540 Fix grammar 2015-01-09 14:20:06 +00:00
David Wragg 949150454c Merge pull request #325 from errordeveloper/issue_310 2015-01-09 14:14:45 +00:00
Matthias Radestock 11b87f879a make weave status show whether encryption is on/off
Closes #323.
2015-01-09 12:56:26 +00:00
Michael Bridgen b114735a41 Reform logging in weaveDNS
* Put prefixes (including msg IDs) in front of log messages to
   distinguish them
 * Failed lookups get logged under Info as per issue #311
 * More accurate messages as per issue #311
2015-01-09 12:49:07 +00:00
Michael Bridgen 97bece2d7b Move testing utils to its own package
So as not to pollute command-line flags with test.* options.

It appears that importing "testing" (from stdlib) will add flags. To
avoid this, we need code to not depend on testing. But, since
testing_utils was previously in the same package as the rest of the
code, it dragged testing in.

Renaming testing_util.go to e.g., util_for_test.go might be enough to
avoid it being compiled with the regular code. We may want it to be
available to tests for other packages -- after all it is utilities for
testing -- so it goes in its own package.
2015-01-09 12:36:32 +00:00
Bryan Boreham f70dfe9de8 Merge pull request #328 from squaremo/refactor_mdns
LGTM.  Fixes #326
2015-01-09 12:32:35 +00:00
Michael Bridgen 15ebc2c9a7 Factor the making of handlers out
NB I removed free variables from the LookupFunc implementations, to
avoid confusion between `s` the receiver and the closed-over `s`.

Also corrected a mistake in the write error log message.
2015-01-09 12:29:28 +00:00
Ilya Dmitrichenko b534076e11 Installation note for systemd 2015-01-09 12:00:13 +00:00
rade 5a12c4cf28 Merge pull request #332 from errordeveloper/password_env_var
Allow password to be specified in WEAVE_PASSWORD, pass that securely, and thus elminate the need for `-passwordfile`.

Closes #330.
2015-01-09 10:05:12 +00:00
Ilya Dmitrichenko 5e08ba677e Remove -passwordfile option
The only two options should be `-password` and `WEAVE_PASSWORD`.
2015-01-09 09:59:28 +00:00
Matthias Radestock f7ad2cd5af wait longer for ethwe interface to come up
in case we are running on a really, really slow machine/VM
2015-01-08 22:22:40 +00:00
Matthias Radestock 52506cb39a ensure we have the weavetools image
prior to entering any time-sensitive code, in particular
post-container-startup network configuration.
2015-01-08 22:14:52 +00:00
Matthias Radestock b1ca832ed5 re-instate 'make publish'
and call it from the release script
2015-01-08 20:06:00 +00:00
Matthias Radestock 89cb1af996 use new Makefile DOCKERHUB_USER override 2015-01-08 19:22:03 +00:00
Matthias Radestock 3090282a15 permit overriding of dockerhub user 2015-01-08 19:15:28 +00:00
Matthias Radestock 1a4aceea21 make various release script vars overridable
and pass SUDO to the `make` invocation
2015-01-08 19:05:25 +00:00
Bryan Boreham f24edf755f Add tests for server.go 2015-01-08 18:38:38 +00:00
Bryan Boreham bd2844d186 Split http-startup code from server-startup 2015-01-08 18:38:38 +00:00
Michael Bridgen 89fa52818c Account for PTR answers in mdns_client
En passant, ResponseA -> the more versatile Response.
2015-01-08 18:30:30 +00:00
Ilya Dmitrichenko 833fcce792 Use WEAVE_PASSWORD environment variable, if no args supplied 2015-01-08 17:08:35 +00:00
Ilya Dmitrichenko 5cbc4fa33d Make passing of password more readable 2015-01-08 17:08:35 +00:00
Matthias Radestock 6c0218d26a oops 2015-01-08 17:00:59 +00:00
Michael Bridgen e9b84c968d Clean up the mdns_server tests so we can test PTR queries 2015-01-08 15:32:59 +00:00
Michael Bridgen dd6ca5ab92 Factor out the particulars of looking up A or PTR queries 2015-01-08 15:32:59 +00:00
Michael Bridgen bf752d2d1f Implement zone lookup with MDNSCLient
This separates Zone into the DB ops and the Lookup ops, and implements
the latter for MDNSClient so it can be used just like the zonedb.

A notable change is that Lookup methods only ever return one result,
while MDNSClient.SendQuery could in theory return multiple results
(though it wouldn't, because no server would supply more than one
result in a response, and it stops waiting after getting one response
..).

ReverseLookupLocal is implemented even though no MDNSServer will
answer at present (hence no test yet).

Lastly, the handler (creating) functions can now be given a list of
Lookup implementations to try in order.
2015-01-08 13:39:33 +00:00
Michael Bridgen 3866bd5e63 Parse reverse address lookup IPs in the Zone
.. rather than making clients of the zone do it. At the minute there's
only one call site, but soon there will be more.
2015-01-08 13:39:33 +00:00
Bryan Boreham 71d96eed7d Merge pull request #320 from errordeveloper/fix_319
LGTM.  Closes #319
2015-01-07 15:09:04 +00:00
Matthias Radestock 844fd54e18 less willing 2015-01-07 12:20:44 +00:00
Matthias Radestock 4ecd4e727f rewording of must of the weaveDNS intro 2015-01-07 12:16:16 +00:00
Matthias Radestock 0777a1e3b0 cosmetic: remove trailing whitespace 2015-01-07 11:43:12 +00:00
Bryan Boreham 27f9a380e8 Explain weaveDNS configuration better 2015-01-07 11:34:16 +00:00
Ilya Dmitrichenko 9d48f942f8 Trim spaces in docker inspect format templates (close #319) 2015-01-06 15:46:22 +00:00
Matthias Radestock 978dc41e60 note that for app isolation CAP_NET_RAW needs to be dropped
See also #316
2015-01-06 09:39:17 +00:00
Matthias Radestock cce5d2417a make location of /proc overridable in script
useful for running weave inside a container
2015-01-04 13:07:52 +00:00
Matthias Radestock 79b1de5483 add 'sudo' to docker commands in release script
overridable with SUDO env var
2015-01-04 09:57:40 +00:00
149 changed files with 14461 additions and 3584 deletions

No files matched your search

+16
View File
@@ -6,6 +6,7 @@
# Folders
_obj
_test
.vagrant
# Architecture specific extensions/prefixes
*.[568vq]
@@ -22,6 +23,8 @@ _testmain.go
*.exe
*.test
*.prof
profile.cov
coverage.html
# Emacs backup files
*~
@@ -29,5 +32,18 @@ _testmain.go
# Project specific
weaver/weaver
weavedns/weavedns
weaveproxy/weaveproxy
sigproxy/sigproxy
weavewait/weavewait
tools/bin
tools/build
releases
weave.tar
weavedns.tar
weaveexec.tar
weaveexec/weave
weaveexec/weaveproxy
weaveexec/weavewait
weaveexec/sigproxy
weaveexec/docker*.tgz
Vagrantfile.local
+28
View File
@@ -0,0 +1,28 @@
language: go
sudo: false
go:
- 1.4
env:
- PATH="${PATH}:${HOME}/.local/bin"
addons:
apt:
packages:
- libpcap-dev
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:
- bin/lint .
- make tests
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
+121 -3
View File
@@ -1,3 +1,121 @@
## 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**:
- **bug fixes**, in particular eradicating several deadlocks that could
cause peer connection failures and hangs.
- **performance and scalability improvements** to the weave control plane,
as a result of which it is now possible to construct much larger
weave networks than previously.
- **improved installation and administration**, particularly the
introduction of remote execution of the weave script in a container,
permitting fully containerised deployment of weave.
- **improved diagnostics**, such as the reporting of connection failures
in `weave status`.
- **new weaveDNS features**, most notably the caching of DNS records
for application containers, which makes finding container IP
addresses via weaveDNS much faster.
More detail in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A0.10.0)
*NB: This release changes the Weave Docker image names. To upgrade
from an older version, 1) stop all application containers, 2) run
`weave reset` from the old version to remove all traces of weave, and
only then 3) install the new version.*
## Release 0.9.0
- Improve WeaveDNS to the point where it can act as the name server
for containers in nearly all situations.
- Diagnose and report peer connectivity more comprehensively.
- Adapt to changes in topology - adding & removing of weave peers,
disruption of connectivity - more rapidly.
- Cope with delays in downloading/running docker images/containers
required for weave operation.
See the
[complete change log](https://github.com/weaveworks/weave/issues?q=milestone%3A0.9.0)
for more details.
## Release 0.8.0
- Align script and image version. When the `weave` script has a
@@ -22,9 +140,9 @@ This is the first release assigned a version number.
When downloading weave you now have the following choices...
1. a specific version, e.g. https://github.com/zettio/weave/releases/download/v0.7.0/weave
2. latest released version: https://github.com/zettio/weave/releases/download/latest_release/weave
3. most recent 'master' commit: https://raw.githubusercontent.com/zettio/weave/master/weave
1. a specific version, e.g. https://github.com/weaveworks/weave/releases/download/v0.7.0/weave
2. latest released version: https://github.com/weaveworks/weave/releases/download/latest_release/weave
3. most recent 'master' commit: https://raw.githubusercontent.com/weaveworks/weave/master/weave
Previously the only documented download location was (3). We recommend
that any automated scripts using that be changed to either (1) or (2).
+1 -1
View File
@@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS
Copyright 2014 Zettio Ltd.
Copyright 2014-2015 Weaveworks Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+72 -23
View File
@@ -1,28 +1,39 @@
PUBLISH=publish_weave publish_weavedns publish_weaveexec
.DEFAULT: all
.PHONY: all update tests clean
.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
DOCKERHUB_USER=weaveworks
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
WEAVER_EXE=weaver/weaver
WEAVEDNS_EXE=weavedns/weavedns
WEAVETOOLS_EXES=tools/bin
WEAVER_IMAGE=zettio/weave
WEAVEDNS_IMAGE=zettio/weavedns
WEAVETOOLS_IMAGE=zettio/weavetools
WEAVER_EXPORT=/var/tmp/weave.tar
WEAVEDNS_EXPORT=/var/tmp/weavedns.tar
WEAVETOOLS_EXPORT=/var/tmp/weavetools.tar
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
WEAVER_EXPORT=weave.tar
WEAVEDNS_EXPORT=weavedns.tar
WEAVEEXEC_EXPORT=weaveexec.tar
all: $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVETOOLS_EXPORT)
WEAVEEXEC_DOCKER_VERSION=1.3.1
DOCKER_DISTRIB=weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
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):
$(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 $@ ./$(shell dirname $@)
go build -ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo -o $@ ./$(@D)
@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
rm $@; \
echo "\nYour go standard library was built without the 'netgo' build tag."; \
@@ -32,11 +43,15 @@ $(WEAVER_EXE) $(WEAVEDNS_EXE):
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
$(WEAVETOOLS_EXES): tools/build.sh
$(SUDO) docker run --rm -v $(realpath $(<D)):/home/weave ubuntu sh /home/weave/build.sh
# Sigproxy needs separate rule as it fails the netgo check in the main
# build stanza due to not importing net package
$(SIGPROXY_EXE): sigproxy/main.go
go build -o $@ ./$(@D)
$(WEAVER_EXPORT): weaver/Dockerfile $(WEAVER_EXE)
$(SUDO) docker build -t $(WEAVER_IMAGE) weaver
@@ -46,15 +61,49 @@ $(WEAVEDNS_EXPORT): weavedns/Dockerfile $(WEAVEDNS_EXE)
$(SUDO) docker build -t $(WEAVEDNS_IMAGE) weavedns
$(SUDO) docker save $(WEAVEDNS_IMAGE):latest > $@
$(WEAVETOOLS_EXPORT): tools/Dockerfile $(WEAVETOOLS_EXES)
$(SUDO) docker build -t $(WEAVETOOLS_IMAGE) tools
$(SUDO) docker save $(WEAVETOOLS_IMAGE):latest > $@
$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE)
cp weave weaveexec/weave
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 > $@
$(DOCKER_DISTRIB):
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
# Add more directories in here as more tests are created
tests:
cd nameserver; go test -tags netgo
echo "mode: count" > profile.cov
fail=0 ; \
for dir in $$(find . -type f -name '*_test.go' | xargs -n1 dirname | sort -u); do \
output=$$(mktemp cover.XXXXXXXXXX) ; \
if ! go test -tags netgo -covermode=count -coverprofile=$$output $$dir ; then \
fail=1 ; \
fi ; \
if [ -f $$output ]; then \
tail -n +2 <$$output >>profile.cov; \
rm $$output; \
fi \
done ; \
exit $$fail
go tool cover -html=profile.cov -o=coverage.html
$(PUBLISH): publish_%:
$(SUDO) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
$(SUDO) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
$(SUDO) docker push $(DOCKERHUB_USER)/$*:latest
publish: $(PUBLISH)
clean:
-$(SUDO) docker rmi $(WEAVER_IMAGE) $(WEAVEDNS_IMAGE) $(WEAVETOOLS_IMAGE)
rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVETOOLS_EXPORT)
$(SUDO) rm -rf $(WEAVETOOLS_EXES)
-$(SUDO) docker rmi $(WEAVER_IMAGE) $(WEAVEDNS_IMAGE) $(WEAVEEXEC_IMAGE)
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
+2 -2
View File
@@ -1,3 +1,3 @@
Weave
Copyright 2012-2014 Zettio Ltd.
This product includes software developed at Zettio Ltd.
Copyright 2014-2015 Weaveworks Ltd.
This product includes software developed at Weaveworks Ltd.
+38 -30
View File
@@ -1,7 +1,9 @@
# Weave - the Docker network
[![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")
@@ -27,13 +29,18 @@ capabilities, so these can continue to be used by containers.
## Installation
Ensure you are running Linux (kernel 3.5 or later) and have Docker
(version 0.9.1 or later) installed. Then install weave with
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 \
https://github.com/zettio/weave/releases/download/latest_release/weave
https://github.com/weaveworks/weave/releases/download/latest_release/weave
sudo chmod a+x /usr/local/bin/weave
CoreOS users see [here](https://github.com/fintanr/weave-gs/blob/master/coreos-simple/user-data) for an example of installing weave using cloud-config.
Weave respects the environment variable `DOCKER_HOST`, so you can run
it locally to control a weave network on a remote host.
## Quick Start Screencast
<a href="http://youtu.be/k6r7yuSr0hE" alt="Click to watch the screencast" target="_blank">
@@ -46,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.0.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
@@ -80,41 +87,42 @@ addresses must, of course, be unique.
We repeat similar steps on `$HOST2`...
host2# weave launch $HOST1
host2# C=$(weave run 10.0.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
with the weave on `$HOST1` (specified as the IP address or hostname, and
optional `:port`, by which `$HOST2` can reach it). NB: if there is a
firewall between `$HOST1` and `$HOST2`, you must open port 6783 for TCP
and UDP.
with the weave on `$HOST1` (specified as the IP address or hostname,
and optional `:port`, by which `$HOST2` can reach it). NB: if there is
a firewall between `$HOST1` and `$HOST2`, you must open the weave port
(6783 by default; this can be overriden by setting `WEAVE_PORT`) for
TCP and UDP.
Note that we could instead have told the weave on `$HOST1` to connect to
`$HOST2`, or told both about each other. Order does not matter here;
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://zettio.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
root@28841bd02eff:/# ping -c 1 -q 10.0.1.2
PING 10.0.1.2 (10.0.1.2): 48 data bytes
--- 10.0.1.2 ping statistics ---
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 ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 1.048/1.048/1.048/0.000 ms
Similarly, on `$HOST2`...
host2# docker attach $C
root@f76829496120:/# ping -c 1 -q 10.0.1.1
PING 10.0.1.1 (10.0.1.1): 48 data bytes
--- 10.0.1.1 ping statistics ---
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 ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 1.034/1.034/1.034/0.000 ms
@@ -123,22 +131,22 @@ to each other.
## Find out more
* [Documentation homepage](http://zettio.github.io/weave/)
* [Features](http://zettio.github.io/weave/features.html)
* [Troubleshooting](http://zettio.github.io/weave/troubleshooting.html)
* [Building](http://zettio.github.io/weave/building.html)
* [How it works](http://zettio.github.io/weave/how-it-works.html)
* [Preview of weaveDNS](http://weaveblog.com/2014/11/04/have-you-met-weavedns/)
* [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
Found a bug, want to suggest a feature, or have a question?
[File an issue](https://github.com/zettio/weave/issues), or email
[File an issue](https://github.com/weaveworks/weave/issues), or email
help@weave.works. When reporting a bug, please include which version of
weave you are running, as shown by `weave version`.
Follow weave on Twitter:
[@weavenetwork](https://twitter.com/weavenetwork).
[@weaveworks](https://twitter.com/weaveworks).
Read the Weave blog:
[Weaveblog](http://weaveblog.com/).
Vendored
+28 -20
View File
@@ -1,32 +1,40 @@
VAGRANTFILE_API_VERSION = "2"
require './vagrant-common.rb'
vm_ip = "172.16.0.3" # arbitrary private IP
pkgs = "lxc-docker build-essential ethtool libpcap-dev git mercurial"
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|
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
end
config.vm.synced_folder "./", "/home/vagrant/src/github.com/zettio/weave"
config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.synced_folder ".", "/home/vagrant/src/github.com/weaveworks/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.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"
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
Executable
+81
View File
@@ -0,0 +1,81 @@
#!/bin/bash
# This scipt lints go files for common errors.
#
# Its runs gofmt and go vet, and optionally golint and
# gocyclo, if they are installed.
#
# With no arguments, it lints the current files staged
# for git commit. Or you can pass it explicit filenames
# (or directories) and it will lint them.
#
# To use this script automatically, run:
# ln -s ../../bin/lint .git/hooks/pre-commit
set -eu
function lint_go {
filename="$1"
result=0
if [ -n "$(gofmt -l "${filename}")" ]; then
result=1
echo "${filename}: run gofmt -w ${filename}!"
fi
go tool vet "${filename}" || result=$?
# golint is completely optional. If you don't like it
# don't have it installed.
if type golint >/dev/null 2>&1; then
# golint doesn't set an exit code it seems
lintoutput=$(golint "${filename}")
if [ "$lintoutput" != "" ]; then
result=1
echo "$lintoutput"
fi
fi
# gocyclo is completely optional. If you don't like it
# don't have it installed. Also never blocks a commit,
# it just warns.
if type gocyclo >/dev/null 2>&1; then
gocyclo -over 25 "${filename}" | while read line; do
echo "${filename}": higher than 25 cyclomatic complexity - "${line}"
done
fi
return $result
}
function lint {
filename="$1"
ext="${filename##*\.}"
# Don't lint this script
if [ "${filename}" = "bin/lint" ]; then
return
fi
case "$ext" in
go) lint_go "${filename}"
;;
esac
}
function lint_files {
lint_result=0
while read filename; do
lint "${filename}" || lint_result=1
done
exit $lint_result
}
function list_files {
if [ $# -gt 0 ]; then
find "$@" -type f
else
git diff --cached --name-only
fi
}
list_files "$@" | lint_files
-20
View File
@@ -1,20 +0,0 @@
#! /bin/bash
set -e
# For info on why/how to use this script, see
# https://github.com/zettio/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:zettio/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
Executable
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
set -e
# You may need to adjust
# echo 4096 >/proc/sys/net/ipv4/neigh/default/gc_thresh3
# where the number is greater than NUM_WEAVES*(NUM_WEAVES-1) plus some
# fudge.
#
# Without that, some connections between the weaves may fail to get
# established, and networking on the host in general becomes
# broken. There will also be some "net_ratelimit: nnn callbacks
# suppressed" messages in the syslog.
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 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)))
PREV="${status#true }"
fi
for i in $(seq $START $FINISH); do
if [ -n "$PREV" ] ; then
echo connecting weave$i to $PREV
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave connect $PREV
fi
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$i)
PREV="${status#true }"
done
;;
stop)
for i in $(seq $START $FINISH); do
echo Stopping weave$i
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave stop
done
;;
esac
-3
View File
@@ -1,3 +0,0 @@
#! /bin/sh
set -e
git push git@github.com:zettio/weave gh-pages:gh-pages
+9 -15
View File
@@ -2,11 +2,13 @@
set -e
SUDO=${SUDO-sudo}
GITHUB_USER=${GITHUB_USER:-weaveworks}
DOCKERHUB_USER=${DOCKERHUB_USER:-weaveworks}
RELEASE_NAME=${RELEASE_NAME:-"Weave"}
RELEASE_DESCRIPTION=${RELEASE_DESCRIPTION:-"Weaving Containers into Applications"}
PWD=`pwd`
GITHUB_USER=zettio
DOCKERHUB_USER=zettio
RELEASE_NAME="Weave"
RELEASE_DESCRIPTION="Weaving Containers into Applications"
setup() {
## Get the new version number from the most recent tag
@@ -49,10 +51,7 @@ build() {
## Inject the version numbers and build the distributables
## (library versions?)
sed -i "/SCRIPT_VERSION=/ c\SCRIPT_VERSION=\"$VERSION\"" ./weave
make \
WEAVE_VERSION=$VERSION \
WEAVER_IMAGE=$DOCKERHUB_USER/weave \
WEAVEDNS_IMAGE=$DOCKERHUB_USER/weavedns
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER
if make tests; then
echo -e '\u2713 Tests pass'
@@ -121,13 +120,8 @@ publish() {
echo '** Sanity checks OK for publishing tag' $LATEST_TAG as $DOCKERHUB_USER/weave:$VERSION
for IMAGE in weave weavedns weavetools ; do
echo == Tagging $DOCKERHUB_USER/$IMAGE as $VERSION
docker tag $DOCKERHUB_USER/$IMAGE $DOCKERHUB_USER/$IMAGE:$VERSION
docker push $DOCKERHUB_USER/$IMAGE:$VERSION
docker push $DOCKERHUB_USER/$IMAGE:latest
echo "** Pushed $DOCKERHUB_USER/$IMAGE:$VERSION to docker hub"
done
echo "== Tagging and publishing images on docker hub as user $DOCKERHUB_USER"
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
echo "== Creating GitHub release $RELEASE_NAME $VERSION"
# This cannot be done as a draft because of a bug in
+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
+21
View File
@@ -0,0 +1,21 @@
FROM ubuntu
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git ca-certificates docker.io mercurial libpcap-dev curl make pkg-config gcc bison flex
# When doing a build in a container, "apt-get update" happens twice,
# which can be a very significant overhead for incremental builds.
# And it's unnecessary, because if any of the prerequisites do change,
# the right thing to do is to rebuild the container image. Rather
# than suppressing the "apt-get update" commands explicitly, we clear
# out sources.list so that 'apt-get update' doesn't do anything.
RUN echo >/etc/apt/sources.list
ENV GO_VERSION 1.4.2
RUN curl -sSL https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz | tar -C /usr/local -xz
ENV PATH /usr/local/go/bin:$PATH
# So that sudo does not trample on PATH
RUN sed -ie '/secure_path/d' /etc/sudoers
COPY build.sh /
ENTRYPOINT ["sh", "/build.sh"]
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
set -e
GOPATH=/home/go
export GOPATH
WEAVE_SRC=$GOPATH/src/github.com/weaveworks/weave
if [ $# -eq 0 ] ; then
# No arguments. Expect that the weave repo will be bind-mounted
# into $GOPATH
if ! [ -e $WEAVE_SRC ] ; then
cat 2>&1 <<EOF
No container arguments supplied, and nothing at ${WEAVE_SRC}. Please
either bind-mount the golang workspace containing weave with the
docker run -v option, e.g.:
$ docker run -v <host gopath>:${GOPATH} \\
-v /var/run/docker.sock:/var/run/docker.sock weaveworks/weave-build
Or supply git clone arguments to retrieve it, e.g.:
$ docker run -v /var/run/docker.sock:/var/run/docker.sock \\
weaveworks/weave-build https://github.com/weaveworks/weave.git
EOF
exit 1
fi
# If we run make directly, any files created on the bind mount
# will have awkward ownership. So we switch to a user with the
# same user and group IDs as source directory. We have to set a
# few things up so that sudo works without complaining later on.
uid=$(stat --format="%u" $WEAVE_SRC)
gid=$(stat --format="%g" $WEAVE_SRC)
echo "weave:x:$uid:$gid::$WEAVE_SRC:/bin/sh" >>/etc/passwd
echo "weave:*:::::::" >>/etc/shadow
echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
else
# There are arguments to pass to git-clone
mkdir -p ${WEAVE_SRC%/*}
git clone "$@" $WEAVE_SRC
make -C $WEAVE_SRC build
fi
+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
+20
View File
@@ -0,0 +1,20 @@
package common
func CheckFatal(e error) {
if e != nil {
Error.Fatal(e)
}
}
func CheckWarn(e error) {
if e != nil {
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())
}
}
}
}
-51
View File
@@ -1,51 +0,0 @@
package common
import (
"fmt"
"reflect"
"runtime"
"strings"
"testing"
)
func CallSite() string {
_, file, line, ok := runtime.Caller(2)
if ok {
// Truncate file name at last file name separator.
if index := strings.LastIndex(file, "/"); index >= 0 {
file = file[index+1:]
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
file = file[index+1:]
}
} else {
file = "???"
line = 1
}
return fmt.Sprintf("%s:%d: ", file, line)
}
func AssertNoErr(t *testing.T, err error) {
if err != nil {
t.Fatal(CallSite(), err)
}
}
func AssertStatus(t *testing.T, got int, wanted int, desc string) {
if got != wanted {
t.Fatalf("%s: Expected %s %d but got %d", CallSite(), desc, wanted, got)
}
}
func AssertErrorInterface(t *testing.T, got interface{}, wanted interface{}, desc string) {
gotT, wantedT := reflect.TypeOf(got), reflect.TypeOf(wanted).Elem()
if !gotT.Implements(wantedT) {
t.Fatalf("%s: Expected %s but got %s (%s)", CallSite(), wantedT.String(), gotT.String(), desc)
}
}
func AssertErrorType(t *testing.T, got interface{}, wanted interface{}, desc string) {
gotT, wantedT := reflect.TypeOf(got), reflect.TypeOf(wanted).Elem()
if gotT != wantedT {
t.Fatalf("%s: Expected %s but got %s (%s)", CallSite(), wantedT.String(), gotT.String(), desc)
}
}
+47
View File
@@ -0,0 +1,47 @@
package updater
import (
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
type ContainerObserver interface {
ContainerDied(ident string) error
}
func checkError(err error, apiPath string) {
if err != nil {
Error.Fatalf("[updater] Unable to connect to Docker API on %s: %s",
apiPath, err)
}
}
func Start(apiPath string, ob ContainerObserver) error {
client, err := docker.NewClient(apiPath)
checkError(err, apiPath)
env, err := client.Version()
checkError(err, apiPath)
events := make(chan *docker.APIEvents)
err = client.AddEventListener(events)
checkError(err, apiPath)
Info.Printf("[updater] Using Docker API on %s: %v", apiPath, env)
go func() {
for event := range events {
handleEvent(ob, event, client)
}
}()
return nil
}
func handleEvent(ob ContainerObserver, event *docker.APIEvents, client *docker.Client) error {
switch event.Status {
case "die":
id := event.ID
ob.ContainerDied(id)
}
return nil
}
+16 -12
View File
@@ -83,13 +83,17 @@ Router:
local peer. It will tell the local connection (communicating to
the actor thread) about the UDP address of the remote peer. The
local connection will then start its forwarder threads as
described in 6a, and start sending slow heartbeats. The local connection
is marked as established which means it is included in network updates
broadcast to our peers. We also send to the remote peer via TCP a
ConnectionEstablished message. The remote peer receives this (on
the TCP receiver process), tells the connection actor process,
which then replaces the fast heartbeater with a slow heartbeater
and similarly marks the connection as established.
described in 6a, and start sending fast heartbeats. We send to the
remote peer via TCP a ConnectionEstablished message. The remote
peer receives this (on the TCP receiver process), tells the
connection actor process, which then replaces the fast heartbeater
with a slow heartbeater and marks the connection as established
(which means it is included in network updates broadcast to our
peers).
6c. When the connection initiator receives the fast heartbeat from
the remote peer it sends to the remote peer via a TCP a
ConnectionEstablished message. This is handled by the remote peer
as described in 6b.
7. Whenever a connection is established or terminated, the local
peer's version is incremented. Whenever this happens, the peer
generates a network update message which is broadcast to its
@@ -156,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.
@@ -183,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
+1 -1
View File
@@ -126,7 +126,7 @@ If all is well, you should now be able to launch weave on VM1
then on VM2:
VM1> weaver -iface veth6 -name vm1
VM2> weaver -iface veth6 -name vm2 192.168.122.1
VM2> weaver -iface veth6 -name vm2 192.168.122.2
Once the connection is up and running, you should be able to connect
from VM1/ns2 to VM2/ns2 and vice versa:
+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")
}
+401
View File
@@ -0,0 +1,401 @@
package nameserver
import (
"bytes"
"container/heap"
"errors"
"fmt"
"math"
"math/rand"
"sync"
"time"
"github.com/benbjohnson/clock"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
)
var (
errInvalidCapacity = errors.New("Invalid cache capacity")
errCouldNotResolve = errors.New("Could not resolve")
errTimeout = errors.New("Timeout while waiting for resolution")
errNoLocalReplies = errors.New("No local replies")
)
const (
defPendingTimeout int = 5 // timeout for a resolution
)
const nullTTL = 0 // a null TTL
type entryStatus uint8
const (
stPending entryStatus = iota // someone is waiting for the resolution
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)
)
// shuffleAnswers reorders answers for very basic load balancing
func shuffleAnswers(answers []dns.RR) []dns.RR {
n := len(answers)
if n > 1 {
rand.Seed(time.Now().UTC().UnixNano())
for i := 0; i < n; i++ {
r := i + rand.Intn(n-i)
answers[r], answers[i] = answers[i], answers[r]
}
}
return answers
}
// a cache entry
type cacheEntry struct {
Status entryStatus // status of the entry
Flags uint8 // some extra flags
ReplyLen int
question dns.Question
protocol dnsProtocol
reply dns.Msg
validUntil time.Time // obtained from the reply and stored here for convenience/speed
putTime time.Time
index int // for fast lookups in the heap
}
func newCacheEntry(question *dns.Question, now time.Time) *cacheEntry {
e := &cacheEntry{
Status: stPending,
validUntil: now.Add(time.Second * time.Duration(defPendingTimeout)),
question: *question,
index: -1,
}
return e
}
// Get a copy of the reply stored in the entry, but with some values adjusted like the TTL
func (e *cacheEntry) getReply(request *dns.Msg, maxLen int, now time.Time) (*dns.Msg, error) {
if e.Status != stResolved {
return nil, nil
}
// if the reply has expired or is invalid, force the caller to start a new resolution
if e.hasExpired(now) {
return nil, nil
}
if e.Flags&CacheNoLocalReplies != 0 {
return nil, errNoLocalReplies
}
if e.ReplyLen >= maxLen {
Debug.Printf("[cache msgid %d] returning truncated reponse: %d > %d", request.MsgHdr.Id, e.ReplyLen, maxLen)
return makeTruncatedReply(request), nil
}
// create a copy of the reply, with values for this particular query
reply := e.reply.Copy()
reply.SetReply(request)
// adjust the TTLs
passedSecs := uint32(now.Sub(e.putTime).Seconds())
for _, rr := range reply.Answer {
hdr := rr.Header()
ttl := hdr.Ttl
if passedSecs < ttl {
hdr.Ttl = ttl - passedSecs
} else {
return nil, nil // it is expired: do not spend more time and return nil...
}
}
reply.Rcode = e.reply.Rcode
reply.Authoritative = true
// shuffle the values, etc...
reply.Answer = shuffleAnswers(reply.Answer)
return reply, nil
}
func (e cacheEntry) hasExpired(now time.Time) bool {
return e.validUntil.Before(now) || e.validUntil == now
}
// set the reply for the entry
// returns True if the entry has changed the validUntil time
func (e *cacheEntry) setReply(reply *dns.Msg, ttl int, flags uint8, now time.Time) bool {
var prevValidUntil time.Time
if e.Status == stResolved {
if reply != nil {
Debug.Printf("[cache msgid %d] replacing response in cache", reply.MsgHdr.Id)
}
prevValidUntil = e.validUntil
}
// 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))
} else if reply != nil {
// calculate the validUntil from the reply TTL
var minTTL uint32 = math.MaxUint32
for _, rr := range reply.Answer {
ttl := rr.Header().Ttl
if ttl < minTTL {
minTTL = ttl // TODO: improve the minTTL calculation (maybe we should skip some RRs)
}
}
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()
}
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.
type entriesPtrsHeap []*cacheEntry
func (h entriesPtrsHeap) Len() int { return len(h) }
func (h entriesPtrsHeap) Less(i, j int) bool { return h[i].validUntil.Before(h[j].validUntil) }
func (h entriesPtrsHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
h[i].index = i
h[j].index = j
}
func (h *entriesPtrsHeap) Push(x interface{}) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
n := len(*h)
entry := x.(*cacheEntry)
entry.index = n
*h = append(*h, entry)
}
func (h *entriesPtrsHeap) Pop() interface{} {
old := *h
n := len(old)
item := old[n-1]
item.index = -1 // for safety
*h = old[0 : n-1]
return item
}
//////////////////////////////////////////////////////////////////////////////////////
// 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
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, clk clock.Clock) (*Cache, error) {
if capacity <= 0 {
return nil, errInvalidCapacity
}
c := &Cache{
capacity: capacity,
entries: make(entries, capacity),
clock: clk,
}
if c.clock == nil {
c.clock = clock.New()
}
heap.Init(&c.entriesH)
return c, nil
}
// Clear removes all the entries in the cache
func (c *Cache) Clear() {
c.lock.Lock()
defer c.lock.Unlock()
c.entries = make(entries, c.capacity)
heap.Init(&c.entriesH)
}
// Purge removes the old elements in the cache
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)
delete(c.entries, cacheKey(entry.question))
} else {
return // all remaining entries must be still valid...
}
}
}
// Add adds a reply to the cache.
// 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]
if found {
updated := ent.setReply(reply, ttl, flags, now)
if updated {
heap.Fix(&c.entriesH, ent.index)
}
} else {
// If we will add a new item and the capacity has been exceeded, make some room...
if len(c.entriesH) >= c.capacity {
lowestEntry := heap.Pop(&c.entriesH).(*cacheEntry)
delete(c.entries, cacheKey(lowestEntry.question))
}
ent = newCacheEntry(&question, now)
ent.setReply(reply, ttl, flags, now)
heap.Push(&c.entriesH, ent)
c.entries[key] = ent
}
return ent.ReplyLen
}
// 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.
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 {
reply, err = ent.getReply(request, maxLen, now)
if ent.hasExpired(now) {
Debug.Printf("[cache msgid %d] expired: removing", request.MsgHdr.Id)
if ent.index > 0 {
heap.Remove(&c.entriesH, ent.index)
}
delete(c.entries, key)
reply = nil
}
} else {
// we are the first asking for this name: create an entry with no reply... the caller must wait
Debug.Printf("[cache msgid %d] addind in pending state", request.MsgHdr.Id)
c.entries[key] = newCacheEntry(&question, now)
}
return
}
// Remove removes the provided question from the cache.
func (c *Cache) Remove(question *dns.Question) {
c.lock.Lock()
defer c.lock.Unlock()
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)
}
delete(c.entries, key)
}
}
// Len returns the number of entries in the cache.
func (c *Cache) Len() int {
c.lock.RLock()
defer c.lock.RUnlock()
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()
}
+228
View File
@@ -0,0 +1,228 @@
package nameserver
import (
"fmt"
"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(testing.Verbose())
Info.Println("TestCacheLength starting")
const cacheLen = 128
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(name, dns.TypeA)
questionMsg.RecursionDesired = true
question := &questionMsg.Question[0]
ip := net.ParseIP(fmt.Sprintf("10.0.1.%d", i))
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)
}
wt.AssertEqualInt(t, l.Len(), cacheLen, "cache length")
minExpectedTime := insTime.Add(time.Duration(cacheLen) * time.Second)
t.Logf("Checking all remaining entries expire after insert_time + %d secs='%s'", cacheLen, minExpectedTime)
for _, entry := range l.entries {
if entry.validUntil.Before(minExpectedTime) {
t.Fatalf("Entry valid until %s", entry.validUntil)
}
}
}
// Check that the cache entries are ok
func TestCacheEntries(t *testing.T) {
InitDefaultLogging(testing.Verbose())
Info.Println("TestCacheEntries starting")
Info.Println("Checking cache consistency")
const cacheLen = 128
clk := clock.NewMock()
l, err := NewCache(cacheLen, clk)
wt.AssertNoErr(t, err)
questionMsg := new(dns.Msg)
questionMsg.SetQuestion("some.name", dns.TypeA)
questionMsg.RecursionDesired = true
question := &questionMsg.Question[0]
t.Logf("Trying to get a name")
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)
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("Inserting the reply")
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)
t.Logf("Checking we can Get() the reply now")
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
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)
wt.AssertNoErr(t, err)
wt.AssertTrue(t, resp != nil, "reponse from a second Get()")
t.Logf("Received '%s'", resp.Answer[0])
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
ttlGet2 := resp.Answer[0].Header().Ttl
wt.AssertEqualInt(t, int(ttlGet1-ttlGet2), 1, "TTL difference")
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)
wt.AssertNoErr(t, err)
if resp != nil {
t.Logf("Got\n%s", resp)
t.Fatalf("ERROR: Did NOT expect a reponse from the second Get()")
}
t.Logf("Checking that an Remove() results in Get() returning nothing")
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)
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")
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)
wt.AssertNoErr(t, err)
wt.AssertTrue(t, resp != nil, "reponse from the Get()")
t.Logf("Received '%s'", resp.Answer[0])
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
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")
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])
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
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), 1, "TTL")
t.Logf("Checking we get empty replies when they are expired...")
lenBefore = l.Len()
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])
t.Fatalf("ERROR: Did NOT expect a reponse from the Get()")
}
wt.AssertEqualInt(t, l.Len(), lenBefore-1, "cache length (after getting an expired entry)")
questionMsg2 := new(dns.Msg)
questionMsg2.SetQuestion("some.other.name", dns.TypeA)
questionMsg2.RecursionDesired = true
question2 := &questionMsg2.Question[0]
t.Logf("Trying to Get() a name")
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")
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)
resp, err = l.Get(questionMsg2, minUDPSize)
wt.AssertNoErr(t, err)
wt.AssertNotNil(t, resp, "reponse from Get()")
questionMsg3 := new(dns.Msg)
questionMsg3.SetQuestion("some.other.name", dns.TypeA)
questionMsg3.RecursionDesired = true
question3 := &questionMsg3.Question[0]
t.Logf("Checking that a entry with CacheNoLocalReplies return an error")
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")
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)
l.Remove(question3)
resp, err = l.Get(questionMsg3, minUDPSize)
wt.AssertNil(t, resp, "Get() reponse with CacheNoLocalReplies")
wt.AssertNil(t, err, "Get() error with CacheNoLocalReplies")
}
+41 -10
View File
@@ -2,12 +2,13 @@ package nameserver
import (
"github.com/miekg/dns"
"net"
)
const (
localTTL uint32 = 300 // somewhat arbitrary; we don't expect anyone
// downstream to cache results
localTTL uint32 = 30 // somewhat arbitrary; we don't expect anyone downstream to cache results
negLocalTTL = 30 // TTL for negative local resolutions
minUDPSize = 512
maxUDPSize = 65535
)
func makeHeader(r *dns.Msg, q *dns.Question) *dns.RR_Header {
@@ -24,20 +25,31 @@ func makeReply(r *dns.Msg, as []dns.RR) *dns.Msg {
return m
}
func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []net.IP) *dns.Msg {
func makeTruncatedReply(r *dns.Msg) *dns.Msg {
// for truncated response, we create a minimal reply with the Truncated bit set
reply := new(dns.Msg)
reply.SetReply(r)
reply.Truncated = true
return reply
}
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{*header, 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{*header, addr}
if ip4 == nil {
answers[count] = &dns.AAAA{Hdr: *header, AAAA: ip}
count++
}
}
@@ -45,11 +57,30 @@ 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{*header, name}
answers[i] = &dns.PTR{Hdr: *header, Ptr: name.Name()}
}
return makeReply(r, answers)
}
func makeDNSFailResponse(r *dns.Msg) *dns.Msg {
m := new(dns.Msg)
m.SetReply(r)
m.RecursionAvailable = true
m.Rcode = dns.RcodeNameError
return m
}
// get the maximum UDP-reply length
func getMaxReplyLen(r *dns.Msg, proto dnsProtocol) int {
maxLen := minUDPSize
if proto == protTCP {
maxLen = maxUDPSize
} else if opt := r.IsEdns0(); opt != nil {
maxLen = int(opt.UDPSize())
}
return maxLen
}
+59 -72
View File
@@ -1,108 +1,95 @@
package nameserver
import (
"errors"
"fmt"
"github.com/gorilla/mux"
"github.com/miekg/dns"
. "github.com/zettio/weave/common"
"io"
. "github.com/weaveworks/weave/common"
"log"
"net"
"net/http"
"strings"
)
// Parse a URL of the form PUT /name/<identifier>/<ip-address>
func parseUrl(url string) (identifier string, ipaddr string, err error) {
parts := strings.Split(url, "/")
if len(parts) != 4 {
return "", "", errors.New("Unable to parse url: " + url)
}
return parts[2], parts[3], nil
}
func httpErrorAndLog(level *log.Logger, w http.ResponseWriter, msg string,
status int, logmsg string, logargs ...interface{}) {
http.Error(w, msg, status)
level.Printf(logmsg, logargs...)
level.Printf("[http] "+logmsg, logargs...)
}
func ListenHttp(domain string, db Zone, port int) {
func ListenHTTP(version string, server *DNSServer, domain string, db Zone, port int) {
http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
} else {
io.WriteString(w, "ok")
}
muxRouter := mux.NewRouter()
muxRouter.Methods("GET").Path("/status").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "weave DNS", version)
fmt.Fprintln(w, server.Status())
})
http.HandleFunc("/name/", func(w http.ResponseWriter, r *http.Request) {
muxRouter.Methods("PUT").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqError := func(msg string, logmsg string, logargs ...interface{}) {
httpErrorAndLog(Warning, w, msg, http.StatusBadRequest,
logmsg, logargs...)
httpErrorAndLog(Warning, w, msg, http.StatusBadRequest, logmsg, logargs...)
}
switch r.Method {
case "PUT":
ident, ipStr, err := parseUrl(r.URL.Path)
name := r.FormValue("fqdn")
if ident == "" || ipStr == "" || name == "" {
reqError("Invalid request", "Invalid request: %s, %s", r.URL, r.Form)
return
}
vars := mux.Vars(r)
idStr := vars["id"]
ipStr := vars["ip"]
name := r.FormValue("fqdn")
ip := net.ParseIP(ipStr)
if ip == nil {
reqError("Invalid IP", "Invalid IP in request: %s", ipStr)
return
}
if name == "" {
reqError("Invalid FQDN", "Invalid FQDN in request: %s, %s", r.URL, r.Form)
return
}
if dns.IsSubDomain(domain, name) {
Info.Printf("Adding %s -> %s", name, ipStr)
if err = db.AddRecord(ident, name, ip); err != nil {
if _, ok := err.(DuplicateError); !ok {
httpErrorAndLog(
Error, w, "Internal error", http.StatusInternalServerError,
"Unexpected error from DB: %s", err)
return
} // oh, I already know this. whatever.
}
} else {
Info.Printf("Ignoring name %s, not in %s", name, domain)
}
ip := net.ParseIP(ipStr)
if ip == nil {
reqError("Invalid IP", "Invalid IP in request: %s", ipStr)
return
}
case "DELETE":
ident, ipStr, err := parseUrl(r.URL.Path)
if ident == "" || ipStr == "" {
reqError("Invalid Request", "Invalid request: %s, %s", r.URL, r.Form)
return
}
ip := net.ParseIP(ipStr)
if ip == nil {
reqError("Invalid IP in request", "Invalid IP in request: %s", ipStr)
return
}
Info.Printf("Deleting %s (%s)", ident, ipStr)
if err = db.DeleteRecord(ident, ip); err != nil {
if _, ok := err.(LookupError); !ok {
if dns.IsSubDomain(domain, name) {
Info.Printf("[http] Adding %s -> %s", name, ipStr)
if err := db.AddRecord(idStr, name, ip); err != nil {
if _, ok := err.(DuplicateError); !ok {
httpErrorAndLog(
Error, w, "Internal error", http.StatusInternalServerError,
"Unexpected error from DB: %s", err)
return
}
} // oh, I already know this. whatever.
}
default:
msg := "Unexpected http method: " + r.Method
reqError(msg, msg)
return
} else {
Info.Printf("[http] Ignoring name %s, not in %s", name, domain)
}
})
muxRouter.Methods("DELETE").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqError := func(msg string, logmsg string, logargs ...interface{}) {
httpErrorAndLog(Warning, w, msg, http.StatusBadRequest, logmsg, logargs...)
}
vars := mux.Vars(r)
idStr := vars["id"]
ipStr := vars["ip"]
ip := net.ParseIP(ipStr)
if ip == nil {
reqError("Invalid IP in request", "Invalid IP in request: %s", ipStr)
return
}
Info.Printf("[http] Deleting %s (%s)", idStr, ipStr)
if err := db.DeleteRecord(idStr, ip); err != nil {
if _, ok := err.(LookupError); !ok {
httpErrorAndLog(
Error, w, "Internal error", http.StatusInternalServerError,
"Unexpected error from DB: %s", err)
return
}
}
})
http.Handle("/", muxRouter)
address := fmt.Sprintf(":%d", port)
if err := http.ListenAndServe(address, nil); err != nil {
Error.Fatal("Unable to create http listener: ", err)
Error.Fatal("[http] Unable to create http listener: ", err)
}
}
+20 -17
View File
@@ -2,7 +2,7 @@ package nameserver
import (
"fmt"
wt "github.com/zettio/weave/common"
wt "github.com/weaveworks/weave/testing"
"math/rand"
"net"
"net/http"
@@ -23,63 +23,66 @@ func TestHttp(t *testing.T) {
containerID = "deadbeef"
testDomain = "weave.local."
successTestName = "test1." + testDomain
testAddr1 = "10.0.2.1/24"
testAddr1 = "10.2.2.1/24"
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(testDomain, zone, port)
go ListenHTTP("", nil, testDomain, zone, port)
time.Sleep(100 * time.Millisecond) // Allow for http server to get going
// Ask the http server to add our test address into the database
addrParts := strings.Split(testAddr1, "/")
addrUrl := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, containerID, addrParts[0])
resp, err := genForm("PUT", addrUrl,
addrURL := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, containerID, addrParts[0])
resp, err := genForm("PUT", addrURL,
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Check that the address is now there.
foundIP, err := zone.LookupLocal(successTestName)
wt.AssertNoErr(t, err)
ip, _, _ := net.ParseCIDR(testAddr1)
if !foundIP.Equal(ip) {
t.Fatal("Unexpected result for", successTestName, foundIP)
foundIP, err := zone.LookupName(successTestName)
wt.AssertNoErr(t, err)
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
resp, err = genForm("PUT", addrUrl,
resp, err = genForm("PUT", addrURL,
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http success response for duplicate add")
// Now try adding the same address again with a different ident --
// again should be fine
otherUrl := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, "other", addrParts[0])
resp, err = genForm("PUT", otherUrl,
otherURL := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, "other", addrParts[0])
resp, err = genForm("PUT", otherURL,
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Delete the address
resp, err = genForm("DELETE", addrUrl, nil)
resp, err = genForm("DELETE", addrURL, nil)
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Check that the address is still resolvable.
_, err = zone.LookupLocal(successTestName)
x, err := zone.LookupName(successTestName)
t.Logf("Got %s", x)
wt.AssertNoErr(t, err)
// Delete the address record mentioning the other container
resp, err = genForm("DELETE", otherUrl, nil)
resp, err = genForm("DELETE", otherURL, nil)
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Check that the address is gone
_, err = zone.LookupLocal(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
+158 -115
View File
@@ -1,6 +1,8 @@
package nameserver
import (
"bytes"
"fmt"
"github.com/miekg/dns"
"math"
"net"
@@ -26,15 +28,54 @@ var (
}
)
type ResponseA struct {
Name string
Addr net.IP
Err error
type Response struct {
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<- *ResponseA
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.
@@ -50,31 +91,36 @@ type inflightQuery struct {
type MDNSClient struct {
// note unorthodox use of 'Server' class in client logic - using it to
// listen on a multicast socket and call callback whenever a message comes in.
listener *dns.Server
conn *net.UDPConn
addr *net.UDPAddr
inflight map[string]*inflightQuery
queryChan chan<- *MDNSInteraction
listener *dns.Server
conn *net.UDPConn
addr *net.UDPAddr
inflight map[string]*inflightQuery
actionChan chan<- MDNSAction
running bool
}
type mDNSQueryInfo struct {
name string
querytype uint16
responseCh chan<- *ResponseA
responseCh chan<- *Response
}
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
@@ -90,10 +136,16 @@ func (c *MDNSClient) Start(ifi *net.Interface) error {
c.listener = &dns.Server{Unsafe: true, PacketConn: multicast, Handler: dns.HandlerFunc(handleMDNS)}
go c.listener.ActivateAndServe()
queryChan := make(chan *MDNSInteraction, MailboxSize)
c.queryChan = queryChan
go c.queryLoop(queryChan)
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
}
@@ -103,34 +155,86 @@ func LinkLocalMulticastListener(ifi *net.Interface) (net.PacketConn, error) {
// ACTOR client API
const (
CSendQuery = iota
CShutdown = iota
CMessageReceived = iota
)
type MDNSInteraction struct {
code int
resultChan chan<- interface{}
payload interface{}
}
type MDNSAction func()
// Async
func (c *MDNSClient) Shutdown() {
c.queryChan <- &MDNSInteraction{code: CShutdown}
}
// Async
func (c *MDNSClient) SendQuery(name string, querytype uint16, responseCh chan<- *ResponseA) {
c.queryChan <- &MDNSInteraction{
code: CSendQuery,
payload: mDNSQueryInfo{name, querytype, responseCh},
func (c *MDNSClient) SendQuery(name string, querytype uint16, insistent bool, responseCh chan<- *Response) {
c.actionChan <- func() {
query, found := c.inflight[name]
if !found {
m := new(dns.Msg)
m.SetQuestion(name, querytype)
m.RecursionDesired = false
buf, err := m.Pack()
if err != nil {
responseCh <- &Response{err: err}
close(responseCh)
return
}
query = &inflightQuery{
name: name,
id: m.Id,
}
if _, err = c.conn.WriteTo(buf, c.addr); err != nil {
responseCh <- &Response{err: err}
close(responseCh)
return
}
c.inflight[name] = query
}
info := &responseInfo{
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
// must be after all existing timeouts.
query.responseInfos = append(query.responseInfos, info)
}
}
// Async - called from dns library multiplexer
func (c *MDNSClient) ResponseCallback(r *dns.Msg) {
c.queryChan <- &MDNSInteraction{code: CMessageReceived, payload: r}
c.actionChan <- func() {
for _, answer := range r.Answer {
var name string
var res *Response
switch rr := answer.(type) {
case *dns.A:
name = rr.Hdr.Name
res = &Response{name: name, addr: rr.A, ttl: int(rr.Hdr.Ttl)}
case *dns.PTR:
name = rr.Hdr.Name
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
// 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
}
} else {
// We've received a response that didn't match a query
// Do we want to cache it?
}
}
}
}
// ACTOR server
@@ -164,28 +268,18 @@ func (c *MDNSClient) checkInFlightQueries() time.Duration {
return after
}
func (c *MDNSClient) queryLoop(queryChan <-chan *MDNSInteraction) {
func (c *MDNSClient) actorLoop(actionChan <-chan MDNSAction) {
timer := time.NewTimer(MaxDuration)
run := func() {
timer.Reset(c.checkInFlightQueries())
}
terminate := false
for !terminate {
run := func() { timer.Reset(c.checkInFlightQueries()) }
c.running = true
for c.running {
select {
case query, ok := <-queryChan:
if !ok {
break
}
switch query.code {
case CShutdown:
case action := <-actionChan:
if action == nil {
c.listener.Shutdown()
terminate = true
case CSendQuery:
c.handleSendQuery(query.payload.(mDNSQueryInfo))
run()
case CMessageReceived:
c.handleResponse(query.payload.(*dns.Msg))
c.running = false
} else {
action()
run()
}
case <-timer.C:
@@ -200,54 +294,3 @@ func (c *MDNSClient) queryLoop(queryChan <-chan *MDNSInteraction) {
}
}
}
func (c *MDNSClient) handleSendQuery(q mDNSQueryInfo) {
query, found := c.inflight[q.name]
if !found {
m := new(dns.Msg)
m.SetQuestion(q.name, q.querytype)
m.RecursionDesired = false
buf, err := m.Pack()
if err != nil {
q.responseCh <- &ResponseA{Err: err}
close(q.responseCh)
return
}
query = &inflightQuery{
name: q.name,
id: m.Id,
}
if _, err = c.conn.WriteTo(buf, c.addr); err != nil {
q.responseCh <- &ResponseA{Err: err}
close(q.responseCh)
return
}
c.inflight[q.name] = query
}
info := &responseInfo{
ch: q.responseCh,
timeout: time.Now().Add(mDNSTimeout),
}
// Invariant on responseInfos: they are in ascending order of timeout.
// Since we use a fixed interval from Now(), this must be after all existing timeouts.
query.responseInfos = append(query.responseInfos, info)
}
func (c *MDNSClient) handleResponse(r *dns.Msg) {
for _, answer := range r.Answer {
switch rr := answer.(type) {
case *dns.A:
name := rr.Hdr.Name
if query, found := c.inflight[name]; found {
for _, resp := range query.responseInfos {
resp.ch <- &ResponseA{Name: rr.Hdr.Name, Addr: rr.A}
close(resp.ch)
}
delete(c.inflight, name)
} else {
// We've received a response that didn't match a query
// Do we want to cache it?
}
}
}
}
+84 -41
View File
@@ -2,7 +2,8 @@ package nameserver
import (
"github.com/miekg/dns"
wt "github.com/zettio/weave/common"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"log"
"net"
"testing"
@@ -13,9 +14,27 @@ var (
successTestName = "test1.weave."
failTestName = "test2.weave."
testAddr = net.ParseIP("9.8.7.6")
testInAddr = "6.7.8.9.in-addr.arpa."
)
func minimalServer(w dns.ResponseWriter, req *dns.Msg) {
sendAnswer := func(m *dns.Msg) {
buf, err := m.Pack()
CheckFatal(err)
if buf == nil {
log.Fatal("Nil buffer")
}
//log.Println("minimalServer sending:", buf)
// 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.DialUDP("udp", nil, ipv4Addr)
CheckFatal(err)
_, err = sendconn.Write(buf)
sendconn.Close()
CheckFatal(err)
}
//log.Println("minimalServer received:", req)
if len(req.Answer) > 0 {
return // Only interested in questions.
@@ -23,74 +42,77 @@ func minimalServer(w dns.ResponseWriter, req *dns.Msg) {
if len(req.Question) != 1 {
return // We only handle single-question messages
}
if req.Question[0].Name != successTestName {
return // This is not the DNS record you are looking for
if req.Question[0].Qtype == dns.TypeA && req.Question[0].Name == successTestName {
m := new(dns.Msg)
m.SetReply(req)
hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeA,
Class: dns.ClassINET, Ttl: 3600}
a := &dns.A{Hdr: hdr, A: testAddr}
m.Answer = append(m.Answer, a)
sendAnswer(m)
} else if req.Question[0].Qtype == dns.TypePTR && req.Question[0].Name == testInAddr {
m := new(dns.Msg)
m.SetReply(req)
hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR,
Class: dns.ClassINET, Ttl: 3600}
a := &dns.PTR{Hdr: hdr, Ptr: successTestName}
m.Answer = append(m.Answer, a)
sendAnswer(m)
}
m := new(dns.Msg)
m.SetReply(req)
hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeA,
Class: dns.ClassINET, Ttl: 3600}
a := &dns.A{hdr, testAddr}
m.Answer = append(m.Answer, a)
buf, err := m.Pack()
checkFatal(err)
if buf == nil {
log.Fatal("Nil buffer")
}
//log.Println("minimalServer sending:", buf)
// 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.DialUDP("udp", nil, ipv4Addr)
checkFatal(err)
_, err = sendconn.Write(buf)
sendconn.Close()
checkFatal(err)
}
func RunLocalMulticastServer() (*dns.Server, error) {
multicast, err := net.ListenMulticastUDP("udp", nil, ipv4Addr)
multicast, err := LinkLocalMulticastListener(nil)
if err != nil {
return nil, err
}
server := &dns.Server{Listener: nil, PacketConn: multicast, Handler: dns.HandlerFunc(minimalServer)}
server := &dns.Server{
PacketConn: multicast,
Handler: dns.HandlerFunc(minimalServer),
ReadTimeout: 100 * time.Millisecond}
go server.ActivateAndServe()
return server, nil
}
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)
}
mdnsClient, err := NewMDNSClient()
wt.AssertNoErr(t, err)
err = mdnsClient.Start(nil)
wt.AssertNoErr(t, err)
server, err := RunLocalMulticastServer()
if err != nil {
t.Fatalf("Unable to run test server: %s", err)
}
return mdnsClient, server, err
}
func (c *MDNSClient) Shutdown() {
c.actionChan <- nil
}
type testContext struct {
receivedAddr net.IP
receivedCount int
channel chan *ResponseA
channel chan *Response
}
func newTestContext() *testContext {
return &testContext{channel: make(chan *ResponseA, 4)}
return &testContext{channel: make(chan *Response, 4)}
}
func (c *testContext) checkResponse(t *testing.T, channelOk bool, resp *ResponseA) {
func (c *testContext) checkResponse(t *testing.T, channelOk bool, resp *Response) {
if !channelOk {
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++
}
@@ -103,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)
}
@@ -114,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)
}
@@ -133,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 {
@@ -152,3 +174,24 @@ outerloop:
t.Fatal("Unexpected result for", successTestName, context1.receivedAddr, context2.receivedAddr, context1.receivedCount, context2.receivedCount)
}
}
func TestAsLookup(t *testing.T) {
mdnsClient, server, _ := setup(t)
defer mdnsClient.Shutdown()
defer server.Shutdown()
ips, err := mdnsClient.LookupName(successTestName)
wt.AssertNoErr(t, err)
if !testAddr.Equal(ips[0].IP()) {
t.Fatalf("Returned address incorrect %s", ips)
}
ips, err = mdnsClient.LookupName("foo.example.com.")
wt.AssertErrorType(t, err, (*LookupError)(nil), "unknown hostname")
names, err := mdnsClient.LookupInaddr(testInAddr)
wt.AssertNoErr(t, err)
if !(successTestName == names[0].Name()) {
t.Fatalf("Expected name %s, got %s", successTestName, names)
}
}
+71
View File
@@ -0,0 +1,71 @@
package nameserver
import (
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
)
func mdnsLookup(client *MDNSClient, name string, qtype uint16, insistent bool) ([]ZoneRecord, error) {
var responses []*Response
channel := make(chan *Response)
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)
return nil, err
}
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)
}
}
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
}
// 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)
}
// 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)
}
+84 -21
View File
@@ -2,23 +2,22 @@ package nameserver
import (
"github.com/miekg/dns"
. "github.com/zettio/weave/common"
. "github.com/weaveworks/weave/common"
"net"
)
type MDNSServer struct {
localAddrs []net.Addr
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
@@ -35,7 +34,19 @@ func (s *MDNSServer) addrIsLocal(testaddr net.Addr) bool {
return false
}
func (s *MDNSServer) Start(ifi *net.Interface) 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
@@ -50,24 +61,76 @@ func (s *MDNSServer) Start(ifi *net.Interface) error {
return err
}
handleMDNS := func(w dns.ResponseWriter, r *dns.Msg) {
// Ignore answers to other questions
handleLocal := s.makeHandler(dns.TypeA,
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 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(s.zone.Domain(), handleLocal)
mux.HandleFunc(RDNSDomain, handleReverse)
s.srv = &dns.Server{
Listener: nil,
PacketConn: conn,
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()
}
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(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
// complication, and easier testing, this is elided on the
// 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 ip, err := s.zone.LookupLocal(q.Name); err == nil {
m := makeAddressReply(r, &q, []net.IP{ip})
if err = s.sendResponse(m); err != nil {
Warning.Printf("Error writing to %s", w)
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] 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] 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] srv: no local answer for answering mDNS query '%s'",
r.MsgHdr.Id, q.Name)
}
} else if s.addrIsLocal(w.RemoteAddr()) {
// ignore this - it's our own query received via multicast
} else {
Debug.Printf("Failed MDNS lookup for %s", q.Name)
}
}
}
go dns.ActivateAndServe(nil, conn, dns.HandlerFunc(handleMDNS))
return err
}
func (s *MDNSServer) sendResponse(m *dns.Msg) error {
+75 -43
View File
@@ -2,46 +2,33 @@ package nameserver
import (
"github.com/miekg/dns"
wt "github.com/zettio/weave/common"
"log"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
"time"
)
var (
containerID = "deadbeef"
testAddr1 = "10.0.2.1/24"
)
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) {
log.Println("TestServerSimpleQuery starting")
var zone = new(ZoneDb)
ip, _, _ := net.ParseCIDR(testAddr1)
zone.AddRecord(containerID, "test.weave.", ip)
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."
)
mdnsServer, err := NewMDNSServer(zone)
InitDefaultLogging(testing.Verbose())
Info.Println("TestServerSimpleQuery starting")
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 receivedAddr net.IP
var receivedAddrs []net.IP
receivedName := ""
recvChan := make(chan interface{})
receivedCount := 0
// Implement a minimal listener for responses
@@ -51,38 +38,83 @@ func TestServerSimpleQuery(t *testing.T) {
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++
}
}
recvChan <- "ok"
}
}
listener := &dns.Server{Unsafe: true, PacketConn: multicast, Handler: dns.HandlerFunc(handleMDNS)}
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,
Handler: dns.HandlerFunc(handleMDNS),
ReadTimeout: 100 * time.Millisecond}
go listener.ActivateAndServe()
defer listener.Shutdown()
time.Sleep(100 * time.Millisecond) // Allow for server to get going
sendQuery("test.weave.", dns.TypeA)
time.Sleep(time.Second)
if receivedCount != 1 {
t.Fatal("Unexpected result count for test.weave", receivedCount)
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.Fatal("Unexpected result for test.weave", receivedAddr)
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())
}
receivedCount = 0
Debug.Printf("Checking that 'testfail.weave.' [A] gets no answers")
sendQuery("testfail.weave.", dns.TypeA)
if receivedCount != 0 {
t.Fatal("Unexpected result count for testfail.weave", receivedCount)
t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
}
Debug.Printf("Checking that '%s' [PTR] gets one name", testInAddr1)
sendQuery(testInAddr1, dns.TypePTR)
if receivedCount != 1 {
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
} 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
}
+305 -119
View File
@@ -1,153 +1,339 @@
package nameserver
import (
"bytes"
"fmt"
"github.com/miekg/dns"
. "github.com/zettio/weave/common"
. "github.com/weaveworks/weave/common"
"net"
"sync"
)
const (
LOCAL_DOMAIN = "weave.local."
RDNS_DOMAIN = "in-addr.arpa."
DefaultLocalDomain = "weave.local." // The default name used for the local domain
DefaultServerPort = 53 // The default server port
DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load
DefaultUDPBuflen = 4096 // bigger than the default 512
DefaultCacheLen = 8192 // default cache capacity
DefaultResolvWorkers = 8 // default number of resolution workers
DefaultTimeout = 5 // default timeout for DNS resolutions
)
// +1 to also exclude a dot
var rdnsDomainLen = len(RDNS_DOMAIN) + 1
type DNSServerConfig struct {
// (Optional) client config file for resolving upstream servers
UpstreamCfgFile string
// (Optional) DNS client config for the fallback server(s)
UpstreamCfg *dns.ClientConfig
// (Optional) port number (for TCP and UDP)
Port int
// (Optional) local domain (ie, "weave.local.")
LocalDomain string
// (Optional) cache size
CacheLen int
// (Optional) timeout for DNS queries
Timeout int
// (Optional) UDP buffer length
UDPBufLen int
}
func checkFatal(e error) {
if e != nil {
Error.Fatal(e)
type dnsProtocol uint8
const (
protUDP dnsProtocol = iota // UDP protocol
protTCP dnsProtocol = iota // TCP protocol
)
func (proto dnsProtocol) String() string {
switch proto {
case protUDP:
return "UDP"
case protTCP:
return "TCP"
}
return "unknown"
}
func checkWarn(e error) {
if e != nil {
Warning.Println(e)
// get a new dns.Client for a protocol
func (proto dnsProtocol) GetNewClient(bufsize int) *dns.Client {
switch proto {
case protTCP:
return &dns.Client{Net: "tcp"}
case protUDP:
return &dns.Client{Net: "udp", UDPSize: uint16(bufsize)}
}
return nil
}
func makeDNSFailResponse(r *dns.Msg) *dns.Msg {
m := new(dns.Msg)
m.SetReply(r)
m.RecursionAvailable = true
m.Rcode = dns.RcodeNameError
return m
type DNSServer struct {
Zone Zone
Iface *net.Interface
Upstream *dns.ClientConfig
udpSrv *dns.Server
tcpSrv *dns.Server
mdnsCli *MDNSClient
mdnsSrv *MDNSServer
cache *Cache
timeout int
udpBuf int
listenersWg *sync.WaitGroup
Domain string // the local domain
ListenAddr string // the address the server is listening at
}
func queryHandler(zone Zone, mdnsClient *MDNSClient) dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
q := r.Question[0]
Debug.Printf("Local query: %+v", q)
if q.Qtype == dns.TypeA {
if ip, err := zone.LookupLocal(q.Name); err == nil {
m := makeAddressReply(r, &q, []net.IP{ip})
w.WriteMsg(m)
} else {
Debug.Printf("Failed lookup for %s; sending mDNS query", q.Name)
// We don't know the answer; see if someone else does
channel := make(chan *ResponseA)
replies := make([]net.IP, 0)
go func() {
for resp := range channel {
Debug.Printf("Got address response %s to query %s addr %s", resp.Name, q.Name, resp.Addr)
replies = append(replies, resp.Addr)
}
var responseMsg *dns.Msg
if len(replies) > 0 {
responseMsg = makeAddressReply(r, &q, replies)
} else {
responseMsg = makeDNSFailResponse(r)
}
w.WriteMsg(responseMsg)
}()
mdnsClient.SendQuery(q.Name, dns.TypeA, channel)
}
} else {
Warning.Printf("Local query not handled: %+v", q)
m := makeDNSFailResponse(r)
w.WriteMsg(m)
// Creates a new DNS server
func NewDNSServer(config DNSServerConfig, zone Zone, iface *net.Interface) (s *DNSServer, err error) {
s = &DNSServer{
Zone: zone,
Iface: iface,
listenersWg: new(sync.WaitGroup),
Domain: DefaultLocalDomain,
ListenAddr: fmt.Sprintf(":%d", DefaultServerPort),
}
// fill empty parameters with defaults...
if config.Port != 0 {
s.ListenAddr = fmt.Sprintf(":%d", config.Port)
}
if len(config.LocalDomain) > 0 {
s.Domain = config.LocalDomain
}
if config.UpstreamCfg != nil {
s.Upstream = config.UpstreamCfg
} else {
cfgFile := DefaultCLICfgFile
if len(config.UpstreamCfgFile) > 0 {
cfgFile = config.UpstreamCfgFile
}
if s.Upstream, err = dns.ClientConfigFromFile(cfgFile); err != nil {
return nil, err
}
}
if config.Timeout > 0 {
s.timeout = config.Timeout
}
if config.UDPBufLen > 0 {
s.udpBuf = config.UDPBufLen
}
s.mdnsCli, err = NewMDNSClient()
if err != nil {
return
}
s.mdnsSrv, err = NewMDNSServer(s.Zone)
if err != nil {
return
}
cacheLen := DefaultCacheLen
if config.CacheLen > 0 {
cacheLen = config.CacheLen
}
Debug.Printf("[dns] Initializing cache: %d entries", config.CacheLen)
s.cache, err = NewCache(cacheLen, nil)
if err != nil {
return
}
// create two DNS request multiplexerers, depending on the protocol used by clients
// (we use the same protocol for asking upstream servers)
mux := func(proto dnsProtocol) *dns.ServeMux {
m := dns.NewServeMux()
m.HandleFunc(s.Domain, s.queryHandler(proto))
m.HandleFunc(RDNSDomain, s.rdnsHandler(proto))
m.HandleFunc(".", s.notUsHandler(proto))
return m
}
s.udpSrv = &dns.Server{Addr: s.ListenAddr, Net: "udp", Handler: mux(protUDP)}
s.tcpSrv = &dns.Server{Addr: s.ListenAddr, Net: "tcp", Handler: mux(protTCP)}
return
}
func rdnsHandler(zone Zone, mdnsClient *MDNSClient) dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
q := r.Question[0]
Debug.Printf("Local rdns query: %+v", q)
if q.Qtype == dns.TypePTR {
if ip := net.ParseIP(q.Name[:len(q.Name)-rdnsDomainLen]); ip != nil {
ip4 := ip.To4()
revIP := []byte{ip4[3], ip4[2], ip4[1], ip4[0]}
Debug.Printf("Looking for address: %+v", revIP)
if name, err := zone.ReverseLookupLocal(revIP); err == nil {
Debug.Printf("Found name: %s", name)
m := makePTRReply(r, &q, []string{name})
w.WriteMsg(m)
} else {
Debug.Printf("Failed lookup for %s; sending mDNS query", q.Name)
// We don't know the answer; see if someone else does
// TODO
}
}
}
}
// Start the DNS server
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)
CheckFatal(err)
s.listenersWg.Add(2)
go func() {
defer s.listenersWg.Done()
Debug.Printf("Listening for DNS on %s (UDP)", s.ListenAddr)
err = s.udpSrv.ListenAndServe()
CheckFatal(err)
Debug.Printf("DNS UDP server exiting...")
}()
go func() {
defer s.listenersWg.Done()
Debug.Printf("Listening for DNS on %s (TCP)", s.ListenAddr)
err = s.tcpSrv.ListenAndServe()
CheckFatal(err)
Debug.Printf("DNS TCP server exiting...")
}()
// Waiting for all goroutines to finish (otherwise they die as main routine dies)
s.listenersWg.Wait()
Info.Printf("WeaveDNS server exiting...")
return nil
}
/* When we receive a request for a name outside of our '.weave' domain, call
the underlying lookup mechanism and return the answer(s) it gives.
Unfortunately, this means that TTLs from a real DNS server are lost - FIXME.
*/
func notUsHandler() dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
q := r.Question[0]
Debug.Printf("Non-local query: %+v", q)
var responseMsg *dns.Msg
if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA {
if addrs, err := net.LookupIP(q.Name); err == nil {
responseMsg = makeAddressReply(r, &q, addrs)
} else {
responseMsg = makeDNSFailResponse(r)
Debug.Print("Failed fallback lookup ", err)
}
} else {
Warning.Printf("Non-local query not handled: %+v", q)
responseMsg = makeDNSFailResponse(r)
}
w.WriteMsg(responseMsg)
}
// Return status string
func (s *DNSServer) Status() string {
var buf bytes.Buffer
fmt.Fprintln(&buf, "Local domain", s.Domain)
fmt.Fprintln(&buf, "Listen address", s.ListenAddr)
fmt.Fprintln(&buf, "mDNS interface", s.Iface)
fmt.Fprintln(&buf, "Fallback DNS config", s.Upstream)
fmt.Fprintf(&buf, "Zone database:\n%s", s.Zone)
return buf.String()
}
func StartServer(zone Zone, iface *net.Interface, dnsPort int, httpPort int, wait int) error {
go ListenHttp(LOCAL_DOMAIN, zone, httpPort)
mdnsClient, err := NewMDNSClient()
checkFatal(err)
ifaceName := "default interface"
if iface != nil {
ifaceName = iface.Name
// Perform a graceful shutdown
func (s *DNSServer) Stop() error {
// Stop the listeners/handlers
if err := s.tcpSrv.Shutdown(); err != nil {
return err
}
Info.Printf("Using mDNS on %s", ifaceName)
err = mdnsClient.Start(iface)
checkFatal(err)
if err := s.udpSrv.Shutdown(); err != nil {
return err
}
s.listenersWg.Wait()
LocalServeMux := dns.NewServeMux()
LocalServeMux.HandleFunc(LOCAL_DOMAIN, queryHandler(zone, mdnsClient))
LocalServeMux.HandleFunc(RDNS_DOMAIN, rdnsHandler(zone, mdnsClient))
LocalServeMux.HandleFunc(".", notUsHandler())
mdnsServer, err := NewMDNSServer(zone)
checkFatal(err)
err = mdnsServer.Start(iface)
checkFatal(err)
address := fmt.Sprintf(":%d", dnsPort)
Info.Printf("Listening for DNS on %s", address)
err = dns.ListenAndServe(address, "udp", LocalServeMux)
checkFatal(err)
// shutdown the mDNS server
s.mdnsSrv.Stop()
return nil
}
func (s *DNSServer) queryHandler(proto dnsProtocol) dns.HandlerFunc {
zoneLookup := func(lookup ZoneLookup, q *dns.Question, r *dns.Msg) (*dns.Msg, []ZoneRecord, error) {
ips, err := lookup.LookupName(q.Name)
if err != nil {
return nil, nil, err
}
return makeAddressReply(r, q, ips), ips, nil
}
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 {
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
}
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 = []ZoneLookup{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
}
// catch unsupported queries
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 m, answers, err := zoneLookup(lookup, &q, r); err == nil {
m.Authoritative = true
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
}
}
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)
fallback(w, r)
}
}
// When we receive a request for a name outside of our '.weave.local.'
// domain, ask the configured DNS server as a fallback.
func (s *DNSServer) notUsHandler(proto dnsProtocol) dns.HandlerFunc {
dnsClient := proto.GetNewClient(DefaultUDPBuflen)
return func(w dns.ResponseWriter, r *dns.Msg) {
maxLen := getMaxReplyLen(r, proto)
q := r.Question[0]
// create a request where we announce our max payload size
rcopy := r
rcopy.SetEdns0(uint16(maxLen), false)
Debug.Printf("[dns msgid %d] Fallback query: %+v [%s, max:%d bytes]", rcopy.MsgHdr.Id, q, proto, maxLen)
for _, server := range s.Upstream.Servers {
reply, _, err := dnsClient.Exchange(rcopy, fmt.Sprintf("%s:%s", server, s.Upstream.Port))
if err != nil {
Debug.Printf("[dns msgid %d] Network error trying %s (%s)",
r.MsgHdr.Id, server, err)
continue
}
if reply != nil && reply.Rcode != dns.RcodeSuccess {
Debug.Printf("[dns msgid %d] Failure reported by %s for query %s",
r.MsgHdr.Id, server, q.Name)
continue
}
Debug.Printf("[dns msgid %d] Given answer by %s for query %s",
r.MsgHdr.Id, server, q.Name)
w.WriteMsg(reply)
return
}
Warning.Printf("[dns msgid %d] Failed lookup for external name %s", r.MsgHdr.Id, q.Name)
w.WriteMsg(makeDNSFailResponse(r))
}
}
+301
View File
@@ -0,0 +1,301 @@
package nameserver
import (
"encoding/binary"
"fmt"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
"time"
)
const (
testRDNSsuccess = "1.2.2.10.in-addr.arpa."
testRDNSfail = "4.3.2.1.in-addr.arpa."
testRDNSnonlocal = "8.8.8.8.in-addr.arpa."
testUDPBufSize = 16384
)
var (
testPort = 17625
)
func setupForTest(t *testing.T) {
// fail early if we cannot find a default multicast interface
multicast, err := LinkLocalMulticastListener(nil)
if err != nil {
t.Fatalf("Unable to create multicast listener: %s. No default multicast interface?", err)
}
multicast.Close()
testPort++
}
func TestUDPDNSServer(t *testing.T) {
setupForTest(t)
const (
containerID = "foobar"
successTestName = "test1.weave.local."
failTestName = "test2.weave.local."
nonLocalName = "weave.works."
testAddr1 = "10.2.2.1"
)
testCIDR1 := testAddr1 + "/24"
InitDefaultLogging(true)
var zone = NewZoneDb(DefaultLocalDomain)
ip, _, _ := net.ParseCIDR(testCIDR1)
zone.AddRecord(containerID, successTestName, ip)
fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
if len(req.Question) == 1 {
q := req.Question[0]
if q.Name == nonLocalName && q.Qtype == dns.TypeMX {
m.Answer = make([]dns.RR, 1)
m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail." + nonLocalName}
} else if q.Name == nonLocalName && q.Qtype == dns.TypeANY {
m.Answer = make([]dns.RR, 512/len("mailn."+nonLocalName)+1)
for i := range m.Answer {
m.Answer[i] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: fmt.Sprintf("mail%d.%s", i, nonLocalName)}
}
} else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR {
m.Answer = make([]dns.RR, 1)
m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."}
} else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR {
m.Rcode = dns.RcodeNameError
}
}
w.WriteMsg(m)
}
// Run another DNS server for fallback
s, fallbackAddr, err := runLocalUDPServer(t, "127.0.0.1:0", fallbackHandler)
wt.AssertNoErr(t, err)
defer s.Shutdown()
_, fallbackPort, err := net.SplitHostPort(fallbackAddr)
wt.AssertNoErr(t, err)
config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort}
srv, err := NewDNSServer(DNSServerConfig{UpstreamCfg: config, Port: testPort}, zone, nil)
wt.AssertNoErr(t, err)
defer srv.Stop()
go srv.Start()
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
var r *dns.Msg
r = assertExchange(t, successTestName, dns.TypeA, 1, 1, 0)
wt.AssertType(t, r.Answer[0], (*dns.A)(nil), "DNS record")
wt.AssertEqualString(t, r.Answer[0].(*dns.A).A.String(), testAddr1, "IP address")
assertExchange(t, failTestName, dns.TypeA, 0, 0, dns.RcodeNameError)
r = assertExchange(t, testRDNSsuccess, dns.TypePTR, 1, 1, 0)
wt.AssertType(t, r.Answer[0], (*dns.PTR)(nil), "DNS record")
wt.AssertEqualString(t, r.Answer[0].(*dns.PTR).Ptr, successTestName, "IP address")
assertExchange(t, testRDNSfail, dns.TypePTR, 0, 0, dns.RcodeNameError)
// 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
assertExchange(t, nonLocalName, dns.TypeMX, 1, -1, 0)
// Now ask a query that we expect to return a lot of data.
assertExchange(t, nonLocalName, dns.TypeANY, 5, -1, 0)
assertExchange(t, testRDNSnonlocal, dns.TypePTR, 1, -1, 0)
// Not testing MDNS functionality of server here (yet), since it
// needs two servers, each listening on its own address
}
func TestTCPDNSServer(t *testing.T) {
setupForTest(t)
const (
numAnswers = 512
nonLocalName = "weave.works."
)
dnsAddr := fmt.Sprintf("localhost:%d", testPort)
InitDefaultLogging(true)
var zone = NewZoneDb(DefaultLocalDomain)
// generate a list of `numAnswers` IP addresses
var addrs []ZoneRecord
bs := make([]byte, 4)
for i := 0; i < numAnswers; i++ {
binary.LittleEndian.PutUint32(bs, uint32(i))
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
fallbackUDPHandler := func(w dns.ResponseWriter, req *dns.Msg) {
maxLen := getMaxReplyLen(req, protUDP)
t.Logf("Fallback UDP server got asked: returning %d answers", numAnswers)
q := req.Question[0]
m := makeAddressReply(req, &q, addrs)
mLen := m.Len()
m.SetEdns0(uint16(maxLen), false)
if mLen > maxLen {
t.Logf("... truncated response (%d > %d)", mLen, maxLen)
m.Truncated = true
}
w.WriteMsg(m)
}
fallbackTCPHandler := func(w dns.ResponseWriter, req *dns.Msg) {
t.Logf("Fallback TCP server got asked: returning %d answers", numAnswers)
q := req.Question[0]
m := makeAddressReply(req, &q, addrs)
w.WriteMsg(m)
}
t.Logf("Running a DNS fallback server with UDP")
us, fallbackUDPAddr, err := runLocalUDPServer(t, "127.0.0.1:0", fallbackUDPHandler)
wt.AssertNoErr(t, err)
defer us.Shutdown()
_, fallbackPort, err := net.SplitHostPort(fallbackUDPAddr)
wt.AssertNoErr(t, err)
t.Logf("Starting another fallback server, with TCP, on the same port as the UDP server")
fallbackTCPAddr := fmt.Sprintf("127.0.0.1:%s", fallbackPort)
ts, fallbackTCPAddr, err := runLocalTCPServer(t, fallbackTCPAddr, fallbackTCPHandler)
wt.AssertNoErr(t, err)
defer ts.Shutdown()
t.Logf("Creating a WeaveDNS server instance, falling back to 127.0.0.1:%s", fallbackPort)
config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort}
srv, err := NewDNSServer(DNSServerConfig{UpstreamCfg: config, Port: testPort}, zone, nil)
wt.AssertNoErr(t, err)
defer srv.Stop()
go srv.Start()
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
t.Logf("Creating a UDP and a TCP client")
uc := new(dns.Client)
uc.UDPSize = minUDPSize
tc := new(dns.Client)
tc.Net = "tcp"
t.Logf("Creating DNS query message")
m := new(dns.Msg)
m.RecursionDesired = true
m.SetQuestion(nonLocalName, dns.TypeA)
t.Logf("Checking the fallback server at %s returns a truncated response with UDP", fallbackUDPAddr)
r, _, err := uc.Exchange(m, fallbackUDPAddr)
t.Logf("Got response from fallback server (UDP) with %d answers", len(r.Answer))
t.Logf("Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
wt.AssertTrue(t, r.MsgHdr.Truncated, "DNS truncated reponse flag")
wt.AssertNotEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP)")
t.Logf("Checking the WeaveDNS server at %s returns a truncated reponse with UDP", dnsAddr)
r, _, err = uc.Exchange(m, dnsAddr)
t.Logf("UDP Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
wt.AssertNotNil(t, r, "response")
t.Logf("%d answers", len(r.Answer))
wt.AssertTrue(t, r.MsgHdr.Truncated, "DNS truncated reponse flag")
wt.AssertNotEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP)")
t.Logf("Checking the WeaveDNS server at %s does not return a truncated reponse with TCP", dnsAddr)
r, _, err = tc.Exchange(m, dnsAddr)
t.Logf("TCP Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
wt.AssertNotNil(t, r, "response")
t.Logf("%d answers", len(r.Answer))
wt.AssertFalse(t, r.MsgHdr.Truncated, "DNS truncated response flag")
wt.AssertEqualInt(t, len(r.Answer), numAnswers, "number of answers (TCP)")
t.Logf("Checking the WeaveDNS server at %s does not return a truncated reponse with UDP with a bigger buffer", dnsAddr)
m.SetEdns0(testUDPBufSize, false)
r, _, err = uc.Exchange(m, dnsAddr)
t.Logf("UDP-large Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
wt.AssertNotNil(t, r, "response")
t.Logf("%d answers", len(r.Answer))
wt.AssertNoErr(t, err)
wt.AssertFalse(t, r.MsgHdr.Truncated, "DNS truncated response flag")
wt.AssertEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP-long)")
}
//////////////////////////////////////////////////////////////////////////////
// perform a DNS query and assert the reply code, number or answers, etc
func assertExchange(t *testing.T, z string, ty uint16, minAnswers int, maxAnswers int, expErr int) *dns.Msg {
c := new(dns.Client)
c.UDPSize = testUDPBufSize
m := new(dns.Msg)
m.RecursionDesired = true
m.SetQuestion(z, ty)
m.SetEdns0(testUDPBufSize, false) // we don't want to play with truncation here...
r, _, err := c.Exchange(m, fmt.Sprintf("127.0.0.1:%d", testPort))
t.Logf("Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
if minAnswers == 0 && maxAnswers == 0 {
wt.AssertStatus(t, r.Rcode, expErr, "DNS response code")
} else {
wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
}
answers := len(r.Answer)
if minAnswers >= 0 && answers < minAnswers {
wt.Fatalf(t, "Number of answers >= %d", minAnswers)
}
if maxAnswers >= 0 && answers > maxAnswers {
wt.Fatalf(t, "Number of answers <= %d", maxAnswers)
}
return r
}
// run a UDP fallback server
func runLocalUDPServer(t *testing.T, laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
t.Logf("Starting fallback UDP server at %s", laddr)
pc, err := net.ListenPacket("udp", laddr)
if err != nil {
return nil, "", err
}
server := &dns.Server{PacketConn: pc, Handler: handler, ReadTimeout: 100 * time.Millisecond}
go func() {
server.ActivateAndServe()
pc.Close()
}()
t.Logf("Fallback UDP server listening at %s", pc.LocalAddr())
return server, pc.LocalAddr().String(), nil
}
// run a TCP fallback server
func runLocalTCPServer(t *testing.T, laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
t.Logf("Starting fallback TCP server at %s", laddr)
laddrTCP, err := net.ResolveTCPAddr("tcp", laddr)
if err != nil {
return nil, "", err
}
l, err := net.ListenTCP("tcp", laddrTCP)
if err != nil {
return nil, "", err
}
server := &dns.Server{Listener: l, Handler: handler, ReadTimeout: 100 * time.Millisecond}
go func() {
server.ActivateAndServe()
l.Close()
}()
t.Logf("Fallback TCP server listening at %s", l.Addr().String())
return server, l.Addr().String(), nil
}
-43
View File
@@ -1,43 +0,0 @@
package nameserver
import (
"github.com/fsouza/go-dockerclient"
. "github.com/zettio/weave/common"
)
func checkError(err error, apiPath string) {
if err != nil {
Error.Fatalf("Unable to connect to Docker API on %s: %s", apiPath, err)
}
}
func StartUpdater(apiPath string, zone Zone) error {
client, err := docker.NewClient(apiPath)
checkError(err, apiPath)
env, err := client.Version()
checkError(err, apiPath)
events := make(chan *docker.APIEvents)
err = client.AddEventListener(events)
checkError(err, apiPath)
Info.Printf("Using Docker API on %s: %v", apiPath, env)
go func() {
for event := range events {
handleEvent(zone, event, client)
}
}()
return nil
}
func handleEvent(zone Zone, event *docker.APIEvents, client *docker.Client) error {
switch event.Status {
case "die":
id := event.ID
Info.Printf("Container %s down. Removing records", id)
zone.DeleteRecordsFor(id)
}
return nil
}
+65 -27
View File
@@ -1,20 +1,30 @@
package nameserver
import (
"bytes"
"fmt"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"net"
"sync"
)
const (
RDNSDomain = "in-addr.arpa."
)
// +1 to also exclude a dot
var rdnsDomainLen = len(RDNSDomain) + 1
type Zone interface {
AddRecord(ident string, name string, ip net.IP) error
DeleteRecord(ident string, ip net.IP) error
DeleteRecordsFor(ident string) error
LookupLocal(name string) (net.IP, error)
ReverseLookupLocal(ip net.IP) (string, error)
Domain() string
ZoneLookup
}
type Record struct {
type dbRecord struct {
Ident string
Name string
IP net.IP
@@ -23,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
@@ -40,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
@@ -49,54 +70,66 @@ func (zone *ZoneDb) indexOf(match func(Record) bool) int {
return -1
}
func (zone *ZoneDb) LookupLocal(name string) (net.IP, error) {
func (zone *ZoneDb) String() string {
zone.mx.RLock()
defer zone.mx.RUnlock()
var buf bytes.Buffer
for _, r := range zone.recs {
fmt.Fprintf(&buf, "%.12s %s %v\n", r.Ident, r.IP, r.Name)
}
return buf.String()
}
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) ReverseLookupLocal(ip net.IP) (string, error) {
zone.mx.RLock()
defer zone.mx.RUnlock()
for _, r := range zone.recs {
if r.IP.Equal(ip) {
return r.Name, nil
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]}
Debug.Printf("[zonedb] Looking for address: %+v", ip)
zone.mx.RLock()
defer zone.mx.RUnlock()
for _, r := range zone.recs {
if r.IP.Equal(ip) {
return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
}
}
return nil, LookupError(inaddr)
}
return "", LookupError(ip.String())
Warning.Printf("[zonedb] Asked to reverse lookup %s", 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)
if index := zone.indexOf(
func(r Record) bool {
return r.Name == fqdn && r.IP.Equal(ip) && r.Ident == ident
}); index != -1 {
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()
if index := zone.indexOf(
func(r Record) bool {
return r.Ident == ident && r.IP.Equal(ip)
}); index == -1 {
return LookupError(ident)
} else {
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
}
return nil
return LookupError(ident)
}
func (zone *ZoneDb) DeleteRecordsFor(ident string) error {
@@ -113,3 +146,8 @@ func (zone *ZoneDb) DeleteRecordsFor(ident string) error {
zone.recs = zone.recs[:w]
return nil
}
func (zone *ZoneDb) ContainerDied(ident string) error {
Info.Printf("[zonedb] Container %s down. Removing records", ident)
return zone.DeleteRecordsFor(ident)
}
+15 -15
View File
@@ -1,7 +1,7 @@
package nameserver
import (
wt "github.com/zettio/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
)
@@ -11,34 +11,34 @@ func TestZone(t *testing.T) {
containerID = "deadbeef"
otherContainerID = "cowjuice"
successTestName = "test1.weave."
testAddr1 = "10.0.2.1/24"
testAddr1 = "10.2.2.1/24"
)
var zone = new(ZoneDb)
var zone = NewZoneDb(DefaultLocalDomain)
ip, _, _ := net.ParseCIDR(testAddr1)
err := zone.AddRecord(containerID, successTestName, ip)
wt.AssertNoErr(t, err)
// Add a few more records to make the job harder
err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.0.0.1"))
err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.2.0.1"))
wt.AssertNoErr(t, err)
err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.0.0.2"))
err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.2.0.2"))
wt.AssertNoErr(t, err)
// Check that the address is now there.
foundIP, err := zone.LookupLocal(successTestName)
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)
}
// See if we can find the address by IP.
foundName, err := zone.ReverseLookupLocal(ip)
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)
}
@@ -50,14 +50,14 @@ func TestZone(t *testing.T) {
err = zone.DeleteRecord(containerID, ip)
wt.AssertNoErr(t, err)
_, err = zone.LookupLocal(successTestName)
_, err = zone.LookupName(successTestName)
wt.AssertNoErr(t, err)
err = zone.DeleteRecord(otherContainerID, ip)
wt.AssertNoErr(t, err)
// Check that the address is not there now.
_, err = zone.LookupLocal(successTestName)
_, err = zone.LookupName(successTestName)
wt.AssertErrorType(t, err, (*LookupError)(nil), "after deleting record")
// Delete a record that isn't there
@@ -69,20 +69,20 @@ func TestDeleteFor(t *testing.T) {
var (
id = "foobar"
name = "foo.weave."
addr1 = "10.1.2.3/24"
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)
wt.AssertNoErr(t, err)
}
_, err := zone.LookupLocal(name)
_, err := zone.LookupName(name)
wt.AssertNoErr(t, err)
err = zone.DeleteRecordsFor(id)
_, err = zone.LookupLocal(name)
_, err = zone.LookupName(name)
wt.AssertErrorType(t, err, (*LookupError)(nil), "after deleting records for ident")
}
+1 -1
View File
@@ -10,7 +10,7 @@ func EnsureInterface(ifaceName string, wait int) (iface *net.Interface, err erro
if iface, err = findInterface(ifaceName); err == nil || wait == 0 {
return
}
for ; err != nil && wait > 0; wait -= 1 {
for ; err != nil && wait > 0; wait-- {
time.Sleep(1 * time.Second)
iface, err = findInterface(ifaceName)
}
+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
}
+357 -387
View File
@@ -3,75 +3,134 @@ package router
import (
"encoding/binary"
"encoding/gob"
"encoding/hex"
"fmt"
"log"
"net"
"strconv"
"sync"
"time"
)
func NewRemoteConnection(from, to *Peer, tcpAddr string) *RemoteConnection {
type Connection interface {
Local() *Peer
Remote() *Peer
RemoteTCPAddr() string
Outbound() bool
Established() bool
BreakTie(Connection) ConnectionTieBreak
Shutdown(error)
Log(args ...interface{})
}
type ConnectionTieBreak int
const (
TieBreakWon ConnectionTieBreak = iota
TieBreakLost
TieBreakTied
)
type RemoteConnection struct {
local *Peer
remote *Peer
remoteTCPAddr string
outbound bool
established bool
}
type LocalConnection struct {
sync.RWMutex
RemoteConnection
TCPConn *net.TCPConn
tcpSender TCPSender
remoteUDPAddr *net.UDPAddr
receivedHeartbeat bool
stackFrag bool
effectivePMTU int
SessionKey *[32]byte
heartbeatTCP *time.Ticker
heartbeatTimeout *time.Timer
heartbeatFrame *ForwardedFrame
heartbeat *time.Ticker
fragTest *time.Ticker
forwarder *Forwarder
forwarderDF *ForwarderDF
Decryptor Decryptor
Router *Router
uid uint64
actionChan chan<- ConnectionAction
finished <-chan struct{} // closed to signal that actorLoop has finished
}
type ConnectionAction func() error
func NewRemoteConnection(from, to *Peer, tcpAddr string, outbound bool, established bool) *RemoteConnection {
return &RemoteConnection{
local: from,
remote: to,
remoteTCPAddr: tcpAddr}
remoteTCPAddr: tcpAddr,
outbound: outbound,
established: established,
}
}
func (conn *RemoteConnection) Local() *Peer {
return conn.local
}
func (conn *RemoteConnection) Local() *Peer { return conn.local }
func (conn *RemoteConnection) Remote() *Peer { return conn.remote }
func (conn *RemoteConnection) RemoteTCPAddr() string { return conn.remoteTCPAddr }
func (conn *RemoteConnection) Outbound() bool { return conn.outbound }
func (conn *RemoteConnection) Established() bool { return conn.established }
func (conn *RemoteConnection) BreakTie(Connection) ConnectionTieBreak { return TieBreakTied }
func (conn *RemoteConnection) Shutdown(error) {}
func (conn *RemoteConnection) Remote() *Peer {
return conn.remote
}
func (conn *RemoteConnection) RemoteTCPAddr() string {
return conn.remoteTCPAddr
}
func (conn *RemoteConnection) Shutdown() {
}
func (conn *RemoteConnection) Established() bool {
return true
func (conn *RemoteConnection) Log(args ...interface{}) {
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.Name.String()
from = conn.local.String()
}
to := "<nil>"
if conn.remote != nil {
to = conn.remote.Name.String()
to = conn.remote.String()
}
return fmt.Sprint("Connection ", from, "->", to)
}
// Async. Does not return anything. If the connection is successful,
// it will end up in the local peer's connections map.
func NewLocalConnection(connRemote *RemoteConnection, acceptNewPeer bool, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router) {
func NewLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router) *LocalConnection {
if connRemote.local != router.Ourself.Peer {
log.Fatal("Attempt to create local connection from a peer which is not ourself")
}
queryChan := make(chan *ConnectionInteraction, ChannelSize)
// NB, we're taking a copy of connRemote here.
connLocal := &LocalConnection{
return &LocalConnection{
RemoteConnection: *connRemote,
Router: router,
TCPConn: tcpConn,
remoteUDPAddr: udpAddr,
effectivePMTU: DefaultPMTU,
queryChan: queryChan}
go connLocal.queryLoop(queryChan, acceptNewPeer)
effectivePMTU: DefaultPMTU}
}
func (conn *LocalConnection) Established() bool {
conn.RLock()
defer conn.RUnlock()
return conn.established
// Async. Does not return anything. If the connection is successful,
// it will end up in the local peer's connections map.
func (conn *LocalConnection) Start(acceptNewPeer bool) {
actionChan := make(chan ConnectionAction, ChannelSize)
conn.actionChan = actionChan
finished := make(chan struct{})
conn.finished = finished
go conn.run(actionChan, finished, acceptNewPeer)
}
func (conn *LocalConnection) BreakTie(dupConn Connection) ConnectionTieBreak {
dupConnLocal := dupConn.(*LocalConnection)
// conn.uid is used as the tie breaker here, in the knowledge that
// both sides will make the same decision.
if conn.uid < dupConnLocal.uid {
return TieBreakWon
} else if dupConnLocal.uid < conn.uid {
return TieBreakLost
} else {
return TieBreakTied
}
}
// Read by the forwarder processes when in the UDP senders
@@ -81,17 +140,10 @@ func (conn *LocalConnection) RemoteUDPAddr() *net.UDPAddr {
return conn.remoteUDPAddr
}
// Called by the forwarder processes in a few places (including
// crypto), but the connection TCP receiver process, and by the local
// peer actor process. Do not call this from the connection's actor
// process itself.
func (conn *LocalConnection) CheckFatal(err error) error {
if err == nil {
return nil
}
conn.log("error:", err)
conn.Shutdown()
return err
func (conn *LocalConnection) Established() bool {
conn.RLock()
defer conn.RUnlock()
return conn.established
}
// Called by forwarder processes, read in Forward (by sniffer and udp
@@ -101,7 +153,7 @@ func (conn *LocalConnection) setEffectivePMTU(pmtu int) {
defer conn.Unlock()
if conn.effectivePMTU != pmtu {
conn.effectivePMTU = pmtu
conn.log("Effective PMTU set to", pmtu)
conn.Log("Effective PMTU set to", pmtu)
}
}
@@ -114,132 +166,91 @@ func (conn *LocalConnection) setStackFrag(frag bool) {
conn.stackFrag = frag
}
func (conn *LocalConnection) log(args ...interface{}) {
v := append([]interface{}{}, fmt.Sprintf("->[%s]:", conn.remote.Name))
v = append(v, args...)
log.Println(v...)
// Called by the connection's TCP receiver process.
func (conn *LocalConnection) pmtuVerified(pmtu int) {
conn.RLock()
fwd := conn.forwarderDF
conn.RUnlock()
if fwd != nil {
fwd.PMTUVerified(pmtu)
}
}
// ACTOR client API
// Send directly, not via the Actor. If it goes via the Actor we can
// get a deadlock where LocalConnection is blocked talking to
// LocalPeer and LocalPeer is blocked trying send a ProtocolMsg via
// LocalConnection, and the channels are full in both directions so
// nothing can proceed.
func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
if err := conn.sendProtocolMsg(m); err != nil {
conn.Shutdown(err)
}
}
const (
CSendTCP = iota
CSetEstablished = iota
CSetRemoteUDPAddr = iota
CShutdown = iota
)
// ACTOR methods
// NB: The conn.* fields are only written by the connection actor
// process, which is the caller of the ConnectionAction funs. Hence we
// do not need locks for reading, and only need write locks for fields
// read by other processes.
// Async
func (conn *LocalConnection) Shutdown() {
conn.queryChan <- &ConnectionInteraction{Interaction: Interaction{code: CShutdown}}
func (conn *LocalConnection) Shutdown(err error) {
// err should always be a real error, even if only io.EOF
if err == nil {
panic("nil error")
}
// Run on its own goroutine in case the channel is backed up
go func() { conn.sendAction(func() error { return err }) }()
}
// Async
func (conn *LocalConnection) SetRemoteUDPAddr(remoteUDPAddr *net.UDPAddr) {
if remoteUDPAddr == nil {
//
// Heartbeating serves two purposes: a) keeping NAT paths alive, and
// b) updating a remote peer's knowledge of our address, in the event
// it changes (e.g. because NAT paths expired).
func (conn *LocalConnection) ReceivedHeartbeat(remoteUDPAddr *net.UDPAddr, connUID uint64) {
if remoteUDPAddr == nil || connUID != conn.uid {
return
}
conn.queryChan <- &ConnectionInteraction{
Interaction: Interaction{code: CSetRemoteUDPAddr},
payload: remoteUDPAddr}
conn.sendAction(func() error {
oldRemoteUDPAddr := conn.remoteUDPAddr
old := conn.receivedHeartbeat
conn.Lock()
conn.remoteUDPAddr = remoteUDPAddr
conn.receivedHeartbeat = true
conn.Unlock()
conn.heartbeatTimeout.Reset(HeartbeatTimeout)
if !old {
if err := conn.sendSimpleProtocolMsg(ProtocolConnectionEstablished); err != nil {
return err
}
}
if oldRemoteUDPAddr == nil {
return conn.sendFastHeartbeats()
} else if oldRemoteUDPAddr.String() != remoteUDPAddr.String() {
log.Println("Peer", conn.remote, "moved from", old, "to", remoteUDPAddr)
}
return nil
})
}
// Async
func (conn *LocalConnection) SetEstablished() {
conn.queryChan <- &ConnectionInteraction{Interaction: Interaction{code: CSetEstablished}}
}
// Async
func (conn *LocalConnection) SendTCP(msg []byte) {
conn.queryChan <- &ConnectionInteraction{
Interaction: Interaction{code: CSendTCP},
payload: msg}
}
// ACTOR server
func (conn *LocalConnection) queryLoop(queryChan <-chan *ConnectionInteraction, acceptNewPeer bool) {
err := conn.handshake(acceptNewPeer)
if err != nil {
log.Printf("->[%s] encountered error during handshake: %v\n", conn.remoteTCPAddr, err)
conn.handleShutdown()
return
}
conn.Router.Ourself.AddConnection(conn)
if conn.remoteUDPAddr != nil {
if err = conn.ensureForwarders(); err == nil {
conn.heartbeat = time.NewTicker(FastHeartbeat)
conn.forwardHeartbeatFrame() // avoid initial wait
conn.sendAction(func() error {
stopTicker(conn.heartbeat)
old := conn.established
conn.Lock()
conn.established = true
conn.Unlock()
if old {
return nil
}
}
terminate := false
for !terminate {
if err != nil {
conn.log("error:", err)
break
}
select {
case query, ok := <-queryChan:
if !ok {
break
}
switch query.code {
case CShutdown:
terminate = true
case CSetEstablished:
err = conn.handleSetEstablished()
case CSetRemoteUDPAddr:
err = conn.handleSetRemoteUDPAddr(query.payload.(*net.UDPAddr))
case CSendTCP:
err = conn.handleSendTCP(query.payload.([]byte))
}
case <-tickerChan(conn.heartbeat):
conn.forwardHeartbeatFrame()
case <-tickerChan(conn.fetchAll):
err = conn.handleSendTCP(ProtocolFetchAllByte)
case <-tickerChan(conn.fragTest):
conn.setStackFrag(false)
err = conn.handleSendTCP(ProtocolStartFragmentationTestByte)
}
}
conn.handleShutdown()
}
func (conn *LocalConnection) handleSetRemoteUDPAddr(remoteUDPAddr *net.UDPAddr) error {
if remoteUDPAddr == nil {
return nil
}
conn.Lock()
old := conn.remoteUDPAddr
conn.remoteUDPAddr = remoteUDPAddr
conn.Unlock()
if old == nil {
if err := conn.handleSendTCP(ProtocolConnectionEstablishedByte); err != nil {
return err
}
return conn.handleSetEstablished()
} else if old.String() != remoteUDPAddr.String() {
log.Println("Peer", conn.remote.Name, "moved from", old, "to", remoteUDPAddr)
}
return nil
}
func (conn *LocalConnection) handleSetEstablished() error {
conn.Lock()
old := conn.established
conn.established = true
conn.Unlock()
if !old {
conn.Router.Ourself.ConnectionEstablished(conn)
if err := conn.ensureForwarders(); err != nil {
return err
}
stopTicker(conn.heartbeat)
conn.heartbeat = time.NewTicker(SlowHeartbeat)
conn.fetchAll = time.NewTicker(FetchAllInterval)
conn.fragTest = time.NewTicker(FragTestInterval)
conn.forwardHeartbeatFrame() // avoid initial wait
// Send a large frame down the DF channel in order to prompt
// PMTU discovery to start.
conn.Forward(true, &ForwardedFrame{
@@ -247,196 +258,187 @@ func (conn *LocalConnection) handleSetEstablished() error {
dstPeer: conn.remote,
frame: PMTUDiscovery},
nil)
conn.heartbeat = time.NewTicker(SlowHeartbeat)
conn.fragTest = time.NewTicker(FragTestInterval)
// avoid initial waits for timers to fire
conn.Forward(true, conn.heartbeatFrame, nil)
conn.setStackFrag(false)
return conn.handleSendTCP(ProtocolStartFragmentationTestByte)
}
return nil
if err := conn.sendSimpleProtocolMsg(ProtocolStartFragmentationTest); err != nil {
return err
}
return nil
})
}
func (conn *LocalConnection) handleSendTCP(msg []byte) error {
return conn.tcpSender.Send(msg)
// Send an actor request to the actorLoop, but don't block if
// actorLoop has exited - see http://blog.golang.org/pipelines for
// pattern
func (conn *LocalConnection) sendAction(action ConnectionAction) {
select {
case conn.actionChan <- action:
case <-conn.finished:
}
}
func (conn *LocalConnection) handleShutdown() {
if conn.remote != nil {
conn.log("connection shutting down")
}
// ACTOR server
// Whilst some of these elements may have been written to whilst
// holding locks, they were only written to by the connection
// actor process. handleShutdown is only called by the connection
// actor process. So there is no need to take locks to read these
// (or write elements which are only read by the same
// process). Taking locks is only done for elements which are read
// by other processes.
if conn.shutdown {
func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished chan<- struct{}, acceptNewPeer bool) {
var err error // important to use this var and not create another one with 'err :='
defer func() { conn.shutdown(err) }()
defer close(finished)
tcpConn := conn.TCPConn
tcpConn.SetLinger(0)
enc := gob.NewEncoder(tcpConn)
dec := gob.NewDecoder(tcpConn)
if err = conn.handshake(enc, dec, acceptNewPeer); err != nil {
return
}
conn.shutdown = true
conn.Log("completed handshake")
// The ordering of the following is very important. [1]
if conn.remoteUDPAddr != nil {
if err = conn.ensureForwarders(); err != nil {
return
}
}
if err = conn.Router.Ourself.AddConnection(conn); err != nil {
return
}
if err = conn.initHeartbeats(); err != nil {
return
}
go conn.receiveTCP(dec)
err = conn.actorLoop(actionChan)
}
// [1] Ordering constraints:
//
// (a) AddConnections must precede initHeartbeats. It is only after
// the former completes that we know the connection is valid, in
// particular is not a duplicate connection to the same peer. Sending
// heartbeats on a duplicate connection can trip up crypto at the
// other end, since the associated UDP packets may get decoded by the
// other connection. It is also generally wasteful to engage in any
// interaction with the remote on a connection that turns out to be
// invald.
//
// (b) AddConnection must precede receiveTCP. In the absence of any
// indirect connectivity to the remote peer, the first we hear about
// it (and any peers reachable from it) is through topology gossip it
// sends us on the connection. We must ensure that the connection has
// been added to Ourself prior to processing any such gossip,
// otherwise we risk immediately gc'ing part of that newly received
// portion of the topology (though not the remote peer itself, since
// that will have a positive ref count), leaving behind dangling
// references to peers. Hence we must invoke AddConnection, which is
// *synchronous*, first.
//
// (c) AddConnection must precede actorLoop. More precisely, it must
// precede shutdown, since that invokes DeleteConnection and is
// invoked on termination of this entire function. Essentially this
// boils down to a prohibition on running AddConnection in a separate
// goroutine, at least not without some synchronisation. Which in turn
// requires us the launching of the receiveTCP goroutine to precede
// actorLoop.
//
// (d) AddConnection should precede receiveTCP. There is no point
// starting the latter if the former fails.
//
// (e) initHeartbeats should precede actorLoop. The former is setting
// LocalConnection fields accessed by the latter. Since the latter
// runs in a separate goroutine, we'd have to add some synchronisation
// if initHeartbeats isn't run first.
//
// (f) ensureForwarders should precede AddConnection. As soon as a
// connection has been added to LocalPeer by the latter, it becomes
// visible to the packet routing logic, which will end up dropping
// packets if the forwarders haven't been created yet. We cannot
// prevent that completely, since, for example, forwarder can only be
// created when we know the remote UDP address, but it helps to try.
func (conn *LocalConnection) initHeartbeats() error {
conn.heartbeatTCP = time.NewTicker(TCPHeartbeat)
conn.heartbeatTimeout = time.NewTimer(HeartbeatTimeout)
heartbeatFrameBytes := make([]byte, EthernetOverhead+8)
binary.BigEndian.PutUint64(heartbeatFrameBytes[EthernetOverhead:], conn.uid)
conn.heartbeatFrame = &ForwardedFrame{
srcPeer: conn.local,
dstPeer: conn.remote,
frame: heartbeatFrameBytes}
if conn.remoteUDPAddr == nil {
return nil
}
return conn.sendFastHeartbeats()
}
func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err error) {
for err == nil {
select {
case action := <-actionChan:
err = action()
case <-conn.heartbeatTCP.C:
err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat)
case <-conn.heartbeatTimeout.C:
err = fmt.Errorf("timed out waiting for UDP heartbeat")
case <-tickerChan(conn.heartbeat):
conn.Forward(true, conn.heartbeatFrame, nil)
case <-tickerChan(conn.fragTest):
conn.setStackFrag(false)
err = conn.sendSimpleProtocolMsg(ProtocolStartFragmentationTest)
}
}
return
}
func (conn *LocalConnection) shutdown(err error) {
if conn.remote == nil {
log.Printf("->[%s] connection shutting down due to error during handshake: %v\n", conn.remoteTCPAddr, err)
} else {
conn.Log("connection shutting down due to error:", err)
}
if conn.TCPConn != nil {
checkWarn(conn.TCPConn.Close())
}
if conn.remote != nil {
conn.remote.DecrementLocalRefCount()
conn.Router.Peers.Dereference(conn.remote)
conn.Router.Ourself.DeleteConnection(conn)
}
if conn.heartbeatTimeout != nil {
conn.heartbeatTimeout.Stop()
}
stopTicker(conn.heartbeatTCP)
stopTicker(conn.heartbeat)
stopTicker(conn.fetchAll)
stopTicker(conn.fragTest)
// blank out the forwardChan so that the router processes don't
// try to send any more
conn.stopForwarders()
conn.Router.ConnectionMaker.ConnectionTerminated(conn.remoteTCPAddr)
conn.Router.ConnectionMaker.ConnectionTerminated(conn.remoteTCPAddr, err)
}
func (conn *LocalConnection) handshake(acceptNewPeer bool) error {
// We do not need to worry about locking in here as at this point,
// the connection is not reachable by any go-routine other than
// ourself. Only when we add this connection to the conn.local
// peer will it be visible from multiple go-routines.
tcpConn := conn.TCPConn
tcpConn.SetLinger(0)
conn.extendReadDeadline()
// Helpers
enc := gob.NewEncoder(tcpConn)
localConnID := randUint64()
versionStr := fmt.Sprint(ProtocolVersion)
handshakeSend := map[string]string{
"Protocol": Protocol,
"ProtocolVersion": versionStr,
"PeerNameFlavour": PeerNameFlavour,
"Name": conn.local.Name.String(),
"UID": fmt.Sprint(conn.local.UID),
"ConnID": fmt.Sprint(localConnID)}
handshakeRecv := map[string]string{}
usingPassword := conn.Router.UsingPassword()
var public, private *[32]byte
var err error
if usingPassword {
public, private, err = GenerateKeyPair()
if err != nil {
return err
}
handshakeSend["PublicKey"] = hex.EncodeToString(public[:])
}
enc.Encode(handshakeSend)
dec := gob.NewDecoder(tcpConn)
err = dec.Decode(&handshakeRecv)
if err != nil {
return err
}
_, err = checkHandshakeStringField("Protocol", Protocol, handshakeRecv)
if err != nil {
return err
}
_, err = checkHandshakeStringField("ProtocolVersion", versionStr, handshakeRecv)
if err != nil {
return err
}
_, err = checkHandshakeStringField("PeerNameFlavour", PeerNameFlavour, handshakeRecv)
if err != nil {
return err
}
nameStr, err := checkHandshakeStringField("Name", "", handshakeRecv)
if err != nil {
return err
}
name, err := PeerNameFromString(nameStr)
if err != nil {
return err
}
if !acceptNewPeer {
if _, found := conn.Router.Peers.Fetch(name); !found {
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", name, existingConn.RemoteTCPAddr())
}
uidStr, err := checkHandshakeStringField("UID", "", handshakeRecv)
if err != nil {
return err
}
uid, err := strconv.ParseUint(uidStr, 10, 64)
if err != nil {
return err
}
remoteConnIdStr, err := checkHandshakeStringField("ConnID", "", handshakeRecv)
if err != nil {
return err
}
remoteConnID, err := strconv.ParseUint(remoteConnIdStr, 10, 64)
if err != nil {
return err
}
conn.UID = localConnID ^ remoteConnID
if usingPassword {
remotePublicStr, rpErr := checkHandshakeStringField("PublicKey", "", handshakeRecv)
if rpErr != nil {
return rpErr
}
remotePublicSlice, rpErr := hex.DecodeString(remotePublicStr)
if rpErr != nil {
return rpErr
}
remotePublic := [32]byte{}
for idx, elem := range remotePublicSlice {
remotePublic[idx] = elem
}
conn.SessionKey = FormSessionKey(&remotePublic, private, conn.Router.Password)
conn.tcpSender = NewEncryptedTCPSender(enc, conn)
conn.Decryptor = NewNaClDecryptor(conn)
} else {
if _, found := handshakeRecv["PublicKey"]; found {
return fmt.Errorf("Remote network is encrypted. Password required.")
}
conn.tcpSender = NewSimpleTCPSender(enc)
conn.Decryptor = NewNonDecryptor(conn)
}
toPeer := NewPeer(name, uid, 0)
toPeer = conn.Router.Peers.FetchWithDefault(toPeer)
if toPeer == nil {
return fmt.Errorf("Connection appears to be with different version of a peer we already know of")
} else if toPeer == conn.local {
// have to do assigment here to ensure Shutdown releases ref count
conn.remote = toPeer
return fmt.Errorf("Cannot connect to ourself")
}
conn.remote = toPeer
go conn.receiveTCP(dec, usingPassword)
return nil
func (conn *LocalConnection) sendSimpleProtocolMsg(tag ProtocolTag) error {
return conn.sendProtocolMsg(ProtocolMsg{tag: tag})
}
func checkHandshakeStringField(fieldName string, expectedValue string, handshake map[string]string) (string, error) {
val, found := handshake[fieldName]
if !found {
return "", fmt.Errorf("Field %s is missing", fieldName)
}
if expectedValue != "" && val != expectedValue {
return "", fmt.Errorf("Field %s has wrong value; expected '%s', received '%s'", fieldName, expectedValue, val)
}
return val, nil
func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
return conn.tcpSender.Send(Concat([]byte{byte(m.tag)}, m.msg))
}
func (conn *LocalConnection) receiveTCP(decoder *gob.Decoder, usingPassword bool) {
defer conn.Decryptor.Shutdown()
func (conn *LocalConnection) receiveTCP(decoder *gob.Decoder) {
usingPassword := conn.SessionKey != nil
var receiver TCPReceiver
if usingPassword {
receiver = NewEncryptedTCPReceiver(conn)
receiver = NewEncryptedTCPReceiver(conn.SessionKey, conn.outbound)
} else {
receiver = NewSimpleTCPReceiver()
}
@@ -444,94 +446,62 @@ func (conn *LocalConnection) receiveTCP(decoder *gob.Decoder, usingPassword bool
for {
var msg []byte
conn.extendReadDeadline()
if conn.CheckFatal(decoder.Decode(&msg)) != nil {
return
if err = decoder.Decode(&msg); err != nil {
break
}
msg, err = receiver.Decode(msg)
if err != nil {
checkWarn(err)
// remote peer may be using wrong password. Or there could
// be some sort of injection attack going on. Just ignore
// the traffic rather than shutting down.
break
}
if len(msg) < 1 {
conn.Log("ignoring blank msg")
continue
}
if msg[0] == ProtocolConnectionEstablished {
// We initiated the connection. We sent fast heartbeats to
// the remote side, which has now received at least one of
// them and thus has informed us via TCP that it considers
// the connection is now up. We now do a fetchAll on it.
conn.SetEstablished()
conn.SendTCP(ProtocolFetchAllByte)
} else if msg[0] == ProtocolStartFragmentationTest {
conn.Forward(false, &ForwardedFrame{
srcPeer: conn.local,
dstPeer: conn.remote,
frame: FragTest},
nil)
} else if msg[0] == ProtocolFragmentationReceived {
conn.setStackFrag(true)
} else if usingPassword && msg[0] == ProtocolNonce {
conn.Decryptor.ReceiveNonce(msg[1:])
} else if msg[0] == ProtocolFetchAll {
// There are exactly two messages that relate to topology
// updates.
//
// 1. FetchAll. This carries no payload. The receiver
// responds with the entire topology model as the receiver
// has it.
//
// 2. Update. This carries a topology payload. The
// receiver merges it with its own topology model. If the
// payload is a subset of the receiver's topology, no
// further action is taken. Otherwise, the receiver sends
// out to all its connections an "improved" update:
// - elements which the original payload added to the
// receiver are included
// - elements which the original payload updated in the
// receiver are included
// - elements which are equal between the receiver and
// the payload are not included
// - elements where the payload was older than the
// receiver's version are updated
conn.SendTCP(Concat(ProtocolUpdateByte, conn.Router.Peers.EncodeAllPeers()))
} else if msg[0] == ProtocolUpdate {
newUpdate, err := conn.Router.Peers.ApplyUpdate(msg[1:])
if _, ok := err.(UnknownPeersError); err != nil && ok {
// That update contained a peer we didn't know about;
// request full update
conn.SendTCP(ProtocolFetchAllByte)
continue
}
if conn.CheckFatal(err) != nil {
return
}
if len(newUpdate) != 0 {
conn.Router.ConnectionMaker.Refresh()
conn.Router.Routes.Recalculate()
conn.Router.Ourself.BroadcastTCP(Concat(ProtocolUpdateByte, newUpdate))
}
} else if msg[0] == ProtocolPMTUVerified {
conn.verifyPMTU <- int(binary.BigEndian.Uint16(msg[1:]))
} else {
conn.log("received unknown msg:\n", msg)
if err = conn.handleProtocolMsg(ProtocolTag(msg[0]), msg[1:]); err != nil {
break
}
}
conn.Shutdown(err)
}
func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) error {
switch tag {
case ProtocolHeartbeat:
case ProtocolConnectionEstablished:
// We sent fast heartbeats to the remote peer, which has now
// received at least one of them and told us via this message.
// We can now consider the connection as established from our
// end.
conn.SetEstablished()
case ProtocolStartFragmentationTest:
conn.Forward(false, &ForwardedFrame{
srcPeer: conn.local,
dstPeer: conn.remote,
frame: FragTest},
nil)
case ProtocolFragmentationReceived:
conn.setStackFrag(true)
case ProtocolPMTUVerified:
conn.pmtuVerified(int(binary.BigEndian.Uint16(payload)))
case ProtocolGossipUnicast, ProtocolGossipBroadcast, ProtocolGossip:
return conn.Router.handleGossip(tag, payload)
default:
conn.Log("ignoring unknown protocol tag:", tag)
}
return nil
}
func (conn *LocalConnection) extendReadDeadline() {
conn.TCPConn.SetReadDeadline(time.Now().Add(ReadTimeout))
conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2))
}
// Heartbeating serves two purposes: a) keeping NAT paths alive, and
// b) updating a remote peer's knowledge of our address, in the event
// it changes (e.g. because NAT paths expired).
// Called only by connection actor process.
func (conn *LocalConnection) forwardHeartbeatFrame() {
heartbeatFrame := &ForwardedFrame{
srcPeer: conn.local,
dstPeer: conn.remote,
frame: []byte{}}
conn.Forward(true, heartbeatFrame, nil)
func (conn *LocalConnection) sendFastHeartbeats() error {
err := conn.ensureForwarders()
if err == nil {
conn.heartbeat = time.NewTicker(FastHeartbeat)
conn.Forward(true, conn.heartbeatFrame, nil) // avoid initial wait
}
return err
}
func tickerChan(ticker *time.Ticker) <-chan time.Time {
+174 -115
View File
@@ -14,96 +14,118 @@ const (
MaxInterval = 10 * time.Minute
)
const (
CMInitiate = iota
CMTerminated = iota
CMRefresh = iota
CMStatus = iota
)
type ConnectionMaker struct {
ourself *LocalPeer
peers *Peers
targets map[string]*Target
cmdLineAddress map[string]bool
queryChan chan<- *ConnectionMakerInteraction
ourself *LocalPeer
peers *Peers
port int
targets map[string]*Target
cmdLinePeers map[string]*net.TCPAddr
actionChan chan<- ConnectionMakerAction
}
// Information about an address where we may find a peer
type Target struct {
attempting bool // are we currently attempting to connect there?
lastError error // reason for disconnection last time
tryAfter time.Time // next time to try this address
tryInterval time.Duration // backoff time on next failure
}
type ConnectionMakerInteraction struct {
Interaction
address string
type ConnectionMakerAction func() bool
func NewConnectionMaker(ourself *LocalPeer, peers *Peers, port int) *ConnectionMaker {
return &ConnectionMaker{
ourself: ourself,
peers: peers,
port: port,
cmdLinePeers: make(map[string]*net.TCPAddr),
targets: make(map[string]*Target)}
}
func StartConnectionMaker(ourself *LocalPeer, peers *Peers) *ConnectionMaker {
queryChan := make(chan *ConnectionMakerInteraction, ChannelSize)
state := &ConnectionMaker{
ourself: ourself,
peers: peers,
queryChan: queryChan,
cmdLineAddress: make(map[string]bool),
targets: make(map[string]*Target)}
go state.queryLoop(queryChan)
return state
func (cm *ConnectionMaker) Start() {
actionChan := make(chan ConnectionMakerAction, ChannelSize)
cm.actionChan = actionChan
go cm.queryLoop(actionChan)
}
func (cm *ConnectionMaker) InitiateConnection(address string) {
cm.queryChan <- &ConnectionMakerInteraction{
Interaction: Interaction{code: CMInitiate},
address: address}
func (cm *ConnectionMaker) InitiateConnection(peer string) error {
host, port, err := net.SplitHostPort(peer)
if err != nil {
host = peer
port = "0" // we use that as an indication that "no port was supplied"
}
addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%s", host, port))
if err != nil {
return err
}
cm.actionChan <- func() bool {
cm.cmdLinePeers[peer] = addr
// curtail any existing reconnect interval
if target, found := cm.targets[addr.String()]; found {
target.tryAfter, target.tryInterval = tryImmediately()
}
return true
}
return nil
}
func (cm *ConnectionMaker) ConnectionTerminated(address string) {
cm.queryChan <- &ConnectionMakerInteraction{
Interaction: Interaction{code: CMTerminated},
address: address}
func (cm *ConnectionMaker) ForgetConnection(peer string) {
cm.actionChan <- func() bool {
delete(cm.cmdLinePeers, peer)
return false
}
}
func (cm *ConnectionMaker) ConnectionTerminated(address string, err error) {
cm.actionChan <- func() bool {
if target, found := cm.targets[address]; found {
target.attempting = false
target.lastError = err
target.tryAfter, target.tryInterval = tryAfter(target.tryInterval)
}
return true
}
}
func (cm *ConnectionMaker) Refresh() {
cm.queryChan <- &ConnectionMakerInteraction{
Interaction: Interaction{code: CMRefresh}}
cm.actionChan <- func() bool { return true }
}
func (cm *ConnectionMaker) String() string {
resultChan := make(chan interface{}, 0)
cm.queryChan <- &ConnectionMakerInteraction{
Interaction: Interaction{code: CMStatus, resultChan: resultChan}}
result := <-resultChan
return result.(string)
// We need to Refresh first in order to clear out any 'attempting'
// connections from cm.targets that have been established since
// the last run of cm.checkStateAndAttemptConnections. These
// entries are harmless but do represent stale state that we do
// not want to report.
cm.Refresh()
resultChan := make(chan string, 0)
cm.actionChan <- func() bool {
var buf bytes.Buffer
for address, target := range cm.targets {
fmt.Fprintf(&buf, "->[%s]", address)
if target.lastError != nil {
fmt.Fprintf(&buf, " (%s)", target.lastError)
}
if target.attempting {
fmt.Fprintf(&buf, " trying since %v\n", target.tryAfter)
} else {
fmt.Fprintf(&buf, " next try at %v\n", target.tryAfter)
}
}
resultChan <- buf.String()
return false
}
return <-resultChan
}
func (cm *ConnectionMaker) queryLoop(queryChan <-chan *ConnectionMakerInteraction) {
func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) {
timer := time.NewTimer(MaxDuration)
run := func() { timer.Reset(cm.checkStateAndAttemptConnections()) }
for {
select {
case query, ok := <-queryChan:
if !ok {
return
}
switch query.code {
case CMInitiate:
cm.cmdLineAddress[NormalisePeerAddr(query.address)] = true
case action := <-actionChan:
if action() {
run()
case CMTerminated:
if target, found := cm.targets[query.address]; found {
target.attempting = false
target.tryAfter, target.tryInterval = tryAfter(target.tryInterval)
}
run()
case CMRefresh:
run()
case CMStatus:
run()
query.resultChan <- cm.status()
default:
log.Fatal("Unexpected connection maker query:", query)
}
case <-timer.C:
run()
@@ -112,62 +134,121 @@ func (cm *ConnectionMaker) queryLoop(queryChan <-chan *ConnectionMakerInteractio
}
func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
validTarget := make(map[string]bool)
// copy the set of things we are connected to, so we can access them without locking
our_connected_peers := make(map[PeerName]bool)
our_connected_targets := make(map[string]bool)
cm.ourself.ForEachConnection(func(peer PeerName, conn Connection) {
our_connected_peers[peer] = true
our_connected_targets[conn.RemoteTCPAddr()] = true
})
var (
validTarget = make(map[string]struct{})
cmdLineTarget = make(map[string]struct{})
)
// Copy the set of things we are connected to, so we can access
// them without locking. Also clear out any entries in cm.targets
// for existing connections.
ourConnectedPeers, ourConnectedTargets, ourInboundIPs := cm.ourConnections()
addTarget := func(address string) {
if !our_connected_targets[address] {
validTarget[address] = true
cm.addTarget(address)
if _, connected := ourConnectedTargets[address]; connected {
return
}
validTarget[address] = void
if _, found := cm.targets[address]; found {
return
}
target := &Target{}
target.tryAfter, target.tryInterval = tryImmediately()
cm.targets[address] = target
}
// Add command-line targets that are not connected
for address, _ := range cm.cmdLineAddress {
addTarget(address)
for _, addr := range cm.cmdLinePeers {
completeAddr := *addr
attempt := true
if completeAddr.Port == 0 {
completeAddr.Port = cm.port
// If a peer was specified w/o a port, then we do not
// attempt to connect to it if we have any inbound
// connections from that IP.
if _, connected := ourInboundIPs[completeAddr.IP.String()]; connected {
attempt = false
}
}
address := completeAddr.String()
cmdLineTarget[address] = void
if attempt {
addTarget(address)
}
}
// Add targets for peers that someone else is connected to, but we
// aren't
cm.peers.ForEach(func(name PeerName, peer *Peer) {
peer.ForEachConnection(func(otherPeer PeerName, conn Connection) {
if otherPeer == cm.ourself.Name || our_connected_peers[otherPeer] {
return
cm.addPeerTargets(ourConnectedPeers, addTarget)
return cm.connectToTargets(validTarget, cmdLineTarget)
}
func (cm *ConnectionMaker) ourConnections() (PeerNameSet, map[string]struct{}, map[string]struct{}) {
var (
ourConnectedPeers = make(PeerNameSet)
ourConnectedTargets = make(map[string]struct{})
ourInboundIPs = make(map[string]struct{})
)
for conn := range cm.ourself.Connections() {
address := conn.RemoteTCPAddr()
delete(cm.targets, address)
ourConnectedPeers[conn.Remote().Name] = void
ourConnectedTargets[address] = void
if conn.Outbound() {
continue
}
if ip, _, err := net.SplitHostPort(address); err == nil { // should always succeed
ourInboundIPs[ip] = void
}
}
return ourConnectedPeers, ourConnectedTargets, ourInboundIPs
}
func (cm *ConnectionMaker) addPeerTargets(ourConnectedPeers PeerNameSet, addTarget func(string)) {
cm.peers.ForEach(func(peer *Peer) {
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
}
if _, connected := ourConnectedPeers[otherPeer]; connected {
continue
}
address := conn.RemoteTCPAddr()
// try both portnumber of connection and standard port
addTarget(address)
if host, _, err := net.SplitHostPort(address); err == nil {
addTarget(NormalisePeerAddr(host))
if conn.Outbound() {
addTarget(address)
} 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))
}
})
}
})
}
func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, cmdLineTarget map[string]struct{}) time.Duration {
now := time.Now() // make sure we catch items just added
after := MaxDuration
for address, target := range cm.targets {
if our_connected_targets[address] {
delete(cm.targets, address)
continue
}
if target.attempting {
continue
}
if !validTarget[address] {
if _, valid := validTarget[address]; !valid {
delete(cm.targets, address)
continue
}
switch duration := target.tryAfter.Sub(now); {
case duration <= 0:
target.attempting = true
go cm.attemptConnection(address, cm.cmdLineAddress[address])
_, isCmdLineTarget := cmdLineTarget[address]
go cm.attemptConnection(address, isCmdLineTarget)
case duration < after:
after = duration
}
@@ -175,33 +256,11 @@ func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
return after
}
func (cm *ConnectionMaker) addTarget(address string) {
if _, found := cm.targets[address]; !found {
target := &Target{}
target.tryAfter, target.tryInterval = tryImmediately()
cm.targets[address] = target
}
}
func (cm *ConnectionMaker) status() string {
var buf bytes.Buffer
for address, target := range cm.targets {
var fmtStr string
if target.attempting {
fmtStr = "%s (trying since %v)\n"
} else {
fmtStr = "%s (next try at %v)\n"
}
buf.WriteString(fmt.Sprintf(fmtStr, address, target.tryAfter))
}
return buf.String()
}
func (cm *ConnectionMaker) attemptConnection(address string, acceptNewPeer bool) {
log.Println("Attempting connection to", address)
log.Printf("->[%s] attempting connection\n", address)
if err := cm.ourself.CreateConnection(address, acceptNewPeer); err != nil {
log.Println(err)
cm.ConnectionTerminated(address)
log.Printf("->[%s] error during connection attempt: %v\n", address, err)
cm.ConnectionTerminated(address, err)
}
}
+20 -39
View File
@@ -6,46 +6,27 @@ import (
)
const (
Protocol = "weave"
ProtocolVersion = 8
EthernetOverhead = 14
UDPOverhead = 28 // 20 bytes for IPv4, 8 bytes for UDP
Port = 6783
HttpPort = Port + 1
DefaultPMTU = 65535
MaxUDPPacketSize = 65536
ChannelSize = 16
UDPNonceSendAt = 8192
FragTestSize = 60001
PMTUDiscoverySize = 60000
FastHeartbeat = 500 * time.Millisecond
SlowHeartbeat = 10 * time.Second
FetchAllInterval = 30 * time.Second
FragTestInterval = 5 * time.Minute
ReadTimeout = 2 * FetchAllInterval
PMTUVerifyAttempts = 8
PMTUVerifyTimeout = 10 * time.Millisecond // gets doubled with every attempt
MaxDuration = time.Duration(math.MaxInt64)
)
const (
ProtocolConnectionEstablished = iota
ProtocolFragmentationReceived = iota
ProtocolStartFragmentationTest = iota
ProtocolNonce = iota
ProtocolFetchAll = iota
ProtocolUpdate = iota
ProtocolPMTUVerified = iota
EthernetOverhead = 14
UDPOverhead = 28 // 20 bytes for IPv4, 8 bytes for UDP
Port = 6783
HTTPPort = Port + 1
DefaultPMTU = 65535
MaxUDPPacketSize = 65536
ChannelSize = 16
FragTestSize = 60001
PMTUDiscoverySize = 60000
TCPHeartbeat = 30 * time.Second
FastHeartbeat = 500 * time.Millisecond
SlowHeartbeat = 10 * time.Second
FragTestInterval = 5 * time.Minute
PMTUVerifyAttempts = 8
PMTUVerifyTimeout = 10 * time.Millisecond // gets doubled with every attempt
MaxDuration = time.Duration(math.MaxInt64)
MaxMissedHeartbeats = 6
HeartbeatTimeout = MaxMissedHeartbeats * SlowHeartbeat
)
var (
FragTest = make([]byte, FragTestSize)
PMTUDiscovery = make([]byte, PMTUDiscoverySize)
ProtocolConnectionEstablishedByte = []byte{ProtocolConnectionEstablished}
ProtocolFragmentationReceivedByte = []byte{ProtocolFragmentationReceived}
ProtocolStartFragmentationTestByte = []byte{ProtocolStartFragmentationTest}
ProtocolNonceByte = []byte{ProtocolNonce}
ProtocolFetchAllByte = []byte{ProtocolFetchAll}
ProtocolUpdateByte = []byte{ProtocolUpdate}
ProtocolPMTUVerifiedByte = []byte{ProtocolPMTUVerified}
FragTest = make([]byte, FragTestSize)
PMTUDiscovery = make([]byte, PMTUDiscoverySize)
)
+261 -317
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,95 +9,50 @@ import (
"encoding/binary"
"encoding/gob"
"fmt"
"log"
"sync"
)
func GenerateKeyPair() (publicKey, privateKey *[32]byte, err error) {
return box.GenerateKey(rand.Reader)
}
func FormSessionKey(remotePublicKey, localPrivateKey *[32]byte, secretKey *[]byte) *[32]byte {
func FormSessionKey(remotePublicKey, localPrivateKey *[32]byte, secretKey []byte) *[32]byte {
var sharedKey [32]byte
box.Precompute(&sharedKey, remotePublicKey, localPrivateKey)
sharedKeySlice := sharedKey[:]
sharedKeySlice = append(sharedKeySlice, *secretKey...)
sharedKeySlice = append(sharedKeySlice, secretKey...)
sessionKey := sha256.Sum256(sharedKeySlice)
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)
return &nonce, Concat(ProtocolNonceByte, 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, error)
AppendFrame(src []byte, dst []byte, frame []byte)
TotalLen() int
}
type NonEncryptor struct {
buf []byte
bufTail []byte
buffered int
prefixLen int
}
type NaClEncryptor struct {
NonEncryptor
buf []byte
prefixLen int
sessionKey *[32]byte
nonce [24]byte
seqNo uint64
df bool
}
func NewNonEncryptor(prefix []byte) *NonEncryptor {
buf := make([]byte, MaxUDPPacketSize)
prefixLen := copy(buf, prefix)
@@ -121,89 +75,71 @@ 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(frame *ForwardedFrame) {
func (ne *NonEncryptor) AppendFrame(src []byte, dst []byte, frame []byte) {
bufTail := ne.bufTail
srcLen := copy(bufTail, frame.srcPeer.NameByte)
srcLen := copy(bufTail, src)
bufTail = bufTail[srcLen:]
dstLen := copy(bufTail, frame.dstPeer.NameByte)
dstLen := copy(bufTail, dst)
bufTail = bufTail[dstLen:]
frameLen := len(frame.frame)
binary.BigEndian.PutUint16(bufTail, uint16(frameLen))
binary.BigEndian.PutUint16(bufTail, uint16(len(frame)))
bufTail = bufTail[2:]
copy(bufTail, frame.frame)
ne.bufTail = bufTail[frameLen:]
ne.buffered += srcLen + dstLen + 2 + frameLen
copy(bufTail, frame)
ne.bufTail = bufTail[len(frame):]
ne.buffered += srcLen + dstLen + 2 + len(frame)
}
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 = ne.conn.CheckFatal(err); err != nil {
return []byte{}
}
ne.conn.SendTCP(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 = ne.conn.CheckFatal(err); err != nil {
return []byte{}
}
ne.nonceChan <- nonce
ne.conn.SendTCP(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 {
@@ -212,170 +148,199 @@ func (ne *NaClEncryptor) TotalLen() int {
// Frame Decryptors
func NewNonDecryptor(conn *LocalConnection) *NonDecryptor {
return &NonDecryptor{conn: conn}
type FrameConsumer func(src []byte, dst []byte, frame []byte)
type Decryptor interface {
IterateFrames([]byte, FrameConsumer) error
}
func (nd *NonDecryptor) IterateFrames(fun FrameConsumer, packet *UDPPacket) error {
buf := packet.Packet
for len(buf) >= (2 + NameSize + NameSize) {
srcNameByte := buf[:NameSize]
buf = buf[NameSize:]
dstNameByte := buf[:NameSize]
buf = buf[NameSize:]
length := binary.BigEndian.Uint16(buf[:2])
buf = buf[2:]
if len(buf) < int(length) {
return PacketDecodingError{Desc: fmt.Sprintf("too short; expected frame of length %d, got %d", length, len(buf))}
}
frame := buf[:length]
buf = buf[length:]
err := fun(nd.conn, packet.Sender, srcNameByte, dstNameByte, length, frame)
if err != nil {
return err
}
type NonDecryptor struct {
}
type NaClDecryptor struct {
NonDecryptor
sessionKey *[32]byte
instance *NaClDecryptorInstance
instanceDF *NaClDecryptorInstance
}
type NaClDecryptorInstance struct {
nonce [24]byte
currentWindow uint64
usedOffsets *bit.Set
previousUsedOffsets *bit.Set
}
func NewNaClDecryptorInstance(outbound bool) *NaClDecryptorInstance {
di := &NaClDecryptorInstance{usedOffsets: bit.New()}
if !outbound {
di.nonce[0] |= (1 << 7)
}
if len(buf) > 0 {
return PacketDecodingError{Desc: fmt.Sprintf("%d octets of trailing garbage", len(buf))}
return di
}
type PacketDecodingError struct {
Desc string
}
func NewNonDecryptor() *NonDecryptor {
return &NonDecryptor{}
}
func (nd *NonDecryptor) IterateFrames(packet []byte, consumer FrameConsumer) error {
for len(packet) >= (2 + NameSize + NameSize) {
srcNameByte := packet[:NameSize]
packet = packet[NameSize:]
dstNameByte := packet[:NameSize]
packet = packet[NameSize:]
length := binary.BigEndian.Uint16(packet[:2])
packet = packet[2:]
if len(packet) < int(length) {
return PacketDecodingError{Desc: fmt.Sprintf("too short; expected frame of length %d, got %d", length, len(packet))}
}
frame := packet[:length]
packet = packet[length:]
consumer(srcNameByte, dstNameByte, frame)
}
if len(packet) > 0 {
return PacketDecodingError{Desc: fmt.Sprintf("%d octets of trailing garbage", len(packet))}
}
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 {
inst := NaClDecryptorInstance{
nonce: nil,
previousNonce: nil,
usedOffsets: bit.New(),
previousUsedOffsets: nil,
highestOffsetSeen: 0,
nonceChan: make(chan *[24]byte, ChannelSize)}
instDF := NaClDecryptorInstance{
nonce: nil,
previousNonce: nil,
usedOffsets: bit.New(),
previousUsedOffsets: nil,
highestOffsetSeen: 0,
nonceChan: make(chan *[24]byte, ChannelSize)}
func NewNaClDecryptor(sessionKey *[32]byte, outbound bool) *NaClDecryptor {
return &NaClDecryptor{
NonDecryptor: *NewNonDecryptor(conn),
instance: &inst,
instanceDF: &instDF}
NonDecryptor: *NewNonDecryptor(),
sessionKey: sessionKey,
instance: NewNaClDecryptorInstance(outbound),
instanceDF: NewNaClDecryptorInstance(outbound)}
}
func (nd *NaClDecryptor) Shutdown() {
close(nd.instance.nonceChan)
close(nd.instanceDF.nonceChan)
func (nd *NaClDecryptor) IterateFrames(packet []byte, consumer FrameConsumer) error {
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) ReceiveNonce(msg []byte) {
df, nonce := DecodeNonce(msg)
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 {
nd.instanceDF.nonceChan <- nonce
di = nd.instanceDF
} else {
nd.instance.nonceChan <- nonce
di = nd.instance
}
binary.BigEndian.PutUint64(di.nonce[16:24], seqNoAndDF)
result, success := secretbox.Open(nil, buf[8:], &di.nonce, nd.sessionKey)
if !success {
return nil, false
}
// 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
}
// 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
}
}
func (nd *NaClDecryptor) IterateFrames(fun FrameConsumer, packet *UDPPacket) error {
buf, err := nd.decrypt(packet.Packet)
if err != nil {
return PacketDecodingError{Fatal: true, Desc: fmt.Sprint("decryption failed; ", err)}
}
packet.Packet = buf
return nd.NonDecryptor.IterateFrames(fun, packet)
// 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 (nd *NaClDecryptor) decrypt(buf []byte) ([]byte, error) {
offset := binary.BigEndian.Uint16(buf[:2])
df := (offset & (1 << 15)) != 0
offsetNoFlags := offset & ((1 << 15) - 1)
var decState *NaClDecryptorInstance
if df {
decState = nd.instanceDF
} else {
decState = nd.instance
}
var nonce *[24]byte
var usedOffsets *bit.Set
var ok bool
if decState.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, fmt.Errorf("Unexpected offset when decrypting UDP packet")
}
decState.nonce, ok = <-decState.nonceChan
if !ok {
return nil, fmt.Errorf("Nonce chan closed")
}
decState.highestOffsetSeen = offsetNoFlags
nonce = decState.nonce
usedOffsets = decState.usedOffsets
} else {
highestOffsetSeen := decState.highestOffsetSeen
if 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
decState.previousUsedOffsets = decState.usedOffsets
decState.usedOffsets = bit.New()
decState.previousNonce = decState.nonce
decState.nonce, ok = <-decState.nonceChan
if !ok {
return nil, fmt.Errorf("Nonce chan closed")
}
decState.highestOffsetSeen = offsetNoFlags
nonce = decState.nonce
usedOffsets = decState.usedOffsets
} else if offsetNoFlags > highestOffsetSeen &&
(offsetNoFlags-highestOffsetSeen) < (1<<13) {
// offset is under a quarter above highestOffsetSeen. This
// is ok - maybe some packet loss
decState.highestOffsetSeen = offsetNoFlags
nonce = decState.nonce
usedOffsets = decState.usedOffsets
} else if 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.
nonce = decState.nonce
usedOffsets = decState.usedOffsets
} else if 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
nonce = decState.previousNonce
usedOffsets = decState.previousUsedOffsets
} else {
return nil, fmt.Errorf("Unexpected offset when decrypting UDP packet")
}
}
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)
if success {
usedOffsets.Add(offsetNoFlagsInt)
return result, nil
} else {
return nil, fmt.Errorf("Unable to decrypt UDP packet")
func NewTCPCryptoState(sessionKey *[32]byte, outbound bool) *TCPCryptoState {
s := &TCPCryptoState{sessionKey: sessionKey}
if outbound {
s.nonce[0] |= (1 << 7)
}
return s
}
// TCP Senders
func (s *TCPCryptoState) advance() {
s.seqNo++
binary.BigEndian.PutUint64(s.nonce[16:24], s.seqNo)
}
type TCPSender interface {
Send([]byte) error
}
type SimpleTCPSender struct {
encoder *gob.Encoder
}
type EncryptedTCPSender struct {
SimpleTCPSender
sync.RWMutex
state *TCPCryptoState
}
func NewSimpleTCPSender(encoder *gob.Encoder) *SimpleTCPSender {
return &SimpleTCPSender{encoder: encoder}
@@ -385,36 +350,31 @@ 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)
}
type SimpleTCPReceiver struct {
}
type EncryptedTCPReceiver struct {
SimpleTCPReceiver
state *TCPCryptoState
}
func NewSimpleTCPReceiver() *SimpleTCPReceiver {
return &SimpleTCPReceiver{}
@@ -424,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
}
+15 -20
View File
@@ -8,6 +8,13 @@ import (
"net"
)
type EthernetDecoder struct {
eth layers.Ethernet
ip layers.IPv4
decoded []gopacket.LayerType
parser *gopacket.DecodingLayerParser
}
func NewEthernetDecoder() *EthernetDecoder {
dec := &EthernetDecoder{}
dec.parser = gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &dec.eth, &dec.ip)
@@ -18,21 +25,7 @@ func (dec *EthernetDecoder) DecodeLayers(data []byte) error {
return dec.parser.DecodeLayers(data, &dec.decoded)
}
func (dec *EthernetDecoder) CheckFrameTooBig(err error, sendFrame func([]byte) error) error {
if ftbe, ok := err.(FrameTooBigError); ok {
// we know: 1. ip is valid, 2. it was ip and DF was set
icmpFrame, err := dec.formICMPMTUPacket(ftbe.EPMTU)
if err != nil {
return err
}
log.Printf("Sending ICMP 3,4 (%v -> %v): PMTU= %v\n", dec.ip.DstIP, dec.ip.SrcIP, ftbe.EPMTU)
return sendFrame(icmpFrame)
} else {
return err
}
}
func (dec *EthernetDecoder) formICMPMTUPacket(mtu int) ([]byte, error) {
func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) error) error {
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
FixLengths: true,
@@ -60,9 +53,11 @@ func (dec *EthernetDecoder) formICMPMTUPacket(mtu int) ([]byte, error) {
Seq: uint16(mtu)},
&payload)
if err != nil {
return []byte{}, err
return err
}
return buf.Bytes(), nil
log.Printf("Sending ICMP 3,4 (%v -> %v): PMTU= %v\n", dec.ip.DstIP, dec.ip.SrcIP, mtu)
return sendFrame(buf.Bytes())
}
var (
@@ -75,7 +70,7 @@ func (dec *EthernetDecoder) DropFrame() bool {
return bytes.Equal(stpMACPrefix, dec.eth.DstMAC[:len(stpMACPrefix)])
}
func (dec *EthernetDecoder) IsPMTUVerify() bool {
return bytes.Equal(zeroMAC, dec.eth.SrcMAC) &&
bytes.Equal(zeroMAC, dec.eth.DstMAC)
func (dec *EthernetDecoder) IsSpecial() bool {
return dec.eth.Length == 0 && dec.eth.EthernetType == layers.EthernetTypeLLC &&
bytes.Equal(zeroMAC, dec.eth.SrcMAC) && bytes.Equal(zeroMAC, dec.eth.DstMAC)
}
+221 -260
View File
@@ -3,14 +3,22 @@ package router
import (
"code.google.com/p/gopacket"
"code.google.com/p/gopacket/layers"
"log"
"net"
"syscall"
"time"
)
type ForwardedFrame struct {
srcPeer *Peer
dstPeer *Peer
frame []byte
}
type FrameTooBigError struct {
EPMTU int // effective pmtu, i.e. what we tell packet senders
}
func (conn *LocalConnection) ensureForwarders() error {
if conn.forwardChan != nil || conn.forwardChanDF != nil {
if conn.forwarder != nil || conn.forwarderDF != nil {
return nil
}
udpSender := NewSimpleUDPSender(conn)
@@ -22,54 +30,36 @@ 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)
}
var (
forwardChan = make(chan *ForwardedFrame, ChannelSize)
forwardChanDF = make(chan *ForwardedFrame, ChannelSize)
stopForward = make(chan interface{}, 0)
stopForwardDF = make(chan interface{}, 0)
verifyPMTU = make(chan int, ChannelSize)
)
//NB: only forwarderDF can ever encounter EMSGSIZE errors, and
//thus perform PMTU verification
forwarder := NewForwarder(conn, forwardChan, stopForward, nil, encryptor, udpSender, DefaultPMTU)
forwarderDF := NewForwarder(conn, forwardChanDF, stopForwardDF, verifyPMTU, encryptorDF, udpSenderDF, DefaultPMTU)
forwarder := NewForwarder(conn, encryptor, udpSender, DefaultPMTU)
forwarderDF := NewForwarderDF(conn, encryptorDF, udpSenderDF, DefaultPMTU)
effectivePMTU := forwarderDF.unverifiedPMTU
forwarder.Start()
forwarderDF.Start()
// Various fields in the conn struct are read by other processes,
// so we have to use locks.
conn.Lock()
conn.forwardChan = forwardChan
conn.forwardChanDF = forwardChanDF
conn.stopForward = stopForward
conn.stopForwardDF = stopForwardDF
conn.verifyPMTU = verifyPMTU
conn.effectivePMTU = forwarder.unverifiedPMTU
conn.forwarder = forwarder
conn.forwarderDF = forwarderDF
conn.effectivePMTU = effectivePMTU
conn.Unlock()
go forwarder.Run()
go forwarderDF.Run()
return nil
}
func (conn *LocalConnection) stopForwarders() {
conn.Lock()
conn.forwardChan = nil
conn.forwardChanDF = nil
conn.Unlock()
// Now signal the forwarder loops to exit. They will drain the
// forwarder chans in order to unblock any router processes
// blocked on sending.
if conn.stopForward != nil {
conn.stopForward <- nil
conn.stopForwardDF <- nil
if conn.forwarder == nil || conn.forwarderDF == nil {
return
}
conn.forwarder.Shutdown()
conn.forwarderDF.Shutdown()
}
// Called from peer.Relay[Broadcast] which is itself invoked from
@@ -79,15 +69,15 @@ func (conn *LocalConnection) stopForwarders() {
func (conn *LocalConnection) Forward(df bool, frame *ForwardedFrame, dec *EthernetDecoder) error {
conn.RLock()
var (
forwardChan = conn.forwardChan
forwardChanDF = conn.forwardChanDF
forwarder = conn.forwarder
forwarderDF = conn.forwarderDF
effectivePMTU = conn.effectivePMTU
stackFrag = conn.stackFrag
)
conn.RUnlock()
if forwardChan == nil || forwardChanDF == nil {
conn.log("Cannot forward frame yet - awaiting contact")
if forwarder == nil || forwarderDF == nil {
conn.Log("Cannot forward frame yet - awaiting contact")
return nil
}
// We could use non-blocking channel sends here, i.e. drop frames
@@ -102,29 +92,29 @@ func (conn *LocalConnection) Forward(df bool, frame *ForwardedFrame, dec *Ethern
// of our pipeline.
if df {
if !frameTooBig(frame, effectivePMTU) {
forwardChanDF <- frame
forwarderDF.Forward(frame)
return nil
}
return FrameTooBigError{EPMTU: effectivePMTU}
} else {
if stackFrag || dec == nil || len(dec.decoded) < 2 {
forwardChan <- frame
return nil
}
// Don't have trustworthy stack, so we're going to have to
// send it DF in any case.
if !frameTooBig(frame, effectivePMTU) {
forwardChanDF <- frame
return nil
}
conn.Router.LogFrame("Fragmenting", frame.frame, &dec.eth)
// We can't trust the stack to fragment, we have IP, and we
// have a frame that's too big for the MTU, so we have to
// fragment it ourself.
return fragment(dec.eth, dec.ip, effectivePMTU, frame, func(segFrame *ForwardedFrame) {
forwardChanDF <- segFrame
})
}
if stackFrag || dec == nil || len(dec.decoded) < 2 {
forwarder.Forward(frame)
return nil
}
// Don't have trustworthy stack, so we're going to have to
// send it DF in any case.
if !frameTooBig(frame, effectivePMTU) {
forwarderDF.Forward(frame)
return nil
}
conn.Router.LogFrame("Fragmenting", frame.frame, &dec.eth)
// We can't trust the stack to fragment, we have IP, and we
// have a frame that's too big for the MTU, so we have to
// fragment it ourself.
return fragment(dec.eth, dec.ip, effectivePMTU, frame, func(segFrame *ForwardedFrame) {
forwarderDF.Forward(segFrame)
})
}
func frameTooBig(frame *ForwardedFrame, effectivePMTU int) bool {
@@ -184,7 +174,7 @@ func fragment(eth layers.Ethernet, ip layers.IPv4, pmtu int, frame *ForwardedFra
return err
}
// make copies of the frame we received
var segFrame ForwardedFrame = *frame
segFrame := *frame
segFrame.frame = buf.Bytes()
forward(&segFrame)
}
@@ -193,33 +183,168 @@ func fragment(eth layers.Ethernet, ip layers.IPv4, pmtu int, frame *ForwardedFra
// Forwarder
func NewForwarder(conn *LocalConnection, ch <-chan *ForwardedFrame, stop <-chan interface{}, verifyPMTU <-chan int, enc Encryptor, udpSender UDPSender, pmtu int) *Forwarder {
fwd := &Forwarder{
conn: conn,
ch: ch,
stop: stop,
verifyPMTU: verifyPMTU,
enc: enc,
udpSender: udpSender}
type Forwarder struct {
conn *LocalConnection
ch chan<- *ForwardedFrame
finished <-chan struct{}
enc Encryptor
udpSender UDPSender
maxPayload int
processSendError func(error) error
}
func NewForwarder(conn *LocalConnection, enc Encryptor, udpSender UDPSender, pmtu int) *Forwarder {
return &Forwarder{
conn: conn,
enc: enc,
udpSender: udpSender,
maxPayload: pmtu - UDPOverhead,
processSendError: func(err error) error { return err }}
}
func (fwd *Forwarder) Start() {
ch := make(chan *ForwardedFrame, ChannelSize)
fwd.ch = ch
finished := make(chan struct{})
fwd.finished = finished
go fwd.run(ch, finished)
}
func (fwd *Forwarder) Shutdown() {
fwd.ch <- nil
}
func (fwd *Forwarder) Forward(frame *ForwardedFrame) {
select {
case fwd.ch <- frame:
case <-fwd.finished:
}
}
func (fwd *Forwarder) run(ch <-chan *ForwardedFrame, finished chan<- struct{}) {
defer fwd.udpSender.Shutdown()
for {
if !fwd.accumulateAndSendFrames(ch, <-ch) {
close(finished)
return
}
}
}
func (fwd *Forwarder) effectiveOverhead() int {
return UDPOverhead + fwd.enc.PacketOverhead() + fwd.enc.FrameOverhead() + EthernetOverhead
}
// Drain the inbound channel of frames, aggregating them into larger
// packets for efficient transmission.
//
// FIXME Depending on the golang scheduler, and the rate at which
// franes get sent to the forwarder, we can be going around this loop
// forever. That is bad since there may be other stuff for us to do,
// i.e. the other branches in the run loop.
func (fwd *Forwarder) accumulateAndSendFrames(ch <-chan *ForwardedFrame, frame *ForwardedFrame) bool {
if frame == nil {
return false
}
if !fwd.appendFrame(frame) {
fwd.logDrop(frame)
// [1] The buffer is empty at this point and therefore we must
// not flush it. The easiest way to accomplish that is simply
// by returning to the surrounding run loop.
return true
}
for {
select {
case frame = <-ch:
if frame == nil {
return false
}
if !fwd.appendFrame(frame) {
fwd.flush()
if !fwd.appendFrame(frame) {
fwd.logDrop(frame)
return true // see [1]
}
}
default:
fwd.flush()
return true
}
}
}
func (fwd *Forwarder) logDrop(frame *ForwardedFrame) {
fwd.conn.Log("Dropping too big frame during forwarding: frame len:", len(frame.frame), "; effective PMTU:", fwd.maxPayload+UDPOverhead-fwd.effectiveOverhead())
}
func (fwd *Forwarder) appendFrame(frame *ForwardedFrame) bool {
frameLen := len(frame.frame)
if fwd.enc.TotalLen()+fwd.enc.FrameOverhead()+frameLen > fwd.maxPayload {
return false
}
fwd.enc.AppendFrame(frame.srcPeer.NameByte, frame.dstPeer.NameByte, frame.frame)
return true
}
func (fwd *Forwarder) flush() {
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)
}
}
type ForwarderDF struct {
Forwarder
verifyPMTUTick <-chan time.Time
verifyPMTU chan<- int
pmtuVerifyCount uint
pmtuVerified bool
highestGoodPMTU int
unverifiedPMTU int
lowestBadPMTU int
}
func NewForwarderDF(conn *LocalConnection, enc Encryptor, udpSender UDPSender, pmtu int) *ForwarderDF {
fwd := &ForwarderDF{
Forwarder: Forwarder{
conn: conn,
enc: enc,
udpSender: udpSender,
maxPayload: pmtu - UDPOverhead}}
fwd.Forwarder.processSendError = fwd.processSendError
fwd.unverifiedPMTU = pmtu - fwd.effectiveOverhead()
fwd.maxPayload = pmtu - UDPOverhead
return fwd
}
func (fwd *Forwarder) Run() {
func (fwd *ForwarderDF) Start() {
ch := make(chan *ForwardedFrame, ChannelSize)
fwd.ch = ch
finished := make(chan struct{})
fwd.finished = finished
verifyPMTU := make(chan int, ChannelSize)
fwd.verifyPMTU = verifyPMTU
go fwd.run(ch, finished, verifyPMTU)
}
func (fwd *ForwarderDF) PMTUVerified(pmtu int) {
select {
case fwd.verifyPMTU <- pmtu:
case <-fwd.finished:
}
}
func (fwd *ForwarderDF) run(ch <-chan *ForwardedFrame, finished chan<- struct{}, verifyPMTU <-chan int) {
defer fwd.udpSender.Shutdown()
var flushed, ok bool
var frame *ForwardedFrame
for {
flushed = false
select {
case <-fwd.stop:
fwd.drain()
return
case <-fwd.verifyPMTUTick:
// We only do this case here when we know the buffers are
// all empty so that we don't risk appending verify-frames
// to other data.
// We only do this case here when we know the
// buffers are all empty so that we don't risk
// appending verify-frames to other data.
fwd.verifyPMTUTick = nil
if fwd.pmtuVerified {
continue
@@ -228,12 +353,12 @@ func (fwd *Forwarder) Run() {
fwd.pmtuVerifyCount--
fwd.attemptVerifyEffectivePMTU()
} else {
// we've exceeded the verification attempts of the
// unverifiedPMTU
// we've exceeded the verification
// attempts of the unverifiedPMTU
fwd.lowestBadPMTU = fwd.unverifiedPMTU
fwd.verifyEffectivePMTU((fwd.highestGoodPMTU + fwd.lowestBadPMTU) / 2)
}
case epmtu := <-fwd.verifyPMTU:
case epmtu := <-verifyPMTU:
if fwd.pmtuVerified || epmtu != fwd.unverifiedPMTU {
continue
}
@@ -244,210 +369,46 @@ func (fwd *Forwarder) Run() {
fwd.pmtuVerified = true
fwd.maxPayload = epmtu + fwd.effectiveOverhead() - UDPOverhead
fwd.conn.setEffectivePMTU(epmtu)
fwd.conn.log("Effective PMTU verified at", epmtu)
fwd.conn.Log("Effective PMTU verified at", epmtu)
}
case frame = <-fwd.ch:
if !fwd.appendFrame(frame) {
fwd.logDrop(frame)
continue
}
for !flushed {
select {
case frame, ok = <-fwd.ch:
if !ok {
return
}
if !fwd.appendFrame(frame) {
fwd.flush()
if !fwd.appendFrame(frame) {
fwd.logDrop(frame)
flushed = true
}
}
default:
fwd.flush()
flushed = true
}
case frame := <-ch:
if !fwd.accumulateAndSendFrames(ch, frame) {
close(finished)
return
}
}
}
}
func (fwd *Forwarder) effectiveOverhead() int {
return UDPOverhead + fwd.enc.PacketOverhead() + fwd.enc.FrameOverhead() + EthernetOverhead
}
func (fwd *Forwarder) verifyEffectivePMTU(newUnverifiedPMTU int) {
func (fwd *ForwarderDF) verifyEffectivePMTU(newUnverifiedPMTU int) {
fwd.unverifiedPMTU = newUnverifiedPMTU
fwd.pmtuVerifyCount = PMTUVerifyAttempts
fwd.attemptVerifyEffectivePMTU()
}
func (fwd *Forwarder) attemptVerifyEffectivePMTU() {
pmtuVerifyFrame := &ForwardedFrame{
srcPeer: fwd.conn.local,
dstPeer: fwd.conn.remote,
frame: make([]byte, fwd.unverifiedPMTU+EthernetOverhead)}
fwd.enc.AppendFrame(pmtuVerifyFrame)
func (fwd *ForwarderDF) attemptVerifyEffectivePMTU() {
fwd.enc.AppendFrame(fwd.conn.local.NameByte, fwd.conn.remote.NameByte,
make([]byte, fwd.unverifiedPMTU+EthernetOverhead))
fwd.flush()
if fwd.verifyPMTUTick == nil {
fwd.verifyPMTUTick = time.After(PMTUVerifyTimeout << (PMTUVerifyAttempts - fwd.pmtuVerifyCount))
}
}
func (fwd *Forwarder) appendFrame(frame *ForwardedFrame) bool {
frameLen := len(frame.frame)
if fwd.enc.TotalLen()+fwd.enc.FrameOverhead()+frameLen > fwd.maxPayload {
return false
}
fwd.enc.AppendFrame(frame)
return true
}
func (fwd *Forwarder) flush() {
err := fwd.udpSender.Send(fwd.enc.Bytes())
if err != nil {
if mtbe, ok := err.(MsgTooBigError); ok {
newUnverifiedPMTU := mtbe.PMTU - fwd.effectiveOverhead()
if newUnverifiedPMTU >= fwd.unverifiedPMTU {
return
}
func (fwd *ForwarderDF) processSendError(err error) error {
if mtbe, ok := err.(MsgTooBigError); ok {
newUnverifiedPMTU := mtbe.PMTU - fwd.effectiveOverhead()
if newUnverifiedPMTU < fwd.unverifiedPMTU {
fwd.pmtuVerified = false
fwd.maxPayload = mtbe.PMTU - UDPOverhead
fwd.highestGoodPMTU = 8
fwd.lowestBadPMTU = newUnverifiedPMTU + 1
fwd.conn.setEffectivePMTU(newUnverifiedPMTU)
fwd.verifyEffectivePMTU(newUnverifiedPMTU)
} else if PosixError(err) == syscall.ENOBUFS {
// TODO handle this better
} else {
fwd.conn.CheckFatal(err)
}
return nil
}
}
func (fwd *Forwarder) drain() {
// We want to drain before exiting otherwise we could get the
// packet sniffer or udp listener blocked on sending to a full
// chan
for {
select {
case <-fwd.ch:
default:
return
}
}
}
func (fwd *Forwarder) logDrop(frame *ForwardedFrame) {
fwd.conn.log("Dropping too big frame during forwarding: frame len:", len(frame.frame), "; effective PMTU:", fwd.maxPayload+UDPOverhead-fwd.effectiveOverhead())
}
// UDP Senders
func NewSimpleUDPSender(conn *LocalConnection) *SimpleUDPSender {
return &SimpleUDPSender{udpConn: conn.Router.UDPListener, conn: conn}
}
func (sender *SimpleUDPSender) Send(msg []byte) error {
_, err := sender.udpConn.WriteToUDP(msg, sender.conn.RemoteUDPAddr())
return err
}
func (sender *SimpleUDPSender) Shutdown() error {
return nil
}
func NewRawUDPSender(conn *LocalConnection) (*RawUDPSender, error) {
ipSocket, err := dialIP(conn)
if err != nil {
return nil, err
}
udpHeader := &layers.UDP{SrcPort: layers.UDPPort(Port)}
ipBuf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
FixLengths: true,
// UDP header is calculated with a phantom IP
// header. Yes, it's totally nuts. Thankfully, for UDP
// over IPv4, the checksum is optional. It's not
// optional for IPv6, but we'll ignore that for
// now. TODO
ComputeChecksums: false}
return &RawUDPSender{
ipBuf: ipBuf,
opts: opts,
udpHeader: udpHeader,
socket: ipSocket,
conn: conn}, nil
}
func (sender *RawUDPSender) Send(msg []byte) error {
payload := gopacket.Payload(msg)
sender.udpHeader.DstPort = layers.UDPPort(sender.conn.RemoteUDPAddr().Port)
err := gopacket.SerializeLayers(sender.ipBuf, sender.opts, sender.udpHeader, &payload)
if err != nil {
return err
}
packet := sender.ipBuf.Bytes()
_, err = sender.socket.Write(packet)
if err == nil || PosixError(err) != syscall.EMSGSIZE {
return err
}
f, err := sender.socket.File()
if err != nil {
return err
}
defer f.Close()
fd := int(f.Fd())
log.Println("EMSGSIZE on send, expecting PMTU update (IP packet was",
len(packet), "bytes, payload was", len(msg), "bytes)")
pmtu, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MTU)
if err != nil {
return err
}
return MsgTooBigError{PMTU: pmtu}
}
func (sender *RawUDPSender) Shutdown() error {
defer func() { sender.socket = nil }()
return sender.socket.Close()
}
func dialIP(conn *LocalConnection) (*net.IPConn, error) {
ipLocalAddr, err := ipAddr(conn.TCPConn.LocalAddr())
if err != nil {
return nil, err
}
ipRemoteAddr, err := ipAddr(conn.TCPConn.RemoteAddr())
if err != nil {
return nil, err
}
ipSocket, err := net.DialIP("ip4:UDP", ipLocalAddr, ipRemoteAddr)
if err != nil {
return nil, err
}
f, err := ipSocket.File()
if err != nil {
return nil, err
}
defer f.Close()
fd := int(f.Fd())
// This Makes sure all packets we send out have DF set on them.
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO)
if err != nil {
return nil, err
}
return ipSocket, nil
}
func ipAddr(addr net.Addr) (*net.IPAddr, error) {
host, _, err := net.SplitHostPort(addr.String())
if err != nil {
return nil, err
}
return &net.IPAddr{
IP: net.ParseIP(host),
Zone: ""}, nil
}
+341
View File
@@ -0,0 +1,341 @@
package router
import (
"bytes"
"encoding/gob"
"fmt"
"log"
"sync"
"time"
)
const GossipInterval = 30 * time.Second
type GossipData interface {
Encode() []byte
Merge(GossipData)
}
type Gossip interface {
// specific message from one peer to another
// intermediate peers relay it using unicast topology.
GossipUnicast(dstPeerName PeerName, msg []byte) error
// send gossip to every peer, relayed using broadcast topology.
GossipBroadcast(update GossipData) error
}
type Gossiper interface {
OnGossipUnicast(sender PeerName, msg []byte) error
// merge received data into state and return a representation of
// the received data, for further propagation
OnGossipBroadcast(update []byte) (GossipData, error)
// return state of everything we know; gets called periodically
Gossip() GossipData
// merge received data into state and return "everything new I've
// just learnt", or nil if nothing in the received data was new
OnGossip(update []byte) (GossipData, error)
}
// Accumulates GossipData that needs to be sent to one destination,
// and sends it when possible.
type GossipSender struct {
send func(GossipData)
cell chan GossipData
}
func NewGossipSender(send func(GossipData)) *GossipSender {
return &GossipSender{send: send}
}
func (sender *GossipSender) Start() {
sender.cell = make(chan GossipData, 1)
go sender.run()
}
func (sender *GossipSender) run() {
for {
if pending := <-sender.cell; pending == nil { // receive zero value when chan is closed
break
} else {
sender.send(pending)
}
}
}
func (sender *GossipSender) Send(data GossipData) {
// NB: this must not be invoked concurrently
select {
case pending := <-sender.cell:
pending.Merge(data)
sender.cell <- pending
default:
sender.cell <- data
}
}
func (sender *GossipSender) Stop() {
close(sender.cell)
}
type connectionSenders map[Connection]*GossipSender
type peerSenders map[PeerName]*GossipSender
type GossipChannel struct {
sync.Mutex
ourself *LocalPeer
routes *Routes
name string
hash uint32
gossiper Gossiper
senders connectionSenders
broadcasters peerSenders
}
func (router *Router) NewGossip(channelName string, g Gossiper) Gossip {
channelHash := hash(channelName)
channel := &GossipChannel{
ourself: router.Ourself,
routes: router.Routes,
name: channelName,
hash: channelHash,
gossiper: g,
senders: make(connectionSenders),
broadcasters: make(peerSenders)}
router.GossipChannels[channelHash] = channel
return channel
}
func (router *Router) SendAllGossip() {
for _, channel := range router.GossipChannels {
if gossip := channel.gossiper.Gossip(); gossip != nil {
channel.Send(router.Ourself.Name, gossip)
}
}
}
func (router *Router) SendAllGossipDown(conn Connection) {
for _, channel := range router.GossipChannels {
if gossip := channel.gossiper.Gossip(); gossip != nil {
channel.SendDown(conn, channel.gossiper.Gossip())
}
}
}
func (router *Router) handleGossip(tag ProtocolTag, payload []byte) error {
decoder := gob.NewDecoder(bytes.NewReader(payload))
var channelHash uint32
if err := decoder.Decode(&channelHash); err != nil {
return err
}
channel, found := router.GossipChannels[channelHash]
if !found {
return fmt.Errorf("[gossip] received unknown channel with hash %v", channelHash)
}
var srcName PeerName
if err := decoder.Decode(&srcName); err != nil {
return err
}
switch tag {
case ProtocolGossipUnicast:
return channel.deliverUnicast(srcName, payload, decoder)
case ProtocolGossipBroadcast:
return channel.deliverBroadcast(srcName, payload, decoder)
case ProtocolGossip:
return channel.deliver(srcName, payload, decoder)
}
return nil
}
func (c *GossipChannel) deliverUnicast(srcName PeerName, origPayload []byte, dec *gob.Decoder) error {
var destName PeerName
if err := dec.Decode(&destName); err != nil {
return err
}
if c.ourself.Name != destName {
return c.relayUnicast(destName, origPayload)
}
var payload []byte
if err := dec.Decode(&payload); err != nil {
return err
}
return c.gossiper.OnGossipUnicast(srcName, payload)
}
func (c *GossipChannel) deliverBroadcast(srcName PeerName, _ []byte, dec *gob.Decoder) error {
var payload []byte
if err := dec.Decode(&payload); err != nil {
return err
}
data, err := c.gossiper.OnGossipBroadcast(payload)
if err != nil || data == nil {
return err
}
return c.relayBroadcast(srcName, data)
}
func (c *GossipChannel) deliver(srcName PeerName, _ []byte, dec *gob.Decoder) error {
var payload []byte
if err := dec.Decode(&payload); err != nil {
return err
}
if data, err := c.gossiper.OnGossip(payload); err != nil {
return err
} else if data != nil {
c.Send(srcName, data)
}
return nil
}
func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
// do this outside the lock below so we avoid lock nesting
c.routes.EnsureRecalculated()
selectedConnections := make(ConnectionSet)
for name := range c.routes.RandomNeighbours(srcName) {
if conn, found := c.ourself.ConnectionTo(name); found {
selectedConnections[conn] = void
}
}
if len(selectedConnections) == 0 {
return
}
connections := c.ourself.Connections()
c.Lock()
defer c.Unlock()
// GC - randomly (courtesy of go's map iterator) pick some
// existing entries and stop&remove them if the associated
// connection is no longer active. We stop as soon as we
// encounter a valid entry; the idea being that when there is
// little or no garbage then this executes close to O(1)[1],
// whereas when there is lots of garbage we remove it quickly.
//
// [1] TODO Unfortunately, due to the desire to avoid nested
// locks, instead of simply invoking Peer.ConnectionTo(name)
// below, we have that Peer.Connections() invocation above. That
// is O(n_our_connections) at best.
for conn, sender := range c.senders {
if _, found := connections[conn]; !found {
delete(c.senders, conn)
sender.Stop()
} else {
break
}
}
for conn := range selectedConnections {
c.sendDown(conn, data)
}
}
func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
c.Lock()
c.sendDown(conn, data)
c.Unlock()
}
func (c *GossipChannel) sendDown(conn Connection, data GossipData) {
sender, found := c.senders[conn]
if !found {
sender = NewGossipSender(func(pending GossipData) {
protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.hash, c.ourself.Name, pending.Encode())}
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
})
c.senders[conn] = sender
sender.Start()
}
sender.Send(data)
}
func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error {
return c.relayUnicast(dstPeerName, GobEncode(c.hash, c.ourself.Name, dstPeerName, msg))
}
func (c *GossipChannel) GossipBroadcast(update GossipData) error {
return c.relayBroadcast(c.ourself.Name, update)
}
func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) error {
if relayPeerName, found := c.routes.UnicastAll(dstPeerName); !found {
c.log("unknown relay destination:", dstPeerName)
} else if conn, found := c.ourself.ConnectionTo(relayPeerName); !found {
c.log("unable to find connection to relay peer", relayPeerName)
} else {
conn.(ProtocolSender).SendProtocolMsg(ProtocolMsg{ProtocolGossipUnicast, buf})
}
return nil
}
func (c *GossipChannel) relayBroadcast(srcName PeerName, update GossipData) error {
names := c.routes.PeerNames() // do this outside the lock so they don't nest
c.Lock()
defer c.Unlock()
// GC - randomly (courtesy of go's map iterator) pick some
// existing broadcasters and stop&remove them if their source peer
// is unknown. We stop as soon as we encounter a valid entry; the
// idea being that when there is little or no garbage then this
// executes close to O(1)[1], whereas when there is lots of
// garbage we remove it quickly.
//
// [1] TODO Unfortunately, due to the desire to avoid nested
// locks, instead of simply invoking Peers.Fetch(name) below, we
// have that Peers.Names() invocation above. That is O(n_peers) at
// best.
for name, broadcaster := range c.broadcasters {
if _, found := names[name]; !found {
delete(c.broadcasters, name)
broadcaster.Stop()
} else {
break
}
}
broadcaster, found := c.broadcasters[srcName]
if !found {
broadcaster = NewGossipSender(func(pending GossipData) { c.sendBroadcast(srcName, pending) })
c.broadcasters[srcName] = broadcaster
broadcaster.Start()
}
broadcaster.Send(update)
return nil
}
func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) {
c.routes.EnsureRecalculated()
nextHops := c.routes.BroadcastAll(srcName)
if len(nextHops) == 0 {
return
}
protocolMsg := ProtocolMsg{ProtocolGossipBroadcast, GobEncode(c.hash, srcName, update.Encode())}
// FIXME a single blocked connection can stall us
for _, conn := range c.ourself.ConnectionsTo(nextHops) {
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
}
}
func (c *GossipChannel) log(args ...interface{}) {
log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...)
}
// for testing
// FIXME this doesn't actually guarantee everything has been sent
// since a GossipSender may be in the process of sending and there is
// no easy way for us to know when that has completed.
func (router *Router) sendPendingGossip() {
for _, channel := range router.GossipChannels {
for _, sender := range channel.senders {
sender.flush()
}
for _, sender := range channel.broadcasters {
sender.flush()
}
}
}
func (sender *GossipSender) flush() {
for {
select {
case pending := <-sender.cell:
sender.send(pending)
default:
return
}
}
}
+150
View File
@@ -0,0 +1,150 @@
package router
import (
wt "github.com/weaveworks/weave/testing"
"testing"
"time"
)
// TODO test gossip unicast and broadcast; atm we only test topology
// gossip, which does not employ unicast or broadcast.
type mockChannelConnection struct {
RemoteConnection
dest *Router
}
// Construct a "passive" Router, i.e. without any goroutines, except
// for Routes and GossipSenders.
func NewTestRouter(name PeerName) *Router {
router := NewRouter(RouterConfig{}, name, "")
// need to create a dummy channel otherwise tests hang on nil
// channels when the Router invoked ConnectionMaker.Refresh
router.ConnectionMaker.actionChan = make(chan ConnectionMakerAction, ChannelSize)
router.Routes.Start()
return router
}
func (conn *mockChannelConnection) SendProtocolMsg(protocolMsg ProtocolMsg) {
if err := conn.dest.handleGossip(protocolMsg.tag, protocolMsg.msg); err != nil {
panic(err)
}
conn.dest.sendPendingGossip()
}
func (router *Router) AddTestChannelConnection(r *Router) {
fromName := router.Ourself.Peer.Name
toName := r.Ourself.Peer.Name
fromPeer := NewPeer(fromName, "", router.Ourself.Peer.UID, 0)
toPeer := NewPeer(toName, "", r.Ourself.Peer.UID, 0)
r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount
router.Peers.FetchWithDefault(toPeer) //
conn := &mockChannelConnection{RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r}
router.Ourself.handleAddConnection(conn)
router.Ourself.handleConnectionEstablished(conn)
router.sendPendingGossip()
}
func (router *Router) DeleteTestChannelConnection(r *Router) {
fromName := router.Ourself.Peer.Name
toName := r.Ourself.Peer.Name
fromPeer, _ := r.Peers.Fetch(fromName)
toPeer, _ := router.Peers.Fetch(toName)
r.Peers.Dereference(fromPeer)
router.Peers.Dereference(toPeer)
conn, _ := router.Ourself.ConnectionTo(toName)
router.Ourself.handleDeleteConnection(conn)
router.sendPendingGossip()
}
func TestGossipTopology(t *testing.T) {
wt.RunWithTimeout(t, 1*time.Second, func() {
implTestGossipTopology(t)
})
}
// Create a Peer representing the receiver router, with connections to
// the routers supplied as arguments, carrying across all UID and
// version information.
func (router *Router) tp(routers ...*Router) *Peer {
peer := NewPeer(router.Ourself.Peer.Name, "", router.Ourself.Peer.UID, 0)
connections := make(map[PeerName]Connection)
for _, r := range routers {
p := NewPeer(r.Ourself.Peer.Name, "", r.Ourself.Peer.UID, r.Ourself.Peer.version)
connections[r.Ourself.Peer.Name] = newMockConnection(peer, p)
}
peer.version = router.Ourself.Peer.version
peer.connections = connections
return peer
}
// Check that the topology of router matches the peers and all of their connections
func checkTopology(t *testing.T, router *Router, wantedPeers ...*Peer) {
checkTopologyPeers(t, true, router.Peers.allPeers(), wantedPeers...)
}
func implTestGossipTopology(t *testing.T) {
// Create some peers that will talk to each other
peer1Name, _ := PeerNameFromString("01:00:00:01:00:00")
peer2Name, _ := PeerNameFromString("02:00:00:02:00:00")
peer3Name, _ := PeerNameFromString("03:00:00:03:00:00")
r1 := NewTestRouter(peer1Name)
r2 := NewTestRouter(peer2Name)
r3 := NewTestRouter(peer3Name)
// Check state when they have no connections
checkTopology(t, r1, r1.tp())
checkTopology(t, r2, r2.tp())
// Now try adding some connections
r1.AddTestChannelConnection(r2)
checkTopology(t, r1, r1.tp(r2), r2.tp())
checkTopology(t, r2, r1.tp(r2), r2.tp())
r2.AddTestChannelConnection(r1)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
// Currently, the connection from 2 to 3 is one-way only
r2.AddTestChannelConnection(r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp())
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp())
// When r2 gossiped to r3, 1 was unreachable from r3 so it got removed from the
// list of peers, but remains referenced in the connection from 1 to 3.
checkTopology(t, r3, r2.tp(r1, r3), r3.tp())
// Add a connection from 3 to 1 and now r1 is reachable.
r3.AddTestChannelConnection(r1)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r3, r1.tp(), r2.tp(r1, r3), r3.tp(r1))
r1.AddTestChannelConnection(r3)
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
// Drop the connection from 2 to 3
r2.DeleteTestChannelConnection(r3)
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1))
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
// Drop the connection from 1 to 3
r1.DeleteTestChannelConnection(r3)
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
// r3 still thinks r1 has a connection to it
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
// On a timer, r3 will gossip to r1
r3.SendAllGossip()
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
}
+164
View File
@@ -0,0 +1,164 @@
package router
import (
"encoding/gob"
"encoding/hex"
"fmt"
"strconv"
)
type FieldValidator struct {
fields map[string]string
err error
}
func NewFieldValidator(fields map[string]string) *FieldValidator {
return &FieldValidator{fields, nil}
}
func (fv *FieldValidator) Value(fieldName string) (string, error) {
if fv.err != nil {
return "", fv.err
}
val, found := fv.fields[fieldName]
if !found {
fv.err = fmt.Errorf("Field %s is missing", fieldName)
return "", fv.err
}
return val, nil
}
func (fv *FieldValidator) CheckEqual(fieldName, expectedValue string) error {
val, err := fv.Value(fieldName)
if err != nil {
return err
}
if val != expectedValue {
fv.err = fmt.Errorf("Field %s has wrong value; expected '%s', received '%s'", fieldName, expectedValue, val)
return fv.err
}
return nil
}
func (fv *FieldValidator) Err() error {
return fv.err
}
func (conn *LocalConnection) handshake(enc *gob.Encoder, dec *gob.Decoder, acceptNewPeer bool) error {
// We do not need to worry about locking in here as at this point
// the connection is not reachable by any go-routine other than
// ourself. Only when we add this connection to the conn.local
// peer will it be visible from multiple go-routines.
conn.extendReadDeadline()
localConnID := randUint64()
usingPassword := conn.Router.UsingPassword()
fv, private, err := conn.handshakeSendRecv(localConnID, usingPassword, enc, dec)
if err != nil {
return err
}
nameStr, _ := fv.Value("Name")
nickNameStr, _ := fv.Value("NickName")
uidStr, _ := fv.Value("UID")
remoteConnIDStr, _ := fv.Value("ConnID")
if err := fv.Err(); err != nil {
return err
}
name, err := PeerNameFromString(nameStr)
if err != nil {
return err
}
if !acceptNewPeer {
if _, found := conn.Router.Peers.Fetch(name); !found {
return fmt.Errorf("Found unknown remote name: %s at %s", name, conn.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 := ParsePeerUID(uidStr)
if err != nil {
return err
}
remoteConnID, err := strconv.ParseUint(remoteConnIDStr, 10, 64)
if err != nil {
return err
}
conn.uid = localConnID ^ remoteConnID
remotePublicStr, rpErr := fv.Value("PublicKey")
if usingPassword {
if rpErr != nil {
return rpErr
}
remotePublicSlice, rpErr := hex.DecodeString(remotePublicStr)
if rpErr != nil {
return rpErr
}
remotePublic := [32]byte{}
for idx, elem := range remotePublicSlice {
remotePublic[idx] = elem
}
conn.SessionKey = FormSessionKey(&remotePublic, private, conn.Router.Password)
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()
}
return conn.setRemote(NewPeer(name, nickNameStr, uid, 0))
}
func (conn *LocalConnection) handshakeSendRecv(localConnID uint64, usingPassword bool, enc *gob.Encoder, dec *gob.Decoder) (*FieldValidator, *[32]byte, error) {
versionStr := fmt.Sprint(ProtocolVersion)
handshakeSend := map[string]string{
"Protocol": Protocol,
"ProtocolVersion": versionStr,
"PeerNameFlavour": PeerNameFlavour,
"Name": conn.local.Name.String(),
"NickName": conn.local.NickName,
"UID": fmt.Sprint(conn.local.UID),
"ConnID": fmt.Sprint(localConnID)}
handshakeRecv := map[string]string{}
var public, private *[32]byte
var err error
if usingPassword {
public, private, err = GenerateKeyPair()
if err != nil {
return nil, nil, err
}
handshakeSend["PublicKey"] = hex.EncodeToString(public[:])
}
enc.Encode(handshakeSend)
err = dec.Decode(&handshakeRecv)
if err != nil {
return nil, nil, err
}
fv := NewFieldValidator(handshakeRecv)
fv.CheckEqual("Protocol", Protocol)
fv.CheckEqual("ProtocolVersion", versionStr)
fv.CheckEqual("PeerNameFlavour", PeerNameFlavour)
return fv, private, nil
}
func (conn *LocalConnection) setRemote(toPeer *Peer) error {
toPeer = conn.Router.Peers.FetchWithDefault(toPeer)
switch toPeer {
case nil:
return fmt.Errorf("Connection appears to be with different version of a peer we already know of")
case conn.local:
conn.remote = toPeer // have to do assigment here to ensure Shutdown releases ref count
return fmt.Errorf("Cannot connect to ourself")
default:
conn.remote = toPeer
return nil
}
}
+29
View File
@@ -0,0 +1,29 @@
package router
import (
wt "github.com/weaveworks/weave/testing"
"testing"
)
func TestFieldValidator(t *testing.T) {
testMap := map[string]string{"a": "a"}
fv := NewFieldValidator(testMap)
val, err := fv.Value("a")
wt.AssertNoErr(t, err)
wt.AssertNoErr(t, fv.Err())
wt.AssertEqualString(t, val, "a", "")
_, err = fv.Value("x")
wt.AssertFalse(t, err == nil || fv.Err() == nil, "Expected error")
_, err = fv.Value("a")
wt.AssertFalse(t, err == nil || fv.Err() == nil, "Previous error should be retained")
fv = NewFieldValidator(testMap)
err = fv.CheckEqual("a", "a")
wt.AssertNoErr(t, err)
wt.AssertNoErr(t, fv.Err())
err = fv.CheckEqual("a", "b")
wt.AssertFalse(t, err == nil || fv.Err() == nil, "Expected error")
err = fv.CheckEqual("a", "a")
wt.AssertFalse(t, err == nil || fv.Err() == nil, "Previous error should be retained")
}
+98
View File
@@ -0,0 +1,98 @@
package router
import (
"encoding/json"
"fmt"
"time"
)
func (router *Router) StatusJSON(version, encryption string) ([]byte, error) {
return json.Marshal(struct {
Version string
Encryption string
Name string
NickName string
Interface string
Macs *MacCache
Peers *Peers
Routes *Routes
}{version, encryption, router.Ourself.Name.String(), router.Ourself.NickName, fmt.Sprintf("%v", router.Iface), router.Macs, router.Peers, router.Routes})
// leaving out ConectionMaker due to async complexities
}
func (cache *MacCache) MarshalJSON() ([]byte, error) {
type cacheEntry struct {
Mac string
Name string
NickName string
LastSeen time.Time
}
var entries []*cacheEntry
for key, entry := range cache.table {
entries = append(entries, &cacheEntry{intmac(key).String(), entry.peer.Name.String(), entry.peer.NickName, entry.lastSeen})
}
return json.Marshal(entries)
}
func (peers *Peers) MarshalJSON() ([]byte, error) {
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)
}
func (routes *Routes) MarshalJSON() ([]byte, error) {
routes.RLock()
defer routes.RUnlock()
type uni struct {
Dest, Via PeerName
}
type broad struct {
Source PeerName
Via []PeerName
}
var r struct {
Unicast []*uni
Broadcast []*broad
}
for name, hop := range routes.unicast {
r.Unicast = append(r.Unicast, &uni{name, hop})
}
for name, hops := range routes.broadcast {
r.Broadcast = append(r.Broadcast, &broad{name, hops})
}
return json.Marshal(r)
}
func (conn *RemoteConnection) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Name string
NickName string
TCPAddr string
}{conn.Remote().Name.String(), conn.Remote().NickName, conn.RemoteTCPAddr()})
}
func (name PeerName) MarshalJSON() ([]byte, error) {
return json.Marshal(name.String())
}
+167 -147
View File
@@ -4,41 +4,43 @@ import (
"fmt"
"log"
"net"
"sync"
"time"
)
type LocalPeer struct {
sync.RWMutex
*Peer
Router *Router
queryChan chan<- *PeerInteraction
router *Router
actionChan chan<- LocalPeerAction
}
type PeerInteraction struct {
Interaction
payload interface{}
type LocalPeerAction func()
func NewLocalPeer(name PeerName, nickName string, router *Router) *LocalPeer {
return &LocalPeer{Peer: NewPeer(name, nickName, 0, 0), router: router}
}
func StartLocalPeer(name PeerName, router *Router) *LocalPeer {
peer := &LocalPeer{Peer: NewPeer(name, 0, 0), Router: router}
queryChan := make(chan *PeerInteraction, ChannelSize)
peer.queryChan = queryChan
go peer.queryLoop(queryChan)
return peer
func (peer *LocalPeer) Start() {
actionChan := make(chan LocalPeerAction, ChannelSize)
peer.actionChan = actionChan
go peer.actorLoop(actionChan)
}
func (peer *LocalPeer) Forward(dstPeer *Peer, df bool, frame []byte, dec *EthernetDecoder) error {
return peer.Relay(peer.Peer, dstPeer, df, frame, dec)
}
func (peer *LocalPeer) Broadcast(df bool, frame []byte, dec *EthernetDecoder) error {
return peer.RelayBroadcast(peer.Peer, df, frame, dec)
func (peer *LocalPeer) Broadcast(df bool, frame []byte, dec *EthernetDecoder) {
peer.RelayBroadcast(peer.Peer, df, frame, dec)
}
func (peer *LocalPeer) Relay(srcPeer, dstPeer *Peer, df bool, frame []byte, dec *EthernetDecoder) error {
relayPeerName, found := peer.Router.Routes.Unicast(dstPeer.Name)
relayPeerName, found := peer.router.Routes.Unicast(dstPeer.Name)
if !found {
// Not necessarily an error as there could be a race with the
// dst disappearing whilst the frame is in flight
log.Println("Received packet for unknown destination:", dstPeer.Name)
log.Println("Received packet for unknown destination:", dstPeer)
return nil
}
conn, found := peer.ConnectionTo(relayPeerName)
@@ -54,46 +56,70 @@ func (peer *LocalPeer) Relay(srcPeer, dstPeer *Peer, df bool, frame []byte, dec
dec)
}
func (peer *LocalPeer) RelayBroadcast(srcPeer *Peer, df bool, frame []byte, dec *EthernetDecoder) error {
nextHops := peer.Router.Routes.Broadcast(srcPeer.Name)
func (peer *LocalPeer) RelayBroadcast(srcPeer *Peer, df bool, frame []byte, dec *EthernetDecoder) {
nextHops := peer.router.Routes.Broadcast(srcPeer.Name)
if len(nextHops) == 0 {
return nil
return
}
// We must not hold a read lock on peer during the conn.Forward
// below, since that is a potentially blocking operation (e.g. if
// the channel is full).
nextConns := make([]*LocalConnection, 0, len(nextHops))
peer.RLock()
for _, hopName := range nextHops {
conn, found := peer.connections[hopName]
// Again, !found could just be due to a race.
if found {
nextConns = append(nextConns, conn.(*LocalConnection))
}
}
peer.RUnlock()
var err error
for _, conn := range nextConns {
err = conn.Forward(df, &ForwardedFrame{
for _, conn := range peer.ConnectionsTo(nextHops) {
err := conn.(*LocalConnection).Forward(df, &ForwardedFrame{
srcPeer: srcPeer,
dstPeer: conn.Remote(),
frame: frame},
dec)
if err != nil {
return err
if ftbe, ok := err.(FrameTooBigError); ok {
log.Printf("dropping too big DF broadcast frame (%v -> %v): PMTU= %v\n", dec.ip.DstIP, dec.ip.SrcIP, ftbe.EPMTU)
} else {
log.Println(err)
}
}
}
return nil
}
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()
defer peer.RUnlock()
for _, name := range names {
conn, found := peer.connections[name]
// Again, !found could just be due to a race.
if found {
conns = append(conns, conn)
}
}
return conns
}
func (peer *LocalPeer) CreateConnection(peerAddr string, acceptNewPeer bool) error {
if err := peer.checkConnectionLimit(); err != nil {
return err
}
// We're dialing the remote so that means connections will come from random ports
addrStr := NormalisePeerAddr(peerAddr)
tcpAddr, tcpErr := net.ResolveTCPAddr("tcp4", addrStr)
udpAddr, udpErr := net.ResolveUDPAddr("udp4", addrStr)
tcpAddr, tcpErr := net.ResolveTCPAddr("tcp4", peerAddr)
udpAddr, udpErr := net.ResolveUDPAddr("udp4", peerAddr)
if tcpErr != nil || udpErr != nil {
// they really should have the same value, but just in case...
if tcpErr == nil {
@@ -105,74 +131,55 @@ func (peer *LocalPeer) CreateConnection(peerAddr string, acceptNewPeer bool) err
if err != nil {
return err
}
connRemote := NewRemoteConnection(peer.Peer, nil, tcpConn.RemoteAddr().String())
NewLocalConnection(connRemote, acceptNewPeer, tcpConn, udpAddr, peer.Router)
connRemote := NewRemoteConnection(peer.Peer, nil, tcpConn.RemoteAddr().String(), true, false)
connLocal := NewLocalConnection(connRemote, tcpConn, udpAddr, peer.router)
connLocal.Start(acceptNewPeer)
return nil
}
// ACTOR client API
const (
PAddConnection = iota
PBroadcastTCP = iota
PDeleteConnection = iota
PConnectionEstablished = iota
)
// Sync.
func (peer *LocalPeer) AddConnection(conn *LocalConnection) error {
resultChan := make(chan error)
peer.actionChan <- func() {
resultChan <- peer.handleAddConnection(conn)
}
return <-resultChan
}
// Async: rely on the peer to shut us down if we shouldn't be adding
// ourselves, so therefore this can be async
func (peer *LocalPeer) AddConnection(conn *LocalConnection) {
peer.queryChan <- &PeerInteraction{
Interaction: Interaction{code: PAddConnection},
payload: conn}
// Async.
func (peer *LocalPeer) ConnectionEstablished(conn *LocalConnection) {
peer.actionChan <- func() {
peer.handleConnectionEstablished(conn)
}
}
// Sync.
func (peer *LocalPeer) DeleteConnection(conn *LocalConnection) {
resultChan := make(chan interface{})
peer.queryChan <- &PeerInteraction{
Interaction: Interaction{code: PDeleteConnection, resultChan: resultChan},
payload: conn}
peer.actionChan <- func() {
peer.handleDeleteConnection(conn)
resultChan <- nil
}
<-resultChan
}
// Async.
func (peer *LocalPeer) ConnectionEstablished(conn *LocalConnection) {
peer.queryChan <- &PeerInteraction{
Interaction: Interaction{code: PConnectionEstablished},
payload: conn}
}
// Async.
func (peer *LocalPeer) BroadcastTCP(msg []byte) {
peer.queryChan <- &PeerInteraction{
Interaction: Interaction{code: PBroadcastTCP},
payload: msg}
}
// ACTOR server
func (peer *LocalPeer) queryLoop(queryChan <-chan *PeerInteraction) {
func (peer *LocalPeer) actorLoop(actionChan <-chan LocalPeerAction) {
gossipTimer := time.Tick(GossipInterval)
for {
query, ok := <-queryChan
if !ok {
return
}
switch query.code {
case PAddConnection:
peer.handleAddConnection(query.payload.(*LocalConnection))
case PDeleteConnection:
peer.handleDeleteConnection(query.payload.(*LocalConnection))
query.resultChan <- nil
case PConnectionEstablished:
peer.handleConnectionEstablished(query.payload.(*LocalConnection))
case PBroadcastTCP:
peer.handleBroadcastTCP(query.payload.([]byte))
select {
case action := <-actionChan:
action()
case <-gossipTimer:
peer.router.SendAllGossip()
}
}
}
func (peer *LocalPeer) handleAddConnection(conn *LocalConnection) {
func (peer *LocalPeer) handleAddConnection(conn Connection) error {
if peer.Peer != conn.Local() {
log.Fatal("Attempt made to add connection to peer where peer is not the source of connection")
}
@@ -180,39 +187,54 @@ func (peer *LocalPeer) handleAddConnection(conn *LocalConnection) {
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", toName, peer.Name)
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 {
return
return nil
}
// conn.UID is used as the tie breaker here, in the
// knowledge that both sides will make the same decision.
dupConnLocal := dupConn.(*LocalConnection)
if conn.UID == dupConnLocal.UID {
switch conn.BreakTie(dupConn) {
case TieBreakWon:
dupConn.Shutdown(dupErr)
peer.handleDeleteConnection(dupConn)
case TieBreakLost:
return dupErr
case TieBreakTied:
// oh good grief. Sod it, just kill both of them.
conn.CheckFatal(dupErr)
dupConnLocal.CheckFatal(dupErr)
peer.handleDeleteConnection(dupConnLocal)
return
} else if conn.UID < dupConnLocal.UID {
dupConnLocal.CheckFatal(dupErr)
peer.handleDeleteConnection(dupConnLocal)
} else {
conn.CheckFatal(dupErr)
return
dupConn.Shutdown(dupErr)
peer.handleDeleteConnection(dupConn)
return dupErr
}
}
if err := peer.checkConnectionLimit(); err != nil {
conn.CheckFatal(err)
return
return err
}
peer.Lock()
peer.connections[toName] = conn
peer.Unlock()
_, isConnectedPeer := peer.router.Routes.Unicast(toName)
peer.addConnection(conn)
if isConnectedPeer {
conn.Log("connection added")
} else {
conn.Log("connection added (new peer)")
peer.router.SendAllGossipDown(conn)
}
peer.broadcastPeerUpdate(conn.Remote())
return nil
}
func (peer *LocalPeer) handleDeleteConnection(conn *LocalConnection) {
func (peer *LocalPeer) handleConnectionEstablished(conn Connection) {
if peer.Peer != conn.Local() {
log.Fatal("Peer informed of active connection where peer is not the source of connection")
}
if dupConn, found := peer.connections[conn.Remote().Name]; !found || conn != dupConn {
conn.Shutdown(fmt.Errorf("Cannot set unknown connection active"))
return
}
peer.connectionEstablished(conn)
conn.Log("connection fully established")
peer.broadcastPeerUpdate()
}
func (peer *LocalPeer) handleDeleteConnection(conn Connection) {
if peer.Peer != conn.Local() {
log.Fatal("Attempt made to delete connection from peer where peer is not the source of connection")
}
@@ -223,53 +245,51 @@ func (peer *LocalPeer) handleDeleteConnection(conn *LocalConnection) {
if connFound, found := peer.connections[toName]; !found || connFound != conn {
return
}
peer.Lock()
delete(peer.connections, toName)
peer.Unlock()
broadcast := false
if conn.Established() {
peer.Lock()
peer.version += 1
peer.Unlock()
broadcast = true
}
peer.deleteConnection(conn)
conn.Log("connection deleted")
// Must do garbage collection first to ensure we don't send out an
// update with unreachable peers (can cause looping)
peer.Router.Peers.GarbageCollect()
if broadcast {
peer.broadcastPeerUpdate()
}
peer.router.Peers.GarbageCollect()
peer.broadcastPeerUpdate()
}
func (peer *LocalPeer) handleConnectionEstablished(conn *LocalConnection) {
if peer.Peer != conn.Local() {
log.Fatal("Peer informed of active connection where peer is not the source of connection")
}
if dupConn, found := peer.connections[conn.Remote().Name]; !found || conn != dupConn {
conn.CheckFatal(fmt.Errorf("Cannot set unknown connection active"))
return
}
peer.Lock()
peer.version += 1
peer.Unlock()
log.Println("Peer", peer.Name, "established active connection to remote peer", conn.Remote().Name, "at", conn.RemoteTCPAddr())
peer.broadcastPeerUpdate(conn.Remote())
}
func (peer *LocalPeer) handleBroadcastTCP(msg []byte) {
peer.ForEachConnection(func(_ PeerName, conn Connection) {
conn.(*LocalConnection).SendTCP(msg)
})
}
// helpers
func (peer *LocalPeer) broadcastPeerUpdate(peers ...*Peer) {
peer.Router.Routes.Recalculate()
peer.handleBroadcastTCP(Concat(ProtocolUpdateByte, EncodePeers(append(peers, peer.Peer)...)))
peer.router.Routes.Recalculate()
peer.router.TopologyGossip.GossipBroadcast(NewTopologyGossipData(peer.router.Peers, append(peers, peer.Peer)...))
}
func (peer *LocalPeer) checkConnectionLimit() error {
if 0 != peer.Router.ConnLimit && peer.ConnectionCount() >= peer.Router.ConnLimit {
return fmt.Errorf("Connection limit reached (%v)", peer.Router.ConnLimit)
limit := peer.router.ConnLimit
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)
}
+7 -6
View File
@@ -22,12 +22,14 @@ type MacCache struct {
}
func NewMacCache(maxAge time.Duration, onExpiry func(net.HardwareAddr, *Peer)) *MacCache {
res := &MacCache{
return &MacCache{
table: make(map[uint64]*MacCacheEntry),
maxAge: maxAge,
onExpiry: onExpiry}
res.setExpiryTimer()
return res
}
func (cache *MacCache) Start() {
cache.setExpiryTimer()
}
func (cache *MacCache) Enter(mac net.HardwareAddr, peer *Peer) bool {
@@ -38,9 +40,8 @@ func (cache *MacCache) Enter(mac net.HardwareAddr, peer *Peer) bool {
if found && entry.peer == peer && now.Before(entry.lastSeen.Add(cache.maxAge/10)) {
cache.RUnlock()
return false
} else {
cache.RUnlock()
}
cache.RUnlock()
cache.Lock()
defer cache.Unlock()
entry, found = cache.table[key]
@@ -88,7 +89,7 @@ func (cache *MacCache) String() string {
cache.RLock()
defer cache.RUnlock()
for key, entry := range cache.table {
buf.WriteString(fmt.Sprintf("%v -> %s (%v)\n", intmac(key), entry.peer.Name, entry.lastSeen))
fmt.Fprintf(&buf, "%v -> %s (%v)\n", intmac(key), entry.peer, entry.lastSeen)
}
return buf.String()
}
+113
View File
@@ -0,0 +1,113 @@
// No mocks are tested by this file.
//
// It supplies some mock implementations to other unit tests, and is
// named "...test.go" so it is only compiled under `go test`.
package router
import (
wt "github.com/weaveworks/weave/testing"
"testing"
)
// Add to peers a connection from peers.ourself to p
func (peers *Peers) AddTestConnection(p *Peer) {
toPeer := NewPeer(p.Name, "", p.UID, 0)
peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
conn := newMockConnection(peers.ourself.Peer, toPeer)
peers.ourself.addConnection(conn)
peers.ourself.connectionEstablished(conn)
}
// Add to peers a connection from p1 to p2
func (peers *Peers) AddTestRemoteConnection(p1, p2 *Peer) {
fromName := p1.Name
fromPeer := NewPeer(fromName, "", p1.UID, 0)
fromPeer = peers.FetchWithDefault(fromPeer)
toName := p2.Name
toPeer := NewPeer(toName, "", p2.UID, 0)
toPeer = peers.FetchWithDefault(toPeer)
peers.ourself.addConnection(&RemoteConnection{fromPeer, toPeer, "", false, false})
}
func (peers *Peers) DeleteTestConnection(p *Peer) {
toName := p.Name
toPeer, _ := peers.Fetch(toName)
peers.Dereference(toPeer)
conn, _ := peers.ourself.ConnectionTo(toName)
peers.ourself.deleteConnection(conn)
}
// mockConnection used in testing is very similar to a
// RemoteConnection, without the RemoteTCPAddr(). We are making it a
// separate type in order to distinguish what is created by the test
// from what is created by the real code.
func newMockConnection(from, to *Peer) Connection {
type mockConnection struct{ RemoteConnection }
return &mockConnection{RemoteConnection{from, to, "", false, false}}
}
func checkEqualConns(t *testing.T, ourName PeerName, got, wanted map[PeerName]Connection) {
checkConns := make(PeerNameSet)
for _, conn := range wanted {
checkConns[conn.Remote().Name] = void
}
for _, conn := range got {
remoteName := conn.Remote().Name
if _, found := checkConns[remoteName]; found {
delete(checkConns, remoteName)
} else {
wt.Fatalf(t, "Unexpected connection from %s to %s", ourName, remoteName)
}
}
if len(checkConns) > 0 {
wt.Fatalf(t, "Expected connections not found: from %s to %v", ourName, checkConns)
}
}
// Get all the peers from a Peers in a slice
func (peers *Peers) allPeers() []*Peer {
var res []*Peer
for _, peer := range peers.table {
res = append(res, peer)
}
return res
}
func (peers *Peers) allPeersExcept(excludeName PeerName) []*Peer {
res := peers.allPeers()
for i, peer := range res {
if peer.Name == excludeName {
return append(res[:i], res[i+1:]...)
}
}
return res
}
// Check that the peers slice matches the wanted peers
func checkPeerArray(t *testing.T, peers []*Peer, wantedPeers ...*Peer) {
checkTopologyPeers(t, false, peers, wantedPeers...)
}
// Check that the peers slice matches the wanted peers and optionally
// all of their connections
func checkTopologyPeers(t *testing.T, checkConns bool, peers []*Peer, wantedPeers ...*Peer) {
check := make(map[PeerName]*Peer)
for _, peer := range wantedPeers {
check[peer.Name] = peer
}
for _, peer := range peers {
name := peer.Name
if wantedPeer, found := check[name]; found {
if checkConns {
checkEqualConns(t, name, peer.connections, wantedPeer.connections)
}
delete(check, name)
} else {
wt.Fatalf(t, "Unexpected peer: %s", name)
}
}
if len(check) > 0 {
wt.Fatalf(t, "Expected peers not found: %v", check)
}
}
+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 {
+46 -84
View File
@@ -3,87 +3,47 @@ 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
UID uint64
NickName string
UID PeerUID
version uint64
localRefCount uint64
localRefCount uint64 // maintained by Peers
connections map[PeerName]Connection
}
func NewPeer(name PeerName, uid uint64, version uint64) *Peer {
type ConnectionSet map[Connection]struct{}
func NewPeer(name PeerName, nickName string, uid PeerUID, version uint64) *Peer {
if uid == 0 {
uid = randUint64()
uid = PeerUID(randUint64())
}
return &Peer{
Name: name,
NameByte: name.Bin(),
NickName: nickName,
UID: uid,
version: version,
connections: make(map[PeerName]Connection)}
}
func (peer *Peer) String() string {
peer.RLock()
defer peer.RUnlock()
return fmt.Sprint("Peer ", peer.Name, " (v", peer.version, ") (UID ", peer.UID, ")")
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 += 1
}
func (peer *Peer) DecrementLocalRefCount() {
peer.Lock()
defer peer.Unlock()
peer.localRefCount -= 1
}
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) ForEachConnection(fun func(PeerName, Connection)) {
peer.RLock()
defer peer.RUnlock()
for name, conn := range peer.connections {
fun(name, conn)
}
}
func (peer *Peer) SetVersionAndConnections(version uint64, connections map[PeerName]Connection) {
peer.Lock()
defer peer.Unlock()
peer.version = version
peer.connections = connections
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
@@ -99,21 +59,17 @@ func (peer *Peer) SetVersionAndConnections(version uint64, connections map[PeerN
// same result. This is important since otherwise we risk message loss
// or routing cycles.
//
// When the 'symmetric' flag is set, only symmetric connections are
// considered, i.e. where both sides indicate they have a connection
// to the other.
// When the 'establishedAndSymmetric' flag is set, only connections
// that are marked as 'established' and are symmetric (i.e. where both
// sides indicate they have a connection to the other) are considered.
//
// When a non-nil stopAt peer is supplied, the widening stops when it
// 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.
func (peer *Peer) Routes(stopAt *Peer, symmetric bool) (bool, map[PeerName]PeerName) {
peer.RLock()
defer peer.RUnlock()
// NB: This function should generally be invoked while holding a read
// lock on Peers and LocalPeer.
func (peer *Peer) Routes(stopAt *Peer, establishedAndSymmetric bool) (bool, map[PeerName]PeerName) {
routes := make(map[PeerName]PeerName)
routes[peer.Name] = UnknownPeerName
nextWorklist := []*Peer{peer}
@@ -125,16 +81,10 @@ func (peer *Peer) Routes(stopAt *Peer, symmetric bool) (bool, map[PeerName]PeerN
if curPeer == stopAt {
return true, routes
}
curName := curPeer.Name
for remoteName, conn := range curPeer.connections {
if _, found := routes[remoteName]; found {
continue
}
remote := conn.Remote()
remote.RLock()
if _, found := remote.connections[curName]; !symmetric || found {
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
@@ -142,13 +92,25 @@ func (peer *Peer) Routes(stopAt *Peer, symmetric bool) (bool, map[PeerName]PeerN
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 (
+156 -142
View File
@@ -10,35 +10,54 @@ import (
type Peers struct {
sync.RWMutex
ourself *Peer
macs *MacCache
ourself *LocalPeer
table map[PeerName]*Peer
onGC func(*Peer)
}
func NewPeers(ourself *Peer, macs *MacCache, onGC func(*Peer)) *Peers {
type UnknownPeerError struct {
Name PeerName
}
type NameCollisionError struct {
Name PeerName
}
type PeerNameSet map[PeerName]struct{}
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,
macs: macs,
table: make(map[PeerName]*Peer),
onGC: onGC}
}
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
}
@@ -49,21 +68,33 @@ func (peers *Peers) Fetch(name PeerName) (*Peer, bool) {
return peer, found // GRRR, why can't I inline this!?
}
func (peers *Peers) ForEach(fun func(PeerName, *Peer)) {
func (peers *Peers) Dereference(peer *Peer) {
peers.Lock()
defer peers.Unlock()
peer.localRefCount--
}
func (peers *Peers) ForEach(fun func(*Peer)) {
peers.RLock()
defer peers.RUnlock()
for name, peer := range peers.table {
fun(name, peer)
for _, peer := range peers.table {
fun(peer)
}
}
func (peers *Peers) ApplyUpdate(update []byte) ([]byte, error) {
// Merge an incoming update with our own topology.
//
// We add peers hitherto unknown to us, and update peers for which the
// update contains a more recent version than known to us. The return
// value is a) a representation of the received update, and b) an
// "improved" update containing just these new/updated elements.
func (peers *Peers) ApplyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) {
peers.Lock()
newPeers, decodedUpdate, decodedConns, err := peers.decodeUpdate(update)
if err != nil {
peers.Unlock()
return nil, err
return nil, nil, err
}
// By this point, we know the update doesn't refer to any peers we
@@ -83,20 +114,34 @@ func (peers *Peers) ApplyUpdate(update []byte) ([]byte, error) {
// Don't need to hold peers lock any longer
peers.Unlock()
return encodePeersMap(newUpdate), nil
updateNames := make(PeerNameSet)
for _, peer := range decodedUpdate {
updateNames[peer.Name] = void
}
return updateNames, setFromPeersMap(newUpdate), nil
}
func (peers *Peers) EncodeAllPeers() []byte {
func (peers *Peers) Names() PeerNameSet {
peers.RLock()
defer peers.RUnlock()
return encodePeersMap(peers.table)
return setFromPeersMap(peers.table)
}
func EncodePeers(peers ...*Peer) []byte {
func (peers *Peers) EncodePeers(names PeerNameSet) []byte {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
for _, peer := range peers {
peer.encodePeer(enc)
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)
}
}
return buf.Bytes()
}
@@ -109,71 +154,76 @@ func (peers *Peers) GarbageCollect() []*Peer {
func (peers *Peers) String() string {
var buf bytes.Buffer
peers.ForEach(func(name PeerName, peer *Peer) {
buf.WriteString(fmt.Sprint(peer, "\n"))
peer.ForEachConnection(func(remoteName PeerName, conn Connection) {
buf.WriteString(fmt.Sprintf(" -> %v [%v]\n", remoteName, conn.RemoteTCPAddr()))
})
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) {
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)
}
}
})
return buf.String()
}
func (peers *Peers) fetchAlias(peer *Peer) (*Peer, bool) {
if existingPeer, found := peers.table[peer.Name]; found {
if existingPeer.UID == peer.UID {
existingPeer.IncrementLocalRefCount()
return existingPeer, true
} else {
return nil, 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 {
found, _ := peers.ourself.Routes(peer, false)
if !found && !peer.IsLocallyReferenced() {
peers.onGC(peer)
if _, found := reached[peer.Name]; !found && peer.localRefCount == 0 {
delete(peers.table, name)
peers.macs.Delete(peer)
peers.onGC(peer)
removed = append(removed, peer)
}
}
return removed
}
func encodePeersMap(peers map[PeerName]*Peer) []byte {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
for _, peer := range peers {
peer.encodePeer(enc)
func setFromPeersMap(peers map[PeerName]*Peer) PeerNameSet {
names := make(PeerNameSet)
for name := range peers {
names[name] = void
}
return buf.Bytes()
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, 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, 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
@@ -183,52 +233,37 @@ func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, de
}
}
unknownPeers := false
for _, connsBuf := range decodedConns {
connsIterator(connsBuf, func(remoteNameByte []byte, _ string) {
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.
unknownPeers = true
})
}
if unknownPeers {
err = UnknownPeersError{}
return
err = UnknownPeerError{remoteName}
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 {
if peer.Version() > newPeer.Version() {
// we know more about this one than the update. If
// peer is ourself, this is slightly racey (further
// changes could occur to ourself in the mean
// time). But it doesn't matter as we know that we're
// already > newPeer.Version() and that's not going to
// change.
newUpdate[name] = peer
continue
} else if peer == peers.ourself {
// nobody but us updates us
continue
} else if peer.Version() == newPeer.Version() {
// implication is that connections are equal too
continue
}
if peer != newPeer &&
(peer == peers.ourself.Peer || peer.version >= newPeer.version) {
// Nobody but us updates us. And if we know more about a
// peer than what's in the the update, we ignore the
// latter.
continue
}
// If we're here, either it was a new peer, or the update has
// more info about the peer than we do. Either case, we need
@@ -240,78 +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) encodePeer(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.UID))
checkFatal(enc.Encode(peer.version))
connsBuf := new(bytes.Buffer)
connsEnc := gob.NewEncoder(connsBuf)
connSummaries := []ConnectionSummary{}
for _, conn := range peer.connections {
// DANGER holding rlock on peer, going to take rlock on conn
if !conn.Established() {
continue
}
checkFatal(connsEnc.Encode(conn.Remote().NameByte))
checkFatal(connsEnc.Encode(conn.RemoteTCPAddr()))
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, uid uint64, version uint64, conns []byte, err error) {
err = dec.Decode(&nameByte)
if 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
}
err = dec.Decode(&uid)
if err != nil {
if err = dec.Decode(&connSummaries); err != nil {
return
}
err = dec.Decode(&version)
if err != nil {
return
}
err = dec.Decode(&conns)
if err == io.EOF {
err = nil
}
return
}
func connsIterator(input []byte, fun func([]byte, string)) {
buf := new(bytes.Buffer)
buf.Write(input)
dec := gob.NewDecoder(buf)
for {
var nameByte []byte
err := dec.Decode(&nameByte)
if err == io.EOF {
return
}
checkFatal(err)
var foundAt string
checkFatal(dec.Decode(&foundAt))
fun(nameByte, string(foundAt))
}
}
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)
connsIterator(buf, func(nameByte []byte, remoteTCPAddr string) {
name := PeerNameFromBin(nameByte)
for _, connSummary := range connSummaries {
name := PeerNameFromBin(connSummary.NameByte)
remotePeer := table[name]
conn := NewRemoteConnection(peer, remotePeer, remoteTCPAddr)
conn := NewRemoteConnection(peer, remotePeer, connSummary.RemoteTCPAddr, connSummary.Outbound, connSummary.Established)
conns[name] = conn
})
}
return conns
}
+113
View File
@@ -0,0 +1,113 @@
package router
import (
"fmt"
wt "github.com/weaveworks/weave/testing"
"math/rand"
"testing"
)
// TODO we should also test:
//
// - applying an incremental update, including the case where that
// leads to an UnknownPeerError
//
// - the "improved update" calculation
//
// - non-gc of peers that are only referenced locally
func newNode(name PeerName) (*Peer, *Peers) {
peer := NewLocalPeer(name, "", nil)
peers := NewPeers(peer, func(*Peer) {})
peers.FetchWithDefault(peer.Peer)
return peer.Peer, peers
}
// Check that ApplyUpdate copies the whole topology from peers
func checkApplyUpdate(t *testing.T, peers *Peers) {
dummyName, _ := PeerNameFromString("99:00:00:01:00:00")
// We need a new node outside of the network, with a connection
// into it.
_, testBedPeers := newNode(dummyName)
testBedPeers.AddTestConnection(peers.ourself.Peer)
testBedPeers.ApplyUpdate(peers.EncodePeers(peers.Names()))
checkTopologyPeers(t, true, testBedPeers.allPeersExcept(dummyName), peers.allPeers()...)
}
func TestPeersEncoding(t *testing.T) {
const numNodes = 20
const numIters = 1000
var peer [numNodes]*Peer
var ps [numNodes]*Peers
for i := 0; i < numNodes; i++ {
name, _ := PeerNameFromString(fmt.Sprintf("%02d:00:00:01:00:00", i))
peer[i], ps[i] = newNode(name)
}
var conns []struct{ from, to int }
for i := 0; i < numIters; i++ {
oper := rand.Intn(2)
switch oper {
case 0:
from, to := rand.Intn(numNodes), rand.Intn(numNodes)
if from != to {
if _, found := peer[from].connections[peer[to].Name]; !found {
ps[from].AddTestConnection(peer[to])
conns = append(conns, struct{ from, to int }{from, to})
checkApplyUpdate(t, ps[from])
}
}
case 1:
if len(conns) > 0 {
n := rand.Intn(len(conns))
c := conns[n]
ps[c.from].DeleteTestConnection(peer[c.to])
ps[c.from].GarbageCollect()
checkApplyUpdate(t, ps[c.from])
conns = append(conns[:n], conns[n+1:]...)
}
}
}
}
func TestPeersGarbageCollection(t *testing.T) {
const (
peer1NameString = "01:00:00:01:00:00"
peer2NameString = "02:00:00:02:00:00"
peer3NameString = "03:00:00:03:00:00"
)
var (
peer1Name, _ = PeerNameFromString(peer1NameString)
peer2Name, _ = PeerNameFromString(peer2NameString)
peer3Name, _ = PeerNameFromString(peer3NameString)
)
// Create some peers with some connections to each other
p1, ps1 := newNode(peer1Name)
p2, ps2 := newNode(peer2Name)
p3, ps3 := newNode(peer3Name)
ps1.AddTestConnection(p2)
ps2.AddTestRemoteConnection(p1, p2)
ps2.AddTestConnection(p1)
ps2.AddTestConnection(p3)
ps3.AddTestConnection(p1)
ps1.AddTestConnection(p3)
ps2.AddTestRemoteConnection(p1, p3)
ps2.AddTestRemoteConnection(p3, p1)
// Every peer is referenced, so nothing should be dropped
wt.AssertEmpty(t, ps1.GarbageCollect(), "peers removed")
wt.AssertEmpty(t, ps2.GarbageCollect(), "peers removed")
wt.AssertEmpty(t, ps3.GarbageCollect(), "peers removed")
// Drop the connection from 2 to 3, and 3 isn't garbage-collected
// because 1 has a connection to 3
ps2.DeleteTestConnection(p3)
wt.AssertEmpty(t, ps2.GarbageCollect(), "peers removed")
// Drop the connection from 1 to 3, and 3 will get removed by
// garbage-collection
ps1.DeleteTestConnection(p3)
checkPeerArray(t, ps1.GarbageCollect(), p3)
}
+28
View File
@@ -0,0 +1,28 @@
package router
const (
Protocol = "weave"
ProtocolVersion = 17
)
type ProtocolTag byte
const (
ProtocolHeartbeat ProtocolTag = iota
ProtocolConnectionEstablished
ProtocolFragmentationReceived
ProtocolStartFragmentationTest
ProtocolPMTUVerified
ProtocolGossip
ProtocolGossipUnicast
ProtocolGossipBroadcast
)
type ProtocolMsg struct {
tag ProtocolTag
msg []byte
}
type ProtocolSender interface {
SendProtocolMsg(m ProtocolMsg)
}
+241 -113
View File
@@ -17,55 +17,100 @@ const macMaxAge = 10 * time.Minute // [1]
// [1] should be greater than typical ARP cache expiries, i.e. > 3/2 *
// /proc/sys/net/ipv4_neigh/*/base_reachable_time_ms on Linux
func NewRouter(iface *net.Interface, name PeerName, password []byte, connLimit int, bufSz int, logFrame func(string, []byte, *layers.Ethernet)) *Router {
type LogFrameFunc func(string, []byte, *layers.Ethernet)
type RouterConfig struct {
Port int
Iface *net.Interface
Password []byte
ConnLimit int
BufSz int
LogFrame LogFrameFunc
}
type Router struct {
RouterConfig
Ourself *LocalPeer
Macs *MacCache
Peers *Peers
Routes *Routes
ConnectionMaker *ConnectionMaker
GossipChannels map[uint32]*GossipChannel
TopologyGossip Gossip
UDPListener *net.UDPConn
}
type PacketSource interface {
ReadPacket() ([]byte, error)
}
type PacketSink interface {
WritePacket([]byte) error
}
type PacketSourceSink interface {
PacketSource
PacketSink
}
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.Name)
log.Println("Expired MAC", mac, "at", peer)
}
onPeerGC := func(peer *Peer) {
log.Println("Removing unreachable", peer)
router.Macs.Delete(peer)
log.Println("Removed unreachable peer", peer)
}
router := &Router{
Iface: iface,
Macs: NewMacCache(macMaxAge, onMacExpiry),
ConnLimit: connLimit,
BufSz: bufSz,
LogFrame: logFrame}
if len(password) > 0 {
router.Password = &password
}
router.Ourself = StartLocalPeer(name, router)
router.Peers = NewPeers(router.Ourself.Peer, router.Macs, onPeerGC)
router.Ourself = NewLocalPeer(name, nickName, router)
router.Macs = NewMacCache(macMaxAge, onMacExpiry)
router.Peers = NewPeers(router.Ourself, onPeerGC)
router.Peers.FetchWithDefault(router.Ourself.Peer)
log.Println("Our name is", router.Ourself.Name)
router.Routes = NewRoutes(router.Ourself, router.Peers)
router.ConnectionMaker = NewConnectionMaker(router.Ourself, router.Peers, router.Port)
router.TopologyGossip = router.NewGossip("topology", router)
return router
}
func (router *Router) Start() {
// we need two pcap handles since they aren't thread-safe
var pio PacketSourceSink
var po PacketSink
var err error
if router.Iface != nil {
pio, err = NewPcapIO(router.Iface.Name, router.BufSz)
checkFatal(err)
po, err = NewPcapO(router.Iface.Name)
checkFatal(err)
}
router.Ourself.Start()
router.Macs.Start()
router.Routes.Start()
router.ConnectionMaker.Start()
router.UDPListener = router.listenUDP(router.Port, po)
router.listenTCP(router.Port)
if pio != nil {
router.sniff(pio)
}
}
func (router *Router) Stop() error {
// TODO: perform graceful shutdown...
return nil
}
func (router *Router) UsingPassword() bool {
return router.Password != nil
}
func (router *Router) Start() {
// we need two pcap handles since they aren't thread-safe
pio, err := NewPcapIO(router.Iface.Name, router.BufSz)
checkFatal(err)
po, err := NewPcapO(router.Iface.Name)
checkFatal(err)
router.Routes = StartRoutes(router.Ourself.Peer, router.Peers)
router.ConnectionMaker = StartConnectionMaker(router.Ourself, router.Peers)
router.UDPListener = router.listenUDP(Port, po)
router.listenTCP(Port)
router.sniff(pio)
}
func (router *Router) Status() string {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintln("Our name is", router.Ourself.Name))
buf.WriteString(fmt.Sprintln("Sniffing traffic on", router.Iface))
buf.WriteString(fmt.Sprintf("MACs:\n%s", router.Macs))
buf.WriteString(fmt.Sprintf("Peers:\n%s", router.Peers))
buf.WriteString(fmt.Sprintf("Routes:\n%s", router.Routes))
buf.WriteString(fmt.Sprintf("Reconnects:\n%s", router.ConnectionMaker))
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)
fmt.Fprintf(&buf, "Routes:\n%s", router.Routes)
fmt.Fprintf(&buf, "Reconnects:\n%s", router.ConnectionMaker)
return buf.String()
}
@@ -73,8 +118,6 @@ func (router *Router) sniff(pio PacketSourceSink) {
log.Println("Sniffing traffic on", router.Iface)
dec := NewEthernetDecoder()
injectFrame := func(frame []byte) error { return pio.WritePacket(frame) }
checkFrameTooBig := func(err error) error { return dec.CheckFrameTooBig(err, injectFrame) }
mac := router.Iface.HardwareAddr
if router.Macs.Enter(mac, router.Ourself.Peer) {
log.Println("Discovered our MAC", mac)
@@ -84,16 +127,16 @@ func (router *Router) sniff(pio PacketSourceSink) {
pkt, err := pio.ReadPacket()
checkFatal(err)
router.LogFrame("Sniffed", pkt, nil)
checkWarn(router.handleCapturedPacket(pkt, dec, checkFrameTooBig))
router.handleCapturedPacket(pkt, dec, pio)
}
}()
}
func (router *Router) handleCapturedPacket(frameData []byte, dec *EthernetDecoder, checkFrameTooBig func(error) error) error {
func (router *Router) handleCapturedPacket(frameData []byte, dec *EthernetDecoder, po PacketSink) {
dec.DecodeLayers(frameData)
decodedLen := len(dec.decoded)
if decodedLen == 0 {
return nil
return
}
srcMac := dec.eth.SrcMAC
srcPeer, found := router.Macs.Lookup(srcMac)
@@ -101,18 +144,18 @@ func (router *Router) handleCapturedPacket(frameData []byte, dec *EthernetDecode
// frames, the srcMAC will have been recorded as associated with a
// different peer.
if found && srcPeer != router.Ourself.Peer {
return nil
return
}
if router.Macs.Enter(srcMac, router.Ourself.Peer) {
log.Println("Discovered local MAC", srcMac)
}
if dec.DropFrame() {
return nil
return
}
dstMac := dec.eth.DstMAC
dstPeer, found := router.Macs.Lookup(dstMac)
if found && dstPeer == router.Ourself.Peer {
return nil
return
}
df := decodedLen == 2 && (dec.ip.Flags&layers.IPv4DontFragment != 0)
if df {
@@ -127,11 +170,18 @@ func (router *Router) handleCapturedPacket(frameData []byte, dec *EthernetDecode
frameCopy := make([]byte, frameLen, frameLen)
copy(frameCopy, frameData)
// If we don't know which peer corresponds to the dest MAC,
// broadcast it.
if !found {
return checkFrameTooBig(router.Ourself.Broadcast(df, frameCopy, dec))
} else {
return checkFrameTooBig(router.Ourself.Forward(dstPeer, df, frameCopy, dec))
router.Ourself.Broadcast(df, frameCopy, dec)
return
}
err := router.Ourself.Forward(dstPeer, df, frameCopy, dec)
if ftbe, ok := err.(FrameTooBigError); ok {
err = dec.sendICMPFragNeeded(ftbe.EPMTU, po.WritePacket)
}
checkWarn(err)
}
func (router *Router) listenTCP(localPort int) {
@@ -154,10 +204,13 @@ func (router *Router) listenTCP(localPort int) {
func (router *Router) acceptTCP(tcpConn *net.TCPConn) {
// someone else is dialing us, so our udp sender is the conn
// on Port and we wait for them to send us something on UDP to
// on router.Port and we wait for them to send us something on UDP to
// start.
connRemote := NewRemoteConnection(router.Ourself.Peer, nil, tcpConn.RemoteAddr().String())
NewLocalConnection(connRemote, true, tcpConn, nil, router)
remoteAddrStr := tcpConn.RemoteAddr().String()
log.Printf("->[%s] connection accepted\n", remoteAddrStr)
connRemote := NewRemoteConnection(router.Ourself.Peer, nil, remoteAddrStr, false, false)
connLocal := NewLocalConnection(connRemote, tcpConn, nil, router)
connLocal.Start(true)
}
func (router *Router) listenUDP(localPort int, po PacketSink) *net.UDPConn {
@@ -179,7 +232,6 @@ func (router *Router) listenUDP(localPort int, po PacketSink) *net.UDPConn {
func (router *Router) udpReader(conn *net.UDPConn, po PacketSink) {
defer conn.Close()
dec := NewEthernetDecoder()
handleUDPPacket := router.handleUDPPacketFunc(dec, po)
buf := make([]byte, MaxUDPPacketSize)
for {
n, sender, err := conn.ReadFromUDP(buf)
@@ -195,10 +247,6 @@ func (router *Router) udpReader(conn *net.UDPConn, po PacketSink) {
name := PeerNameFromBin(buf[:NameSize])
packet := make([]byte, n-NameSize)
copy(packet, buf[NameSize:n])
udpPacket := &UDPPacket{
Name: name,
Packet: packet,
Sender: sender}
peerConn, found := router.Ourself.ConnectionTo(name)
if !found {
continue
@@ -207,94 +255,174 @@ func (router *Router) udpReader(conn *net.UDPConn, po PacketSink) {
if !ok {
continue
}
err = relayConn.Decryptor.IterateFrames(handleUDPPacket, udpPacket)
if pde, ok := err.(PacketDecodingError); ok {
if pde.Fatal {
relayConn.CheckFatal(pde)
} else {
relayConn.log(pde.Error())
}
} else {
checkWarn(err)
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
// happen quite easily if there is some connection churn
// between two peers. After all, UDP isn't a
// connection-oriented protocol, yet we pretend it is.
//
// If anything really is seriously, unrecoverably amiss
// with a connection, that will typically result in missed
// heartbeats and the connection getting shut down because
// of that.
relayConn.Log(err)
}
}
}
func (router *Router) handleUDPPacketFunc(dec *EthernetDecoder, po PacketSink) FrameConsumer {
checkFrameTooBig := func(err error, srcPeer *Peer) error {
if err == nil { // optimisation: avoid closure creation in common case
return nil
}
return dec.CheckFrameTooBig(err,
func(icmpFrame []byte) error {
return router.Ourself.Forward(srcPeer, false, icmpFrame, nil)
})
}
return func(relayConn *LocalConnection, sender *net.UDPAddr, srcNameByte, dstNameByte []byte, frameLen uint16, frame []byte) error {
srcName := PeerNameFromBin(srcNameByte)
dstName := PeerNameFromBin(dstNameByte)
srcPeer, found := router.Peers.Fetch(srcName)
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 nil
return
}
dstPeer, found := router.Peers.Fetch(dstName)
dstPeer, found := router.Peers.Fetch(PeerNameFromBin(dstNameByte))
if !found {
return nil
return
}
dec.DecodeLayers(frame)
decodedLen := len(dec.decoded)
if decodedLen == 0 {
return
}
// Handle special frames produced internally (rather than
// captured/forwarded) by the remote router.
//
// We really shouldn't be decoding these above, since they are
// not genuine Ethernet frames. However, it is actually more
// efficient to do so, as we want to optimise for the common
// (i.e. non-special) frames. These always need decoding, and
// detecting special frames is cheaper post decoding than pre.
if decodedLen == 1 && dec.IsSpecial() {
if srcPeer == relayConn.Remote() && dstPeer == router.Ourself.Peer {
handleSpecialFrame(relayConn, sender, frame)
}
return
}
df := decodedLen == 2 && (dec.ip.Flags&layers.IPv4DontFragment != 0)
srcMac := dec.eth.SrcMAC
if dstPeer != router.Ourself.Peer {
// it's not for us, we're just relaying it
if decodedLen == 0 {
return nil
}
if df {
router.LogFrame("Relaying DF", frame, &dec.eth)
} else {
router.LogFrame("Relaying", frame, &dec.eth)
}
return checkFrameTooBig(router.Ourself.Relay(srcPeer, dstPeer, df, frame, dec), srcPeer)
}
if relayConn.Remote().Name == srcPeer.Name {
if frameLen == 0 {
relayConn.SetRemoteUDPAddr(sender)
return nil
} else if frameLen == FragTestSize && bytes.Equal(frame, FragTest) {
relayConn.SendTCP(ProtocolFragmentationReceivedByte)
return nil
} else if frameLen == PMTUDiscoverySize && bytes.Equal(frame, PMTUDiscovery) {
return nil
err := router.Ourself.Relay(srcPeer, dstPeer, df, frame, dec)
if ftbe, ok := err.(FrameTooBigError); ok {
err = dec.sendICMPFragNeeded(ftbe.EPMTU, func(icmpFrame []byte) error {
return router.Ourself.Forward(srcPeer, false, icmpFrame, nil)
})
}
checkWarn(err)
return
}
if decodedLen == 0 {
return nil
}
if dec.IsPMTUVerify() && relayConn.Remote().Name == srcPeer.Name {
frameLenBytes := []byte{0, 0}
binary.BigEndian.PutUint16(frameLenBytes, uint16(frameLen-EthernetOverhead))
relayConn.SendTCP(Concat(ProtocolPMTUVerifiedByte, frameLenBytes))
return nil
}
srcMac := dec.eth.SrcMAC
dstMac := dec.eth.DstMAC
if router.Macs.Enter(srcMac, srcPeer) {
log.Println("Discovered remote MAC", srcMac, "at", srcPeer.Name)
log.Println("Discovered remote MAC", srcMac, "at", srcPeer)
}
if po != nil {
router.LogFrame("Injecting", frame, &dec.eth)
checkWarn(po.WritePacket(frame))
}
router.LogFrame("Injecting", frame, &dec.eth)
checkWarn(po.WritePacket(frame))
dstPeer, found = router.Macs.Lookup(dec.eth.DstMAC)
dstPeer, found = router.Macs.Lookup(dstMac)
if !found || dstPeer != router.Ourself.Peer {
return checkFrameTooBig(router.Ourself.RelayBroadcast(srcPeer, df, frame, dec), srcPeer)
router.LogFrame("Relaying broadcast", frame, &dec.eth)
router.Ourself.RelayBroadcast(srcPeer, df, frame, dec)
}
return nil
}
}
func handleSpecialFrame(relayConn *LocalConnection, sender *net.UDPAddr, frame []byte) {
frameLen := len(frame)
switch {
case frameLen == EthernetOverhead+8:
relayConn.ReceivedHeartbeat(sender, binary.BigEndian.Uint64(frame[EthernetOverhead:]))
case frameLen == FragTestSize && bytes.Equal(frame, FragTest):
relayConn.SendProtocolMsg(ProtocolMsg{ProtocolFragmentationReceived, nil})
case frameLen == PMTUDiscoverySize && bytes.Equal(frame, PMTUDiscovery):
default:
frameLenBytes := []byte{0, 0}
binary.BigEndian.PutUint16(frameLenBytes, uint16(frameLen-EthernetOverhead))
relayConn.SendProtocolMsg(ProtocolMsg{ProtocolPMTUVerified, frameLenBytes})
}
}
// Gossiper methods - the Router is the topology Gossiper
type TopologyGossipData struct {
peers *Peers
update PeerNameSet
}
func NewTopologyGossipData(peers *Peers, update ...*Peer) *TopologyGossipData {
names := make(PeerNameSet)
for _, p := range update {
names[p.Name] = void
}
return &TopologyGossipData{peers: peers, update: names}
}
func (d *TopologyGossipData) Merge(other GossipData) {
for name := range other.(*TopologyGossipData).update {
d.update[name] = void
}
}
func (d *TopologyGossipData) Encode() []byte {
return d.peers.EncodePeers(d.update)
}
func (router *Router) OnGossipUnicast(sender PeerName, msg []byte) error {
return fmt.Errorf("unexpected topology gossip unicast: %v", msg)
}
func (router *Router) OnGossipBroadcast(update []byte) (GossipData, error) {
origUpdate, _, err := router.applyTopologyUpdate(update)
if err != nil || len(origUpdate) == 0 {
return nil, err
}
return &TopologyGossipData{peers: router.Peers, update: origUpdate}, nil
}
func (router *Router) Gossip() GossipData {
return &TopologyGossipData{peers: router.Peers, update: router.Peers.Names()}
}
func (router *Router) OnGossip(update []byte) (GossipData, error) {
_, newUpdate, err := router.applyTopologyUpdate(update)
if err != nil || len(newUpdate) == 0 {
return nil, err
}
return &TopologyGossipData{peers: router.Peers, update: newUpdate}, nil
}
func (router *Router) applyTopologyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) {
origUpdate, newUpdate, err := router.Peers.ApplyUpdate(update)
if _, ok := err.(UnknownPeerError); err != nil && ok {
// That update contained a reference to a peer which wasn't
// itself included in the update, and we didn't know about
// already. We ignore this; eventually we should receive an
// update containing a complete topology.
log.Println("Topology gossip:", err)
return nil, nil, nil
}
if err != nil {
return nil, nil, err
}
if len(newUpdate) > 0 {
router.ConnectionMaker.Refresh()
router.Routes.Recalculate()
}
return origUpdate, newUpdate, nil
}
+147 -65
View File
@@ -3,17 +3,49 @@ package router
import (
"bytes"
"fmt"
"log"
"math"
"sync"
)
type Routes struct {
sync.RWMutex
ourself *Peer
peers *Peers
unicast map[PeerName]PeerName
broadcast map[PeerName][]PeerName
queryChan chan<- *Interaction
ourself *LocalPeer
peers *Peers
unicast map[PeerName]PeerName
unicastAll map[PeerName]PeerName // [1]
broadcast map[PeerName][]PeerName
broadcastAll map[PeerName][]PeerName // [1]
recalculate chan<- *struct{}
wait chan<- chan struct{}
// [1] based on *all* connections, not just established &
// symmetric ones
}
func NewRoutes(ourself *LocalPeer, peers *Peers) *Routes {
routes := &Routes{
ourself: ourself,
peers: peers,
unicast: make(map[PeerName]PeerName),
unicastAll: make(map[PeerName]PeerName),
broadcast: make(map[PeerName][]PeerName),
broadcastAll: make(map[PeerName][]PeerName)}
routes.unicast[ourself.Name] = UnknownPeerName
routes.unicastAll[ourself.Name] = UnknownPeerName
routes.broadcast[ourself.Name] = []PeerName{}
routes.broadcastAll[ourself.Name] = []PeerName{}
return routes
}
func (routes *Routes) Start() {
recalculate := make(chan *struct{}, 1)
wait := make(chan chan struct{})
routes.recalculate = recalculate
routes.wait = wait
go routes.run(recalculate, wait)
}
func (routes *Routes) PeerNames() PeerNameSet {
return routes.peers.Names()
}
func (routes *Routes) Unicast(name PeerName) (PeerName, bool) {
@@ -23,6 +55,13 @@ func (routes *Routes) Unicast(name PeerName) (PeerName, bool) {
return hop, found
}
func (routes *Routes) UnicastAll(name PeerName) (PeerName, bool) {
routes.RLock()
defer routes.RUnlock()
hop, found := routes.unicastAll[name]
return hop, found
}
func (routes *Routes) Broadcast(name PeerName) []PeerName {
routes.RLock()
defer routes.RUnlock()
@@ -33,68 +72,119 @@ func (routes *Routes) Broadcast(name PeerName) []PeerName {
return hops
}
func (routes *Routes) BroadcastAll(name PeerName) []PeerName {
routes.RLock()
defer routes.RUnlock()
hops, found := routes.broadcastAll[name]
if !found {
return []PeerName{}
}
return hops
}
// Choose min(log2(n_peers), n_neighbouring_peers) neighbours, with a
// random distribution that is topology-sensitive, favouring
// neighbours at the end of "bottleneck links". We determine the
// latter based on the unicast routing table. If a neighbour appears
// as the value more frequently than others - meaning that we reach a
// higher proportion of peers via that neighbour than other neighbours
// - then it is chosen with a higher probability.
//
// Note that we choose log2(n_peers) *neighbours*, not
// peers. Consequently, on sparsely connected peers this function
// returns a higher proportion of neighbours than elsewhere. In
// extremis, on peers with fewer than log2(n_peers) neighbours, all
// neighbours are returned.
func (routes *Routes) RandomNeighbours(except PeerName) PeerNameSet {
res := make(PeerNameSet)
routes.RLock()
defer routes.RUnlock()
count := int(math.Log2(float64(len(routes.unicastAll))))
// depends on go's random map iteration
for _, dst := range routes.unicastAll {
if dst != UnknownPeerName && dst != except {
res[dst] = void
if len(res) >= count {
break
}
}
}
return res
}
func (routes *Routes) String() string {
var buf bytes.Buffer
routes.RLock()
defer routes.RUnlock()
buf.WriteString(fmt.Sprintln("unicast:"))
fmt.Fprintln(&buf, "unicast:")
for name, hop := range routes.unicast {
buf.WriteString(fmt.Sprintf("%s -> %s\n", name, hop))
fmt.Fprintf(&buf, "%s -> %s\n", name, hop)
}
buf.WriteString(fmt.Sprintln("broadcast:"))
fmt.Fprintln(&buf, "broadcast:")
for name, hops := range routes.broadcast {
buf.WriteString(fmt.Sprintf("%s -> %v\n", name, hops))
fmt.Fprintf(&buf, "%s -> %v\n", name, hops)
}
// We don't include the 'all' routes here since they are of
// limited utility in troubleshooting
return buf.String()
}
func StartRoutes(ourself *Peer, peers *Peers) *Routes {
queryChan := make(chan *Interaction, ChannelSize)
state := &Routes{
ourself: ourself,
peers: peers,
unicast: make(map[PeerName]PeerName),
broadcast: make(map[PeerName][]PeerName),
queryChan: queryChan}
state.unicast[ourself.Name] = UnknownPeerName
state.broadcast[ourself.Name] = []PeerName{}
go state.queryLoop(queryChan)
return state
}
// ACTOR client API
const (
RRecalculate = iota
)
// Async.
// Request recalculation of the routing table. This is async but can
// effectively be made synchronous with a subsequent call to
// EnsureRecalculated.
func (routes *Routes) Recalculate() {
routes.queryChan <- &Interaction{code: RRecalculate}
// The use of a 1-capacity channel in combination with the
// non-blocking send is an optimisation that results in multiple
// requests being coalesced.
select {
case routes.recalculate <- nil:
default:
}
}
// ACTOR server
// Wait for any preceding Recalculate requests to be processed.
func (routes *Routes) EnsureRecalculated() {
done := make(chan struct{})
routes.wait <- done
<-done
}
func (routes *Routes) queryLoop(queryChan <-chan *Interaction) {
func (routes *Routes) run(recalculate <-chan *struct{}, wait <-chan chan struct{}) {
for {
query, ok := <-queryChan
if !ok {
return
}
switch query.code {
case RRecalculate:
unicast := routes.calculateUnicast()
broadcast := routes.calculateBroadcast()
routes.Lock()
routes.unicast = unicast
routes.broadcast = broadcast
routes.Unlock()
default:
log.Fatal("Unexpected routes query:", query)
select {
case <-recalculate:
routes.calculate()
case done := <-wait:
select {
case <-recalculate:
routes.calculate()
default:
}
close(done)
}
}
}
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
routes.broadcast = broadcast
routes.broadcastAll = broadcastAll
routes.Unlock()
}
// Calculate all the routes for the question: if *we* want to send a
// packet to Peer X, what is the next hop?
//
@@ -104,8 +194,8 @@ func (routes *Routes) queryLoop(queryChan <-chan *Interaction) {
// any knowledge of the MAC address at all. Thus there's no need
// to exchange knowledge of MAC addresses, nor any constraints on
// the routes that we construct.
func (routes *Routes) calculateUnicast() map[PeerName]PeerName {
_, unicast := routes.ourself.Routes(nil, true)
func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) map[PeerName]PeerName {
_, unicast := routes.ourself.Routes(nil, establishedAndSymmetric)
return unicast
}
@@ -127,23 +217,15 @@ func (routes *Routes) calculateUnicast() map[PeerName]PeerName {
// Y =/= Z /\ X.Routes(Y) <= X.Routes(Z) =>
// X.Routes(Y) u [P | Y.HasSymmetricConnectionTo(P)] <= X.Routes(Z)
// where <= is the subset relationship on keys of the returned map.
func (routes *Routes) calculateBroadcast() map[PeerName][]PeerName {
func (routes *Routes) calculateBroadcast(establishedAndSymmetric bool) map[PeerName][]PeerName {
broadcast := make(map[PeerName][]PeerName)
ourself := routes.ourself
routes.peers.ForEach(func(name PeerName, peer *Peer) {
for _, peer := range routes.peers.table {
hops := []PeerName{}
if found, reached := peer.Routes(ourself, true); found {
ourself.ForEachConnection(func(remoteName PeerName, conn Connection) {
if _, found := reached[remoteName]; found {
return
}
if _, found := conn.Remote().ConnectionTo(ourself.Name); found {
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[name] = hops
})
broadcast[peer.Name] = hops
}
return broadcast
}
-269
View File
@@ -1,269 +0,0 @@
package router
import (
"bytes"
"code.google.com/p/go-bit/bit"
"code.google.com/p/gopacket"
"code.google.com/p/gopacket/layers"
"encoding/gob"
"net"
"sync"
"time"
)
type Router struct {
Iface *net.Interface
Ourself *LocalPeer
Macs *MacCache
Peers *Peers
Routes *Routes
ConnectionMaker *ConnectionMaker
UDPListener *net.UDPConn
Password *[]byte
ConnLimit int
BufSz int
LogFrame func(string, []byte, *layers.Ethernet)
}
type Connection interface {
Local() *Peer
Remote() *Peer
RemoteTCPAddr() string
Established() bool
Shutdown()
}
type RemoteConnection struct {
local *Peer
remote *Peer
remoteTCPAddr string
}
type LocalConnection struct {
sync.RWMutex
RemoteConnection
TCPConn *net.TCPConn
tcpSender TCPSender
remoteUDPAddr *net.UDPAddr
established bool
stackFrag bool
effectivePMTU int
SessionKey *[32]byte
heartbeat *time.Ticker
fetchAll *time.Ticker
fragTest *time.Ticker
forwardChan chan<- *ForwardedFrame
forwardChanDF chan<- *ForwardedFrame
stopForward chan<- interface{}
stopForwardDF chan<- interface{}
verifyPMTU chan<- int
Decryptor Decryptor
Router *Router
UID uint64
shutdown bool
queryChan chan<- *ConnectionInteraction
}
type ConnectionInteraction struct {
Interaction
payload interface{}
}
type Forwarder struct {
conn *LocalConnection
ch <-chan *ForwardedFrame
stop <-chan interface{}
verifyPMTUTick <-chan time.Time
verifyPMTU <-chan int
pmtuVerifyCount uint
enc Encryptor
udpSender UDPSender
maxPayload int
pmtuVerified bool
highestGoodPMTU int
unverifiedPMTU int
lowestBadPMTU int
}
type UDPPacket struct {
Name PeerName
Packet []byte
Sender *net.UDPAddr
}
type EthernetDecoder struct {
eth layers.Ethernet
ip layers.IPv4
decoded []gopacket.LayerType
parser *gopacket.DecodingLayerParser
}
type MsgTooBigError struct {
PMTU int // actual pmtu, i.e. what the kernel told us
}
type FrameTooBigError struct {
EPMTU int // effective pmtu, i.e. what we tell packet senders
}
type UnknownPeersError struct {
}
type NameCollisionError struct {
Name PeerName
}
type PacketDecodingError struct {
Fatal bool
Desc string
}
type LocalAddress struct {
ip net.IP
network *net.IPNet
}
type ForwardedFrame struct {
srcPeer *Peer
dstPeer *Peer
frame []byte
}
type Interaction struct {
code int
resultChan chan<- interface{}
}
// UDPSender interface and implementations
type UDPSender interface {
Send([]byte) error
Shutdown() error
}
type SimpleUDPSender struct {
conn *LocalConnection
udpConn *net.UDPConn
}
type RawUDPSender struct {
ipBuf gopacket.SerializeBuffer
opts gopacket.SerializeOptions
udpHeader *layers.UDP
socket *net.IPConn
conn *LocalConnection
}
// TCPSender interface and implementations
type TCPSender interface {
Send([]byte) error
}
type SimpleTCPSender struct {
encoder *gob.Encoder
}
type EncryptedTCPSender struct {
sync.RWMutex
outerEncoder *gob.Encoder
innerEncoder *gob.Encoder
buffer *bytes.Buffer
conn *LocalConnection
msgCount int
}
type EncryptedTCPMessage struct {
Number int
Body []byte
}
// TCPReceiver interface and implementations
type TCPReceiver interface {
Decode([]byte) ([]byte, error)
}
type SimpleTCPReceiver struct {
}
type EncryptedTCPReceiver struct {
conn *LocalConnection
decoder *gob.Decoder
buffer *bytes.Buffer
msgCount int
}
// Encryptor interface and implementations
type Encryptor interface {
FrameOverhead() int
PacketOverhead() int
IsEmpty() bool
Bytes() []byte
AppendFrame(*ForwardedFrame)
TotalLen() int
}
type NonEncryptor struct {
buf []byte
bufTail []byte
buffered int
prefixLen int
}
type NaClEncryptor struct {
NonEncryptor
buf []byte
offset uint16
nonce *[24]byte
nonceChan chan *[24]byte
flags uint16
prefixLen int
conn *LocalConnection
df bool
}
// Decryptor interface and implementations
type FrameConsumer func(*LocalConnection, *net.UDPAddr, []byte, []byte, uint16, []byte) error
type Decryptor interface {
IterateFrames(FrameConsumer, *UDPPacket) error
ReceiveNonce([]byte)
Shutdown()
}
type NonDecryptor struct {
conn *LocalConnection
}
type NaClDecryptor struct {
NonDecryptor
instance *NaClDecryptorInstance
instanceDF *NaClDecryptorInstance
}
type NaClDecryptorInstance struct {
nonce *[24]byte
previousNonce *[24]byte
usedOffsets *bit.Set
previousUsedOffsets *bit.Set
highestOffsetSeen uint16
nonceChan chan *[24]byte
}
// Packet capture/inject interfaces
type PacketSource interface {
ReadPacket() ([]byte, error)
}
type PacketSink interface {
WritePacket([]byte) error
}
type PacketSourceSink interface {
PacketSource
PacketSink
}
+138
View File
@@ -0,0 +1,138 @@
package router
import (
"code.google.com/p/gopacket"
"code.google.com/p/gopacket/layers"
"log"
"net"
"syscall"
)
type UDPSender interface {
Send([]byte) error
Shutdown() error
}
type SimpleUDPSender struct {
conn *LocalConnection
udpConn *net.UDPConn
}
type RawUDPSender struct {
ipBuf gopacket.SerializeBuffer
opts gopacket.SerializeOptions
udpHeader *layers.UDP
socket *net.IPConn
conn *LocalConnection
}
type MsgTooBigError struct {
PMTU int // actual pmtu, i.e. what the kernel told us
}
func NewSimpleUDPSender(conn *LocalConnection) *SimpleUDPSender {
return &SimpleUDPSender{udpConn: conn.Router.UDPListener, conn: conn}
}
func (sender *SimpleUDPSender) Send(msg []byte) error {
_, err := sender.udpConn.WriteToUDP(msg, sender.conn.RemoteUDPAddr())
return err
}
func (sender *SimpleUDPSender) Shutdown() error {
return nil
}
func NewRawUDPSender(conn *LocalConnection) (*RawUDPSender, error) {
ipSocket, err := dialIP(conn)
if err != nil {
return nil, err
}
udpHeader := &layers.UDP{SrcPort: layers.UDPPort(conn.Router.Port)}
ipBuf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
FixLengths: true,
// UDP header is calculated with a phantom IP
// header. Yes, it's totally nuts. Thankfully, for UDP
// over IPv4, the checksum is optional. It's not
// optional for IPv6, but we'll ignore that for
// now. TODO
ComputeChecksums: false}
return &RawUDPSender{
ipBuf: ipBuf,
opts: opts,
udpHeader: udpHeader,
socket: ipSocket,
conn: conn}, nil
}
func (sender *RawUDPSender) Send(msg []byte) error {
payload := gopacket.Payload(msg)
sender.udpHeader.DstPort = layers.UDPPort(sender.conn.RemoteUDPAddr().Port)
err := gopacket.SerializeLayers(sender.ipBuf, sender.opts, sender.udpHeader, &payload)
if err != nil {
return err
}
packet := sender.ipBuf.Bytes()
_, err = sender.socket.Write(packet)
if err == nil || PosixError(err) != syscall.EMSGSIZE {
return err
}
f, err := sender.socket.File()
if err != nil {
return err
}
defer f.Close()
fd := int(f.Fd())
log.Println("EMSGSIZE on send, expecting PMTU update (IP packet was",
len(packet), "bytes, payload was", len(msg), "bytes)")
pmtu, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MTU)
if err != nil {
return err
}
return MsgTooBigError{PMTU: pmtu}
}
func (sender *RawUDPSender) Shutdown() error {
defer func() { sender.socket = nil }()
return sender.socket.Close()
}
func dialIP(conn *LocalConnection) (*net.IPConn, error) {
ipLocalAddr, err := ipAddr(conn.TCPConn.LocalAddr())
if err != nil {
return nil, err
}
ipRemoteAddr, err := ipAddr(conn.TCPConn.RemoteAddr())
if err != nil {
return nil, err
}
ipSocket, err := net.DialIP("ip4:UDP", ipLocalAddr, ipRemoteAddr)
if err != nil {
return nil, err
}
f, err := ipSocket.File()
if err != nil {
return nil, err
}
defer f.Close()
fd := int(f.Fd())
// This Makes sure all packets we send out have DF set on them.
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO)
if err != nil {
return nil, err
}
return ipSocket, nil
}
func ipAddr(addr net.Addr) (*net.IPAddr, error) {
host, _, err := net.SplitHostPort(addr.String())
if err != nil {
return nil, err
}
return &net.IPAddr{
IP: net.ParseIP(host),
Zone: ""}, nil
}
+22 -17
View File
@@ -1,12 +1,17 @@
package router
import (
"bytes"
"crypto/rand"
"encoding/gob"
"fmt"
"hash/fnv"
"log"
"net"
)
var void = struct{}{}
func checkFatal(e error) {
if e != nil {
log.Fatal(e)
@@ -38,8 +43,8 @@ func (ftbe FrameTooBigError) Error() string {
return fmt.Sprint("Frame too big error. Effective PMTU is ", ftbe.EPMTU)
}
func (upe UnknownPeersError) Error() string {
return fmt.Sprint("Reference to unknown peers")
func (upe UnknownPeerError) Error() string {
return fmt.Sprint("Reference to unknown peer ", upe.Name)
}
func (nce NameCollisionError) Error() string {
@@ -50,10 +55,6 @@ func (pde PacketDecodingError) Error() string {
return fmt.Sprint("Failed to decode packet: ", pde.Desc)
}
func (packet UDPPacket) String() string {
return fmt.Sprintf("UDP Packet\n name: %s\n sender: %v\n payload: % X", packet.Name, packet.Sender, packet.Packet)
}
func Concat(elems ...[]byte) []byte {
res := []byte{}
for _, e := range elems {
@@ -73,6 +74,21 @@ func randUint64() (r uint64) {
return
}
func hash(s string) uint32 {
h := fnv.New32a()
h.Write([]byte(s))
return h.Sum32()
}
func GobEncode(items ...interface{}) []byte {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
for _, i := range items {
checkFatal(enc.Encode(i))
}
return buf.Bytes()
}
func macint(mac net.HardwareAddr) (r uint64) {
for _, b := range mac {
r <<= 8
@@ -101,14 +117,3 @@ func (lop ListOfPeers) Swap(i, j int) {
func (lop ListOfPeers) Less(i, j int) bool {
return lop[i].Name < lop[j].Name
}
// given an address like '1.2.3.4:567', return the address if it has a port,
// otherwise return the address with weave's standard port number
func NormalisePeerAddr(peerAddr string) string {
_, _, err := net.SplitHostPort(peerAddr)
if err == nil {
return peerAddr
} else {
return fmt.Sprintf("%s:%d", peerAddr, Port)
}
}
+50
View File
@@ -0,0 +1,50 @@
package main
import (
"log"
"os"
"os/exec"
"os/signal"
"syscall"
)
func main() {
if len(os.Args) == 1 {
log.Fatal("USAGE: sigproxy <command> [arguments ...]")
}
// Install signal handler as soon as possible - channel is buffered so
// we'll catch signals that arrive whilst child process is starting
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
cmd := exec.Command(os.Args[1], os.Args[2:]...)
// These default to /dev/null, so set them explicitly to ours
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
// Only begin delivering signals after the child has started
go func() {
for {
// Signalling PID 0 delivers to our process group
syscall.Kill(0, (<-sc).(syscall.Signal))
}
}()
if err := cmd.Wait(); err != nil {
// Exit status is platform specific so not directly accessible - casts
// required to access system-dependent exit information
if exitErr, ok := err.(*exec.ExitError); ok {
waitStatus := exitErr.Sys().(syscall.WaitStatus)
os.Exit(waitStatus.ExitStatus())
}
os.Exit(1)
}
os.Exit(0)
}
+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
@@ -15,7 +15,7 @@
<!-- HEADER -->
<div id="header_wrap" class="outer">
<header class="inner">
<a id="forkme_banner" href="https://github.com/zettio/weave">View on GitHub</a>
<a id="forkme_banner" href="https://github.com/weaveworks/weave">View on GitHub</a>
<h1 id="project_title">weave</h1>
<h2 id="project_tagline">The Docker Network</h2>
+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.0.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
Loaded 100 of 149 files, more files were not shown because too many files have changed in this diff. Show more