mirror of
https://github.com/matrix-org/pinecone.git
synced 2026-10-24 11:59:18 +03:00
Send teardowns instead of paths expiring automatically
This commit is contained in:
@@ -91,6 +91,9 @@ func (r *Router) reader(conn net.Conn) {
|
||||
}
|
||||
r.recv <- frame
|
||||
|
||||
case types.TypeVirtualSnakeTeardown:
|
||||
r.recv <- frame
|
||||
|
||||
case types.TypeSource:
|
||||
// Check if the source path seems to be finished.
|
||||
if len(frame.Destination) > 0 {
|
||||
|
||||
@@ -101,9 +101,9 @@ func (e virtualSnakeTable) MarshalJSON() ([]byte, error) {
|
||||
"source_port": value.SourcePort,
|
||||
"last_seen": time.Since(value.LastSeen).String(),
|
||||
}
|
||||
if time.Since(value.LastSeen) > virtualSnakePathExpiryPeriod {
|
||||
entry["expired"] = true
|
||||
}
|
||||
//if time.Since(value.LastSeen) > virtualSnakePathExpiryPeriod {
|
||||
// entry["expired"] = true
|
||||
//}
|
||||
out = append(out, entry)
|
||||
}
|
||||
return json.Marshal(out)
|
||||
|
||||
@@ -52,6 +52,9 @@ func (p *Peer) getNextHops(frame *types.Frame, from types.SwitchPortID) types.Sw
|
||||
return nextHops
|
||||
}
|
||||
|
||||
case types.TypeVirtualSnakeTeardown:
|
||||
return p.r.snake.getVirtualSnakeTeardownNextHop(p, frame)
|
||||
|
||||
case types.TypeVirtualSnakeSetup:
|
||||
nextHops := p.r.getGreedyRoutedNextHop(p, frame)
|
||||
defer p.r.snake.handleSetup(p, frame, nextHops)
|
||||
|
||||
@@ -25,8 +25,7 @@ import (
|
||||
)
|
||||
|
||||
const virtualSnakeSetupInterval = time.Second * 16
|
||||
const virtualSnakePathExpiryPeriod = virtualSnakeSetupInterval * 2
|
||||
const virtualSnakeNeighExpiryPeriod = virtualSnakePathExpiryPeriod * 2
|
||||
const virtualSnakeNeighExpiryPeriod = time.Minute * 10
|
||||
|
||||
type virtualSnake struct {
|
||||
r *Router
|
||||
@@ -43,8 +42,9 @@ type virtualSnake struct {
|
||||
type virtualSnakeTable map[types.PublicKey]virtualSnakeEntry
|
||||
|
||||
type virtualSnakeEntry struct {
|
||||
SourcePort types.SwitchPortID
|
||||
LastSeen time.Time
|
||||
SourcePort types.SwitchPortID
|
||||
DestinationPort types.SwitchPortID
|
||||
LastSeen time.Time
|
||||
}
|
||||
|
||||
type virtualSnakeNeighbour struct {
|
||||
@@ -128,18 +128,6 @@ func (v *virtualSnake) maintain() {
|
||||
// portWasDisconnected is called by the router when a peer connects
|
||||
// allowing us to start a new bootstrap.
|
||||
func (t *virtualSnake) rootNodeChanged(root types.PublicKey) {
|
||||
t.ascendingMutex.Lock()
|
||||
if t.ascending != nil {
|
||||
t.clearRoutingEntriesForPublicKey(t.ascending.PublicKey)
|
||||
}
|
||||
t.ascending = nil
|
||||
t.ascendingMutex.Unlock()
|
||||
t.descendingMutex.Lock()
|
||||
if t.descending != nil {
|
||||
t.clearRoutingEntriesForPublicKey(t.descending.PublicKey)
|
||||
}
|
||||
t.descending = nil
|
||||
t.descendingMutex.Unlock()
|
||||
t.maintainNow.Dispatch()
|
||||
}
|
||||
|
||||
@@ -153,25 +141,7 @@ func (t *virtualSnake) portWasConnected(port types.SwitchPortID) {
|
||||
// allowing us to clean up the virtual snake state.
|
||||
func (t *virtualSnake) portWasDisconnected(port types.SwitchPortID) {
|
||||
defer t.maintainNow.Dispatch()
|
||||
// Check if our descending node was routable via the disconnected
|
||||
// port. If they were, clear the entry so that the maintain function
|
||||
// will send new bootstraps to find the next descending node.
|
||||
t.descendingMutex.Lock()
|
||||
if p := t.descending; p != nil && p.Port == port {
|
||||
t.clearRoutingEntriesForPublicKey(t.descending.PublicKey)
|
||||
t.descending = nil
|
||||
}
|
||||
t.descendingMutex.Unlock()
|
||||
// Check if our ascending node was routable via the disconnected
|
||||
// port. If they were then clear the entry too, it will get
|
||||
// re-populated the next time we bootstrap.
|
||||
t.ascendingMutex.Lock()
|
||||
if s := t.ascending; s != nil && s.Port == port {
|
||||
t.clearRoutingEntriesForPublicKey(t.ascending.PublicKey)
|
||||
t.ascending = nil
|
||||
}
|
||||
t.ascendingMutex.Unlock()
|
||||
// Finally, check if any of our routing table entries are
|
||||
// Check if any of our routing table entries are
|
||||
// via the port in question. If they were then let's nuke those
|
||||
// too, otherwise we'll be trying to route traffic into black
|
||||
// holes.
|
||||
@@ -222,9 +192,6 @@ func (t *virtualSnake) getVirtualSnakeBootstrapNextHop(from *Peer, destKey types
|
||||
// Check our DHT entries
|
||||
t.tableMutex.RLock()
|
||||
for dhtKey, entry := range t.table {
|
||||
if time.Since(entry.LastSeen) > virtualSnakePathExpiryPeriod {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case util.DHTOrdered(destKey, dhtKey, bestKey):
|
||||
// If the DHT entry routes to a key that is closer to the
|
||||
@@ -290,12 +257,6 @@ func (t *virtualSnake) getVirtualSnakeNextHop(from *Peer, destKey types.PublicKe
|
||||
// Next, look at the routing table. This includes source-side paths
|
||||
// from setups only.
|
||||
for dhtKey, entry := range t.table {
|
||||
if time.Since(entry.LastSeen) > virtualSnakePathExpiryPeriod {
|
||||
// The routing table entry hasn't been refreshed recently so we'll
|
||||
// ignore it. This will eventually get picked up and removed from
|
||||
// the routing table.
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case destKey.EqualTo(dhtKey):
|
||||
newCandidate(dhtKey, entry.SourcePort)
|
||||
@@ -321,23 +282,54 @@ func (t *virtualSnake) getVirtualSnakeNextHop(from *Peer, destKey types.PublicKe
|
||||
return candidates[canlength:]
|
||||
}
|
||||
|
||||
func (t *virtualSnake) clearRoutingEntriesForPort(port types.SwitchPortID) {
|
||||
func (t *virtualSnake) getVirtualSnakeTeardownNextHop(from *Peer, rx *types.Frame) types.SwitchPorts {
|
||||
t.ascendingMutex.Lock()
|
||||
if asc := t.ascending; asc != nil && asc.PublicKey == rx.DestinationKey {
|
||||
t.ascending = nil
|
||||
t.maintainNow.Dispatch()
|
||||
}
|
||||
t.ascendingMutex.Unlock()
|
||||
t.descendingMutex.Lock()
|
||||
if desc := t.descending; desc != nil && desc.PublicKey == rx.DestinationKey {
|
||||
t.ascending = nil
|
||||
t.maintainNow.Dispatch()
|
||||
}
|
||||
t.descendingMutex.Unlock()
|
||||
t.tableMutex.Lock()
|
||||
defer t.tableMutex.Unlock()
|
||||
for k, v := range t.table {
|
||||
if v.SourcePort == port {
|
||||
if k == rx.DestinationKey {
|
||||
delete(t.table, k)
|
||||
if from.port == v.SourcePort {
|
||||
return types.SwitchPorts{v.DestinationPort}
|
||||
} else {
|
||||
return types.SwitchPorts{v.SourcePort}
|
||||
}
|
||||
}
|
||||
}
|
||||
return types.SwitchPorts{}
|
||||
}
|
||||
|
||||
func (t *virtualSnake) clearRoutingEntriesForPort(port types.SwitchPortID) {
|
||||
t.tableMutex.RLock()
|
||||
var key types.PublicKey
|
||||
for k, v := range t.table {
|
||||
if v.DestinationPort == port || v.SourcePort == port {
|
||||
key = k
|
||||
break
|
||||
}
|
||||
}
|
||||
t.tableMutex.RUnlock()
|
||||
t.r.send <- types.Frame{
|
||||
Type: types.TypeVirtualSnakeTeardown,
|
||||
DestinationKey: key,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *virtualSnake) clearRoutingEntriesForPublicKey(key types.PublicKey) {
|
||||
t.tableMutex.Lock()
|
||||
defer t.tableMutex.Unlock()
|
||||
for k := range t.table {
|
||||
if k == key {
|
||||
delete(t.table, k)
|
||||
}
|
||||
func (t *virtualSnake) clearRoutingEntriesForPublicKey(pk types.PublicKey) {
|
||||
t.r.send <- types.Frame{
|
||||
Type: types.TypeVirtualSnakeTeardown,
|
||||
DestinationKey: pk,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,6 +415,9 @@ func (t *virtualSnake) handleBootstrapACK(from *Peer, rx *types.Frame) {
|
||||
// new node that we've received a bootstrap from is actually closer to
|
||||
// us than the previous node. We'll update our record to use the new
|
||||
// node instead and then send a new path setup message to it.
|
||||
if t.ascending != nil && rx.SourceKey != t.ascending.PublicKey {
|
||||
t.clearRoutingEntriesForPublicKey(t.ascending.PublicKey)
|
||||
}
|
||||
t.ascending = &virtualSnakeNeighbour{
|
||||
PublicKey: rx.SourceKey,
|
||||
Port: from.port,
|
||||
@@ -452,20 +447,6 @@ func (t *virtualSnake) handleBootstrapACK(from *Peer, rx *types.Frame) {
|
||||
// instead called for every setup packet being transited through this node.
|
||||
// It will update the routing table with the new path.
|
||||
func (t *virtualSnake) handleSetup(from *Peer, rx *types.Frame, nextHops types.SwitchPorts) {
|
||||
if rx.SourceKey.EqualTo(t.r.public) {
|
||||
return
|
||||
}
|
||||
|
||||
t.tableMutex.Lock()
|
||||
defer t.tableMutex.Unlock()
|
||||
|
||||
// Evict old entries.
|
||||
for k, v := range t.table {
|
||||
if time.Since(v.LastSeen) > virtualSnakePathExpiryPeriod {
|
||||
delete(t.table, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the setup packet has a valid signed timestamp.
|
||||
if !util.VerifySignedTimestamp(rx.SourceKey, rx.Payload) {
|
||||
return
|
||||
@@ -477,10 +458,16 @@ func (t *virtualSnake) handleSetup(from *Peer, rx *types.Frame, nextHops types.S
|
||||
// bound the routing table safely, we may want to make sure that we have
|
||||
// a reasonable spread of routes across keyspace so that we don't create
|
||||
// any obvious routing holes.
|
||||
t.table[rx.SourceKey] = virtualSnakeEntry{
|
||||
entry := virtualSnakeEntry{
|
||||
LastSeen: time.Now(),
|
||||
SourcePort: from.port,
|
||||
}
|
||||
if len(nextHops) > 0 {
|
||||
entry.DestinationPort = nextHops[0]
|
||||
}
|
||||
t.tableMutex.Lock()
|
||||
t.table[rx.SourceKey] = entry
|
||||
t.tableMutex.Unlock()
|
||||
|
||||
// If we're at the destination of the setup then update our predecessor
|
||||
// with information from the bootstrap.
|
||||
@@ -513,6 +500,9 @@ func (t *virtualSnake) handleSetup(from *Peer, rx *types.Frame, nextHops types.S
|
||||
// new node that we've received a bootstrap from is actually closer to
|
||||
// us than the previous node. We'll update our record to use the new
|
||||
// node instead and then send back a bootstrap ACK.
|
||||
if t.descending != nil && rx.SourceKey != t.descending.PublicKey {
|
||||
t.clearRoutingEntriesForPublicKey(t.descending.PublicKey)
|
||||
}
|
||||
t.descending = &virtualSnakeNeighbour{
|
||||
PublicKey: rx.SourceKey,
|
||||
Port: from.port,
|
||||
|
||||
@@ -224,6 +224,10 @@ func (p *Peer) reader() {
|
||||
coordsLen := int(binary.BigEndian.Uint16(header[4:6]))
|
||||
expecting = 10 + coordsLen + (ed25519.PublicKeySize * 2) + payloadLen
|
||||
|
||||
case types.TypeVirtualSnakeTeardown:
|
||||
payloadLen := int(binary.BigEndian.Uint16(header[2:4]))
|
||||
expecting = 8 + payloadLen + ed25519.PublicKeySize
|
||||
|
||||
case types.TypeVirtualSnake, types.TypeVirtualSnakePathfind:
|
||||
payloadLen := int(binary.BigEndian.Uint16(header[2:4]))
|
||||
expecting = 8 + payloadLen + (ed25519.PublicKeySize * 2)
|
||||
@@ -300,7 +304,7 @@ func (p *Peer) reader() {
|
||||
continue
|
||||
}
|
||||
|
||||
case types.TypeDHTRequest, types.TypeDHTResponse, types.TypeVirtualSnakeBootstrap, types.TypeVirtualSnakeBootstrapACK, types.TypeVirtualSnakeSetup:
|
||||
case types.TypeDHTRequest, types.TypeDHTResponse, types.TypeVirtualSnakeBootstrap, types.TypeVirtualSnakeBootstrapACK, types.TypeVirtualSnakeSetup, types.TypeVirtualSnakeTeardown:
|
||||
select {
|
||||
case dest.protoOut <- frame.Borrow():
|
||||
dest.statistics.txProtoSuccessful.Inc()
|
||||
|
||||
@@ -209,7 +209,7 @@ func (r *Router) Predecessor() *types.PublicKey {
|
||||
r.snake.descendingMutex.RLock()
|
||||
pr := r.snake.descending
|
||||
r.snake.descendingMutex.RUnlock()
|
||||
if pr == nil || time.Since(pr.LastSeen) >= virtualSnakeNeighExpiryPeriod {
|
||||
if pr == nil { // || time.Since(pr.LastSeen) >= virtualSnakeNeighExpiryPeriod {
|
||||
return nil
|
||||
}
|
||||
pk := pr.PublicKey
|
||||
@@ -222,7 +222,7 @@ func (r *Router) Successor() *types.PublicKey {
|
||||
r.snake.ascendingMutex.RLock()
|
||||
su := r.snake.ascending
|
||||
r.snake.ascendingMutex.RUnlock()
|
||||
if su == nil || time.Since(su.LastSeen) >= virtualSnakeNeighExpiryPeriod {
|
||||
if su == nil { //|| time.Since(su.LastSeen) >= virtualSnakeNeighExpiryPeriod {
|
||||
return nil
|
||||
}
|
||||
pk := su.PublicKey
|
||||
|
||||
@@ -371,6 +371,9 @@ func (t *spanningTree) Update(p *Peer, a *types.SwitchAnnouncement) error {
|
||||
}
|
||||
|
||||
// If the root has changed then let's do something about it.
|
||||
if newRoot.RootPublicKey != oldRoot.RootPublicKey {
|
||||
go t.r.snake.rootNodeChanged(newRoot.RootPublicKey)
|
||||
}
|
||||
if newRoot != oldRoot {
|
||||
t.rootMutex.Lock()
|
||||
t.root = newRoot
|
||||
@@ -379,8 +382,6 @@ func (t *spanningTree) Update(p *Peer, a *types.SwitchAnnouncement) error {
|
||||
if p.port == t.updateCoordinates() {
|
||||
t.advertise.Dispatch()
|
||||
}
|
||||
|
||||
go t.r.snake.rootNodeChanged(newRoot.RootPublicKey)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -53,6 +53,7 @@ const (
|
||||
TypeVirtualSnakeSetup // protocol frame, forwarded using greedy routing
|
||||
TypeVirtualSnake // traffic frame, forwarded using SNEK successor ordering
|
||||
TypeVirtualSnakePathfind // protocol frame, forwarded using SNEK successor ordering
|
||||
TypeVirtualSnakeTeardown // protocol frame
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -161,6 +162,15 @@ func (f *Frame) MarshalBinary(buffer []byte) (int, error) {
|
||||
offset += copy(buffer[offset:], f.Payload[:payloadLen])
|
||||
}
|
||||
|
||||
case TypeVirtualSnakeTeardown: // destination = key
|
||||
payloadLen := len(f.Payload)
|
||||
binary.BigEndian.PutUint16(buffer[offset+0:offset+2], uint16(payloadLen))
|
||||
offset += 2
|
||||
offset += copy(buffer[offset:], f.DestinationKey[:ed25519.PublicKeySize])
|
||||
if f.Payload != nil {
|
||||
offset += copy(buffer[:offset], f.Payload[:payloadLen])
|
||||
}
|
||||
|
||||
case TypeVirtualSnake, TypeVirtualSnakePathfind: // destination = key, source = key
|
||||
payloadLen := len(f.Payload)
|
||||
binary.BigEndian.PutUint16(buffer[offset+0:offset+2], uint16(payloadLen))
|
||||
@@ -250,6 +260,13 @@ func (f *Frame) UnmarshalBinary(data []byte) (int, error) {
|
||||
f.Payload = append([]byte{}, data[offset:offset+payloadLen]...)
|
||||
return offset, nil
|
||||
|
||||
case TypeVirtualSnakeTeardown: // destination = key
|
||||
payloadLen := int(binary.BigEndian.Uint16(data[offset+0 : offset+2]))
|
||||
offset += 2
|
||||
offset += copy(f.DestinationKey[:], data[offset:])
|
||||
f.Payload = append([]byte{}, data[offset:offset+payloadLen]...)
|
||||
return offset, nil
|
||||
|
||||
case TypeVirtualSnake, TypeVirtualSnakePathfind: // destination = key, source = key
|
||||
payloadLen := int(binary.BigEndian.Uint16(data[offset+0 : offset+2]))
|
||||
offset += 2
|
||||
|
||||
@@ -242,6 +242,53 @@ func TestMarshalUnmarshalSNEKSetupFrame(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalSNEKTeardownFrame(t *testing.T) {
|
||||
pk1, _, _ := ed25519.GenerateKey(nil)
|
||||
input := Frame{
|
||||
Version: Version0,
|
||||
Type: TypeVirtualSnakeTeardown,
|
||||
}
|
||||
copy(input.DestinationKey[:], pk1)
|
||||
expected := []byte{
|
||||
0x70, 0x69, 0x6e, 0x65, // magic bytes
|
||||
0, // version 0
|
||||
byte(TypeVirtualSnakeTeardown), // type greedy
|
||||
0, 0, // payload length
|
||||
}
|
||||
expected = append(expected, pk1...)
|
||||
buf := make([]byte, 65535)
|
||||
n, err := input.MarshalBinary(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(expected) {
|
||||
t.Fatalf("wrong marshalled length, \ngot %d, \nexpected %d", n, len(expected))
|
||||
}
|
||||
if !bytes.Equal(buf[:n], expected) {
|
||||
t.Fatalf("wrong marshalled output, \ngot %v, \nexpected %v", buf[:n], expected)
|
||||
}
|
||||
|
||||
t.Log("Got: ", buf[:n])
|
||||
t.Log("Want:", expected)
|
||||
|
||||
var output Frame
|
||||
if _, err := output.UnmarshalBinary(buf[:n]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if output.Version != input.Version {
|
||||
t.Fatal("wrong version")
|
||||
}
|
||||
if output.Type != input.Type {
|
||||
t.Fatal("wrong version")
|
||||
}
|
||||
if !output.Destination.EqualTo(input.Destination) {
|
||||
t.Fatal("wrong path")
|
||||
}
|
||||
if !bytes.Equal(input.Payload, output.Payload) {
|
||||
t.Fatal("wrong payload")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalSNEKFrame(t *testing.T) {
|
||||
pk1, _, _ := ed25519.GenerateKey(nil)
|
||||
pk2, _, _ := ed25519.GenerateKey(nil)
|
||||
|
||||
Reference in New Issue
Block a user