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.
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.
conn.shutdown was only set/accessed in handleShutdown, which in turn
is only called on termination of queryLoop and hence multiple
invocations are impossible.
There is no need to go via Topology. Fetching does not access or
modify the Topology state, and none of the functions on Topology
update the PeerCache. So there is no point serialising the fetching
with RebuildRoutes (or anything else).
This does mean the connections' receiveTCP goroutines will take a
direct lock on PeerCache, but a) it is only a read lock, and b) they
already take a write lock in another code path.
There may be some extra load on the PeerCache as a result of this
change, due to the topology fetching of multiple connections no longer
being funneled through the single Topology goroutine. Consequently,
write locks on the PeerCache may take a little longer to
acquire. Until we encounter this as a genuine problem though it's
better to stick with the simpler code.
The weave protocol already effectively performs heartbeating -
FetchAll is performed at regular intervals. Hence we can detect stale
connections by the lack of receiving any data for approximately that
interval (we use x2).
We use the same interval to set a time limit on the handshake too, and
any subsequent interactions prior to the connection being fully
established.
Fixes#272.
...by moving the logic into the connection actor goroutine.
The heartbeater goroutine was doing a lot more than just heartbeating;
namely it also had tickers for triggering a ProtocolFetchAll and
initiating a fragmentation test. Furhermore, ensureHeartbeats also
spawned the forwarders.
So this was quite a mix of functionality, and, furthermore, most of it
related quite closely to the connection actor. So moving the
functionality into that simplifies the code.
In the process we also fix#294.
There is no point, and it populates the cache with entries we don't
need. Local containers will still need to issue an ARP request when
they first talk to an IP corresponding to the MAC, so we learn about
the MAC at that point, just as normally happens.