From b67c102fe5df93b502b788a0ad8c3013607ead4d Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 2 Jul 2015 11:58:26 +0100 Subject: [PATCH 1/4] Remove simple sleep from router accept --- router/router.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/router/router.go b/router/router.go index 141dfc38..1c7dcbb2 100644 --- a/router/router.go +++ b/router/router.go @@ -16,17 +16,12 @@ import ( ) const ( - macMaxAge = 10 * time.Minute // [1] - tcpAcceptDelay = 1 * time.Millisecond // [2] + 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 -// [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. - type LogFrameFunc func(string, []byte, *layers.Ethernet) type Config struct { @@ -212,7 +207,6 @@ func (router *Router) listenTCP(localPort int) { continue } router.acceptTCP(tcpConn) - time.Sleep(tcpAcceptDelay) } }() } From 9634526a67b0aa553b5a1275987a8ae1ba8751b2 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 2 Jul 2015 11:58:40 +0100 Subject: [PATCH 2/4] Add token bucket implementation --- router/token_bucket.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 router/token_bucket.go diff --git a/router/token_bucket.go b/router/token_bucket.go new file mode 100644 index 00000000..254de6eb --- /dev/null +++ b/router/token_bucket.go @@ -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) +} From f3c4a9fc6d723c57eb1273c194d11d319b8eb9d0 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 2 Jul 2015 12:03:08 +0100 Subject: [PATCH 3/4] Limit router accept rate with token bucket --- router/router.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/router/router.go b/router/router.go index 1c7dcbb2..b498690b 100644 --- a/router/router.go +++ b/router/router.go @@ -16,12 +16,18 @@ import ( ) const ( - macMaxAge = 10 * time.Minute // [1] + macMaxAge = 10 * time.Minute // [1] + acceptMaxTokens = 100 // [2] + acceptTokenDelay = 100 * time.Millisecond // [3] ) // [1] should be greater than typical ARP cache expiries, i.e. > 3/2 * // /proc/sys/net/ipv4_neigh/*/base_reachable_time_ms on Linux +// [2] capacity of token bucket for rate limiting accepts + +// [3] control rate at which new tokens are added to the bucket + type LogFrameFunc func(string, []byte, *layers.Ethernet) type Config struct { @@ -45,6 +51,7 @@ type Router struct { gossipChannels GossipChannels TopologyGossip Gossip UDPListener *net.UDPConn + acceptLimiter *TokenBucket } type PacketSource interface { @@ -76,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 } @@ -207,6 +215,7 @@ func (router *Router) listenTCP(localPort int) { continue } router.acceptTCP(tcpConn) + router.acceptLimiter.Wait() } }() } From e2f8b6f9fdc8e770f68b8f46b43c92b1245b3246 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 9 Jul 2015 16:45:37 +0100 Subject: [PATCH 4/4] Document /dev/random blocking issue --- site/how-it-works.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/site/how-it-works.md b/site/how-it-works.md index 4e570c7c..70b97ff1 100644 --- a/site/how-it-works.md +++ b/site/how-it-works.md @@ -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. + 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