Merge branch '1.0' into latest_release_doc_updates

This commit is contained in:
Matthias Radestock committed 2015-08-18 08:49:31 +01:00
commit a6e2296e0a
52 files changed
+614 -245

No files matched your search

+12
View File
@@ -1,3 +1,15 @@
## Release 1.0.2
This release fixes a number of bugs, including some security
vulnerabilities in the Weave Docker API proxy, hangs and failures in
address allocation, and sporadic failures in name resolution.
More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.0.2).
The release is fully compatible with other 1.0.x versions, so existing
clusters can be upgraded incrementally.
## Release 1.0.1
This is a bug fix release, addressing the following issue:
+1 -1
View File
@@ -38,7 +38,7 @@ all: $(EXPORTS)
travis: $(EXES)
update: $(EXES)
update:
go get -u -f -v -tags -netgo $(addprefix ./,$(dir $(EXES)))
$(WEAVER_EXE) $(WEAVEDNS_EXE) $(WEAVEPROXY_EXE) $(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
+1
View File
@@ -26,6 +26,7 @@ dependencies:
docker save weaveworks/weave-build >$WEAVE_BUILD;
fi
post:
- sudo apt-get update
- sudo apt-get install bc
- pip install requests
- curl https://sdk.cloud.google.com | bash
+9 -3
View File
@@ -2,6 +2,7 @@ package ipam
import (
"fmt"
"github.com/weaveworks/weave/ipam/address"
)
@@ -42,9 +43,14 @@ func (g *allocate) Try(alloc *Allocator) bool {
}
// out of space
if donor, err := alloc.ring.ChoosePeerToAskForSpace(g.r.Start, g.r.End); err == nil {
alloc.debugln("Decided to ask peer", donor, "for space in range", g.r)
alloc.sendSpaceRequest(donor, g.r)
donors := alloc.ring.ChoosePeersToAskForSpace(g.r.Start, g.r.End)
for _, donor := range donors {
if err := alloc.sendSpaceRequest(donor, g.r); err != nil {
alloc.debugln("Problem asking peer", donor, "for space:", err)
} else {
alloc.debugln("Decided to ask peer", donor, "for space in range", g.r)
break
}
}
return false
+4 -5
View File
@@ -202,10 +202,9 @@ func (alloc *Allocator) Lookup(ident string, r address.Range) (address.Address,
}
// Claim an address that we think we should own (Sync)
func (alloc *Allocator) Claim(ident string, addr address.Address, cancelChan <-chan bool) error {
func (alloc *Allocator) Claim(ident string, addr address.Address) error {
resultChan := make(chan error)
op := &claim{resultChan: resultChan, ident: ident, addr: addr,
hasBeenCancelled: hasBeenCancelled(cancelChan)}
op := &claim{resultChan: resultChan, ident: ident, addr: addr}
alloc.doOperation(op, &alloc.pendingClaims)
return <-resultChan
}
@@ -575,14 +574,14 @@ func (alloc *Allocator) propose() {
alloc.gossip.GossipBroadcast(alloc.Gossip())
}
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) {
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) error {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
if err := enc.Encode(r); err != nil {
panic(err)
}
msg := router.Concat([]byte{msgSpaceRequest}, buf.Bytes())
alloc.gossip.GossipUnicast(dest, msg)
return alloc.gossip.GossipUnicast(dest, msg)
}
func (alloc *Allocator) sendRingUpdate(dest router.PeerName) {
+8 -3
View File
@@ -143,7 +143,7 @@ func TestAllocatorClaim(t *testing.T) {
alloc.claimRingForTesting()
addr1, _ := address.ParseIP(testAddr1)
err := alloc.Claim(container3, addr1, nil)
err := alloc.Claim(container3, addr1)
wt.AssertNoErr(t, err)
// Check we get this address back if we try an allocate
addr3, _ := alloc.Allocate(container3, subnet, nil)
@@ -168,7 +168,7 @@ func TestCancel(t *testing.T) {
CIDR = "10.0.1.7/26"
)
router := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
router := TestGossipRouter{make(map[router.PeerName]chan interface{}), 0.0}
alloc1, subnet := makeAllocator("01:00:00:02:00:00", CIDR, 2)
alloc1.SetInterfaces(router.connect(alloc1.ourName, alloc1))
@@ -256,13 +256,17 @@ func TestTransfer(t *testing.T) {
wt.AssertTrue(t, err == nil, "Failed to get address")
router.GossipBroadcast(alloc2.Gossip())
router.flush()
router.GossipBroadcast(alloc3.Gossip())
router.flush()
router.removePeer(alloc2.ourName)
router.removePeer(alloc3.ourName)
alloc2.Stop()
alloc3.Stop()
router.flush()
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc2.ourName.String()))
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc3.ourName.String()))
router.flush()
wt.AssertEquals(t, alloc1.NumFreeAddresses(subnet), address.Offset(1022))
@@ -282,7 +286,8 @@ func TestFakeRouterSimple(t *testing.T) {
alloc1 := allocs[0]
//alloc2 := allocs[1]
alloc1.Allocate("foo", subnet, nil)
_, err := alloc1.Allocate("foo", subnet, nil)
wt.AssertTrue(t, err == nil, "Failed to get address")
}
func TestAllocatorFuzz(t *testing.T) {
+24 -16
View File
@@ -3,28 +3,35 @@ package ipam
import (
"fmt"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/router"
)
type claim struct {
resultChan chan<- error
ident string
addr address.Address
hasBeenCancelled func() bool
resultChan chan<- error
ident string
addr address.Address
}
func (c *claim) sendResult(result error) {
// Make sure we only send a result once, since listener stops listening after that
if c.resultChan != nil {
c.resultChan <- result
close(c.resultChan)
c.resultChan = nil
}
if result != nil {
common.Error.Println("[allocator] " + result.Error())
}
}
// 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
c.sendResult(nil)
return true
}
@@ -34,6 +41,7 @@ func (c *claim) Try(alloc *Allocator) bool {
owner := alloc.ring.Owner(c.addr)
if owner == router.UnknownPeerName {
alloc.infof("Ring is empty; will try later.\n", c.addr, owner)
c.sendResult(nil) // don't make the caller wait
return false
}
if owner != alloc.ourName {
@@ -41,34 +49,34 @@ func (c *claim) Try(alloc *Allocator) bool {
if found {
name = " (" + name + ")"
}
c.resultChan <- fmt.Errorf("address %s is owned by other peer %s%s", c.addr.String(), owner, name)
c.sendResult(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
c.sendResult(nil)
return true
}
if existingIdent == "" {
err := alloc.space.Claim(c.addr)
if err != nil {
c.resultChan <- err
c.sendResult(err)
return true
}
alloc.debugln("Claimed", c.addr, "for", c.ident)
alloc.addOwned(c.ident, c.addr)
c.resultChan <- nil
c.sendResult(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)
c.sendResult(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.")
c.sendResult(fmt.Errorf("Operation cancelled."))
}
func (c *claim) String() string {
+1 -2
View File
@@ -19,14 +19,13 @@ func badRequest(w http.ResponseWriter, err error) {
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
router.Methods("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 {
} else if err := alloc.Claim(ident, ip); err != nil {
badRequest(w, fmt.Errorf("Unable to claim: %s", err))
return
}
+1
View File
@@ -2,6 +2,7 @@ package paxos
import (
"fmt"
"github.com/weaveworks/weave/router"
)
+2 -1
View File
@@ -2,10 +2,11 @@ package paxos
import (
"fmt"
"github.com/weaveworks/weave/router"
"math/rand"
"testing"
"time"
"github.com/weaveworks/weave/router"
)
type TestNode struct {
+28 -19
View File
@@ -348,9 +348,9 @@ func (r *Ring) String() 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.
// ReportFree is used by the allocator to tell the ring how many free
// ips are in a given range, so that ChoosePeersToAskForSpace can make
// more intelligent decisions.
func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
r.assertInvariants()
defer r.assertInvariants()
@@ -392,9 +392,20 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
}
}
// ChoosePeerToAskForSpace chooses a weighted-random peer to ask
// for space in the range [start, end). Assumes start<end.
func (r *Ring) ChoosePeerToAskForSpace(start, end address.Address) (result router.PeerName, err error) {
type weightedPeer struct {
weight float64
peername router.PeerName
}
type weightedPeers []weightedPeer
// Note Less is using > so that bigger weights sort earlier
func (ws weightedPeers) Less(i, j int) bool { return ws[i].weight > ws[j].weight }
func (ws weightedPeers) Len() int { return len(ws) }
func (ws weightedPeers) Swap(i, j int) { ws[i], ws[j] = ws[j], ws[i] }
// ChoosePeersToAskForSpace returns all peers we can ask for space in
// the range [start, end), in weighted-random order. Assumes start<end.
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []router.PeerName {
var (
sum address.Offset
totalSpacePerPeer = make(map[router.PeerName]address.Offset) // Compute total free space per peer
@@ -423,21 +434,19 @@ func (r *Ring) ChoosePeerToAskForSpace(start, end address.Address) (result route
sum += entry.Free
}
if sum == 0 {
err = ErrNoFreeSpace
return
}
// Pick random peer, weighted by total free space
rn := rand.Int63n(int64(sum))
// Compute weighted random numbers, then sort.
// This isn't perfect, e.g. an item with weight 2 will get chosen more than
// twice as often as an item with weight 1, but it's good enough for our purposes.
ws := make(weightedPeers, 0, len(totalSpacePerPeer))
for peername, space := range totalSpacePerPeer {
rn -= int64(space)
if rn < 0 {
return peername, nil
}
ws = append(ws, weightedPeer{weight: float64(space) * rand.Float64(), peername: peername})
}
panic("Should never reach this point")
sort.Sort(ws)
result := make([]router.PeerName, len(ws))
for i, wp := range ws {
result[i] = wp.peername
}
return result
}
func (r *Ring) PickPeerForTransfer() router.PeerName {
+20 -17
View File
@@ -345,39 +345,42 @@ func TestGossip(t *testing.T) {
func TestFindFree(t *testing.T) {
ring1 := New(start, end, peer1name)
_, err := ring1.ChoosePeerToAskForSpace(start, end)
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
peers := ring1.ChoosePeersToAskForSpace(start, end)
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
_, err = ring1.ChoosePeerToAskForSpace(start, end)
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
peers = ring1.ChoosePeersToAskForSpace(start, end)
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
// We shouldn't return outselves
ring1.ReportFree(map[address.Address]address.Offset{start: 10})
_, err = ring1.ChoosePeerToAskForSpace(start, end)
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
peers = ring1.ChoosePeersToAskForSpace(start, end)
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 1},
{Token: middle, Peer: peer1name, Free: 1}}
_, err = ring1.ChoosePeerToAskForSpace(start, end)
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
peers = ring1.ChoosePeersToAskForSpace(start, end)
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
ring1.assertInvariants()
// We should return others
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
peer, err := ring1.ChoosePeerToAskForSpace(start, end)
wt.AssertSuccess(t, err)
wt.AssertEquals(t, peer, peer2name)
peers = ring1.ChoosePeersToAskForSpace(start, end)
wt.AssertTrue(t, len(peers) == 1, "Expected one peer")
wt.AssertEquals(t, peers[0], peer2name)
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1},
{Token: middle, Peer: peer3name, Free: 1}}
peer, err = ring1.ChoosePeerToAskForSpace(start, middle)
wt.AssertSuccess(t, err)
wt.AssertEquals(t, peer, peer2name)
peers = ring1.ChoosePeersToAskForSpace(start, middle)
wt.AssertTrue(t, len(peers) == 1, "Expected one peer")
wt.AssertEquals(t, peers[0], peer2name)
peer, err = ring1.ChoosePeerToAskForSpace(middle, end)
wt.AssertSuccess(t, err)
wt.AssertEquals(t, peer, peer3name)
peers = ring1.ChoosePeersToAskForSpace(middle, end)
wt.AssertTrue(t, len(peers) == 1, "Expected one peer")
wt.AssertEquals(t, peers[0], peer3name)
peers = ring1.ChoosePeersToAskForSpace(start, end)
wt.AssertTrue(t, len(peers) == 2, "Expected two peers")
ring1.assertInvariants()
}
+58 -36
View File
@@ -200,33 +200,47 @@ func AssertNothingSentErr(t *testing.T, ch <-chan error) {
}
// Router to convey gossip from one gossiper to another, for testing
type gossipMessage struct {
isUnicast bool
sender *router.PeerName
buf []byte // for unicast
data router.GossipData // for broadcast
exitChan chan bool
type unicastMessage struct {
sender router.PeerName
buf []byte
}
type broadcastMessage struct {
data router.GossipData
}
type exitMessage struct {
exitChan chan struct{}
}
type flushMessage struct {
flushChan chan struct{}
}
type TestGossipRouter struct {
gossipChans map[router.PeerName]chan gossipMessage
gossipChans map[router.PeerName]chan interface{}
loss float32 // 0.0 means no loss
}
func (grouter *TestGossipRouter) GossipBroadcast(update router.GossipData) error {
for _, gossipChan := range grouter.gossipChans {
select {
case gossipChan <- gossipMessage{data: update}:
case gossipChan <- broadcastMessage{data: update}:
default: // drop the message if we cannot send it
}
}
return nil
}
func (grouter *TestGossipRouter) flush() {
for _, gossipChan := range grouter.gossipChans {
flushChan := make(chan struct{})
gossipChan <- flushMessage{flushChan: flushChan}
<-flushChan
}
}
func (grouter *TestGossipRouter) removePeer(peer router.PeerName) {
gossipChan := grouter.gossipChans[peer]
resultChan := make(chan bool)
gossipChan <- gossipMessage{exitChan: resultChan}
resultChan := make(chan struct{})
gossipChan <- exitMessage{exitChan: resultChan}
<-resultChan
delete(grouter.gossipChans, peer)
}
@@ -236,39 +250,47 @@ type TestGossipRouterClient struct {
sender router.PeerName
}
func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router.Gossiper) router.Gossip {
gossipChan := make(chan gossipMessage, 100)
func (grouter *TestGossipRouter) run(gossiper router.Gossiper, gossipChan chan interface{}) {
gossipTimer := time.Tick(10 * time.Second)
for {
select {
case gossip := <-gossipChan:
switch message := gossip.(type) {
case exitMessage:
close(message.exitChan)
return
go func() {
gossipTimer := time.Tick(10 * time.Second)
for {
select {
case message := <-gossipChan:
if message.exitChan != nil {
message.exitChan <- true
return
}
case flushMessage:
close(message.flushChan)
case unicastMessage:
if rand.Float32() > (1.0 - grouter.loss) {
continue
}
if err := gossiper.OnGossipUnicast(message.sender, message.buf); err != nil {
panic(fmt.Sprintf("Error doing gossip unicast to %s: %s", message.sender, err))
}
if message.isUnicast {
if err := gossiper.OnGossipUnicast(*message.sender, message.buf); err != nil {
panic(fmt.Sprintf("Error doing gossip unicast to %s: %s", sender, err))
}
} else {
for _, msg := range message.data.Encode() {
if _, err := gossiper.OnGossipBroadcast(msg); err != nil {
panic(fmt.Sprintf("Error doing gossip broadcast to %s: %s", sender, err))
}
case broadcastMessage:
if rand.Float32() > (1.0 - grouter.loss) {
continue
}
for _, msg := range message.data.Encode() {
if _, err := gossiper.OnGossipBroadcast(msg); err != nil {
panic(fmt.Sprintf("Error doing gossip broadcast: %s", err))
}
}
case <-gossipTimer:
grouter.GossipBroadcast(gossiper.Gossip())
}
case <-gossipTimer:
grouter.GossipBroadcast(gossiper.Gossip())
}
}()
}
}
func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router.Gossiper) router.Gossip {
gossipChan := make(chan interface{}, 100)
go grouter.run(gossiper, gossipChan)
grouter.gossipChans[sender] = gossipChan
return TestGossipRouterClient{grouter, sender}
@@ -276,7 +298,7 @@ func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router
func (client TestGossipRouterClient) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
select {
case client.router.gossipChans[dstPeerName] <- gossipMessage{isUnicast: true, sender: &client.sender, buf: buf}:
case client.router.gossipChans[dstPeerName] <- unicastMessage{sender: client.sender, buf: buf}:
default: // drop the message if we cannot send it
common.Error.Printf("Dropping message")
}
@@ -289,7 +311,7 @@ func (client TestGossipRouterClient) GossipBroadcast(update router.GossipData) e
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, TestGossipRouter, address.Range) {
gossipRouter := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
gossipRouter := TestGossipRouter{make(map[router.PeerName]chan interface{}), 0.0}
allocs := make([]*Allocator, size)
var subnet address.Range
@@ -303,7 +325,7 @@ func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, TestGossipRou
}
gossipRouter.GossipBroadcast(allocs[size-1].Gossip())
time.Sleep(1000 * time.Millisecond)
gossipRouter.flush()
return allocs, gossipRouter, subnet
}
+3
View File
@@ -395,6 +395,9 @@ func (s *DNSServer) notUsHandler(proto dnsProtocol) dns.HandlerFunc {
}
Debug.Printf("[dns msgid %d] Given answer by %s for query %s",
r.MsgHdr.Id, server, q.Name)
if reply.Len() > maxLen {
reply.Compress = true
}
w.WriteMsg(reply)
return
}
+20 -5
View File
@@ -3,12 +3,14 @@ package nameserver
import (
"encoding/binary"
"fmt"
"math/rand"
"net"
"testing"
"time"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
wt "github.com/weaveworks/weave/testing"
)
@@ -32,11 +34,12 @@ func TestUDPDNSServer(t *testing.T) {
setupForTest(t)
const (
successTestName = "test1.weave.local."
failTestName = "fail.weave.local."
nonLocalName = "weave.works."
testAddr1 = "10.2.2.1"
containerID = "somecontainer"
successTestName = "test1.weave.local."
failTestName = "fail.weave.local."
compressedTestName = "compressed.weave.works."
nonLocalName = "weave.works."
testAddr1 = "10.2.2.1"
containerID = "somecontainer"
)
testCIDR1 := testAddr1 + "/24"
@@ -72,6 +75,15 @@ func TestUDPDNSServer(t *testing.T) {
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
} else if q.Name == compressedTestName {
// Construct a response that is >512 when uncompressed, <512 when compressed
header := makeHeader(m, &q, 300)
for m.Len() <= minUDPSize {
ip := address.Address(rand.Uint32()).IP4()
m.Answer = append(m.Answer, &dns.A{Hdr: *header, A: ip})
}
m.Compress = true
wt.AssertTrue(t, m.Len() <= minUDPSize, fmt.Sprintf("length: %d", m.Len()))
}
w.WriteMsg(m)
}
@@ -125,6 +137,9 @@ func TestUDPDNSServer(t *testing.T) {
assertExchange(t, testRDNSnonlocal, dns.TypePTR, testPort, 1, -1, 0)
_, r = assertExchange(t, compressedTestName, dns.TypeA, testPort, -1, -1, 0)
wt.AssertTrue(t, r.Len() > minUDPSize, "unexpected message length")
// Not testing MDNS functionality of server here (yet), since it
// needs two servers, each listening on its own address
}
+5 -3
View File
@@ -103,9 +103,11 @@ func (zone *ZoneDb) domainLookup(target string, lfun ZoneLookupFunc) (res []Zone
for _, answer := range lanswers {
r, err := remoteIdent.addIPToName(answer, now)
if err != nil {
zone.mx.Unlock()
Warning.Printf("[zonedb] '%s' insertion for %s failed: %s", answer, target, err)
return nil, err
if _, ok := err.(DuplicateError); !ok {
zone.mx.Unlock()
Warning.Printf("[zonedb] '%s' insertion for %s failed: %s", answer, target, err)
return nil, err
}
}
uniq.add(r)
}
+8 -1
View File
@@ -2,6 +2,7 @@ package net
import (
"fmt"
"math"
"net"
"time"
)
@@ -10,11 +11,17 @@ const (
sleepTime = 100 * time.Millisecond
)
// Wait `wait` seconds for an interface to come up. Pass zero to check once
// and return immediately, or a negative value to wait indefinitely.
func EnsureInterface(ifaceName string, wait int) (iface *net.Interface, err error) {
if iface, err = findInterface(ifaceName); err == nil || wait == 0 {
return
}
for i := time.Duration(wait) * time.Second / sleepTime; err != nil && i > 0; i-- {
i := int64(math.MaxInt64)
if wait > 0 {
i = int64(time.Duration(wait) * time.Second / sleepTime)
}
for ; err != nil && i > 0; i-- {
time.Sleep(sleepTime)
iface, err = findInterface(ifaceName)
}
+1 -1
View File
@@ -3,4 +3,4 @@ MAINTAINER Weaveworks Inc <help@weave.works>
WORKDIR /home/weave
ADD ./weavedns /home/weave/
EXPOSE 53/udp
ENTRYPOINT ["/home/weave/weavedns", "-wait", "20"]
ENTRYPOINT ["/home/weave/weavedns"]
+1 -1
View File
@@ -46,7 +46,7 @@ func main() {
flag.StringVar(&ifaceName, "iface", "", "name of interface to use for multicast")
flag.StringVar(&apiPath, "api", "unix:///var/run/docker.sock", "path to Docker API socket")
flag.StringVar(&domain, "domain", weavedns.DefaultLocalDomain, "local domain (ie, 'weave.local.')")
flag.IntVar(&wait, "wait", 0, "number of seconds to wait for interface to be created and come up")
flag.IntVar(&wait, "wait", -1, "number of seconds to wait for interfaces to come up (0=don't wait, -1=wait forever)")
flag.IntVar(&dnsPort, "dnsport", weavedns.DefaultServerPort, "port to listen to DNS requests")
flag.StringVar(&httpIfaceName, "httpiface", "", "interface on which to listen for HTTP requests (defaults to empty string which listens on all interfaces)")
flag.IntVar(&httpPort, "httpport", weavedns.DefaultHTTPPort, "port to listen to HTTP requests")
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"os"
"strings"
"code.google.com/p/getopt"
"github.com/pborman/getopt"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/proxy"
)
+1 -1
View File
@@ -2,4 +2,4 @@ FROM scratch
MAINTAINER Weaveworks Inc <help@weave.works>
WORKDIR /home/weave
ADD ./weaver /home/weave/
ENTRYPOINT ["/home/weave/weaver", "-wait", "20"]
ENTRYPOINT ["/home/weave/weaver"]
+2 -2
View File
@@ -11,8 +11,8 @@ import (
"runtime"
"strings"
"code.google.com/p/gopacket/layers"
"github.com/davecheney/profile"
"github.com/google/gopacket/layers"
"github.com/gorilla/mux"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
@@ -64,7 +64,7 @@ func main() {
flag.StringVar(&routerName, "name", "", "name of router (defaults to MAC of interface)")
flag.StringVar(&nickName, "nickname", "", "nickname of peer (defaults to hostname)")
flag.StringVar(&password, "password", "", "network password")
flag.IntVar(&wait, "wait", 0, "number of seconds to wait for interface to be created and come up (0 = don't wait)")
flag.IntVar(&wait, "wait", -1, "number of seconds to wait for interface to come up (0=don't wait, -1=wait forever)")
flag.BoolVar(&debug, "debug", false, "enable debug logging")
flag.BoolVar(&pktdebug, "pktdebug", false, "enable per-packet debug logging")
flag.StringVar(&prof, "profile", "", "enable profiling and write profiles to given path")
+14 -18
View File
@@ -6,33 +6,29 @@ import (
"os/exec"
"os/signal"
"syscall"
"time"
"github.com/weaveworks/weave/net"
weavenet "github.com/weaveworks/weave/net"
"github.com/weaveworks/weave/proxy"
)
func main() {
if len(os.Args) <= 1 {
os.Exit(0)
}
args := os.Args[1:]
signalWait := 20
if args[0] == "-s" {
signalWait = 0
args = args[1:]
}
interfaceWait := 20 - signalWait
usr2 := make(chan os.Signal)
signal.Notify(usr2, syscall.SIGUSR2)
select {
case <-usr2:
case <-time.After(time.Duration(signalWait) * time.Second):
if len(args) > 0 && args[0] == "-s" {
args = args[1:]
} else {
usr2 := make(chan os.Signal)
signal.Notify(usr2, syscall.SIGUSR2)
<-usr2
}
_, err := net.EnsureInterface("ethwe", interfaceWait)
_, err := weavenet.EnsureInterface("ethwe", -1)
checkErr(err)
if len(args) == 0 {
checkErr(proxy.ErrNoCommandSpecified)
}
binary, err := exec.LookPath(args[0])
checkErr(err)
+8 -1
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"regexp"
@@ -22,7 +23,13 @@ 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"}
cmd.Env = []string{
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PROCFS=/hostproc",
}
if bridge := os.Getenv("DOCKER_BRIDGE"); bridge != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_BRIDGE=%s", bridge))
}
out, err := cmd.CombinedOutput()
return out, err
}
+41 -8
View File
@@ -3,6 +3,7 @@ package proxy
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
@@ -13,6 +14,12 @@ import (
"github.com/weaveworks/weave/nameserver"
)
const MaxDockerHostname = 64
var (
ErrNoCommandSpecified = errors.New("No command specified")
)
type createContainerInterceptor struct{ proxy *Proxy }
type createContainerRequestBody struct {
@@ -21,9 +28,9 @@ type createContainerRequestBody struct {
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
}
// Replacement for docker.NoSuchImage, which does not contain the
// image name, which in turn breaks docker clients post 1.7.0 since
// they expect the image name to be present in errors.
// ErrNoSuchImage replaces docker.NoSuchImage, which does not contain the image
// name, which in turn breaks docker clients post 1.7.0 since they expect the
// image name to be present in errors.
type ErrNoSuchImage struct {
Name string
}
@@ -44,15 +51,17 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
return err
}
if cidrs, ok := i.proxy.weaveCIDRsFromConfig(container.Config); ok {
if cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig); err != nil {
Info.Printf("Ignoring container due to %s", err)
} else {
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{}
}
i.addWeaveWaitVolume(container.HostConfig)
if err := i.setWeaveWaitEntrypoint(container.Config); err != nil {
return err
}
@@ -71,6 +80,18 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
return nil
}
func (i *createContainerInterceptor) addWeaveWaitVolume(hostConfig *docker.HostConfig) {
var binds []string
for _, bind := range hostConfig.Binds {
s := strings.Split(bind, ":")
if len(s) >= 2 && s[1] == "/w" {
continue
}
binds = append(binds, bind)
}
hostConfig.Binds = append(binds, fmt.Sprintf("%s:/w:ro", i.proxy.weaveWaitVolume))
}
func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error {
if len(container.Entrypoint) == 0 {
image, err := i.proxy.client.InspectImage(container.Image)
@@ -89,7 +110,14 @@ func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Co
}
}
container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...)
if len(container.Entrypoint) == 0 && len(container.Cmd) == 0 {
return ErrNoCommandSpecified
}
if len(container.Entrypoint) == 0 || container.Entrypoint[0] != weaveWaitEntrypoint[0] {
container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...)
}
return nil
}
@@ -107,9 +135,14 @@ func (i *createContainerInterceptor) setWeaveDNS(container *createContainerReque
name := r.URL.Query().Get("name")
if container.Hostname == "" && name != "" {
container.Hostname = name
// Strip trailing period because it's unusual to see it used on the end of a host name
container.Domainname = strings.TrimSuffix(dnsDomain, ".")
trimmedDNSDomain := strings.TrimSuffix(dnsDomain, ".")
if len(name)+1+len(trimmedDNSDomain) > MaxDockerHostname {
Warning.Printf("Container name [%s] too long to be used as hostname", name)
} else {
container.Hostname = name
container.Domainname = trimmedDNSDomain
}
}
if len(container.HostConfig.DNSSearch) == 0 {
+5 -1
View File
@@ -29,7 +29,11 @@ func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
return err
}
if cidrs, ok := i.proxy.weaveCIDRsFromConfig(container.Config); ok {
_, hasWeaveWait := container.Volumes["/w"]
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
if err != nil {
Info.Printf("Ignoring container %s due to %s", container.ID, err)
} else if hasWeaveWait {
Info.Printf("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " "))
cmd := append(weaveWaitEntrypoint, "-s")
options.Cmd = append(cmd, options.Cmd...)
+50 -8
View File
@@ -2,6 +2,8 @@ package proxy
import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"net/url"
@@ -22,14 +24,18 @@ 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$")
ErrWeaveCIDRNone = errors.New("WEAVE_CIDR=none")
ErrNoDefaultIPAM = errors.New("--no-default-ipam option")
)
type Proxy struct {
Config
dial func() (net.Conn, error)
client *docker.Client
dockerBridgeIP string
dial func() (net.Conn, error)
client *docker.Client
dockerBridgeIP string
weaveWaitVolume string
}
type Config struct {
@@ -75,14 +81,41 @@ func NewProxy(c Config) (*Proxy, error) {
Error.Fatalf("Could not configure tls for proxy: %s", err)
}
p.client, err = docker.NewClient(p.DockerAddr)
// We pin the protocol version to 1.15 (which corresponds to Docker 1.3.x;
// the earliest version supported by weave) in order to insulate ourselves
// from breaking changes to the API, as happened in 1.20 (Docker 1.8.0) when
// the presentation of volumes changed in inspect.
p.client, err = docker.NewVersionedClient(p.DockerAddr, "1.15")
if err != nil {
return nil, err
}
if err = p.findWeaveWaitVolume(); err != nil {
return nil, err
}
return p, nil
}
func (proxy *Proxy) findWeaveWaitVolume() error {
container, err := proxy.client.InspectContainer("weaveproxy")
if err != nil {
return fmt.Errorf("Could not find the weavewait volume: %s", err)
}
if container.Volumes == nil {
return fmt.Errorf("Could not find the weavewait volume")
}
volume, ok := container.Volumes["/w"]
if !ok {
return fmt.Errorf("Could not find the weavewait volume")
}
proxy.weaveWaitVolume = volume
return nil
}
func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Info.Printf("%s %s", r.Method, r.URL)
path := r.URL.Path
@@ -115,14 +148,23 @@ func (proxy *Proxy) ListenAndServe() error {
return (&http.Server{Handler: proxy}).Serve(listener)
}
func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config) ([]string, bool) {
func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *docker.HostConfig) ([]string, error) {
if hostConfig != nil &&
hostConfig.NetworkMode != "" &&
hostConfig.NetworkMode != "default" &&
hostConfig.NetworkMode != "bridge" {
return nil, fmt.Errorf("--net option: %q", hostConfig.NetworkMode)
}
for _, e := range config.Env {
if strings.HasPrefix(e, "WEAVE_CIDR=") {
if e[11:] == "none" {
return nil, false
return nil, ErrWeaveCIDRNone
}
return strings.Fields(e[11:]), true
return strings.Fields(e[11:]), nil
}
}
return nil, !proxy.NoDefaultIPAM
if proxy.NoDefaultIPAM {
return nil, ErrNoDefaultIPAM
}
return nil, nil
}
+20 -14
View File
@@ -9,6 +9,7 @@ import (
"net"
"net/http"
"net/http/httputil"
"sync"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
@@ -94,25 +95,30 @@ func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.Cl
return
}
upDone := make(chan struct{})
downDone := make(chan struct{})
go copyStream(down, io.MultiReader(rem, up), upDone)
go copyStream(up, downBuf, downDone)
<-upDone
<-downDone
var wg sync.WaitGroup
wg.Add(2)
go copyStream(down, io.MultiReader(rem, up), &wg)
go copyStream(up, downBuf, &wg)
wg.Wait()
}
func copyStream(dst io.Writer, src io.Reader, done chan struct{}) {
defer close(done)
type closeWriter interface {
CloseWrite() error
}
func copyStream(dst io.WriteCloser, src io.Reader, wg *sync.WaitGroup) {
defer wg.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)
}
var err error
if c, ok := dst.(closeWriter); ok {
err = c.CloseWrite()
} else {
err = dst.Close()
}
if err != nil {
Warning.Printf("Error closing connection: %s", err)
}
}
+7 -3
View File
@@ -16,14 +16,18 @@ func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error {
}
func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
if r.StatusCode < 200 || r.StatusCode >= 300 { // Docker didn't do the start
return nil
}
container, err := inspectContainerInPath(i.proxy.client, r.Request.URL.Path)
if err != nil {
return err
}
cidrs, ok := i.proxy.weaveCIDRsFromConfig(container.Config)
if !ok {
Debug.Print("No Weave CIDR, ignoring")
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
if err != nil {
Info.Printf("Ignoring container %s due to %s", container.ID, err)
return nil
}
Info.Printf("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
+1 -1
View File
@@ -231,7 +231,7 @@ func (conn *LocalConnection) ReceivedHeartbeat(remoteUDPAddr *net.UDPAddr, connU
if oldRemoteUDPAddr == nil {
return conn.sendFastHeartbeats()
} else if oldRemoteUDPAddr.String() != remoteUDPAddr.String() {
log.Println("Peer", conn.remote, "moved from", old, "to", remoteUDPAddr)
log.Println("Peer", conn.remote, "moved from", oldRemoteUDPAddr, "to", remoteUDPAddr)
}
return nil
})
+4 -3
View File
@@ -1,15 +1,16 @@
package router
import (
"code.google.com/p/go-bit/bit"
"code.google.com/p/go.crypto/nacl/box"
"code.google.com/p/go.crypto/nacl/secretbox"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"encoding/gob"
"fmt"
"sync"
"github.com/andybalholm/go-bit"
"golang.org/x/crypto/nacl/box"
"golang.org/x/crypto/nacl/secretbox"
)
func GenerateKeyPair() (publicKey, privateKey *[32]byte, err error) {
+3 -2
View File
@@ -2,10 +2,11 @@ package router
import (
"bytes"
"code.google.com/p/gopacket"
"code.google.com/p/gopacket/layers"
"log"
"net"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
type EthernetDecoder struct {
+3 -2
View File
@@ -1,10 +1,11 @@
package router
import (
"code.google.com/p/gopacket"
"code.google.com/p/gopacket/layers"
"syscall"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
type ForwardedFrame struct {
+11 -5
View File
@@ -3,6 +3,7 @@ package router
import (
"bytes"
"encoding/gob"
"fmt"
"log"
"sync"
)
@@ -58,7 +59,12 @@ func (c *GossipChannel) deliverUnicast(srcName PeerName, origPayload []byte, dec
return err
}
if c.ourself.Name != destName {
return c.relayUnicast(destName, origPayload)
if err := c.relayUnicast(destName, origPayload); err != nil {
// just log errors from relayUnicast; a problem between us and destination
// is not enough reason to break the connection from the source
c.log(err)
}
return nil
}
var payload []byte
if err := dec.Decode(&payload); err != nil {
@@ -160,15 +166,15 @@ func (c *GossipChannel) GossipBroadcast(update GossipData) error {
return c.relayBroadcast(c.ourself.Name, update)
}
func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) error {
func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) (err error) {
if relayPeerName, found := c.routes.UnicastAll(dstPeerName); !found {
c.log("unknown relay destination:", dstPeerName)
err = fmt.Errorf("unknown relay destination: %s", dstPeerName)
} else if conn, found := c.ourself.ConnectionTo(relayPeerName); !found {
c.log("unable to find connection to relay peer", relayPeerName)
err = fmt.Errorf("unable to find connection to relay peer %s", relayPeerName)
} else {
conn.(ProtocolSender).SendProtocolMsg(ProtocolMsg{ProtocolGossipUnicast, buf})
}
return nil
return err
}
func (c *GossipChannel) relayBroadcast(srcName PeerName, update GossipData) error {
+2 -1
View File
@@ -1,8 +1,9 @@
package router
import (
wt "github.com/weaveworks/weave/testing"
"testing"
wt "github.com/weaveworks/weave/testing"
)
func TestFieldValidator(t *testing.T) {
+2 -1
View File
@@ -6,8 +6,9 @@
package router
import (
wt "github.com/weaveworks/weave/testing"
"testing"
wt "github.com/weaveworks/weave/testing"
)
// Add to peers a connection from peers.ourself to p
+2 -1
View File
@@ -1,8 +1,9 @@
package router
import (
"code.google.com/p/gopacket/pcap"
"fmt"
"github.com/google/gopacket/pcap"
)
type PcapIO struct {
+2 -1
View File
@@ -2,9 +2,10 @@ package router
import (
"fmt"
wt "github.com/weaveworks/weave/testing"
"math/rand"
"testing"
wt "github.com/weaveworks/weave/testing"
)
// TODO we should also test:
+10 -7
View File
@@ -11,21 +11,22 @@ import (
"syscall"
"time"
"code.google.com/p/gopacket/layers"
"github.com/google/gopacket/layers"
. "github.com/weaveworks/weave/common"
)
const (
macMaxAge = 10 * time.Minute // [1]
tcpAcceptDelay = 1 * time.Millisecond // [2]
macMaxAge = 10 * time.Minute // [1]
acceptMaxTokens = 100 // [2]
acceptTokenDelay = 100 * time.Millisecond // [3]
)
// [1] should be greater than typical ARP cache expiries, i.e. > 3/2 *
// /proc/sys/net/ipv4_neigh/*/base_reachable_time_ms on Linux
// [2] time to wait between accepting tcp connections. This guards
// against brute-force attacks on the password when encryption is in
// use. It is also a basic DoS defence.
// [2] capacity of token bucket for rate limiting accepts
// [3] control rate at which new tokens are added to the bucket
type LogFrameFunc func(string, []byte, *layers.Ethernet)
@@ -50,6 +51,7 @@ type Router struct {
gossipChannels GossipChannels
TopologyGossip Gossip
UDPListener *net.UDPConn
acceptLimiter *TokenBucket
}
type PacketSource interface {
@@ -81,6 +83,7 @@ func NewRouter(config Config, name PeerName, nickName string) *Router {
router.Routes = NewRoutes(router.Ourself, router.Peers)
router.ConnectionMaker = NewConnectionMaker(router.Ourself, router.Peers, router.Port, router.PeerDiscovery)
router.TopologyGossip = router.NewGossip("topology", router)
router.acceptLimiter = NewTokenBucket(acceptMaxTokens, acceptTokenDelay)
return router
}
@@ -212,7 +215,7 @@ func (router *Router) listenTCP(localPort int) {
continue
}
router.acceptTCP(tcpConn)
time.Sleep(tcpAcceptDelay)
router.acceptLimiter.Wait()
}
}()
}
+42
View File
@@ -0,0 +1,42 @@
package router
import (
"time"
)
type TokenBucket struct {
capacity int64 // Maximum capacity of bucket
tokenInterval time.Duration // Token replenishment rate
refillDuration time.Duration // Time to refill from empty
earliestUnspentToken time.Time
}
func NewTokenBucket(capacity int64, tokenInterval time.Duration) *TokenBucket {
tb := TokenBucket{
capacity: capacity,
tokenInterval: tokenInterval,
refillDuration: tokenInterval * time.Duration(capacity)}
tb.earliestUnspentToken = tb.capacityToken()
return &tb
}
func (tb *TokenBucket) Wait() {
// If earliest unspent token is in the future, sleep until then
time.Sleep(tb.earliestUnspentToken.Sub(time.Now()))
// Alternatively, enforce bucket capacity if necessary
capacityToken := tb.capacityToken()
if tb.earliestUnspentToken.Before(capacityToken) {
tb.earliestUnspentToken = capacityToken
}
// 'Remove' a token from the bucket
tb.earliestUnspentToken = tb.earliestUnspentToken.Add(tb.tokenInterval)
}
// Determine the historic token timestamp representing a full bucket
func (tb *TokenBucket) capacityToken() time.Time {
return time.Now().Add(-tb.refillDuration).Truncate(tb.tokenInterval)
}
+3 -2
View File
@@ -1,11 +1,12 @@
package router
import (
"code.google.com/p/gopacket"
"code.google.com/p/gopacket/layers"
"log"
"net"
"syscall"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
type UDPSender interface {
+34 -1
View File
@@ -278,7 +278,7 @@ password, and the result is hashed through SHA256, to form the final
ephemeral session key. Thus the supplied password is never exchanged
directly, and is thoroughly mixed into the shared secret. Furthermore,
the rate at which TCP connections are accepted is limited by weave to
1kHz, which twarts online dictionary attacks on reasonably strong
10Hz, which twarts online dictionary attacks on reasonably strong
passwords.
The shared key formed by Diffie-Hellman is 256 bits long, appending
@@ -293,6 +293,39 @@ not be able to form valid ephemeral session keys.
The same ephemeral session key is used for both TCP and UDP traffic
between two peers.
<a name="csprng"></a> Generating fresh keys for every connection
provides forward secrecy at the cost of placing a demand on the Linux
CSPRNG (accessed by `GenerateKey` via `/dev/urandom`) proportional to
the number of inbound connection attempts. Weave has accept throttling
to mitigate against denial of service attacks that seek to deplete the
CSPRNG entropy pool, however even at the lower bound of ten requests
per second there may not be enough entropy gathered on a headless
system to keep pace.
Under such conditions, the consequences will be limited to slowing
down processes reading from the blocking `/dev/random` device as the
kernel waits for enough new entropy to be harvested. It is important
to note that contrary to intuition this low entropy state does not
compromise the ongoing use of `/dev/urandom` - [expert
opinion](http://blog.cr.yp.to/20140205-entropy.html)
asserts that as long as the CSPRNG is seeded with enough entropy (e.g.
256 bits) before random number generation commences then the output is
entirely safe for use as key material.
By way of comparison, this is exactly how OpenSSL works - it reads 256
bits of entropy at startup, and uses that to seed an internal CSPRNG
which is used thenceforth to generate keys. Whilst we could have taken
the same approach and built our own CSPRNG to work around the
potential `/dev/random` blocking issue, we thought it was much more
prudent to rely on the [heavily
scrutinised](http://eprint.iacr.org/2012/251.pdf) Linux random number
generator as [advised
here](http://cr.yp.to/highspeed/coolnacl-20120725.pdf) (page 10,
'Centralizing randomness'). The aforementioned notwithstanding, if
weave's demand on `/dev/urandom` is causing you problems with blocking
`/dev/random` reads, please get in touch with us - we'd love to hear
about your use case.
#### TCP
TCP connection are only used to exchange topology information between
+4
View File
@@ -33,4 +33,8 @@ assert_expected_fqdn "$HOSTNAME" -h $HOSTNAME --name=$NAME
assert_expected_fqdn "$HOSTNAME" --hostname=$HOSTNAME --name=$NAME
assert_expected_fqdn "$HOSTNAME" --hostname $HOSTNAME --name=$NAME
# Ensure we can launch a container with a name that is too long for hostname derivation
SIXTY_FIVE_CHARS=01234567890123456789012345678901234567890123456789012345678901234
assert_raises "start_container $HOST1 --name=$SIXTY_FIVE_CHARS"
end_suite
+13 -7
View File
@@ -4,13 +4,19 @@
start_suite "Run docker-py test suite against the proxy"
docker_on $HOST1 pull joffrey/docker-py >/dev/null
weave_on $HOST1 launch-proxy --no-default-ipam
assert_raises "docker_on $HOST1 ps | grep weaveproxy"
assert_raises "docker_on $HOST1 run \
-e NOT_ON_HOST=true \
-e DOCKER_HOST=tcp://172.17.42.1:12375 \
-v /tmp:/tmp \
-v /var/run/docker.sock:/var/run/docker.sock \
joffrey/docker-py python tests/integration_test.py"
if docker_on $HOST1 run \
-e NOT_ON_HOST=true \
-e DOCKER_HOST=tcp://172.17.42.1:12375 \
-v /tmp:/tmp \
-v /var/run/docker.sock:/var/run/docker.sock \
joffrey/docker-py python tests/integration_test.py ; then
assert_raises "true"
else
assert_raises "false"
fi
end_suite
+27 -1
View File
@@ -2,6 +2,10 @@
. ./config.sh
entrypoint() {
docker_on $HOST1 inspect --format="{{.Config.Entrypoint}}" "$@"
}
start_suite "Proxy waits for weave to be ready before running container commands"
weave_on $HOST1 launch-proxy
BASE_IMAGE=busybox
@@ -10,6 +14,28 @@ if (docker_on $HOST1 images $BASE_IMAGE | grep -q $BASE_IMAGE); then
docker_on $HOST1 rmi $BASE_IMAGE
fi
assert_raises "proxy docker_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.1/24' $BASE_IMAGE $CHECK_ETHWE_UP"
assert_raises "proxy docker_on $HOST1 run --name c1 -e 'WEAVE_CIDR=10.2.1.1/24' $BASE_IMAGE $CHECK_ETHWE_UP"
# Check committed containers only have one weavewait prepended
COMMITTED_IMAGE=$(proxy docker_on $HOST1 commit c1)
assert_raises "proxy docker_on $HOST1 run --name c2 $COMMITTED_IMAGE"
assert "entrypoint c2" "$(entrypoint $COMMITTED_IMAGE)"
# Check exec works on containers without weavewait
docker_on $HOST1 run -dit --name c3 $SMALL_IMAGE /bin/sh
assert_raises "proxy docker_on $HOST1 exec c3 true"
# Check we can't modify weavewait
assert_raises "proxy docker_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.2/24' $BASE_IMAGE touch /w/w" 1
# Check only user-specified volumes and /w are mounted
dirs_sans_proxy=$(docker_on $HOST1 run --rm $SMALL_IMAGE mount | wc -l)
dirs_with_proxy=$(proxy docker_on $HOST1 run --rm -v /tmp/1:/srv1 -v /tmp/2:/srv2 -e 'WEAVE_CIDR=10.2.1.3/24' $SMALL_IMAGE mount | wc -l)
assert "echo $((dirs_with_proxy-3))" $dirs_sans_proxy
# Check errors are returned (when docker returns an error code)
assert_raises "proxy docker_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.3/24' $SMALL_IMAGE foo 2>&1 | grep 'exec: \"foo\": executable file not found in \$PATH'"
# Check errors still happen when no command is specified
assert_raises "proxy docker_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.3/24' $SMALL_IMAGE 2>&1 | grep 'Error response from daemon: No command specified'"
end_suite
+2 -2
View File
@@ -8,8 +8,8 @@ C1_NAME=c1.weave.local
C2_NAME=seetwo.weave.local
boot_containers() {
proxy docker_on $HOST1 run -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $C2_NAME $DNS_IMAGE /bin/sh
proxy docker_on $HOST1 run -e WEAVE_CIDR=$C1/24 -dt --name=c1 $DNS_IMAGE /bin/sh
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $C2_NAME
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C1/24 -dt --name=c1
}
kill_containers() {
+4 -8
View File
@@ -9,19 +9,15 @@ C4=10.2.0.99
DOMAIN=weave.local
NAME=seeone.$DOMAIN
start_container() {
proxy docker_on $HOST1 run "$@" -dt $DNS_IMAGE /bin/sh
}
start_suite "Resolve unqualified names"
weave_on $HOST1 launch-dns 10.2.254.1/24
weave_on $HOST1 launch-proxy
start_container -e WEAVE_CIDR=$C1/24 --name=c1 -h $NAME
start_container -e WEAVE_CIDR=$C2/24 --name=c2 -h seetwo.$DOMAIN
start_container -e WEAVE_CIDR=$C3/24 --name=c3 --dns-search=$DOMAIN
container=$(start_container -e WEAVE_CIDR=$C4/24)
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C1/24 --name=c1 -h $NAME
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C2/24 --name=c2 -h seetwo.$DOMAIN
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C3/24 --name=c3 --dns-search=$DOMAIN
container=$(proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C4/24)
check() {
assert "proxy exec_on $HOST1 $1 getent hosts seeone | tr -s ' '" "$C1 $NAME"
+2 -2
View File
@@ -10,8 +10,8 @@ start_suite "Proxy restart reattaches networking to containers"
launch_dns_on $HOST1 10.2.254.1/24
weave_on $HOST1 launch-proxy --with-dns
proxy docker_on $HOST1 run -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $NAME $SMALL_IMAGE /bin/sh
proxy docker_on $HOST1 run -e WEAVE_CIDR=$C1/24 -dt --name=c1 $DNS_IMAGE /bin/sh
proxy_start_container $HOST1 -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $NAME
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C1/24 -dt --name=c1
proxy docker_on $HOST1 restart c2
assert_raises "proxy exec_on $HOST1 c2 $CHECK_ETHWE_UP"
+12 -10
View File
@@ -4,12 +4,6 @@
UNIVERSE=10.2.2.0/24
start() {
host=$1
shift
proxy docker_on "$host" run "$@" -dt $SMALL_IMAGE /bin/sh
}
assert_no_ethwe() {
assert_raises "container_ip $1 $2" 1
assert_raises "proxy exec_on $1 $2 ip link show | grep -v ethwe"
@@ -22,17 +16,25 @@ weave_on $HOST2 launch -iprange $UNIVERSE $HOST1
weave_on $HOST1 launch-proxy
weave_on $HOST2 launch-proxy --no-default-ipam
start $HOST1 --name=auto
start $HOST1 --name=none -e WEAVE_CIDR=none
start $HOST2 --name=zero -e WEAVE_CIDR=
start $HOST2 --name=no-default
proxy_start_container $HOST1 --name=auto
proxy_start_container $HOST1 --name=none -e WEAVE_CIDR=none
proxy_start_container $HOST2 --name=zero -e WEAVE_CIDR=
proxy_start_container $HOST2 --name=no-default
proxy_start_container $HOST1 --name=bridge --net=bridge
proxy_start_container $HOST1 --name=host --net=host
proxy_start_container $HOST1 --name=other --net=container:auto
AUTO=$(container_ip $HOST1 auto)
ZERO=$(container_ip $HOST2 zero)
BRIDGE=$(container_ip $HOST1 bridge)
OTHER=$(container_ip $HOST1 other)
assert_raises "proxy exec_on $HOST1 auto $PING $ZERO"
assert_raises "proxy exec_on $HOST2 zero $PING $AUTO"
assert_raises "proxy exec_on $HOST2 zero $PING $BRIDGE"
assert_raises "proxy exec_on $HOST2 zero $PING $OTHER"
assert_no_ethwe $HOST1 none
assert_no_ethwe $HOST2 no-default
assert_no_ethwe $HOST1 host
end_suite
+16
View File
@@ -0,0 +1,16 @@
#! /bin/bash
. ./config.sh
C1=10.2.1.13
start_suite "Start containers with 'weave run' via proxy"
weave_on $HOST1 launch
weave_on $HOST1 launch-proxy
proxy start_container $HOST1 $C1/24 --name=c1
# check we get exactly one IP back, the one specified
assert "weave_on $HOST1 ps c1 | cut -d ' ' -f 3-" $C1/24
end_suite
+13 -1
View File
@@ -120,6 +120,18 @@ start_container_with_dns() {
weave_on $host run --with-dns "$@" -t $DNS_IMAGE /bin/sh
}
proxy_start_container() {
host=$1
shift 1
proxy docker_on $host run "$@" -dt $SMALL_IMAGE /bin/sh
}
proxy_start_container_with_dns() {
host=$1
shift 1
proxy docker_on $host run "$@" -dt $DNS_IMAGE /bin/sh
}
rm_containers() {
host=$1
shift
@@ -170,8 +182,8 @@ assert_dns_ptr_record() {
start_suite() {
for host in $HOST1 $HOST2; do
[ -z "$DEBUG" ] || echo "Cleaning up on $host: removing all containers and resetting weave"
rm_containers $host $(docker_on $host ps -aq 2>/dev/null)
weave_on $host reset 2>/dev/null
rm_containers $host $(docker_on $host ps -aq 2>/dev/null)
done
whitely echo "$@"
}
+46 -16
View File
@@ -93,6 +93,22 @@ check_docker_version() {
fi
}
enforce_docker_bridge_addr_assign_type() {
if ! ADDR_ASSIGN_TYPE=$(cat /sys/class/net/$DOCKER_BRIDGE/addr_assign_type 2>/dev/null) ; then
echo "Could not determine address assignment type of $DOCKER_BRIDGE" >&2
return
fi
# From include/uapi/linux/netdevice.h
# #define NET_ADDR_PERM 0 /* address is permanent (default) */
# #define NET_ADDR_RANDOM 1 /* address is generated randomly */
# #define NET_ADDR_STOLEN 2 /* address is stolen from other device */
# #define NET_ADDR_SET 3 /* address is set using dev_set_mac_address() */
if [ $ADDR_ASSIGN_TYPE != 3 ] ; then
echo "Setting $DOCKER_BRIDGE MAC (mitigate https://github.com/docker/docker/issues/14908)" >&2
ip link set dev $DOCKER_BRIDGE address $(random_mac) || true
fi
}
is_cidr() {
echo "$1" | grep -E "^$CIDR_REGEXP$" >/dev/null
}
@@ -142,7 +158,7 @@ if [ "$1" = "run" ] ; then
shift $(dns_arg_count "$@")
collect_cidr_args "$@"
shift $CIDR_ARG_COUNT
CONTAINER=$(docker $DOCKER_CLIENT_ARGS run $DNS_ARGS -d "$@")
CONTAINER=$(docker $DOCKER_CLIENT_ARGS run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
exec_remote attach $CIDR_ARGS --or-die $CONTAINER
echo $CONTAINER
exit 0
@@ -359,7 +375,7 @@ netnsenter() {
}
connect_container_to_bridge() {
if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "$PROCFS/$$/ns/net" -a "$(readlink $PROCFS/$CONTAINER_PID/ns/net)" = "$(readlink $PROCFS/$$/ns/net)" ] ; then
if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "/proc/$$/ns/net" -a "$(readlink $PROCFS/$CONTAINER_PID/ns/net)" = "$(readlink /proc/$$/ns/net)" ] ; then
echo "Container is running in the host network namespace, and therefore cannot be" >&2
echo "connected to weave. Perhaps the container was started with --net=host." >&2
return 1
@@ -433,7 +449,9 @@ attach() {
netnsenter ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME
fi
arp_update $CONTAINER_IFNAME $1 "netnsenter"
for ADDR in "$@" ; do
arp_update $CONTAINER_IFNAME $ADDR "netnsenter"
done
}
detach() {
@@ -664,8 +682,11 @@ dns_args() {
WITHOUT_DNS=
while [ $# -gt 0 ] ; do
case "$1" in
--dns-search=*)
DNS_SEARCH_SPECIFIED=1
--with-dns)
WITH_DNS=1
;;
--without-dns)
WITHOUT_DNS=1
;;
--name)
NAME_ARG="$2"
@@ -677,11 +698,8 @@ dns_args() {
-h|--hostname|--hostname=*)
HOSTNAME_SPECIFIED=1
;;
--with-dns)
WITH_DNS=1
;;
--without-dns)
WITHOUT_DNS=1
--dns-search|--dns-search=*)
DNS_SEARCH_SPECIFIED=1
;;
esac
shift
@@ -693,8 +711,13 @@ dns_args() {
DNS_ARGS="--dns $DOCKER_BRIDGE_IP"
if [ -n "$NAME_ARG" -a -z "$HOSTNAME_SPECIFIED" ] ; then
DOMAIN=$(dns_domain)
DNS_ARGS="$DNS_ARGS --hostname=$NAME_ARG.${DOMAIN%.}"
HOSTNAME_SPECIFIED=1
HOSTNAME="$NAME_ARG.${DOMAIN%.}"
if [ ${#HOSTNAME} -gt 64 ] ; then
echo "Container name too long to be used as hostname" >&2
else
DNS_ARGS="$DNS_ARGS --hostname=$HOSTNAME"
HOSTNAME_SPECIFIED=1
fi
fi
if [ -z "$DNS_SEARCH_SPECIFIED" ] ; then
if [ -z "$HOSTNAME_SPECIFIED" ] ; then
@@ -859,7 +882,7 @@ ipam_cidrs_or_die() {
# TODO: Handle args with spaces
tls_arg() {
filename=${3:-$2}
PROXY_VOLUMES="$PROXY_VOLUMES -v $1:/home/weave/tls/$filename.pem"
PROXY_VOLUMES="$PROXY_VOLUMES -v $1:/home/weave/tls/$filename.pem:ro"
PROXY_ARGS="$PROXY_ARGS --tls$2 /home/weave/tls/$filename.pem"
}
@@ -971,6 +994,7 @@ case "$COMMAND" in
;;
launch)
check_not_running $CONTAINER_NAME $BASE_IMAGE
enforce_docker_bridge_addr_assign_type
create_bridge
# We set the router name to the bridge mac since that is
# stable across re-creations of the containers.
@@ -1017,10 +1041,15 @@ case "$COMMAND" in
if [ -z "$IPRANGE_SPECIFIED" ] ; then
IPRANGE="10.128.0.0/10"
if command_exists netcheck && ! netcheck $IPRANGE ; then
echo "Default -iprange $IPRANGE overlaps with existing route." >&2
echo "Please pick another range and set it on all hosts." >&2
echo "ERROR: Default -iprange $IPRANGE overlaps with existing route on host." >&2
echo "You must pick another range and set it on all hosts." >&2
exit 1
fi
else
if command_exists netcheck && ! netcheck $IPRANGE ; then
echo "WARNING: Specified -iprange $IPRANGE overlaps with existing route on host." >&2
echo "Unless this is deliberate, you must pick another range and set it on all hosts." >&2
fi
fi
# Set WEAVE_DOCKER_ARGS in the environment in order to supply
# additional parameters, such as resource limits, to docker
@@ -1071,6 +1100,7 @@ case "$COMMAND" in
-v /proc:/hostproc \
-e PROCFS=/hostproc \
-e WEAVE_CIDR=none \
-e DOCKER_BRIDGE \
--entrypoint=/home/weave/weaveproxy \
$WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $PROXY_ARGS)
wait_for_log $PROXY_CONTAINER_NAME "proxy listening"
@@ -1130,7 +1160,7 @@ case "$COMMAND" in
shift $(dns_arg_count "$@")
collect_cidr_args "$@"
shift $CIDR_ARG_COUNT
CONTAINER=$(docker run $DNS_ARGS -d "$@")
CONTAINER=$(docker run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
create_bridge
ipam_cidrs_or_die $CONTAINER $CIDR_ARGS
with_container_netns_or_die $CONTAINER attach $ALL_CIDRS