From ac952eae418163b52453682d7bf2dc31b3323800 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Fri, 28 Jan 2022 11:20:32 -0300 Subject: [PATCH 01/36] Add basic integration test using the simulator --- .../tests/basic_integration_test.go | 92 +++++++++++ cmd/pineconesim/tests/scenario_fixture.go | 151 ++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 cmd/pineconesim/tests/basic_integration_test.go create mode 100644 cmd/pineconesim/tests/scenario_fixture.go diff --git a/cmd/pineconesim/tests/basic_integration_test.go b/cmd/pineconesim/tests/basic_integration_test.go new file mode 100644 index 00000000..733474b6 --- /dev/null +++ b/cmd/pineconesim/tests/basic_integration_test.go @@ -0,0 +1,92 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "log" + "testing" + "time" + + "github.com/matrix-org/pinecone/cmd/pineconesim/simulator" +) + +type TreeValidationState struct { + roots map[string]string + correctRoot string +} + +func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { + t.Parallel() + // Arrange + scenario := NewScenarioFixture(t) + nodes := []string{"Alice", "Bob"} + scenario.AddStandardNodes(nodes) + + // Act + scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}}) + + // Assert + stateCapture := func(state simulator.State) interface{} { + lastRoots := make(map[string]string) + lastRoots["Alice"] = state.Nodes["Alice"].Announcement.Root + lastRoots["Bob"] = state.Nodes["Bob"].Announcement.Root + + correctRoot := "" + if state.Nodes["Alice"].PeerID > state.Nodes["Bob"].PeerID { + correctRoot = "Alice" + } else { + correctRoot = "Bob" + } + + return TreeValidationState{roots: lastRoots, correctRoot: correctRoot} + } + + nodesAgreeOnCorrectTreeRoot := func(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) { + switch state := prevState.(type) { + case TreeValidationState: + action := DoNothing + switch e := event.(type) { + case simulator.TreeRootAnnUpdate: + if state.roots[e.Node] != e.Root { + log.Printf("Root changed for %s to %s", e.Node, e.Root) + state.roots[e.Node] = e.Root + } else { + log.Printf("Got duplicate root info for %s", e.Node) + break + } + + if state.roots["Alice"] == state.roots["Bob"] { + log.Println("Nodes agree on root") + if state.roots["Alice"] == state.correctRoot { + log.Println("The agreed root is the correct root") + action = StartSettlingTimer + } else { + log.Println("The agreed root is not the correct root") + action = StopSettlingTimer + } + } else { + log.Println("Nodes disagree on root") + action = StopSettlingTimer + } + } + + return state, action + } + + return prevState, StopSettlingTimer + } + + scenario.Validate(stateCapture, nodesAgreeOnCorrectTreeRoot, 2*time.Second, 5*time.Second) +} diff --git a/cmd/pineconesim/tests/scenario_fixture.go b/cmd/pineconesim/tests/scenario_fixture.go new file mode 100644 index 00000000..b692f61a --- /dev/null +++ b/cmd/pineconesim/tests/scenario_fixture.go @@ -0,0 +1,151 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "fmt" + "log" + "os" + "testing" + "time" + + "github.com/matrix-org/pinecone/cmd/pineconesim/simulator" +) + +type EventHandlerResult int + +const ( + DoNothing EventHandlerResult = iota + StopSettlingTimer + StartSettlingTimer +) + +type InitialStateCapture func(state simulator.State) interface{} +type EventHandler func(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) + +type NodePair struct { + A string + B string +} + +type ScenarioFixture struct { + t *testing.T + log *log.Logger + sim *simulator.Simulator +} + +func NewScenarioFixture(t *testing.T) ScenarioFixture { + log := log.New(os.Stdout, "\u001b[36m***\u001b[0m ", 0) + useSockets := false + runPing := false + acceptCommands := true + simulator := simulator.NewSimulator(log, useSockets, runPing, acceptCommands) + + return ScenarioFixture{ + t: t, + log: log, + sim: simulator, + } +} + +func (s *ScenarioFixture) AddStandardNodes(nodes []string) { + for _, node := range nodes { + cmd := simulator.AddNode{ + Node: node, + NodeType: simulator.DefaultNode, + } + cmd.Run(s.log, s.sim) + } +} + +func (s *ScenarioFixture) AddAdversaryNodes(nodes []string) { + for _, node := range nodes { + cmd := simulator.AddNode{ + Node: node, + NodeType: simulator.GeneralAdversaryNode, + } + cmd.Run(s.log, s.sim) + } +} + +func (s *ScenarioFixture) AddPeerConnections(conns []NodePair) { + for _, pair := range conns { + cmd := simulator.AddPeer{ + Node: pair.A, + Peer: pair.B, + } + cmd.Run(s.log, s.sim) + } +} + +func (s *ScenarioFixture) SubscribeToSimState(ch chan simulator.SimEvent) simulator.State { + return s.sim.State.Subscribe(ch) +} + +func (s *ScenarioFixture) Validate(initialState InitialStateCapture, eventHandler EventHandler, settlingTime time.Duration, timeout time.Duration) { + testTimeout := time.NewTimer(timeout) + defer testTimeout.Stop() + + quit := make(chan bool) + output := make(chan string) + go assertState(s, initialState, eventHandler, quit, output, settlingTime) + + failed := false + + select { + case <-testTimeout.C: + failed = true + quit <- true + case <-output: + log.Println("Test passed") + } + + if failed { + state := <-output + s.t.Fatalf("Test timeout reached. Current State: %s", state) + } +} + +func assertState(scenario *ScenarioFixture, stateCapture InitialStateCapture, eventHandler EventHandler, quit chan bool, output chan string, settlingTime time.Duration) { + settlingTimer := time.NewTimer(settlingTime) + settlingTimer.Stop() + + simUpdates := make(chan simulator.SimEvent) + state := scenario.SubscribeToSimState(simUpdates) + + prevState := stateCapture(state) + + for { + select { + case <-quit: + output <- fmt.Sprintf("Root Map: %v", prevState) + return + case <-settlingTimer.C: + output <- "PASS" + case event := <-simUpdates: + newState, newResult := eventHandler(prevState, event) + switch newResult { + case StartSettlingTimer: + log.Println("Starting settling timer") + settlingTimer.Reset(settlingTime) + case StopSettlingTimer: + log.Println("Stopping settling timer") + settlingTimer.Stop() + } + + prevState = newState + } + } +} From 7b02958ff20991161cf2edc77f8524c4e42d795b Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Fri, 28 Jan 2022 14:40:40 -0300 Subject: [PATCH 02/36] Add basic integration test for snake neighbours --- .../tests/basic_integration_test.go | 121 +++++++++++++++++- 1 file changed, 119 insertions(+), 2 deletions(-) diff --git a/cmd/pineconesim/tests/basic_integration_test.go b/cmd/pineconesim/tests/basic_integration_test.go index 733474b6..12ab1ca9 100644 --- a/cmd/pineconesim/tests/basic_integration_test.go +++ b/cmd/pineconesim/tests/basic_integration_test.go @@ -16,6 +16,7 @@ package integration import ( "log" + "sort" "testing" "time" @@ -40,8 +41,9 @@ func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { // Assert stateCapture := func(state simulator.State) interface{} { lastRoots := make(map[string]string) - lastRoots["Alice"] = state.Nodes["Alice"].Announcement.Root - lastRoots["Bob"] = state.Nodes["Bob"].Announcement.Root + for _, node := range nodes { + lastRoots[node] = state.Nodes[node].Announcement.Root + } correctRoot := "" if state.Nodes["Alice"].PeerID > state.Nodes["Bob"].PeerID { @@ -90,3 +92,118 @@ func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { scenario.Validate(stateCapture, nodesAgreeOnCorrectTreeRoot, 2*time.Second, 5*time.Second) } + +type SnakeNeighbours struct { + asc string + desc string +} + +type SnakeValidationState struct { + snake map[string]SnakeNeighbours + correctSnake map[string]SnakeNeighbours +} + +type Node struct { + name string + key string +} + +type byKey []Node + +func (l byKey) Len() int { + return len(l) +} + +func (l byKey) Less(i, j int) bool { + return l[i].key < l[j].key +} + +func (l byKey) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +func TestNodesAgreeOnCorrectSnakeFormation(t *testing.T) { + t.Parallel() + // Arrange + scenario := NewScenarioFixture(t) + nodes := []string{"Alice", "Bob", "Charlie"} + scenario.AddStandardNodes(nodes) + + // Act + scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}}) + + // Assert + stateCapture := func(state simulator.State) interface{} { + snakeNeighbours := make(map[string]SnakeNeighbours) + for _, node := range nodes { + asc := state.Nodes[node].AscendingPeer + desc := state.Nodes[node].DescendingPeer + snakeNeighbours[node] = SnakeNeighbours{asc: asc, desc: desc} + } + + nodesByKey := make(byKey, 0, len(state.Nodes)) + for key, value := range state.Nodes { + nodesByKey = append(nodesByKey, Node{key, value.PeerID}) + } + sort.Sort(nodesByKey) + + correctSnake := make(map[string]SnakeNeighbours) + lowest := SnakeNeighbours{asc: nodesByKey[1].name, desc: ""} + middle := SnakeNeighbours{asc: nodesByKey[2].name, desc: nodesByKey[0].name} + highest := SnakeNeighbours{asc: "", desc: nodesByKey[1].name} + correctSnake[nodesByKey[0].name] = lowest + correctSnake[nodesByKey[1].name] = middle + correctSnake[nodesByKey[2].name] = highest + + return SnakeValidationState{snakeNeighbours, correctSnake} + } + + nodesAgreeOnCorrectSnakeFormation := func(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) { + switch state := prevState.(type) { + case SnakeValidationState: + isSnakeCorrect := func() bool { + snakeIsCorrect := true + for key, val := range state.snake { + if val.asc != state.correctSnake[key].asc || val.desc != state.correctSnake[key].desc { + snakeIsCorrect = false + break + } + } + return snakeIsCorrect + } + + snakeWasCorrect := isSnakeCorrect() + + action := DoNothing + updateReceived := false + switch e := event.(type) { + case simulator.SnakeAscUpdate: + updateReceived = true + if node, ok := state.snake[e.Node]; ok { + node.asc = e.Peer + state.snake[e.Node] = node + } + case simulator.SnakeDescUpdate: + updateReceived = true + if node, ok := state.snake[e.Node]; ok { + node.desc = e.Peer + state.snake[e.Node] = node + } + } + + if updateReceived { + if isSnakeCorrect() && !snakeWasCorrect { + action = StartSettlingTimer + } else { + action = StopSettlingTimer + } + } + + return state, action + } + + return prevState, StopSettlingTimer + } + + scenario.Validate(stateCapture, nodesAgreeOnCorrectSnakeFormation, 2*time.Second, 5*time.Second) +} From 3a055b0d3ba00d46dd10b13f32c3aa13b422cd18 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sat, 29 Jan 2022 11:12:53 -0300 Subject: [PATCH 03/36] Fix event api race condition when first subscribing to a node --- .../simulator/adversary/drop_packets.go | 4 +- cmd/pineconesim/simulator/nodes.go | 6 +- cmd/pineconesim/simulator/router.go | 6 +- cmd/pineconesim/simulator/simulator.go | 2 + cmd/pineconesim/simulator/state.go | 57 ++++++++++++++++--- .../tests/basic_integration_test.go | 46 +++++++++------ cmd/pineconesim/tests/scenario_fixture.go | 2 +- router/api.go | 55 +++++++++++++++++- 8 files changed, 142 insertions(+), 36 deletions(-) diff --git a/cmd/pineconesim/simulator/adversary/drop_packets.go b/cmd/pineconesim/simulator/adversary/drop_packets.go index a6f44ab4..216d6d97 100644 --- a/cmd/pineconesim/simulator/adversary/drop_packets.go +++ b/cmd/pineconesim/simulator/adversary/drop_packets.go @@ -122,8 +122,8 @@ func NewAdversaryRouter(log *log.Logger, sk ed25519.PrivateKey, debug bool) *Adv return adversary } -func (a *AdversaryRouter) Subscribe(ch chan events.Event) { - a.rtr.Subscribe(ch) +func (a *AdversaryRouter) Subscribe(ch chan events.Event) router.NodeState { + return a.rtr.Subscribe(ch) } func (a *AdversaryRouter) PublicKey() types.PublicKey { diff --git a/cmd/pineconesim/simulator/nodes.go b/cmd/pineconesim/simulator/nodes.go index 30994506..989cb5ec 100644 --- a/cmd/pineconesim/simulator/nodes.go +++ b/cmd/pineconesim/simulator/nodes.go @@ -118,14 +118,14 @@ func (sim *Simulator) StartNodeEventHandler(t string, nodeType APINodeType) { ch := make(chan events.Event) handler := eventHandler{node: t, ch: ch} quit := make(chan bool) - go handler.Run(quit, sim) - sim.nodes[t].Subscribe(ch) + nodeState := sim.nodes[t].Subscribe(ch) sim.nodeRunnerChannelsMutex.Lock() sim.nodeRunnerChannels[t] = append(sim.nodeRunnerChannels[t], quit) sim.nodeRunnerChannelsMutex.Unlock() - phony.Block(sim.State, func() { sim.State._addNode(t, sim.nodes[t].PublicKey().String(), nodeType) }) + phony.Block(sim.State, func() { sim.State._addNode(t, sim.nodes[t].PublicKey().String(), nodeType, nodeState) }) + go handler.Run(quit, sim) } func (sim *Simulator) RemoveNode(node string) { diff --git a/cmd/pineconesim/simulator/router.go b/cmd/pineconesim/simulator/router.go index c3fb2fd1..5393af64 100644 --- a/cmd/pineconesim/simulator/router.go +++ b/cmd/pineconesim/simulator/router.go @@ -28,7 +28,7 @@ import ( type SimRouter interface { PublicKey() types.PublicKey Connect(conn net.Conn, options ...router.ConnectionOption) (types.SwitchPortID, error) - Subscribe(ch chan events.Event) + Subscribe(ch chan events.Event) router.NodeState Ping(ctx context.Context, a net.Addr) (uint16, time.Duration, error) Coords() types.Coordinates ConfigureFilterDefaults(rates adversary.DropRates) @@ -39,8 +39,8 @@ type DefaultRouter struct { rtr *router.Router } -func (r *DefaultRouter) Subscribe(ch chan events.Event) { - r.rtr.Subscribe(ch) +func (r *DefaultRouter) Subscribe(ch chan events.Event) router.NodeState { + return r.rtr.Subscribe(ch) } func (r *DefaultRouter) PublicKey() types.PublicKey { diff --git a/cmd/pineconesim/simulator/simulator.go b/cmd/pineconesim/simulator/simulator.go index e201b073..2ea36838 100644 --- a/cmd/pineconesim/simulator/simulator.go +++ b/cmd/pineconesim/simulator/simulator.go @@ -217,6 +217,8 @@ func (sim *Simulator) handleTreeRootAnnUpdate(node string, root string, sequence rootName := "" if peerNode, err := sim.State.GetNodeName(root); err == nil { rootName = peerNode + } else { + log.Fatalf("Cannot convert %s to root for %s", root, node) } sim.State.Act(nil, func() { sim.State._updateTreeRootAnnouncement(node, rootName, sequence, time, coords) }) } diff --git a/cmd/pineconesim/simulator/state.go b/cmd/pineconesim/simulator/state.go index f628a305..f597fa34 100644 --- a/cmd/pineconesim/simulator/state.go +++ b/cmd/pineconesim/simulator/state.go @@ -19,6 +19,7 @@ import ( "reflect" "github.com/Arceliar/phony" + "github.com/matrix-org/pinecone/router" ) type RootAnnouncement struct { @@ -148,13 +149,7 @@ func (s *StateAccessor) GetNodeName(peerID string) (string, error) { node := "" err := fmt.Errorf("Provided peerID is not associated with a known node") - phony.Block(s, func() { - for k, v := range s._state.Nodes { - if v.PeerID == peerID { - node, err = k, nil - } - } - }) + phony.Block(s, func() { node, err = s._getNodeName(peerID) }) return node, err } @@ -169,8 +164,54 @@ func (s *StateAccessor) GetNodeCoords(name string) []uint64 { return coords } -func (s *StateAccessor) _addNode(name string, peerID string, nodeType APINodeType) { +func (s *StateAccessor) _getNodeName(peerID string) (string, error) { + node := "" + err := fmt.Errorf("Provided peerID is not associated with a known node") + + for k, v := range s._state.Nodes { + if v.PeerID == peerID { + node, err = k, nil + } + } + + return node, err +} + +func (s *StateAccessor) _addNode(name string, peerID string, nodeType APINodeType, nodeState router.NodeState) { s._state.Nodes[name] = NewNodeState(peerID, nodeType) + if peernode, err := s._getNodeName(nodeState.Parent); err == nil { + s._state.Nodes[name].Parent = peernode + } + connections := map[int]string{} + for i, node := range nodeState.Connections { + if i == 0 { + // NOTE : Skip connection on port 0 since it is the loopback port + continue + } + if peernode, err := s._getNodeName(node); err == nil { + connections[i] = peernode + } + } + s._state.Nodes[name].Connections = connections + s._state.Nodes[name].Coords = nodeState.Coords + root := "" + if peernode, err := s._getNodeName(nodeState.Announcement.RootPublicKey.String()); err == nil { + root = peernode + } + announcement := RootAnnouncement{ + Root: root, + Sequence: uint64(nodeState.Announcement.RootSequence), + Time: nodeState.AnnouncementTime, + } + s._state.Nodes[name].Announcement = announcement + if peernode, err := s._getNodeName(nodeState.AscendingPeer); err == nil { + s._state.Nodes[name].AscendingPeer = peernode + } + s._state.Nodes[name].AscendingPathID = nodeState.AscendingPathID + if peernode, err := s._getNodeName(nodeState.DescendingPeer); err == nil { + s._state.Nodes[name].DescendingPeer = peernode + } + s._state.Nodes[name].DescendingPathID = nodeState.DescendingPathID s._publish(NodeAdded{Node: name, PublicKey: peerID, NodeType: int(nodeType)}) } diff --git a/cmd/pineconesim/tests/basic_integration_test.go b/cmd/pineconesim/tests/basic_integration_test.go index 12ab1ca9..5adad0b4 100644 --- a/cmd/pineconesim/tests/basic_integration_test.go +++ b/cmd/pineconesim/tests/basic_integration_test.go @@ -23,6 +23,9 @@ import ( "github.com/matrix-org/pinecone/cmd/pineconesim/simulator" ) +const SettlingTime time.Duration = time.Second * 2 +const TestTimeout time.Duration = time.Second * 5 + type TreeValidationState struct { roots map[string]string correctRoot string @@ -32,11 +35,11 @@ func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { t.Parallel() // Arrange scenario := NewScenarioFixture(t) - nodes := []string{"Alice", "Bob"} + nodes := []string{"Alice", "Bob", "Charlie"} scenario.AddStandardNodes(nodes) // Act - scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}}) + scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}}) // Assert stateCapture := func(state simulator.State) interface{} { @@ -45,12 +48,13 @@ func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { lastRoots[node] = state.Nodes[node].Announcement.Root } - correctRoot := "" - if state.Nodes["Alice"].PeerID > state.Nodes["Bob"].PeerID { - correctRoot = "Alice" - } else { - correctRoot = "Bob" + nodesByKey := make(byKey, 0, len(state.Nodes)) + for key, value := range state.Nodes { + nodesByKey = append(nodesByKey, Node{key, value.PeerID}) } + sort.Sort(nodesByKey) + + correctRoot := nodesByKey[len(nodesByKey)-1].name return TreeValidationState{roots: lastRoots, correctRoot: correctRoot} } @@ -69,17 +73,23 @@ func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { break } - if state.roots["Alice"] == state.roots["Bob"] { - log.Println("Nodes agree on root") - if state.roots["Alice"] == state.correctRoot { - log.Println("The agreed root is the correct root") - action = StartSettlingTimer - } else { - log.Println("The agreed root is not the correct root") - action = StopSettlingTimer + nodesAgreeOnRoot := true + rootSample := "" + for _, node := range state.roots { + rootSample = node + for _, comparison := range state.roots { + if node != comparison { + nodesAgreeOnRoot = false + break + } } + } + + if nodesAgreeOnRoot && state.correctRoot == rootSample { + log.Println("Start settling for tree test") + action = StartSettlingTimer } else { - log.Println("Nodes disagree on root") + log.Println("Stop settling for tree test") action = StopSettlingTimer } } @@ -90,7 +100,7 @@ func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { return prevState, StopSettlingTimer } - scenario.Validate(stateCapture, nodesAgreeOnCorrectTreeRoot, 2*time.Second, 5*time.Second) + scenario.Validate(stateCapture, nodesAgreeOnCorrectTreeRoot, SettlingTime, TestTimeout) } type SnakeNeighbours struct { @@ -205,5 +215,5 @@ func TestNodesAgreeOnCorrectSnakeFormation(t *testing.T) { return prevState, StopSettlingTimer } - scenario.Validate(stateCapture, nodesAgreeOnCorrectSnakeFormation, 2*time.Second, 5*time.Second) + scenario.Validate(stateCapture, nodesAgreeOnCorrectSnakeFormation, SettlingTime, TestTimeout) } diff --git a/cmd/pineconesim/tests/scenario_fixture.go b/cmd/pineconesim/tests/scenario_fixture.go index b692f61a..33718de7 100644 --- a/cmd/pineconesim/tests/scenario_fixture.go +++ b/cmd/pineconesim/tests/scenario_fixture.go @@ -130,7 +130,7 @@ func assertState(scenario *ScenarioFixture, stateCapture InitialStateCapture, ev for { select { case <-quit: - output <- fmt.Sprintf("Root Map: %v", prevState) + output <- fmt.Sprintf("%+v", prevState) return case <-settlingTimer.C: output <- "PASS" diff --git a/router/api.go b/router/api.go index 1b3f6c35..f32d297f 100644 --- a/router/api.go +++ b/router/api.go @@ -42,11 +42,64 @@ type PeerInfo struct { Zone string } +type NodeState struct { + PeerID string + Connections map[int]string + Parent string + Coords []uint64 + Announcement types.SwitchAnnouncement + AnnouncementTime uint64 + AscendingPeer string + AscendingPathID string + DescendingPeer string + DescendingPathID string +} + // Subscribe registers a subscriber to this node's events -func (r *Router) Subscribe(ch chan<- events.Event) { +func (r *Router) Subscribe(ch chan<- events.Event) NodeState { + var stateCopy NodeState phony.Block(r, func() { r._subscribers[ch] = &phony.Inbox{} + stateCopy.PeerID = r.public.String() + connections := map[int]string{} + for _, p := range r.state._peers { + if p == nil { + continue + } + connections[int(p.port)] = p.public.String() + } + stateCopy.Connections = connections + parent := "" + if r.state._parent != nil { + parent = r.state._parent.public.String() + } + stateCopy.Parent = parent + coords := []uint64{} + for _, coord := range r.Coords() { + coords = append(coords, uint64(coord)) + } + stateCopy.Coords = coords + announcement := r.state._rootAnnouncement() + stateCopy.Announcement = announcement.SwitchAnnouncement + stateCopy.AnnouncementTime = uint64(announcement.receiveTime.UnixNano()) + asc := "" + ascPath := "" + if r.state._ascending != nil { + asc = r.state._ascending.PublicKey.String() + ascPath = hex.EncodeToString(r.state._ascending.PathID[:]) + } + stateCopy.AscendingPeer = asc + stateCopy.AscendingPathID = ascPath + desc := "" + descPath := "" + if r.state._descending != nil { + desc = r.state._descending.PublicKey.String() + descPath = hex.EncodeToString(r.state._descending.PathID[:]) + } + stateCopy.DescendingPeer = desc + stateCopy.DescendingPathID = descPath }) + return stateCopy } func (r *Router) Coords() types.Coordinates { From 51e0672c433ff3c78eb945abb7b3f5ad22e1d2ba Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sat, 29 Jan 2022 11:33:32 -0300 Subject: [PATCH 04/36] Move integration tests to live alongside router code --- {cmd/pineconesim => router}/tests/basic_integration_test.go | 0 {cmd/pineconesim => router}/tests/scenario_fixture.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {cmd/pineconesim => router}/tests/basic_integration_test.go (100%) rename {cmd/pineconesim => router}/tests/scenario_fixture.go (100%) diff --git a/cmd/pineconesim/tests/basic_integration_test.go b/router/tests/basic_integration_test.go similarity index 100% rename from cmd/pineconesim/tests/basic_integration_test.go rename to router/tests/basic_integration_test.go diff --git a/cmd/pineconesim/tests/scenario_fixture.go b/router/tests/scenario_fixture.go similarity index 100% rename from cmd/pineconesim/tests/scenario_fixture.go rename to router/tests/scenario_fixture.go From 1d8ddb65bfb005f725428b9cf6fe0f2199d81a9b Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sat, 29 Jan 2022 14:10:46 -0300 Subject: [PATCH 05/36] Refactor integration tests to be able to reuse validation functions --- router/tests/basic_integration_test.go | 192 ++----------------------- router/tests/snake_validation.go | 105 ++++++++++++++ router/tests/tree_validation.go | 92 ++++++++++++ router/tests/util.go | 34 +++++ 4 files changed, 243 insertions(+), 180 deletions(-) create mode 100644 router/tests/snake_validation.go create mode 100644 router/tests/tree_validation.go create mode 100644 router/tests/util.go diff --git a/router/tests/basic_integration_test.go b/router/tests/basic_integration_test.go index 5adad0b4..0347be23 100644 --- a/router/tests/basic_integration_test.go +++ b/router/tests/basic_integration_test.go @@ -1,36 +1,27 @@ -// Copyright 2022 The Matrix.org Foundation C.I.C. +// copyright 2022 the matrix.org foundation c.i.c. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// licensed under the apache license, version 2.0 (the "license"); +// you may not use this file except in compliance with the license. +// you may obtain a copy of the license at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/license-2.0 // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// unless required by applicable law or agreed to in writing, software +// distributed under the license is distributed on an "as is" basis, +// without warranties or conditions of any kind, either express or implied. +// see the license for the specific language governing permissions and +// limitations under the license. package integration import ( - "log" - "sort" "testing" "time" - - "github.com/matrix-org/pinecone/cmd/pineconesim/simulator" ) const SettlingTime time.Duration = time.Second * 2 const TestTimeout time.Duration = time.Second * 5 -type TreeValidationState struct { - roots map[string]string - correctRoot string -} - func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { t.Parallel() // Arrange @@ -42,94 +33,7 @@ func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}}) // Assert - stateCapture := func(state simulator.State) interface{} { - lastRoots := make(map[string]string) - for _, node := range nodes { - lastRoots[node] = state.Nodes[node].Announcement.Root - } - - nodesByKey := make(byKey, 0, len(state.Nodes)) - for key, value := range state.Nodes { - nodesByKey = append(nodesByKey, Node{key, value.PeerID}) - } - sort.Sort(nodesByKey) - - correctRoot := nodesByKey[len(nodesByKey)-1].name - - return TreeValidationState{roots: lastRoots, correctRoot: correctRoot} - } - - nodesAgreeOnCorrectTreeRoot := func(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) { - switch state := prevState.(type) { - case TreeValidationState: - action := DoNothing - switch e := event.(type) { - case simulator.TreeRootAnnUpdate: - if state.roots[e.Node] != e.Root { - log.Printf("Root changed for %s to %s", e.Node, e.Root) - state.roots[e.Node] = e.Root - } else { - log.Printf("Got duplicate root info for %s", e.Node) - break - } - - nodesAgreeOnRoot := true - rootSample := "" - for _, node := range state.roots { - rootSample = node - for _, comparison := range state.roots { - if node != comparison { - nodesAgreeOnRoot = false - break - } - } - } - - if nodesAgreeOnRoot && state.correctRoot == rootSample { - log.Println("Start settling for tree test") - action = StartSettlingTimer - } else { - log.Println("Stop settling for tree test") - action = StopSettlingTimer - } - } - - return state, action - } - - return prevState, StopSettlingTimer - } - - scenario.Validate(stateCapture, nodesAgreeOnCorrectTreeRoot, SettlingTime, TestTimeout) -} - -type SnakeNeighbours struct { - asc string - desc string -} - -type SnakeValidationState struct { - snake map[string]SnakeNeighbours - correctSnake map[string]SnakeNeighbours -} - -type Node struct { - name string - key string -} - -type byKey []Node - -func (l byKey) Len() int { - return len(l) -} - -func (l byKey) Less(i, j int) bool { - return l[i].key < l[j].key -} - -func (l byKey) Swap(i, j int) { - l[i], l[j] = l[j], l[i] + scenario.Validate(createTreeStateCapture(nodes), nodesAgreeOnCorrectTreeRoot, SettlingTime, TestTimeout) } func TestNodesAgreeOnCorrectSnakeFormation(t *testing.T) { @@ -143,77 +47,5 @@ func TestNodesAgreeOnCorrectSnakeFormation(t *testing.T) { scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}}) // Assert - stateCapture := func(state simulator.State) interface{} { - snakeNeighbours := make(map[string]SnakeNeighbours) - for _, node := range nodes { - asc := state.Nodes[node].AscendingPeer - desc := state.Nodes[node].DescendingPeer - snakeNeighbours[node] = SnakeNeighbours{asc: asc, desc: desc} - } - - nodesByKey := make(byKey, 0, len(state.Nodes)) - for key, value := range state.Nodes { - nodesByKey = append(nodesByKey, Node{key, value.PeerID}) - } - sort.Sort(nodesByKey) - - correctSnake := make(map[string]SnakeNeighbours) - lowest := SnakeNeighbours{asc: nodesByKey[1].name, desc: ""} - middle := SnakeNeighbours{asc: nodesByKey[2].name, desc: nodesByKey[0].name} - highest := SnakeNeighbours{asc: "", desc: nodesByKey[1].name} - correctSnake[nodesByKey[0].name] = lowest - correctSnake[nodesByKey[1].name] = middle - correctSnake[nodesByKey[2].name] = highest - - return SnakeValidationState{snakeNeighbours, correctSnake} - } - - nodesAgreeOnCorrectSnakeFormation := func(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) { - switch state := prevState.(type) { - case SnakeValidationState: - isSnakeCorrect := func() bool { - snakeIsCorrect := true - for key, val := range state.snake { - if val.asc != state.correctSnake[key].asc || val.desc != state.correctSnake[key].desc { - snakeIsCorrect = false - break - } - } - return snakeIsCorrect - } - - snakeWasCorrect := isSnakeCorrect() - - action := DoNothing - updateReceived := false - switch e := event.(type) { - case simulator.SnakeAscUpdate: - updateReceived = true - if node, ok := state.snake[e.Node]; ok { - node.asc = e.Peer - state.snake[e.Node] = node - } - case simulator.SnakeDescUpdate: - updateReceived = true - if node, ok := state.snake[e.Node]; ok { - node.desc = e.Peer - state.snake[e.Node] = node - } - } - - if updateReceived { - if isSnakeCorrect() && !snakeWasCorrect { - action = StartSettlingTimer - } else { - action = StopSettlingTimer - } - } - - return state, action - } - - return prevState, StopSettlingTimer - } - - scenario.Validate(stateCapture, nodesAgreeOnCorrectSnakeFormation, SettlingTime, TestTimeout) + scenario.Validate(createSnakeStateCapture(nodes), nodesAgreeOnCorrectSnakeFormation, SettlingTime, TestTimeout) } diff --git a/router/tests/snake_validation.go b/router/tests/snake_validation.go new file mode 100644 index 00000000..a0e90b67 --- /dev/null +++ b/router/tests/snake_validation.go @@ -0,0 +1,105 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "sort" + + "github.com/matrix-org/pinecone/cmd/pineconesim/simulator" +) + +type SnakeNeighbours struct { + asc string + desc string +} + +type SnakeValidationState struct { + snake map[string]SnakeNeighbours + correctSnake map[string]SnakeNeighbours +} + +func createSnakeStateCapture(nodes []string) InitialStateCapture { + return func(state simulator.State) interface{} { + snakeNeighbours := make(map[string]SnakeNeighbours) + for _, node := range nodes { + asc := state.Nodes[node].AscendingPeer + desc := state.Nodes[node].DescendingPeer + snakeNeighbours[node] = SnakeNeighbours{asc: asc, desc: desc} + } + + nodesByKey := make(byKey, 0, len(state.Nodes)) + for key, value := range state.Nodes { + nodesByKey = append(nodesByKey, Node{key, value.PeerID}) + } + sort.Sort(nodesByKey) + + correctSnake := make(map[string]SnakeNeighbours) + lowest := SnakeNeighbours{asc: nodesByKey[1].name, desc: ""} + middle := SnakeNeighbours{asc: nodesByKey[2].name, desc: nodesByKey[0].name} + highest := SnakeNeighbours{asc: "", desc: nodesByKey[1].name} + correctSnake[nodesByKey[0].name] = lowest + correctSnake[nodesByKey[1].name] = middle + correctSnake[nodesByKey[2].name] = highest + + return SnakeValidationState{snakeNeighbours, correctSnake} + } +} + +func nodesAgreeOnCorrectSnakeFormation(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) { + switch state := prevState.(type) { + case SnakeValidationState: + isSnakeCorrect := func() bool { + snakeIsCorrect := true + for key, val := range state.snake { + if val.asc != state.correctSnake[key].asc || val.desc != state.correctSnake[key].desc { + snakeIsCorrect = false + break + } + } + return snakeIsCorrect + } + + snakeWasCorrect := isSnakeCorrect() + + action := DoNothing + updateReceived := false + switch e := event.(type) { + case simulator.SnakeAscUpdate: + updateReceived = true + if node, ok := state.snake[e.Node]; ok { + node.asc = e.Peer + state.snake[e.Node] = node + } + case simulator.SnakeDescUpdate: + updateReceived = true + if node, ok := state.snake[e.Node]; ok { + node.desc = e.Peer + state.snake[e.Node] = node + } + } + + if updateReceived { + if isSnakeCorrect() && !snakeWasCorrect { + action = StartSettlingTimer + } else { + action = StopSettlingTimer + } + } + + return state, action + } + + return prevState, StopSettlingTimer +} diff --git a/router/tests/tree_validation.go b/router/tests/tree_validation.go new file mode 100644 index 00000000..74c18a6a --- /dev/null +++ b/router/tests/tree_validation.go @@ -0,0 +1,92 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "log" + "sort" + + "github.com/matrix-org/pinecone/cmd/pineconesim/simulator" +) + +type TreeValidationState struct { + roots map[string]string + correctRoot string +} + +func createTreeStateCapture(nodes []string) InitialStateCapture { + return func(state simulator.State) interface{} { + lastRoots := make(map[string]string) + for _, node := range nodes { + lastRoots[node] = state.Nodes[node].Announcement.Root + } + + nodesByKey := make(byKey, 0, len(state.Nodes)) + for key, value := range state.Nodes { + nodesByKey = append(nodesByKey, Node{key, value.PeerID}) + } + sort.Sort(nodesByKey) + + correctRoot := nodesByKey[len(nodesByKey)-1].name + + return TreeValidationState{roots: lastRoots, correctRoot: correctRoot} + } +} + +func nodesAgreeOnCorrectTreeRoot(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) { + switch state := prevState.(type) { + case TreeValidationState: + action := DoNothing + switch e := event.(type) { + case simulator.TreeRootAnnUpdate: + if _, ok := state.roots[e.Node]; !ok { + // NOTE : only process events for nodes we care about + break + } + + if state.roots[e.Node] != e.Root { + log.Printf("Root changed for %s to %s", e.Node, e.Root) + state.roots[e.Node] = e.Root + } else { + log.Printf("Got duplicate root info for %s", e.Node) + break + } + + nodesAgreeOnRoot := true + rootSample := "" + for _, node := range state.roots { + rootSample = node + for _, comparison := range state.roots { + if node != comparison { + nodesAgreeOnRoot = false + break + } + } + } + + if nodesAgreeOnRoot && state.correctRoot == rootSample { + log.Println("Start settling for tree test") + action = StartSettlingTimer + } else { + log.Println("Stop settling for tree test") + action = StopSettlingTimer + } + } + + return state, action + } + + return prevState, StopSettlingTimer +} diff --git a/router/tests/util.go b/router/tests/util.go new file mode 100644 index 00000000..243ba03c --- /dev/null +++ b/router/tests/util.go @@ -0,0 +1,34 @@ +// copyright 2022 the matrix.org foundation c.i.c. +// +// licensed under the apache license, version 2.0 (the "license"); +// you may not use this file except in compliance with the license. +// you may obtain a copy of the license at +// +// http://www.apache.org/licenses/license-2.0 +// +// unless required by applicable law or agreed to in writing, software +// distributed under the license is distributed on an "as is" basis, +// without warranties or conditions of any kind, either express or implied. +// see the license for the specific language governing permissions and +// limitations under the license. + +package integration + +type Node struct { + name string + key string +} + +type byKey []Node + +func (l byKey) Len() int { + return len(l) +} + +func (l byKey) Less(i, j int) bool { + return l[i].key < l[j].key +} + +func (l byKey) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} From f40c3d1cfaf2fe80a1722720a95a444aa581a863 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sun, 30 Jan 2022 10:03:44 -0300 Subject: [PATCH 06/36] Add simple adversary integration tests --- cmd/pineconesim/simulator/commands.go | 30 ++++++-- cmd/pineconesim/simulator/nodes.go | 15 ++-- router/tests/adversary_test.go | 100 ++++++++++++++++++++++++++ router/tests/scenario_fixture.go | 31 ++++++++ router/tests/snake_validation.go | 37 ++++++++-- 5 files changed, 196 insertions(+), 17 deletions(-) create mode 100644 router/tests/adversary_test.go diff --git a/cmd/pineconesim/simulator/commands.go b/cmd/pineconesim/simulator/commands.go index 69295ac5..369af300 100644 --- a/cmd/pineconesim/simulator/commands.go +++ b/cmd/pineconesim/simulator/commands.go @@ -15,6 +15,7 @@ package simulator import ( + "crypto/ed25519" "fmt" "log" "strconv" @@ -47,6 +48,8 @@ func UnmarshalCommandJSON(command *SimCommandMsg) (SimCommand, error) { case SimAddNode: name := "" nodeType := UnknownType + privKeyStr := "" + var privKey *ed25519.PrivateKey = nil if val, ok := command.Event.(map[string]interface{})["Name"]; ok { name = val.(string) } else { @@ -58,7 +61,16 @@ func UnmarshalCommandJSON(command *SimCommandMsg) (SimCommand, error) { } else { err = fmt.Errorf("%sAddNode.NodeType field doesn't exist", FAILURE_PREAMBLE) } - msg = AddNode{name, nodeType} + + if val, ok := command.Event.(map[string]interface{})["PrivKey"]; ok { + privKeyStr = val.(string) + if len(privKeyStr) == ed25519.PrivateKeySize { + newPrivKey := ed25519.PrivateKey{} + copy(newPrivKey[:], privKeyStr) + privKey = &newPrivKey + } + } + msg = AddNode{name, nodeType, privKey} case SimRemoveNode: name := "" if val, ok := command.Event.(map[string]interface{})["Name"]; ok { @@ -315,21 +327,29 @@ func (c Delay) String() string { type AddNode struct { Node string NodeType APINodeType + PrivKey *ed25519.PrivateKey } // Tag AddNode as a Command func (c AddNode) Run(log *log.Logger, sim *Simulator) { log.Printf("Executing command %s", c) - if err := sim.CreateNode(c.Node, c.NodeType); err != nil { - log.Printf("Failed creating new node %s: %s", c.Node, err) - return + if c.PrivKey != nil { + if err := sim.CreateNodeWithKey(c.Node, c.NodeType, *c.PrivKey); err != nil { + log.Printf("Failed creating new node %s: %s", c.Node, err) + return + } + } else { + if err := sim.CreateNode(c.Node, c.NodeType); err != nil { + log.Printf("Failed creating new node %s: %s", c.Node, err) + return + } } sim.StartNodeEventHandler(c.Node, c.NodeType) } func (c AddNode) String() string { - return fmt.Sprintf("AddNode{Name:%s}", c.Node) + return fmt.Sprintf("AddNode{Name:%s, Type:%d, Private Key:%v}", c.Node, c.NodeType, c.PrivKey) } type RemoveNode struct { diff --git a/cmd/pineconesim/simulator/nodes.go b/cmd/pineconesim/simulator/nodes.go index 989cb5ec..a85bab3b 100644 --- a/cmd/pineconesim/simulator/nodes.go +++ b/cmd/pineconesim/simulator/nodes.go @@ -35,6 +35,15 @@ func (sim *Simulator) Node(t string) *Node { } func (sim *Simulator) CreateNode(t string, nodeType APINodeType) error { + _, sk, err := ed25519.GenerateKey(nil) + if err != nil { + return fmt.Errorf("ed25519.GenerateKey: %w", err) + } + + return sim.CreateNodeWithKey(t, nodeType, sk) +} + +func (sim *Simulator) CreateNodeWithKey(t string, nodeType APINodeType, privKey ed25519.PrivateKey) error { if _, ok := sim.nodes[t]; ok { return fmt.Errorf("%s already exists!", t) } @@ -56,16 +65,12 @@ func (sim *Simulator) CreateNode(t string, nodeType APINodeType) error { return fmt.Errorf("net.Listen: %w", err) } } - _, sk, err := ed25519.GenerateKey(nil) - if err != nil { - return fmt.Errorf("ed25519.GenerateKey: %w", err) - } crc := crc32.ChecksumIEEE([]byte(t)) color := 31 + (crc % 6) logger := log.New(sim.log.Writer(), fmt.Sprintf("\033[%dmNode %s:\033[0m ", color, t), 0) n := &Node{ - SimRouter: sim.routerCreationMap[nodeType](logger, sk, true), + SimRouter: sim.routerCreationMap[nodeType](logger, privKey, true), l: l, ListenAddr: tcpaddr, } diff --git a/router/tests/adversary_test.go b/router/tests/adversary_test.go new file mode 100644 index 00000000..11633699 --- /dev/null +++ b/router/tests/adversary_test.go @@ -0,0 +1,100 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "crypto/ed25519" + "testing" + "time" + + "github.com/matrix-org/pinecone/cmd/pineconesim/simulator/adversary" + "github.com/matrix-org/pinecone/types" +) + +var keyLowest = ed25519.PrivateKey{150, 150, 180, 13, 144, 213, 73, 184, 153, 38, 175, 150, 74, 58, 119, 123, 76, 148, 143, 63, 27, 138, 34, 91, 7, 38, 158, 206, 187, 106, 5, 206, 20, 81, 99, 14, 255, 61, 112, 173, 221, 15, 128, 30, 180, 215, 21, 109, 186, 3, 122, 27, 48, 140, 154, 155, 180, 71, 40, 161, 53, 169, 178, 140} +var keyMidOne = ed25519.PrivateKey{250, 250, 106, 34, 162, 70, 252, 104, 244, 18, 56, 2, 223, 2, 28, 64, 149, 194, 91, 54, 85, 52, 202, 74, 75, 200, 2, 101, 29, 37, 206, 40, 38, 137, 220, 170, 86, 99, 138, 172, 211, 245, 221, 195, 61, 62, 222, 18, 80, 19, 236, 194, 157, 70, 200, 159, 246, 207, 149, 55, 172, 29, 79, 49} +var keyMidTwo = ed25519.PrivateKey{200, 200, 47, 30, 166, 161, 173, 147, 101, 144, 249, 22, 165, 179, 104, 35, 58, 85, 45, 118, 228, 204, 236, 113, 82, 246, 216, 61, 240, 42, 198, 185, 105, 79, 204, 91, 148, 97, 125, 43, 194, 168, 128, 141, 110, 94, 108, 246, 60, 64, 200, 37, 31, 175, 4, 155, 178, 130, 210, 149, 21, 184, 152, 116} +var keyHighest = ed25519.PrivateKey{255, 255, 22, 151, 14, 92, 84, 175, 249, 1, 162, 19, 90, 51, 235, 125, 144, 252, 195, 128, 133, 219, 115, 134, 235, 245, 47, 201, 95, 100, 24, 28, 221, 105, 40, 142, 161, 4, 70, 1, 88, 44, 10, 118, 228, 71, 67, 0, 108, 195, 10, 244, 32, 186, 117, 77, 54, 109, 163, 23, 245, 161, 89, 211} + +func TestNodesAgreeOnCorrectTreeRootAdversaryNoDrops(t *testing.T) { + t.Parallel() + // Arrange + scenario := NewScenarioFixture(t) + genericNodes := []string{"Alice", "Bob", "Charlie"} + scenario.AddStandardNodes(genericNodes) + adversaries := []string{"Mallory"} + scenario.AddAdversaryNodes(adversaries) + + defaultDropRates := adversary.NewDropRates() + peerDropRates := map[string]adversary.DropRates{} + scenario.ConfigureAdversary("Mallory", defaultDropRates, peerDropRates) + + // Act + scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Charlie", "Mallory"}}) + + // Assert + nodesExpectedToConverge := append(genericNodes, adversaries...) + scenario.Validate(createTreeStateCapture(nodesExpectedToConverge), nodesAgreeOnCorrectTreeRoot, 2*time.Second, 5*time.Second) +} + +func TestNodesAgreeOnCorrectTreeRootAdversaryDropTreeAnnouncements(t *testing.T) { + t.Parallel() + // Arrange + scenario := NewScenarioFixture(t) + genericNodes := []string{"Alice", "Bob", "Charlie"} + scenario.AddStandardNodes(genericNodes) + + adversaries := []string{"Mallory"} + scenario.AddAdversaryNodes(adversaries) + + defaultDropRates := adversary.NewDropRates() + defaultDropRates.Frames[types.TypeTreeAnnouncement] = 100 + peerDropRates := map[string]adversary.DropRates{} + scenario.ConfigureAdversary("Mallory", defaultDropRates, peerDropRates) + + // Act + scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Charlie", "Mallory"}}) + + // Assert + nodesExpectedToConverge := (genericNodes) + scenario.Validate(createTreeStateCapture(nodesExpectedToConverge), nodesAgreeOnCorrectTreeRoot, 2*time.Second, 5*time.Second) +} + +func TestNodesAgreeOnCorrectTreeRootAdversaryDropSnakeHighestKey(t *testing.T) { + t.Parallel() + // Arrange + scenario := NewScenarioFixture(t) + genericNodes := map[string]*ed25519.PrivateKey{"Alice": &keyLowest, "Bob": &keyMidOne, "Charlie": &keyMidTwo} + scenario.AddStandardNodesWithKeys(genericNodes) + + adversaries := map[string]*ed25519.PrivateKey{"Mallory": &keyHighest} + scenario.AddAdversaryNodesWithKeys(adversaries) + + defaultDropRates := adversary.NewDropRates() + defaultDropRates.Frames[types.TypeVirtualSnakeBootstrap] = 100 + peerDropRates := map[string]adversary.DropRates{} + scenario.ConfigureAdversary("Mallory", defaultDropRates, peerDropRates) + + // Act + scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Charlie", "Mallory"}}) + + // Assert + nodes := make([]string, 0, len(genericNodes)) + for node := range genericNodes { + nodes = append(nodes, node) + } + nodesExpectedToConverge := nodes + scenario.Validate(createSnakeStateCapture(nodesExpectedToConverge), nodesAgreeOnCorrectSnakeFormation, 2*time.Second, 5*time.Second) +} diff --git a/router/tests/scenario_fixture.go b/router/tests/scenario_fixture.go index 33718de7..a69581a8 100644 --- a/router/tests/scenario_fixture.go +++ b/router/tests/scenario_fixture.go @@ -15,6 +15,7 @@ package integration import ( + "crypto/ed25519" "fmt" "log" "os" @@ -22,6 +23,7 @@ import ( "time" "github.com/matrix-org/pinecone/cmd/pineconesim/simulator" + "github.com/matrix-org/pinecone/cmd/pineconesim/simulator/adversary" ) type EventHandlerResult int @@ -70,6 +72,17 @@ func (s *ScenarioFixture) AddStandardNodes(nodes []string) { } } +func (s *ScenarioFixture) AddStandardNodesWithKeys(nodes map[string]*ed25519.PrivateKey) { + for node, key := range nodes { + cmd := simulator.AddNode{ + Node: node, + NodeType: simulator.DefaultNode, + PrivKey: key, + } + cmd.Run(s.log, s.sim) + } +} + func (s *ScenarioFixture) AddAdversaryNodes(nodes []string) { for _, node := range nodes { cmd := simulator.AddNode{ @@ -80,6 +93,17 @@ func (s *ScenarioFixture) AddAdversaryNodes(nodes []string) { } } +func (s *ScenarioFixture) AddAdversaryNodesWithKeys(nodes map[string]*ed25519.PrivateKey) { + for node, key := range nodes { + cmd := simulator.AddNode{ + Node: node, + NodeType: simulator.GeneralAdversaryNode, + PrivKey: key, + } + cmd.Run(s.log, s.sim) + } +} + func (s *ScenarioFixture) AddPeerConnections(conns []NodePair) { for _, pair := range conns { cmd := simulator.AddPeer{ @@ -90,6 +114,13 @@ func (s *ScenarioFixture) AddPeerConnections(conns []NodePair) { } } +func (s *ScenarioFixture) ConfigureAdversary(node string, defaults adversary.DropRates, peerDropRates map[string]adversary.DropRates) { + s.sim.ConfigureFilterDefaults(node, defaults) + for peer, rates := range peerDropRates { + s.sim.ConfigureFilterPeer(node, peer, rates) + } +} + func (s *ScenarioFixture) SubscribeToSimState(ch chan simulator.SimEvent) simulator.State { return s.sim.State.Subscribe(ch) } diff --git a/router/tests/snake_validation.go b/router/tests/snake_validation.go index a0e90b67..48715981 100644 --- a/router/tests/snake_validation.go +++ b/router/tests/snake_validation.go @@ -30,6 +30,15 @@ type SnakeValidationState struct { correctSnake map[string]SnakeNeighbours } +func contains(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + return false +} + func createSnakeStateCapture(nodes []string) InitialStateCapture { return func(state simulator.State) interface{} { snakeNeighbours := make(map[string]SnakeNeighbours) @@ -41,17 +50,31 @@ func createSnakeStateCapture(nodes []string) InitialStateCapture { nodesByKey := make(byKey, 0, len(state.Nodes)) for key, value := range state.Nodes { - nodesByKey = append(nodesByKey, Node{key, value.PeerID}) + if contains(nodes, key) { + nodesByKey = append(nodesByKey, Node{key, value.PeerID}) + } } sort.Sort(nodesByKey) correctSnake := make(map[string]SnakeNeighbours) - lowest := SnakeNeighbours{asc: nodesByKey[1].name, desc: ""} - middle := SnakeNeighbours{asc: nodesByKey[2].name, desc: nodesByKey[0].name} - highest := SnakeNeighbours{asc: "", desc: nodesByKey[1].name} - correctSnake[nodesByKey[0].name] = lowest - correctSnake[nodesByKey[1].name] = middle - correctSnake[nodesByKey[2].name] = highest + for i, node := range nodesByKey { + asc := "" + desc := "" + if i == 0 { + if len(nodesByKey) > 1 { + asc = nodesByKey[i+1].name + } + } else if i == len(nodesByKey)-1 { + if len(nodesByKey) > 1 { + desc = nodesByKey[i-1].name + } + } else { + asc = nodesByKey[i+1].name + desc = nodesByKey[i-1].name + } + + correctSnake[node.name] = SnakeNeighbours{asc: asc, desc: desc} + } return SnakeValidationState{snakeNeighbours, correctSnake} } From 1eabba56f3274be4a6beaab99a3ed469a5e9c302 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sun, 30 Jan 2022 10:25:50 -0300 Subject: [PATCH 07/36] Annotate integration test functions with param names --- router/tests/scenario_fixture.go | 4 ++-- router/tests/snake_validation.go | 2 +- router/tests/tree_validation.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/router/tests/scenario_fixture.go b/router/tests/scenario_fixture.go index a69581a8..b5ab0dde 100644 --- a/router/tests/scenario_fixture.go +++ b/router/tests/scenario_fixture.go @@ -34,8 +34,8 @@ const ( StartSettlingTimer ) -type InitialStateCapture func(state simulator.State) interface{} -type EventHandler func(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) +type InitialStateCapture func(state simulator.State) (initialState interface{}) +type EventHandler func(prevState interface{}, event simulator.SimEvent) (newState interface{}, result EventHandlerResult) type NodePair struct { A string diff --git a/router/tests/snake_validation.go b/router/tests/snake_validation.go index 48715981..2c4ef1ca 100644 --- a/router/tests/snake_validation.go +++ b/router/tests/snake_validation.go @@ -80,7 +80,7 @@ func createSnakeStateCapture(nodes []string) InitialStateCapture { } } -func nodesAgreeOnCorrectSnakeFormation(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) { +func nodesAgreeOnCorrectSnakeFormation(prevState interface{}, event simulator.SimEvent) (newState interface{}, result EventHandlerResult) { switch state := prevState.(type) { case SnakeValidationState: isSnakeCorrect := func() bool { diff --git a/router/tests/tree_validation.go b/router/tests/tree_validation.go index 74c18a6a..97741e42 100644 --- a/router/tests/tree_validation.go +++ b/router/tests/tree_validation.go @@ -45,7 +45,7 @@ func createTreeStateCapture(nodes []string) InitialStateCapture { } } -func nodesAgreeOnCorrectTreeRoot(prevState interface{}, event simulator.SimEvent) (interface{}, EventHandlerResult) { +func nodesAgreeOnCorrectTreeRoot(prevState interface{}, event simulator.SimEvent) (newState interface{}, result EventHandlerResult) { switch state := prevState.(type) { case TreeValidationState: action := DoNothing From 54033d7c48b8b1dea1d075cd3aa9d52ac893ab88 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Mon, 31 Jan 2022 18:34:37 -0300 Subject: [PATCH 08/36] Add null events to sim command interface --- cmd/pineconesim/simulator/events.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/pineconesim/simulator/events.go b/cmd/pineconesim/simulator/events.go index 08e0fe7f..61161858 100644 --- a/cmd/pineconesim/simulator/events.go +++ b/cmd/pineconesim/simulator/events.go @@ -20,6 +20,11 @@ type SimEvent interface { isEvent() } +type NullEvent struct{} + +// Tag NullEvent as an Event +func (e NullEvent) isEvent() {} + type NodeAdded struct { Node string PublicKey string From f1536990c4b504cf1a9f19428e25359db8abb673 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Mon, 31 Jan 2022 18:35:01 -0300 Subject: [PATCH 09/36] Add ping/pong packet types to drop packets filter --- cmd/pineconesim/simulator/adversary/drop_packets.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/pineconesim/simulator/adversary/drop_packets.go b/cmd/pineconesim/simulator/adversary/drop_packets.go index 216d6d97..4e3044e7 100644 --- a/cmd/pineconesim/simulator/adversary/drop_packets.go +++ b/cmd/pineconesim/simulator/adversary/drop_packets.go @@ -93,6 +93,10 @@ func defaultFrameCount() PeerFrameCount { frameCount[types.TypeVirtualSnakeTeardown] = atomic.NewUint64(0) frameCount[types.TypeTreeRouted] = atomic.NewUint64(0) frameCount[types.TypeVirtualSnakeRouted] = atomic.NewUint64(0) + frameCount[types.TypeTreePing] = atomic.NewUint64(0) + frameCount[types.TypeTreePong] = atomic.NewUint64(0) + frameCount[types.TypeSNEKPing] = atomic.NewUint64(0) + frameCount[types.TypeSNEKPong] = atomic.NewUint64(0) peerFrameCount := PeerFrameCount{ frameCount: frameCount, From 61d48ce48e5c78fbb12e6f453e40c453be297a84 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Mon, 31 Jan 2022 18:36:21 -0300 Subject: [PATCH 10/36] Update router filter logging for more details --- router/state_forward.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/state_forward.go b/router/state_forward.go index 4998bfee..14335d2b 100644 --- a/router/state_forward.go +++ b/router/state_forward.go @@ -52,7 +52,7 @@ func (s *state) _nextHopsFor(from *peer, frame *types.Frame) *peer { // teardowns, special handling will be done before forwarding if needed. func (s *state) _forward(p *peer, f *types.Frame) error { if s._filterPacket != nil && s._filterPacket(p.public, f) { - s.r.log.Printf("Packet of type %s destined for port %d [%s] was dropped due to filter rules", f.Type.String(), p.port, p.public.String()[:8]) + s.r.log.Printf("Packet of type %s destined for port %d [%s] from node %s [%s] was dropped due to filter rules", f.Type.String(), p.port, p.public.String()[:8], f.Source.String(), f.SourceKey.String()[:8]) return nil } From 4f9086f1d313ec8c53a8cedba55f1e19f9e5fc0c Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Mon, 31 Jan 2022 18:41:12 -0300 Subject: [PATCH 11/36] Don't update sim graph if not being used --- cmd/pineconesim/simulator/interface.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/pineconesim/simulator/interface.go b/cmd/pineconesim/simulator/interface.go index 6ab182bf..48fc727d 100644 --- a/cmd/pineconesim/simulator/interface.go +++ b/cmd/pineconesim/simulator/interface.go @@ -36,11 +36,13 @@ func (sim *Simulator) ReportDistance(a, b string, l int64, snek bool) { sim.dists[a][b].ObservedTree = l } if sim.dists[a][b].Real == 0 { - na, _ := sim.graph.GetMapping(a) - nb, _ := sim.graph.GetMapping(b) - path, err := sim.graph.Shortest(na, nb) - if err == nil { - sim.dists[a][b].Real = path.Distance + if sim.graph != nil { + na, _ := sim.graph.GetMapping(a) + nb, _ := sim.graph.GetMapping(b) + path, err := sim.graph.Shortest(na, nb) + if err == nil { + sim.dists[a][b].Real = path.Distance + } } } } From 67e9c1f281ad8f1aa52511729116851232c89bf5 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Mon, 31 Jan 2022 18:41:32 -0300 Subject: [PATCH 12/36] Add logging on successful sim snek ping/pong --- cmd/pineconesim/simulator/pathfind.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/pineconesim/simulator/pathfind.go b/cmd/pineconesim/simulator/pathfind.go index 19add920..d8e1ec72 100644 --- a/cmd/pineconesim/simulator/pathfind.go +++ b/cmd/pineconesim/simulator/pathfind.go @@ -70,6 +70,7 @@ func (sim *Simulator) PingSNEK(from, to string) (uint16, time.Duration, error) { } success = true + sim.log.Printf("Successful ping from %s to %s", from, to) sim.ReportDistance(from, to, int64(hops), true) return hops, rtt, nil } From 58555a233522832e86d6ebd9a523b632d2753f3d Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Mon, 31 Jan 2022 18:41:58 -0300 Subject: [PATCH 13/36] Add adversarial integration tests --- router/tests/adversary_test.go | 218 ++++++++++++++++++++----- router/tests/basic_integration_test.go | 4 +- router/tests/scenario_fixture.go | 38 +++-- router/tests/snake_validation.go | 69 ++++++-- router/tests/tree_validation.go | 46 +++--- 5 files changed, 282 insertions(+), 93 deletions(-) diff --git a/router/tests/adversary_test.go b/router/tests/adversary_test.go index 11633699..69c08263 100644 --- a/router/tests/adversary_test.go +++ b/router/tests/adversary_test.go @@ -12,10 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build test_adversary +// +build test_adversary + package integration import ( "crypto/ed25519" + "fmt" + "log" + "strings" "testing" "time" @@ -23,31 +29,14 @@ import ( "github.com/matrix-org/pinecone/types" ) -var keyLowest = ed25519.PrivateKey{150, 150, 180, 13, 144, 213, 73, 184, 153, 38, 175, 150, 74, 58, 119, 123, 76, 148, 143, 63, 27, 138, 34, 91, 7, 38, 158, 206, 187, 106, 5, 206, 20, 81, 99, 14, 255, 61, 112, 173, 221, 15, 128, 30, 180, 215, 21, 109, 186, 3, 122, 27, 48, 140, 154, 155, 180, 71, 40, 161, 53, 169, 178, 140} -var keyMidOne = ed25519.PrivateKey{250, 250, 106, 34, 162, 70, 252, 104, 244, 18, 56, 2, 223, 2, 28, 64, 149, 194, 91, 54, 85, 52, 202, 74, 75, 200, 2, 101, 29, 37, 206, 40, 38, 137, 220, 170, 86, 99, 138, 172, 211, 245, 221, 195, 61, 62, 222, 18, 80, 19, 236, 194, 157, 70, 200, 159, 246, 207, 149, 55, 172, 29, 79, 49} -var keyMidTwo = ed25519.PrivateKey{200, 200, 47, 30, 166, 161, 173, 147, 101, 144, 249, 22, 165, 179, 104, 35, 58, 85, 45, 118, 228, 204, 236, 113, 82, 246, 216, 61, 240, 42, 198, 185, 105, 79, 204, 91, 148, 97, 125, 43, 194, 168, 128, 141, 110, 94, 108, 246, 60, 64, 200, 37, 31, 175, 4, 155, 178, 130, 210, 149, 21, 184, 152, 116} -var keyHighest = ed25519.PrivateKey{255, 255, 22, 151, 14, 92, 84, 175, 249, 1, 162, 19, 90, 51, 235, 125, 144, 252, 195, 128, 133, 219, 115, 134, 235, 245, 47, 201, 95, 100, 24, 28, 221, 105, 40, 142, 161, 4, 70, 1, 88, 44, 10, 118, 228, 71, 67, 0, 108, 195, 10, 244, 32, 186, 117, 77, 54, 109, 163, 23, 245, 161, 89, 211} - -func TestNodesAgreeOnCorrectTreeRootAdversaryNoDrops(t *testing.T) { - t.Parallel() - // Arrange - scenario := NewScenarioFixture(t) - genericNodes := []string{"Alice", "Bob", "Charlie"} - scenario.AddStandardNodes(genericNodes) - adversaries := []string{"Mallory"} - scenario.AddAdversaryNodes(adversaries) +var keyAA = ed25519.PrivateKey{162, 26, 23, 81, 135, 60, 226, 48, 76, 67, 143, 253, 156, 159, 49, 135, 0, 98, 62, 97, 233, 228, 220, 156, 6, 159, 71, 51, 158, 168, 98, 248, 170, 170, 180, 9, 248, 84, 20, 240, 141, 118, 183, 23, 7, 120, 31, 109, 72, 88, 6, 104, 51, 155, 117, 44, 50, 157, 81, 104, 18, 73, 249, 94} +var keyBB = ed25519.PrivateKey{126, 39, 115, 163, 236, 170, 93, 74, 222, 226, 27, 212, 18, 17, 52, 167, 199, 150, 49, 224, 81, 21, 77, 182, 245, 137, 255, 250, 142, 60, 57, 29, 187, 187, 168, 181, 14, 162, 182, 255, 57, 139, 163, 231, 33, 194, 45, 112, 112, 137, 68, 12, 23, 50, 62, 47, 208, 49, 37, 65, 112, 114, 153, 215} +var keyCC = ed25519.PrivateKey{88, 94, 100, 40, 136, 89, 209, 11, 241, 92, 136, 72, 232, 113, 52, 96, 173, 4, 73, 213, 223, 110, 222, 181, 169, 63, 140, 37, 169, 202, 227, 181, 204, 204, 102, 150, 191, 219, 16, 52, 178, 118, 12, 115, 3, 167, 138, 52, 255, 183, 159, 124, 134, 166, 167, 137, 65, 168, 16, 196, 41, 127, 21, 206} +var keyDD = ed25519.PrivateKey{70, 46, 50, 7, 34, 2, 71, 228, 187, 108, 175, 125, 158, 249, 114, 239, 111, 86, 10, 167, 27, 199, 131, 111, 50, 252, 162, 178, 37, 150, 219, 54, 221, 221, 113, 44, 216, 192, 87, 137, 164, 138, 177, 86, 229, 65, 246, 146, 191, 204, 107, 219, 4, 102, 213, 251, 71, 255, 51, 133, 168, 124, 30, 224} +var keyEE = ed25519.PrivateKey{8, 74, 248, 201, 148, 80, 73, 116, 112, 92, 53, 189, 238, 60, 68, 7, 18, 107, 63, 201, 29, 167, 243, 27, 21, 227, 204, 90, 225, 16, 113, 143, 238, 238, 73, 38, 91, 255, 177, 179, 179, 151, 55, 84, 129, 232, 94, 187, 42, 206, 159, 99, 249, 146, 32, 0, 133, 61, 101, 91, 77, 9, 179, 158} +var keyFF = ed25519.PrivateKey{110, 213, 18, 180, 196, 140, 66, 142, 132, 208, 11, 196, 242, 179, 47, 227, 153, 156, 76, 146, 254, 72, 89, 93, 42, 134, 28, 64, 153, 61, 18, 246, 255, 255, 144, 7, 51, 1, 43, 177, 12, 48, 76, 248, 107, 197, 3, 223, 189, 198, 162, 38, 4, 122, 61, 58, 142, 251, 63, 162, 33, 134, 9, 141} - defaultDropRates := adversary.NewDropRates() - peerDropRates := map[string]adversary.DropRates{} - scenario.ConfigureAdversary("Mallory", defaultDropRates, peerDropRates) - - // Act - scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Charlie", "Mallory"}}) - - // Assert - nodesExpectedToConverge := append(genericNodes, adversaries...) - scenario.Validate(createTreeStateCapture(nodesExpectedToConverge), nodesAgreeOnCorrectTreeRoot, 2*time.Second, 5*time.Second) -} +var sortedKeys = []ed25519.PrivateKey{keyAA, keyBB, keyCC, keyDD, keyEE, keyFF} func TestNodesAgreeOnCorrectTreeRootAdversaryDropTreeAnnouncements(t *testing.T) { t.Parallel() @@ -65,36 +54,181 @@ func TestNodesAgreeOnCorrectTreeRootAdversaryDropTreeAnnouncements(t *testing.T) scenario.ConfigureAdversary("Mallory", defaultDropRates, peerDropRates) // Act - scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Charlie", "Mallory"}}) + scenario.ConnectNodes([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Charlie", "Mallory"}}) // Assert nodesExpectedToConverge := (genericNodes) scenario.Validate(createTreeStateCapture(nodesExpectedToConverge), nodesAgreeOnCorrectTreeRoot, 2*time.Second, 5*time.Second) } -func TestNodesAgreeOnCorrectTreeRootAdversaryDropSnakeHighestKey(t *testing.T) { +type TestCase struct { + desc string + genericNodes []string + adversaries []string + connections []NodePair + framesToDrop []types.FrameType +} + +type KeyMap map[string]*ed25519.PrivateKey + +type SystemUnderTest struct { + scenario ScenarioFixture + genericNodeKeys KeyMap + adversaryKeys KeyMap +} + +func TestNodesCanPingAdversaryDropsSnakeBootstraps(t *testing.T) { t.Parallel() - // Arrange - scenario := NewScenarioFixture(t) - genericNodes := map[string]*ed25519.PrivateKey{"Alice": &keyLowest, "Bob": &keyMidOne, "Charlie": &keyMidTwo} - scenario.AddStandardNodesWithKeys(genericNodes) - adversaries := map[string]*ed25519.PrivateKey{"Mallory": &keyHighest} - scenario.AddAdversaryNodesWithKeys(adversaries) + cases := []TestCase{ + { + "Test2NodeLineSNEKBootstrap", + []string{"Alice", "Bob"}, + []string{"Mallory"}, + []NodePair{NodePair{"Alice", "Bob"}, NodePair{"Alice", "Mallory"}}, + []types.FrameType{types.TypeVirtualSnakeBootstrap}, + }, + { + "Test3NodeLineSNEKBootstrap", + []string{"Alice", "Bob", "Charlie"}, + []string{"Mallory"}, + []NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Alice", "Mallory"}}, + []types.FrameType{types.TypeVirtualSnakeBootstrap}, + }, + { + "Test3NodeLineSNEKBootstrapACK", + []string{"Alice", "Bob", "Charlie"}, + []string{"Mallory"}, + []NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Alice", "Mallory"}}, + []types.FrameType{types.TypeVirtualSnakeBootstrapACK}, + }, + { + "Test3NodeLineSNEKSetup", + []string{"Alice", "Bob", "Charlie"}, + []string{"Mallory"}, + []NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Alice", "Mallory"}}, + []types.FrameType{types.TypeVirtualSnakeSetup}, + }, + { + "Test3NodeLineSNEKSetupACK", + []string{"Alice", "Bob", "Charlie"}, + []string{"Mallory"}, + []NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Alice", "Mallory"}}, + []types.FrameType{types.TypeVirtualSnakeSetupACK}, + }, + { + "Test3NodeTriangleSNEKBootstrap", + []string{"Alice", "Bob", "Charlie"}, + []string{"Mallory"}, + []NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Charlie", "Alice"}, NodePair{"Alice", "Mallory"}}, + []types.FrameType{types.TypeVirtualSnakeBootstrap}, + }, + { + "Test4NodeFanSNEKBootstrap", + []string{"Alice", "Bob", "Charlie", "Dan"}, + []string{"Mallory"}, + []NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Bob", "Dan"}, NodePair{"Dan", "Alice"}, NodePair{"Alice", "Mallory"}}, + []types.FrameType{types.TypeVirtualSnakeBootstrap}, + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + totalNodes := len(tc.genericNodes) + len(tc.adversaries) + if totalNodes > len(sortedKeys) { + t.Fatalf("Too many nodes specified in the test case. There are only %d keys available.", len(sortedKeys)) + } + + keySubset := []*ed25519.PrivateKey{} + for i := 0; i < totalNodes; i++ { + keySubset = append(keySubset, &sortedKeys[i]) + } + keyPermutations := permutations(keySubset) + + for i, keyOrder := range keyPermutations { + i, keyOrder := i, keyOrder + t.Run(fmt.Sprintf("KeyPermutation#%d", i), func(t *testing.T) { + t.Parallel() + + log.Printf("Starting Test Case: %s", tc.desc) + // Arrange + sut := createSystemUnderTest(t, tc, keyOrder, []types.FrameType{types.TypeVirtualSnakeBootstrap}) + + // Act + sut.scenario.ConnectNodes(tc.connections) + + // Assert + sut.scenario.Validate(createTreeStateCapture(tc.genericNodes), nodesAgreeOnCorrectTreeRoot, SettlingTime, TestTimeout) + if !nodesCanAllPingEachOther(&sut.scenario, tc.genericNodes) { + printSystemUnderTest(tc, &sut) + } + }) + } + }) + } +} + +func createSystemUnderTest(t *testing.T, tc TestCase, keyOrder []*ed25519.PrivateKey, dropTypes []types.FrameType) SystemUnderTest { + fixture := NewScenarioFixture(t) + keyIndex := 0 + genericNodeKeys := KeyMap{} + for _, node := range tc.genericNodes { + genericNodeKeys[node] = keyOrder[keyIndex] + keyIndex++ + } + fixture.AddStandardNodesWithKeys(genericNodeKeys) + + adversaryKeys := KeyMap{} + for _, node := range tc.adversaries { + adversaryKeys[node] = keyOrder[keyIndex] + keyIndex++ + } + fixture.AddAdversaryNodesWithKeys(adversaryKeys) defaultDropRates := adversary.NewDropRates() - defaultDropRates.Frames[types.TypeVirtualSnakeBootstrap] = 100 + for _, frameType := range dropTypes { + defaultDropRates.Frames[frameType] = 100 + } peerDropRates := map[string]adversary.DropRates{} - scenario.ConfigureAdversary("Mallory", defaultDropRates, peerDropRates) + for _, adversary := range tc.adversaries { + fixture.ConfigureAdversary(adversary, defaultDropRates, peerDropRates) + } - // Act - scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}, NodePair{"Charlie", "Mallory"}}) + return SystemUnderTest{fixture, genericNodeKeys, adversaryKeys} +} - // Assert - nodes := make([]string, 0, len(genericNodes)) - for node := range genericNodes { - nodes = append(nodes, node) +func printSystemUnderTest(tc TestCase, sut *SystemUnderTest) { + keys := map[string]string{} + for k, v := range sut.genericNodeKeys { + var sk types.PrivateKey + copy(sk[:], *v) + keys[k] = strings.ToUpper(sk.Public().String()[:8]) + } + for k, v := range sut.adversaryKeys { + var sk types.PrivateKey + copy(sk[:], *v) + keys[k] = strings.ToUpper(sk.Public().String()[:8]) } - nodesExpectedToConverge := nodes - scenario.Validate(createSnakeStateCapture(nodesExpectedToConverge), nodesAgreeOnCorrectSnakeFormation, 2*time.Second, 5*time.Second) + + sut.scenario.t.Errorf("Test Parameters: \nTest Case: %+v\nNode Keys: %+v", tc, keys) +} + +func permutations(xs []*ed25519.PrivateKey) (permuts [][]*ed25519.PrivateKey) { + var rc func([]*ed25519.PrivateKey, int) + rc = func(a []*ed25519.PrivateKey, k int) { + if k == len(a) { + permuts = append(permuts, append([]*ed25519.PrivateKey{}, a...)) + } else { + for i := k; i < len(xs); i++ { + a[k], a[i] = a[i], a[k] + rc(a, k+1) + a[k], a[i] = a[i], a[k] + } + } + } + rc(xs, 0) + + return permuts } diff --git a/router/tests/basic_integration_test.go b/router/tests/basic_integration_test.go index 0347be23..4d103dae 100644 --- a/router/tests/basic_integration_test.go +++ b/router/tests/basic_integration_test.go @@ -30,7 +30,7 @@ func TestNodesAgreeOnCorrectTreeRoot(t *testing.T) { scenario.AddStandardNodes(nodes) // Act - scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}}) + scenario.ConnectNodes([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}}) // Assert scenario.Validate(createTreeStateCapture(nodes), nodesAgreeOnCorrectTreeRoot, SettlingTime, TestTimeout) @@ -44,7 +44,7 @@ func TestNodesAgreeOnCorrectSnakeFormation(t *testing.T) { scenario.AddStandardNodes(nodes) // Act - scenario.AddPeerConnections([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}}) + scenario.ConnectNodes([]NodePair{NodePair{"Alice", "Bob"}, NodePair{"Bob", "Charlie"}}) // Assert scenario.Validate(createSnakeStateCapture(nodes), nodesAgreeOnCorrectSnakeFormation, SettlingTime, TestTimeout) diff --git a/router/tests/scenario_fixture.go b/router/tests/scenario_fixture.go index b5ab0dde..5c209d43 100644 --- a/router/tests/scenario_fixture.go +++ b/router/tests/scenario_fixture.go @@ -35,7 +35,7 @@ const ( ) type InitialStateCapture func(state simulator.State) (initialState interface{}) -type EventHandler func(prevState interface{}, event simulator.SimEvent) (newState interface{}, result EventHandlerResult) +type EventHandler func(prevState interface{}, event simulator.SimEvent, isSettling bool) (newState interface{}, result EventHandlerResult) type NodePair struct { A string @@ -104,7 +104,7 @@ func (s *ScenarioFixture) AddAdversaryNodesWithKeys(nodes map[string]*ed25519.Pr } } -func (s *ScenarioFixture) AddPeerConnections(conns []NodePair) { +func (s *ScenarioFixture) ConnectNodes(conns []NodePair) { for _, pair := range conns { cmd := simulator.AddPeer{ Node: pair.A, @@ -140,7 +140,7 @@ func (s *ScenarioFixture) Validate(initialState InitialStateCapture, eventHandle failed = true quit <- true case <-output: - log.Println("Test passed") + log.Println("Successful validation") } if failed { @@ -157,6 +157,26 @@ func assertState(scenario *ScenarioFixture, stateCapture InitialStateCapture, ev state := scenario.SubscribeToSimState(simUpdates) prevState := stateCapture(state) + isSettling := false + + handleResult := func(newResult EventHandlerResult) { + switch newResult { + case StartSettlingTimer: + log.Println("Starting validation settling timer") + settlingTimer.Reset(settlingTime) + isSettling = true + case StopSettlingTimer: + if isSettling { + log.Println("Stopping validation settling timer") + settlingTimer.Stop() + isSettling = false + } + } + } + + newState, initialResult := eventHandler(prevState, simulator.NullEvent{}, isSettling) + handleResult(initialResult) + prevState = newState for { select { @@ -166,16 +186,8 @@ func assertState(scenario *ScenarioFixture, stateCapture InitialStateCapture, ev case <-settlingTimer.C: output <- "PASS" case event := <-simUpdates: - newState, newResult := eventHandler(prevState, event) - switch newResult { - case StartSettlingTimer: - log.Println("Starting settling timer") - settlingTimer.Reset(settlingTime) - case StopSettlingTimer: - log.Println("Stopping settling timer") - settlingTimer.Stop() - } - + newState, newResult := eventHandler(prevState, event, isSettling) + handleResult(newResult) prevState = newState } } diff --git a/router/tests/snake_validation.go b/router/tests/snake_validation.go index 2c4ef1ca..ca2fa5d2 100644 --- a/router/tests/snake_validation.go +++ b/router/tests/snake_validation.go @@ -15,7 +15,9 @@ package integration import ( + "log" "sort" + "sync" "github.com/matrix-org/pinecone/cmd/pineconesim/simulator" ) @@ -80,9 +82,10 @@ func createSnakeStateCapture(nodes []string) InitialStateCapture { } } -func nodesAgreeOnCorrectSnakeFormation(prevState interface{}, event simulator.SimEvent) (newState interface{}, result EventHandlerResult) { +func nodesAgreeOnCorrectSnakeFormation(prevState interface{}, event simulator.SimEvent, isSettling bool) (newState interface{}, result EventHandlerResult) { switch state := prevState.(type) { case SnakeValidationState: + snakeWasCorrect := isSettling isSnakeCorrect := func() bool { snakeIsCorrect := true for key, val := range state.snake { @@ -94,31 +97,25 @@ func nodesAgreeOnCorrectSnakeFormation(prevState interface{}, event simulator.Si return snakeIsCorrect } - snakeWasCorrect := isSnakeCorrect() - - action := DoNothing - updateReceived := false switch e := event.(type) { case simulator.SnakeAscUpdate: - updateReceived = true if node, ok := state.snake[e.Node]; ok { node.asc = e.Peer state.snake[e.Node] = node } case simulator.SnakeDescUpdate: - updateReceived = true if node, ok := state.snake[e.Node]; ok { node.desc = e.Peer state.snake[e.Node] = node } } - if updateReceived { - if isSnakeCorrect() && !snakeWasCorrect { - action = StartSettlingTimer - } else { - action = StopSettlingTimer - } + action := DoNothing + isCorrect := isSnakeCorrect() + if isCorrect && !snakeWasCorrect { + action = StartSettlingTimer + } else if !isCorrect { + action = StopSettlingTimer } return state, action @@ -126,3 +123,49 @@ func nodesAgreeOnCorrectSnakeFormation(prevState interface{}, event simulator.Si return prevState, StopSettlingTimer } + +func pingNode(scenario *ScenarioFixture, from string, to string, ch chan bool, wg *sync.WaitGroup) { + log.Printf("Pinging from %s to %s", from, to) + passed := true + if _, _, err := scenario.sim.PingSNEK(from, to); err != nil { + passed = false + scenario.t.Errorf("Failed pinging from %s to %s: %s", from, to, err) + } + + ch <- passed + wg.Done() +} + +func nodesCanAllPingEachOther(scenario *ScenarioFixture, nodes []string) bool { + wg := sync.WaitGroup{} + + numberOfPings := 0 + for range nodes { + for range nodes { + numberOfPings++ + } + } + + ch := make(chan bool, numberOfPings) + + for _, from := range nodes { + for _, to := range nodes { + if from != to { + wg.Add(1) + go pingNode(scenario, from, to, ch, &wg) + } + } + } + + wg.Wait() + close(ch) + + overallPass := true + for passed := range ch { + if !passed { + overallPass = false + } + } + + return overallPass +} diff --git a/router/tests/tree_validation.go b/router/tests/tree_validation.go index 97741e42..f29f77d1 100644 --- a/router/tests/tree_validation.go +++ b/router/tests/tree_validation.go @@ -15,7 +15,6 @@ package integration import ( - "log" "sort" "github.com/matrix-org/pinecone/cmd/pineconesim/simulator" @@ -45,25 +44,11 @@ func createTreeStateCapture(nodes []string) InitialStateCapture { } } -func nodesAgreeOnCorrectTreeRoot(prevState interface{}, event simulator.SimEvent) (newState interface{}, result EventHandlerResult) { +func nodesAgreeOnCorrectTreeRoot(prevState interface{}, event simulator.SimEvent, isSettling bool) (newState interface{}, result EventHandlerResult) { switch state := prevState.(type) { case TreeValidationState: - action := DoNothing - switch e := event.(type) { - case simulator.TreeRootAnnUpdate: - if _, ok := state.roots[e.Node]; !ok { - // NOTE : only process events for nodes we care about - break - } - - if state.roots[e.Node] != e.Root { - log.Printf("Root changed for %s to %s", e.Node, e.Root) - state.roots[e.Node] = e.Root - } else { - log.Printf("Got duplicate root info for %s", e.Node) - break - } - + treeWasConverged := isSettling + isTreeConverged := func() bool { nodesAgreeOnRoot := true rootSample := "" for _, node := range state.roots { @@ -75,16 +60,31 @@ func nodesAgreeOnCorrectTreeRoot(prevState interface{}, event simulator.SimEvent } } } + return nodesAgreeOnRoot && rootSample == state.correctRoot + } + + switch e := event.(type) { + case simulator.TreeRootAnnUpdate: + if _, ok := state.roots[e.Node]; !ok { + // NOTE : only process events for nodes we care about + break + } - if nodesAgreeOnRoot && state.correctRoot == rootSample { - log.Println("Start settling for tree test") - action = StartSettlingTimer + if state.roots[e.Node] != e.Root { + state.roots[e.Node] = e.Root } else { - log.Println("Stop settling for tree test") - action = StopSettlingTimer + break } } + action := DoNothing + isConverged := isTreeConverged() + if isConverged && !treeWasConverged { + action = StartSettlingTimer + } else if !isConverged { + action = StopSettlingTimer + } + return state, action } From a7133f73795cdc0545f9c2a8b3298129e4c0e18f Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Thu, 10 Feb 2022 12:18:04 -0300 Subject: [PATCH 14/36] Allow for running specific test cases since using run isn't working --- router/tests/adversary_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/router/tests/adversary_test.go b/router/tests/adversary_test.go index 69c08263..3b7b9907 100644 --- a/router/tests/adversary_test.go +++ b/router/tests/adversary_test.go @@ -132,7 +132,13 @@ func TestNodesCanPingAdversaryDropsSnakeBootstraps(t *testing.T) { }, } + runSpecificTest := false + specificTest, specificPermutation := "Test4NodeFanSNEKBootstrap", 2 + for _, tc := range cases { + if runSpecificTest && tc.desc != specificTest { + continue + } tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() @@ -148,6 +154,9 @@ func TestNodesCanPingAdversaryDropsSnakeBootstraps(t *testing.T) { keyPermutations := permutations(keySubset) for i, keyOrder := range keyPermutations { + if runSpecificTest && i != specificPermutation { + continue + } i, keyOrder := i, keyOrder t.Run(fmt.Sprintf("KeyPermutation#%d", i), func(t *testing.T) { t.Parallel() From d0b3cd8ea6ae00a788d8131d30165a27affae69b Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Thu, 10 Feb 2022 14:30:01 -0300 Subject: [PATCH 15/36] Rename adversary test to better align with new test parameters --- router/tests/adversary_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/tests/adversary_test.go b/router/tests/adversary_test.go index 3b7b9907..bf5b6c16 100644 --- a/router/tests/adversary_test.go +++ b/router/tests/adversary_test.go @@ -77,7 +77,7 @@ type SystemUnderTest struct { adversaryKeys KeyMap } -func TestNodesCanPingAdversaryDropsSnakeBootstraps(t *testing.T) { +func TestNodesCanPingAdversaryDropsFrames(t *testing.T) { t.Parallel() cases := []TestCase{ From 02a66ca2ff762f06e80a187624248ed2d1b2cc9e Mon Sep 17 00:00:00 2001 From: devonh Date: Wed, 23 Feb 2022 10:44:32 +0000 Subject: [PATCH 16/36] Fix bug where bootstrap ack was handled incorrectly (#31) --- router/state_snek.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/state_snek.go b/router/state_snek.go index 539b0e3f..8e0a01e5 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -417,7 +417,7 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame) error { case asc != nil && asc.valid(): // We already have an ascending entry and it hasn't expired yet. switch { - case asc.PublicKey == rx.SourceKey && bootstrapACK.PathID != asc.PathID: + case asc.Origin == rx.SourceKey && bootstrapACK.PathID != asc.PathID: // We've received another bootstrap ACK from our direct ascending node. // Just refresh the record and then send a new path setup message to // that node. From 32cd2d9bf3e83e4d4c121fe4e76e9d01ba89ea86 Mon Sep 17 00:00:00 2001 From: devonh Date: Thu, 24 Feb 2022 15:40:10 +0000 Subject: [PATCH 17/36] Fix buffer size checks for un/marshal snake bootstrap (#32) * Fix buffer size checks for un/marshal snake bootstrap * Move root length calculations to a common location --- types/announcement.go | 8 ++++++++ types/virtualsnake.go | 16 ++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/types/announcement.go b/types/announcement.go index 3a48d7a4..5902b9f5 100644 --- a/types/announcement.go +++ b/types/announcement.go @@ -25,6 +25,14 @@ type Root struct { RootSequence Varu64 } +func (r *Root) Length() int { + return ed25519.PublicKeySize + r.RootSequence.Length() +} + +func (r *Root) MinLength() int { + return ed25519.PublicKeySize + 1 +} + type SwitchAnnouncement struct { Root Signatures []SignatureWithHop diff --git a/types/virtualsnake.go b/types/virtualsnake.go index dbc86b38..2c8f6563 100644 --- a/types/virtualsnake.go +++ b/types/virtualsnake.go @@ -22,7 +22,7 @@ type VirtualSnakeBootstrap struct { } func (v *VirtualSnakeBootstrap) MarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+ed25519.PublicKeySize+ed25519.SignatureSize { + if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+ed25519.SignatureSize { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -38,7 +38,7 @@ func (v *VirtualSnakeBootstrap) MarshalBinary(buf []byte) (int, error) { } func (v *VirtualSnakeBootstrap) UnmarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+ed25519.PublicKeySize+ed25519.SignatureSize { + if len(buf) < VirtualSnakePathIDLength+v.Root.MinLength()+ed25519.SignatureSize { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -61,7 +61,7 @@ type VirtualSnakeBootstrapACK struct { } func (v *VirtualSnakeBootstrapACK) MarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+ed25519.PublicKeySize+v.RootSequence.Length()+(ed25519.SignatureSize*2) { + if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+(ed25519.SignatureSize*2) { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -78,7 +78,7 @@ func (v *VirtualSnakeBootstrapACK) MarshalBinary(buf []byte) (int, error) { } func (v *VirtualSnakeBootstrapACK) UnmarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+ed25519.PublicKeySize+1+(ed25519.SignatureSize*2) { + if len(buf) < VirtualSnakePathIDLength+v.Root.MinLength()+(ed25519.SignatureSize*2) { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -102,7 +102,7 @@ type VirtualSnakeSetup struct { } func (v *VirtualSnakeSetup) MarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+ed25519.PublicKeySize+v.RootSequence.Length()+(ed25519.SignatureSize*2) { + if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+(ed25519.SignatureSize*2) { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -119,7 +119,7 @@ func (v *VirtualSnakeSetup) MarshalBinary(buf []byte) (int, error) { } func (v *VirtualSnakeSetup) UnmarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+ed25519.PublicKeySize+1+(ed25519.SignatureSize*2) { + if len(buf) < VirtualSnakePathIDLength+v.Root.MinLength()+(ed25519.SignatureSize*2) { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -142,7 +142,7 @@ type VirtualSnakeSetupACK struct { } func (v *VirtualSnakeSetupACK) MarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+ed25519.PublicKeySize+v.RootSequence.Length()+ed25519.SignatureSize { + if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+ed25519.SignatureSize { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -158,7 +158,7 @@ func (v *VirtualSnakeSetupACK) MarshalBinary(buf []byte) (int, error) { } func (v *VirtualSnakeSetupACK) UnmarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+ed25519.PublicKeySize+1+ed25519.SignatureSize { + if len(buf) < VirtualSnakePathIDLength+v.Root.MinLength()+ed25519.SignatureSize { return 0, fmt.Errorf("buffer too small") } offset := 0 From 70b73bb01ed715a82c0751a2fbd77d077077438a Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sat, 26 Feb 2022 17:11:53 -0300 Subject: [PATCH 18/36] Initial prototype to assist nodes failing to bootstrap --- router/state.go | 46 +++++++---- router/state_forward.go | 19 +++-- router/state_snek.go | 132 +++++++++++++++++++++++++++++-- router/state_tree.go | 4 + router/tests/adversary_test.go | 7 +- router/tests/snake_validation.go | 10 ++- types/bootstrap_signature.go | 48 +++++++++++ types/virtualsnake.go | 80 ++++++++++++++++++- 8 files changed, 311 insertions(+), 35 deletions(-) create mode 100644 types/bootstrap_signature.go diff --git a/router/state.go b/router/state.go index ac28c0ff..a9c4a62e 100644 --- a/router/state.go +++ b/router/state.go @@ -32,23 +32,32 @@ type FilterFn func(from types.PublicKey, f *types.Frame) bool // NOTE: Functions prefixed with an underscore (_) are only safe to be called // from the actor that owns them, in order to prevent data races. +type PeerScoreTable map[*peer]*PeerScore + +type PeerScore struct { + BootstrapFailures uint +} + // state is an actor that owns all of the mutable state for the Pinecone router. type state struct { phony.Inbox - r *Router - _peers []*peer // All switch ports, connected and disconnected - _ascending *virtualSnakeEntry // Next ascending node in keyspace - _descending *virtualSnakeEntry // Next descending node in keyspace - _candidate *virtualSnakeEntry // Candidate to replace the ascending node - _parent *peer // Our chosen parent in the tree - _announcements announcementTable // Announcements received from our peers - _table virtualSnakeTable // Virtual snake DHT entries - _ordering uint64 // Used to order incoming tree announcements - _sequence uint64 // Used to sequence our root tree announcements - _treetimer *time.Timer // Tree maintenance timer - _snaketimer *time.Timer // Virtual snake maintenance timer - _waiting bool // Is the tree waiting to reparent? - _filterPacket FilterFn // Function called when forwarding packets + r *Router + _peers []*peer // All switch ports, connected and disconnected + _ascending *virtualSnakeEntry // Next ascending node in keyspace + _descending *virtualSnakeEntry // Next descending node in keyspace + _candidate *virtualSnakeEntry // Candidate to replace the ascending node + _parent *peer // Our chosen parent in the tree + _announcements announcementTable // Announcements received from our peers + _table virtualSnakeTable // Virtual snake DHT entries + _neglectedNodes neglectedNodeTable // Nodes that are struggling to bootstrap + _peerScores PeerScoreTable // Keeps track of peer behaviour + _bootstrapAttempt uint64 // Count of bootstrap attempts since last success + _ordering uint64 // Used to order incoming tree announcements + _sequence uint64 // Used to sequence our root tree announcements + _treetimer *time.Timer // Tree maintenance timer + _snaketimer *time.Timer // Virtual snake maintenance timer + _waiting bool // Is the tree waiting to reparent? + _filterPacket FilterFn // Function called when forwarding packets } // _start resets the state and starts tree and virtual snake maintenance. @@ -64,6 +73,10 @@ func (s *state) _start() { s._announcements = make(announcementTable, portCount) s._table = virtualSnakeTable{} + s._bootstrapAttempt = 0 + s._neglectedNodes = make(neglectedNodeTable) + s._peerScores = make(PeerScoreTable) + if s._treetimer == nil { s._treetimer = time.AfterFunc(announcementInterval, func() { s.Act(nil, s._maintainTree) @@ -71,7 +84,7 @@ func (s *state) _start() { } if s._snaketimer == nil { - s._snaketimer = time.AfterFunc(time.Second, func() { + s._snaketimer = time.AfterFunc(virtualSnakeMaintainInterval, func() { s.Act(nil, s._maintainSnake) }) } @@ -137,6 +150,8 @@ func (s *state) _addPeer(conn net.Conn, public types.PublicKey, uri ConnectionUR new.reader.Act(nil, new._read) new.writer.Act(nil, new._write) + s._peerScores[new] = &PeerScore{} + s.r.Act(nil, func() { s.r._publish(events.PeerAdded{Port: types.SwitchPortID(i), PeerID: new.public.String()}) }) @@ -149,6 +164,7 @@ func (s *state) _addPeer(conn net.Conn, public types.PublicKey, uri ConnectionUR // _removePeer removes the Peer from the specified switch port func (s *state) _removePeer(port types.SwitchPortID) { peerID := s._peers[port].public.String() + s._peerScores[s._peers[port]] = nil s._peers[port] = nil s.r.Act(nil, func() { s.r._publish(events.PeerRemoved{Port: port, PeerID: peerID}) diff --git a/router/state_forward.go b/router/state_forward.go index 14335d2b..fc8c1f99 100644 --- a/router/state_forward.go +++ b/router/state_forward.go @@ -56,6 +56,12 @@ func (s *state) _forward(p *peer, f *types.Frame) error { return nil } + if s._ascending != nil { + if f.SourceKey.CompareTo(s.r.public) > 0 && f.SourceKey.CompareTo(s._ascending.PublicKey) < 0 { + s.r.log.Printf("Node detected that is a better ascending neighbour...") + } + } + nexthop := s._nextHopsFor(p, f) deadend := nexthop == p.router.local @@ -73,14 +79,13 @@ func (s *state) _forward(p *peer, f *types.Frame) error { return nil case types.TypeVirtualSnakeBootstrap: - // Bootstrap messages are only handled specially when they reach a dead end. - // Otherwise they are forwarded normally by falling through. - if deadend { - if err := s._handleBootstrap(p, f); err != nil { - return fmt.Errorf("s._handleBootstrap (port %d): %w", p.port, err) - } - return nil + // Bootstrap messages are handled at each node on the path. _handleBootstrap + // determines whether to respond to the bootstrap or further it along to the + // next hop so the packet is not forwarded here. + if err := s._handleBootstrap(p, f, nexthop, deadend); err != nil { + return fmt.Errorf("s._handleBootstrap (port %d): %w", p.port, err) } + return nil case types.TypeVirtualSnakeBootstrapACK: // Bootstrap ACK messages are only handled specially when they reach a dead end. diff --git a/router/state_snek.go b/router/state_snek.go index 8e0a01e5..79cbb066 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -29,6 +29,8 @@ import ( const virtualSnakeMaintainInterval = time.Second const virtualSnakeNeighExpiryPeriod = time.Hour +const bootstrapAttemptResetPoint = 100 // TODO : Pick a more meaningful value +const neglectedNodeTrackingPoint = 5 // NOTE : Start tracking a neglected node's bootstraps when their attempt count reaches this number type virtualSnakeTable map[virtualSnakeIndex]*virtualSnakeEntry @@ -48,6 +50,14 @@ type virtualSnakeEntry struct { Active bool } +type neglectedNodeTable map[types.PublicKey]*neglectedNodeEntry + +type neglectedNodeEntry struct { + AttemptCount []uint64 // List of attempt counts seen in the order they were seen + HopCount uint64 // The hop count from the attempt when the entry was created + LastAttempt time.Time // The time that the last attempt was seen +} + // valid returns true if the update hasn't expired, or false if it has. It is // required for updates to time out eventually, in the case that paths don't get // torn down properly for some reason. @@ -144,8 +154,10 @@ func (s *state) _bootstrapNow() { b := frameBufferPool.Get().(*[types.MaxFrameSize]byte) defer frameBufferPool.Put(b) bootstrap := types.VirtualSnakeBootstrap{ - Root: ann.Root, + Root: ann.Root, + AttemptCount: types.Varu64(s._bootstrapAttempt + 1), } + // Generate a random path ID. if _, err := rand.Read(bootstrap.PathID[:]); err != nil { return @@ -158,6 +170,9 @@ func (s *state) _bootstrapNow() { ed25519.Sign(s.r.private[:], append(s.r.public[:], bootstrap.PathID[:]...)), ) } + // if err := bootstrap.Sign(s.r.private[:]); err != nil { + // return + // } n, err := bootstrap.MarshalBinary(b[:]) if err != nil { return @@ -174,6 +189,11 @@ func (s *state) _bootstrapNow() { // bootstrap packets. if p := s._nextHopsSNEK(send, true); p != nil && p.proto != nil { p.proto.push(send) + s._bootstrapAttempt++ + if s._bootstrapAttempt >= bootstrapAttemptResetPoint { + s._bootstrapAttempt = 0 + s.r.log.Println("Resetting bootstrap attempt count") + } } } @@ -303,12 +323,16 @@ func getNextHopSNEK(params virtualSnakeNextHopParams) *peer { // _handleBootstrap is called in response to receiving a bootstrap packet. // This function will send a bootstrap ACK back to the sender. -func (s *state) _handleBootstrap(from *peer, rx *types.Frame) error { +func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, deadend bool) error { // Unmarshal the bootstrap. var bootstrap types.VirtualSnakeBootstrap if _, err := bootstrap.UnmarshalBinary(rx.Payload); err != nil { return fmt.Errorf("bootstrap.UnmarshalBinary: %w", err) } + if err := bootstrap.SanityCheck(from.public, s.r.public, rx.DestinationKey); err != nil { + return nil + } + if s.r.secure { // Check that the bootstrap message was signed by the node that claims // to have sent it. Silently drop it if there's a signature problem. @@ -320,6 +344,95 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame) error { return nil } } + + if !deadend { + attemptSafetyLink := false + var frame *types.Frame = nil + if bootstrap.AttemptCount >= neglectedNodeTrackingPoint { + // TODO : Change this to increase the score more the further away you are from the source + // TODO : when should this score reset? + // TODO : teardown safety links when kicking a peer for this reason so they can rebootstrap + s._peerScores[nexthop].BootstrapFailures++ + if score, ok := s._peerScores[nexthop]; ok { + if score.BootstrapFailures > 50 { + // TODO : This is vulnerable to a malicious node sending failing bootstrap attempts through us with a high hop count... + // Check to make sure the bootstrap is legit + // right seq, signatures, is for a node less than root, etc. + // the malicious node could be refusing to accept bootstraps and continually sending bootstraps with high attempt counts and hop counts through this node + // in that case you should see ACKs & setup messages being returned through this node + // and they could help pinpoint the malicious peer + // malicious node could spoof sending ACKs through the peer though... + // think more on it... + nexthop.stop(fmt.Errorf("Too many bootstrap failures go through this peer")) + } + } else { + panic("Uhh where's my peer") + } + if entry, ok := s._neglectedNodes[rx.DestinationKey]; ok { + if entry.AttemptCount[len(entry.AttemptCount)-1] < uint64(bootstrap.AttemptCount) { + entry.AttemptCount = append(entry.AttemptCount, uint64(bootstrap.AttemptCount)) + if 10-int(entry.HopCount)-len(entry.AttemptCount) == 0 { + if s.r.public.CompareTo(rx.DestinationKey) > 0 { + s.r.log.Printf("Replying to neglected node %s. Hop count: %d", rx.DestinationKey, entry.HopCount) + attemptSafetyLink = true + } + } + } else { + entry.AttemptCount = []uint64{uint64(bootstrap.AttemptCount)} + entry.HopCount = uint64(len(bootstrap.Signatures)) + entry.LastAttempt = time.Now() + } + } else { + entry := &neglectedNodeEntry{ + AttemptCount: []uint64{uint64(bootstrap.AttemptCount)}, + HopCount: uint64(len(bootstrap.Signatures)), + LastAttempt: time.Now(), + } + s._neglectedNodes[rx.DestinationKey] = entry + } + + // NOTE : Only add additional signatures if the node is struggling + if s.r.public.CompareTo(rx.DestinationKey) > 0 { + switch { + case len(bootstrap.Signatures) == 0: + // Received a bootstrap with no signatures. Either it is directly + // from the originating node or the bootstrap hasn't passed by any + // bootstrap candidate. We are a candidate so sign the bootstrap. + fallthrough + case s.r.public.CompareTo(bootstrap.Signatures[len(bootstrap.Signatures)-1].PublicKey) < 0: + if err := bootstrap.Sign(s.r.private[:]); err != nil { + return fmt.Errorf("failed signing bootstrap: %w", err) + } + frame = getFrame() + frame.Type = types.TypeVirtualSnakeBootstrap + n, err := bootstrap.MarshalBinary(frame.Payload[:cap(frame.Payload)]) + if err != nil { + panic("failed to marshal bootstrap: " + err.Error()) + } + frame.Payload = frame.Payload[:n] + } + } + } + + if !attemptSafetyLink { + if frame != nil { + of := rx + defer framePool.Put(of) + frame.DestinationKey = of.DestinationKey + frame.SourceKey = of.SourceKey + frame.Destination = of.Destination + frame.Source = of.Source + frame.Version = of.Version + frame.Extra = of.Extra + } else { + frame = rx + } + + nexthop.proto.push(frame) + return nil + } + } + // Check that the root key and sequence number in the update match our // current root, otherwise we won't be able to route back to them using // tree routing anyway. If they don't match, silently drop the bootstrap. @@ -509,6 +622,7 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame) error { s._sendTeardownForExistingPath(s.r.local, dhtKey.PublicKey, dhtKey.PathID) } } + // Install the new route into the DHT. s._table[index] = entry s._candidate = entry @@ -601,11 +715,16 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { // there's a node out there that hasn't converged to a closer node // yet, so we'll just ignore the bootstrap. } - if !update { + isNeglectedNode := false + if _, ok := s._neglectedNodes[rx.SourceKey]; ok { + s.r.log.Println("Reply to neglected node") + isNeglectedNode = true + } + if !update && !isNeglectedNode { s._sendTeardownForRejectedPath(rx.SourceKey, setup.PathID, from) return nil } - if desc != nil { + if desc != nil && !isNeglectedNode { // Tear down the previous path, if there was one. s._sendTeardownForExistingPath(s.r.local, desc.PublicKey, desc.PathID) } @@ -619,7 +738,9 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { Root: setup.Root, } s._table[index] = entry - s._setDescendingNode(entry) + if !isNeglectedNode { + s._setDescendingNode(entry) + } // Send back a setup ACK to the remote side. setupACK := types.VirtualSnakeSetupACK{ PathID: setup.PathID, @@ -704,6 +825,7 @@ func (s *state) _handleSetupACK(from *peer, rx *types.Frame, nexthop *peer) erro if v == s._candidate { s._setAscendingNode(v) s._candidate = nil + s._bootstrapAttempt = 0 } } } diff --git a/router/state_tree.go b/router/state_tree.go index 30edb39c..fbe118ca 100644 --- a/router/state_tree.go +++ b/router/state_tree.go @@ -341,8 +341,11 @@ func (s *state) _handleTreeAnnouncement(p *peer, f *types.Frame) error { case AcceptNewParent: s._setParent(p) s._sendTreeAnnouncements() + // why not bootstrap here as well? + // s._bootstrapNow() case SelectNewParent: if s._selectNewParent() { + // why bootstrap here as well when we can wait for snek maintenance s._bootstrapNow() } case SelectNewParentWithWait: @@ -353,6 +356,7 @@ func (s *state) _handleTreeAnnouncement(p *peer, f *types.Frame) error { s.Act(nil, func() { s._waiting = false if s._selectNewParent() { + // why bootstrap here as well when we can wait for snek maintenance s._bootstrapNow() } }) diff --git a/router/tests/adversary_test.go b/router/tests/adversary_test.go index bf5b6c16..6ee3d85a 100644 --- a/router/tests/adversary_test.go +++ b/router/tests/adversary_test.go @@ -170,8 +170,11 @@ func TestNodesCanPingAdversaryDropsFrames(t *testing.T) { // Assert sut.scenario.Validate(createTreeStateCapture(tc.genericNodes), nodesAgreeOnCorrectTreeRoot, SettlingTime, TestTimeout) - if !nodesCanAllPingEachOther(&sut.scenario, tc.genericNodes) { - printSystemUnderTest(tc, &sut) + if !nodesCanAllPingEachOther(&sut.scenario, tc.genericNodes, false) { + time.Sleep(time.Second * 15) + if !nodesCanAllPingEachOther(&sut.scenario, tc.genericNodes, true) { + printSystemUnderTest(tc, &sut) + } } }) } diff --git a/router/tests/snake_validation.go b/router/tests/snake_validation.go index ca2fa5d2..4347e9c3 100644 --- a/router/tests/snake_validation.go +++ b/router/tests/snake_validation.go @@ -124,19 +124,21 @@ func nodesAgreeOnCorrectSnakeFormation(prevState interface{}, event simulator.Si return prevState, StopSettlingTimer } -func pingNode(scenario *ScenarioFixture, from string, to string, ch chan bool, wg *sync.WaitGroup) { +func pingNode(scenario *ScenarioFixture, from string, to string, ch chan bool, wg *sync.WaitGroup, shouldFail bool) { log.Printf("Pinging from %s to %s", from, to) passed := true if _, _, err := scenario.sim.PingSNEK(from, to); err != nil { passed = false - scenario.t.Errorf("Failed pinging from %s to %s: %s", from, to, err) + if shouldFail { + scenario.t.Errorf("Failed pinging from %s to %s: %s", from, to, err) + } } ch <- passed wg.Done() } -func nodesCanAllPingEachOther(scenario *ScenarioFixture, nodes []string) bool { +func nodesCanAllPingEachOther(scenario *ScenarioFixture, nodes []string, shouldFail bool) bool { wg := sync.WaitGroup{} numberOfPings := 0 @@ -152,7 +154,7 @@ func nodesCanAllPingEachOther(scenario *ScenarioFixture, nodes []string) bool { for _, to := range nodes { if from != to { wg.Add(1) - go pingNode(scenario, from, to, ch, &wg) + go pingNode(scenario, from, to, ch, &wg, shouldFail) } } } diff --git a/types/bootstrap_signature.go b/types/bootstrap_signature.go new file mode 100644 index 00000000..73d8df82 --- /dev/null +++ b/types/bootstrap_signature.go @@ -0,0 +1,48 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "crypto/ed25519" + "fmt" +) + +type BootstrapSignature struct { + PublicKey PublicKey + Signature Signature +} + +const BootstrapSignatureSize = ed25519.PublicKeySize + ed25519.SignatureSize + +func (s *BootstrapSignature) UnmarshalBinary(data []byte) (int, error) { + if size := len(data); size < BootstrapSignatureSize { + return 0, fmt.Errorf("BootstrapSignature expects at least %d bytes, got %d bytes", BootstrapSignatureSize, size) + } + offset := 0 + remaining := data[offset:] + remaining = remaining[copy(s.PublicKey[:], remaining):] + remaining = remaining[copy(s.Signature[:], remaining):] + return len(data) - len(remaining), nil +} + +func (s *BootstrapSignature) MarshalBinary(data []byte) (int, error) { + if len(data) < BootstrapSignatureSize { + return 0, fmt.Errorf("buffer is not big enough (must be %d bytes)", BootstrapSignatureSize) + } + offset := 0 + offset += copy(data[offset:offset+ed25519.PublicKeySize], s.PublicKey[:]) + offset += copy(data[offset:offset+ed25519.SignatureSize], s.Signature[:]) + return offset, nil +} diff --git a/types/virtualsnake.go b/types/virtualsnake.go index 2c8f6563..4ed0f1bf 100644 --- a/types/virtualsnake.go +++ b/types/virtualsnake.go @@ -4,6 +4,7 @@ import ( "crypto/ed25519" "encoding/hex" "fmt" + "os" ) const VirtualSnakePathIDLength = 8 @@ -19,10 +20,13 @@ type VirtualSnakeBootstrap struct { PathID VirtualSnakePathID SourceSig VirtualSnakePathSig Root + AttemptCount Varu64 + Signatures []BootstrapSignature } func (v *VirtualSnakeBootstrap) MarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+ed25519.SignatureSize { + // TODO : Add hop signatures size to below check + if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+ed25519.SignatureSize+v.AttemptCount.Length() { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -34,11 +38,24 @@ func (v *VirtualSnakeBootstrap) MarshalBinary(buf []byte) (int, error) { } offset += n offset += copy(buf[offset:], v.SourceSig[:]) + n, err = v.AttemptCount.MarshalBinary(buf[offset:]) + if err != nil { + return 0, fmt.Errorf("v.AttemptCount.MarshalBinary: %w", err) + } + offset += n + for _, sig := range v.Signatures { + n, err := sig.MarshalBinary(buf[offset:]) + if err != nil { + return 0, fmt.Errorf("sig.MarshalBinary: %w", err) + } + offset += n + } return offset, nil } func (v *VirtualSnakeBootstrap) UnmarshalBinary(buf []byte) (int, error) { - if len(buf) < VirtualSnakePathIDLength+v.Root.MinLength()+ed25519.SignatureSize { + // TODO : Add hop signatures size to below check + if len(buf) < VirtualSnakePathIDLength+v.Root.MinLength()+ed25519.SignatureSize+1 { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -50,9 +67,68 @@ func (v *VirtualSnakeBootstrap) UnmarshalBinary(buf []byte) (int, error) { } offset += l offset += copy(v.SourceSig[:], buf[offset:]) + l, err = v.AttemptCount.UnmarshalBinary(buf[offset:]) + if err != nil { + return 0, fmt.Errorf("v.AttemptCount.UnmarshalBinary: %w", err) + } + offset += l + remaining := buf[offset:] + for i := uint64(0); len(remaining) >= BootstrapSignatureSize; i++ { + var signature BootstrapSignature + n, err := signature.UnmarshalBinary(remaining[:]) + if err != nil { + return 0, fmt.Errorf("signature.UnmarshalBinary: %w", err) + } + if _, ok := os.LookupEnv("PINECONE_DISABLE_SIGNATURES"); !ok { + if !ed25519.Verify(signature.PublicKey[:], buf[:len(buf)-len(remaining)], signature.Signature[:]) { + return 0, fmt.Errorf("signature verification failed for hop %d", i) + } + } + v.Signatures = append(v.Signatures, signature) + remaining = remaining[n:] + } + return offset, nil } +func (v *VirtualSnakeBootstrap) Sign(privKey ed25519.PrivateKey) error { + var body [65535]byte + n, err := v.MarshalBinary(body[:]) + if err != nil { + return fmt.Errorf("v.MarshalBinary: %w", err) + } + sig := BootstrapSignature{} + copy(sig.PublicKey[:], privKey.Public().(ed25519.PublicKey)) + if _, ok := os.LookupEnv("PINECONE_DISABLE_SIGNATURES"); !ok { + copy(sig.Signature[:], ed25519.Sign(privKey, body[:n])) + } + v.Signatures = append(v.Signatures, sig) + + return nil +} + +func (v *VirtualSnakeBootstrap) SanityCheck(from PublicKey, thisNode PublicKey, source PublicKey) error { + sigs := make(map[PublicKey]struct{}, len(v.Signatures)) + for index, sig := range v.Signatures { + if sig.PublicKey.CompareTo(thisNode) == 0 { + return fmt.Errorf("update already contains this node's signature") + } + if _, ok := sigs[sig.PublicKey]; ok { + return fmt.Errorf("update contains routing loop") + } + // NOTE : ensure each sig is a lower key than the last + if index > 0 && sig.PublicKey.CompareTo(v.Signatures[index-1].PublicKey) >= 0 { + return fmt.Errorf("update contains an invalid sequence of candidates") + } + // NOTE : ensure each sig is a higher key than the source + if sig.PublicKey.CompareTo(source) <= 0 { + return fmt.Errorf("update contains an invalid candidate") + } + sigs[sig.PublicKey] = struct{}{} + } + return nil +} + type VirtualSnakeBootstrapACK struct { PathID VirtualSnakePathID SourceSig VirtualSnakePathSig From b01af6d8d35e20df05fabef748c08cd5de9cd417 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Tue, 8 Mar 2022 11:15:47 -0300 Subject: [PATCH 19/36] Test out peer scoring on bootstrap setup frames --- .../simulator/adversary/drop_packets.go | 1 + router/peer.go | 27 ++++ router/router.go | 6 + router/state.go | 19 ++- router/state_snek.go | 148 +++++++++++------- 5 files changed, 139 insertions(+), 62 deletions(-) diff --git a/cmd/pineconesim/simulator/adversary/drop_packets.go b/cmd/pineconesim/simulator/adversary/drop_packets.go index 4e3044e7..374dad7d 100644 --- a/cmd/pineconesim/simulator/adversary/drop_packets.go +++ b/cmd/pineconesim/simulator/adversary/drop_packets.go @@ -123,6 +123,7 @@ func NewAdversaryRouter(log *log.Logger, sk ed25519.PrivateKey, debug bool) *Adv } rtr.InjectPacketFilter(adversary.selectivelyDrop) + rtr.DisablePeerScoring() return adversary } diff --git a/router/peer.go b/router/peer.go index a58614ac..68a1aaef 100644 --- a/router/peer.go +++ b/router/peer.go @@ -21,6 +21,7 @@ import ( "encoding/hex" "fmt" "io" + "math" "net" "time" @@ -34,6 +35,7 @@ import ( const peerKeepaliveInterval = time.Second * 3 const peerKeepaliveTimeout = time.Second * 5 +const lowScoreThreshold = -50 // NOTE : peer scoring can go from -100 to 100 const ( // These need to be a simple int type for gobind/gomobile to export them... PeerTypeMulticast int = iota @@ -64,6 +66,31 @@ type peer struct { traffic *lifoQueue // Thread-safe queue for outbound traffic messages. } +func (p *peer) EvaluatePeerScore(bootstraps neglectedNodeTable) int { + peerScore := 0.0 + + for _, node := range bootstraps { + for _, setup := range node.FailedSetups { + // NOTE : more failing nodes through me lower peer score - no, this could result + // in a scenario where multiple attackers across the network aim to cause nodes + // near the center of the network to begin cutting off their peers. + // Distance truly is the best measure for proper attack isolation in this case. + // And the distance factor needs to grow exponentially to really ensure that + // central nodes don't inadvertently cutoff peers. + if setup.Acknowledged && setup.Prev == p { + // NOTE : 1 for each new infraction + peerScore -= 1 + 6/float64(node.HopCount-3) // TODO : Better equation? + } else if !setup.Acknowledged && setup.Next == p { + // NOTE : 1 for each new infraction + peerScore -= 1 + 0.01*math.Pow(float64(node.HopCount), 4) // TODO : Better equation? + } + } + } + + p.router.log.Printf("PeerScore: %s --- %f", p.public.String()[:8], peerScore) + return int(peerScore) +} + func (p *peer) String() string { // to make sim less ugly if p == nil { return "nil" diff --git a/router/router.go b/router/router.go index de2366dc..98e1027b 100644 --- a/router/router.go +++ b/router/router.go @@ -51,6 +51,7 @@ type Router struct { state *state secure bool _subscribers map[chan<- events.Event]*phony.Inbox + scorePeers bool } func NewRouter(logger *log.Logger, sk ed25519.PrivateKey, debug bool) *Router { @@ -65,6 +66,7 @@ func NewRouter(logger *log.Logger, sk ed25519.PrivateKey, debug bool) *Router { cancel: cancel, secure: !insecure, _subscribers: make(map[chan<- events.Event]*phony.Inbox), + scorePeers: true, } // Populate the node keys from the supplied private key. copy(r.private[:], sk) @@ -85,6 +87,10 @@ func NewRouter(logger *log.Logger, sk ed25519.PrivateKey, debug bool) *Router { return r } +func (r *Router) DisablePeerScoring() { + r.scorePeers = false +} + func (r *Router) InjectPacketFilter(fn FilterFn) { phony.Block(r.state, func() { r.state._filterPacket = fn diff --git a/router/state.go b/router/state.go index a9c4a62e..bde77bd3 100644 --- a/router/state.go +++ b/router/state.go @@ -50,7 +50,6 @@ type state struct { _announcements announcementTable // Announcements received from our peers _table virtualSnakeTable // Virtual snake DHT entries _neglectedNodes neglectedNodeTable // Nodes that are struggling to bootstrap - _peerScores PeerScoreTable // Keeps track of peer behaviour _bootstrapAttempt uint64 // Count of bootstrap attempts since last success _ordering uint64 // Used to order incoming tree announcements _sequence uint64 // Used to sequence our root tree announcements @@ -58,6 +57,7 @@ type state struct { _snaketimer *time.Timer // Virtual snake maintenance timer _waiting bool // Is the tree waiting to reparent? _filterPacket FilterFn // Function called when forwarding packets + _peerScoreReset *time.Timer } // _start resets the state and starts tree and virtual snake maintenance. @@ -75,7 +75,6 @@ func (s *state) _start() { s._bootstrapAttempt = 0 s._neglectedNodes = make(neglectedNodeTable) - s._peerScores = make(PeerScoreTable) if s._treetimer == nil { s._treetimer = time.AfterFunc(announcementInterval, func() { @@ -89,10 +88,23 @@ func (s *state) _start() { }) } + if s._peerScoreReset == nil { + s._peerScoreReset = time.AfterFunc(0, func() { + s.Act(nil, s._resetPeerScoring) + }) + } + s._maintainTreeIn(0) s._maintainSnakeIn(0) } +func (s *state) _resetPeerScoring() { + s.r.log.Println("Reseting peer scores") + for pk := range s._neglectedNodes { + delete(s._neglectedNodes, pk) + } +} + // _maintainTreeIn resets the tree maintenance timer to the specified // duration. func (s *state) _maintainTreeIn(d time.Duration) { @@ -150,8 +162,6 @@ func (s *state) _addPeer(conn net.Conn, public types.PublicKey, uri ConnectionUR new.reader.Act(nil, new._read) new.writer.Act(nil, new._write) - s._peerScores[new] = &PeerScore{} - s.r.Act(nil, func() { s.r._publish(events.PeerAdded{Port: types.SwitchPortID(i), PeerID: new.public.String()}) }) @@ -164,7 +174,6 @@ func (s *state) _addPeer(conn net.Conn, public types.PublicKey, uri ConnectionUR // _removePeer removes the Peer from the specified switch port func (s *state) _removePeer(port types.SwitchPortID) { peerID := s._peers[port].public.String() - s._peerScores[s._peers[port]] = nil s._peers[port] = nil s.r.Act(nil, func() { s.r._publish(events.PeerRemoved{Port: port, PeerID: peerID}) diff --git a/router/state_snek.go b/router/state_snek.go index 79cbb066..470de1ca 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -18,6 +18,7 @@ import ( "crypto/ed25519" "crypto/rand" "fmt" + "math" "time" "github.com/matrix-org/pinecone/types" @@ -29,8 +30,8 @@ import ( const virtualSnakeMaintainInterval = time.Second const virtualSnakeNeighExpiryPeriod = time.Hour -const bootstrapAttemptResetPoint = 100 // TODO : Pick a more meaningful value -const neglectedNodeTrackingPoint = 5 // NOTE : Start tracking a neglected node's bootstraps when their attempt count reaches this number +const bootstrapAttemptResetPoint = math.MaxUint32 // TODO : Pick a more meaningful value +const neglectedNodeTrackingPoint = 5 // NOTE : Start tracking a neglected node's bootstraps when their attempt count reaches this number type virtualSnakeTable map[virtualSnakeIndex]*virtualSnakeEntry @@ -50,12 +51,22 @@ type virtualSnakeEntry struct { Active bool } +type neglectedSetupData struct { + PathID types.VirtualSnakePathID + Acknowledged bool + Prev *peer + Next *peer +} + +type neglectedSetupTable map[types.VirtualSnakePathID]*neglectedSetupData + type neglectedNodeTable map[types.PublicKey]*neglectedNodeEntry type neglectedNodeEntry struct { - AttemptCount []uint64 // List of attempt counts seen in the order they were seen - HopCount uint64 // The hop count from the attempt when the entry was created - LastAttempt time.Time // The time that the last attempt was seen + AttemptCount []uint64 // List of attempt counts seen in the order they were seen + HopCount uint64 // The hop count from the attempt when the entry was created + LastAttempt time.Time // The time that the last attempt was seen + FailedSetups neglectedSetupTable // Map of failed setup attempts } // valid returns true if the update hasn't expired, or false if it has. It is @@ -346,47 +357,23 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea } if !deadend { - attemptSafetyLink := false var frame *types.Frame = nil if bootstrap.AttemptCount >= neglectedNodeTrackingPoint { - // TODO : Change this to increase the score more the further away you are from the source - // TODO : when should this score reset? - // TODO : teardown safety links when kicking a peer for this reason so they can rebootstrap - s._peerScores[nexthop].BootstrapFailures++ - if score, ok := s._peerScores[nexthop]; ok { - if score.BootstrapFailures > 50 { - // TODO : This is vulnerable to a malicious node sending failing bootstrap attempts through us with a high hop count... - // Check to make sure the bootstrap is legit - // right seq, signatures, is for a node less than root, etc. - // the malicious node could be refusing to accept bootstraps and continually sending bootstraps with high attempt counts and hop counts through this node - // in that case you should see ACKs & setup messages being returned through this node - // and they could help pinpoint the malicious peer - // malicious node could spoof sending ACKs through the peer though... - // think more on it... - nexthop.stop(fmt.Errorf("Too many bootstrap failures go through this peer")) - } - } else { - panic("Uhh where's my peer") - } if entry, ok := s._neglectedNodes[rx.DestinationKey]; ok { if entry.AttemptCount[len(entry.AttemptCount)-1] < uint64(bootstrap.AttemptCount) { entry.AttemptCount = append(entry.AttemptCount, uint64(bootstrap.AttemptCount)) - if 10-int(entry.HopCount)-len(entry.AttemptCount) == 0 { - if s.r.public.CompareTo(rx.DestinationKey) > 0 { - s.r.log.Printf("Replying to neglected node %s. Hop count: %d", rx.DestinationKey, entry.HopCount) - attemptSafetyLink = true - } - } } else { entry.AttemptCount = []uint64{uint64(bootstrap.AttemptCount)} entry.HopCount = uint64(len(bootstrap.Signatures)) entry.LastAttempt = time.Now() + entry.FailedSetups = make(neglectedSetupTable) } } else { entry := &neglectedNodeEntry{ AttemptCount: []uint64{uint64(bootstrap.AttemptCount)}, HopCount: uint64(len(bootstrap.Signatures)), LastAttempt: time.Now(), + FailedSetups: make(neglectedSetupTable), } s._neglectedNodes[rx.DestinationKey] = entry } @@ -414,23 +401,21 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea } } - if !attemptSafetyLink { - if frame != nil { - of := rx - defer framePool.Put(of) - frame.DestinationKey = of.DestinationKey - frame.SourceKey = of.SourceKey - frame.Destination = of.Destination - frame.Source = of.Source - frame.Version = of.Version - frame.Extra = of.Extra - } else { - frame = rx - } - - nexthop.proto.push(frame) - return nil + if frame != nil { + of := rx + defer framePool.Put(of) + frame.DestinationKey = of.DestinationKey + frame.SourceKey = of.SourceKey + frame.Destination = of.Destination + frame.Source = of.Source + frame.Version = of.Version + frame.Extra = of.Extra + } else { + frame = rx } + + nexthop.proto.push(frame) + return nil } // Check that the root key and sequence number in the update match our @@ -680,6 +665,37 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { s._sendTeardownForRejectedPath(rx.SourceKey, setup.PathID, from) // second call sends back to origin return nil } + + if node, ok := s._neglectedNodes[rx.SourceKey]; ok { + if nexthop != nil { + node.FailedSetups[setup.PathID] = &neglectedSetupData{ + PathID: setup.PathID, + Acknowledged: false, + Prev: from, + Next: nexthop, + } + + score := nexthop.EvaluatePeerScore(s._neglectedNodes) + if score <= lowScoreThreshold { + if s.r.scorePeers { + nexthop.stop(fmt.Errorf("peer score below threshold: %d", score)) + // TODO : reset scores + } + } + + longestHopCount := 1 + for _, node := range s._neglectedNodes { + if node.HopCount > uint64(longestHopCount) { + longestHopCount = int(node.HopCount) + } + } + duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + // TODO : more failing nodes through me, shorter duration + s._peerScoreReset.Reset(time.Second * duration) + s.r.log.Printf("Duration: %d", duration) + } + } + // If we're at the destination of the setup then update our predecessor // with information from the bootstrap. if rx.DestinationKey == s.r.public { @@ -715,16 +731,12 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { // there's a node out there that hasn't converged to a closer node // yet, so we'll just ignore the bootstrap. } - isNeglectedNode := false - if _, ok := s._neglectedNodes[rx.SourceKey]; ok { - s.r.log.Println("Reply to neglected node") - isNeglectedNode = true - } - if !update && !isNeglectedNode { + + if !update { s._sendTeardownForRejectedPath(rx.SourceKey, setup.PathID, from) return nil } - if desc != nil && !isNeglectedNode { + if desc != nil { // Tear down the previous path, if there was one. s._sendTeardownForExistingPath(s.r.local, desc.PublicKey, desc.PathID) } @@ -738,9 +750,7 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { Root: setup.Root, } s._table[index] = entry - if !isNeglectedNode { - s._setDescendingNode(entry) - } + s._setDescendingNode(entry) // Send back a setup ACK to the remote side. setupACK := types.VirtualSnakeSetupACK{ PathID: setup.PathID, @@ -821,6 +831,30 @@ func (s *state) _handleSetupACK(from *peer, rx *types.Frame, nexthop *peer) erro continue } if v.Source.local() || v.Source.send(rx) { + if node, ok := s._neglectedNodes[rx.SourceKey]; ok { + if data, ok := node.FailedSetups[setup.PathID]; ok { + data.Acknowledged = true + score := data.Prev.EvaluatePeerScore(s._neglectedNodes) + if score <= lowScoreThreshold { + if s.r.scorePeers { + data.Prev.stop(fmt.Errorf("peer score below threshold: %d", score)) + } + } + + longestHopCount := 1 + for _, node := range s._neglectedNodes { + if node.HopCount > uint64(longestHopCount) { + longestHopCount = int(node.HopCount) + } + } + duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + s.r.log.Printf("ACK Duration: %ds", duration) + s._peerScoreReset.Reset(time.Second * duration) + } else { + // TODO : should never get here! + } + } + v.Active = true if v == s._candidate { s._setAscendingNode(v) From 23b411be4a3317f33771158e3a84a1fbfa7b4756 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Tue, 8 Mar 2022 16:50:24 -0300 Subject: [PATCH 20/36] Add peer scoring to failing bootstrap frames --- router/peer.go | 20 ++++++- router/state.go | 1 + router/state_forward.go | 8 ++- router/state_snek.go | 112 +++++++++++++++++++++++++++++++++------- 4 files changed, 114 insertions(+), 27 deletions(-) diff --git a/router/peer.go b/router/peer.go index 68a1aaef..4cb7b88f 100644 --- a/router/peer.go +++ b/router/peer.go @@ -35,7 +35,7 @@ import ( const peerKeepaliveInterval = time.Second * 3 const peerKeepaliveTimeout = time.Second * 5 -const lowScoreThreshold = -50 // NOTE : peer scoring can go from -100 to 100 +const lowScoreThreshold = -100 // NOTE : peer scoring can go from -100 to 100 const ( // These need to be a simple int type for gobind/gomobile to export them... PeerTypeMulticast int = iota @@ -82,7 +82,23 @@ func (p *peer) EvaluatePeerScore(bootstraps neglectedNodeTable) int { peerScore -= 1 + 6/float64(node.HopCount-3) // TODO : Better equation? } else if !setup.Acknowledged && setup.Next == p { // NOTE : 1 for each new infraction - peerScore -= 1 + 0.01*math.Pow(float64(node.HopCount), 4) // TODO : Better equation? + peerScore -= 0.5 + 0.01*math.Pow(float64(node.HopCount), 4) // TODO : Better equation? + } + } + + for _, bootstrap := range node.FailedBootstraps { + // NOTE : more failing nodes through me lower peer score - no, this could result + // in a scenario where multiple attackers across the network aim to cause nodes + // near the center of the network to begin cutting off their peers. + // Distance truly is the best measure for proper attack isolation in this case. + // And the distance factor needs to grow exponentially to really ensure that + // central nodes don't inadvertently cutoff peers. + if bootstrap.Acknowledged && bootstrap.Prev == p { + // NOTE : 1 for each new infraction + peerScore -= 1 + 6/float64(node.HopCount-3) // TODO : Better equation? + } else if !bootstrap.Acknowledged && bootstrap.Next == p { + // NOTE : 1 for each new infraction + peerScore -= 0.5 + 0.01*math.Pow(float64(node.HopCount), 4) // TODO : Better equation? } } } diff --git a/router/state.go b/router/state.go index bde77bd3..dac8f2b1 100644 --- a/router/state.go +++ b/router/state.go @@ -100,6 +100,7 @@ func (s *state) _start() { func (s *state) _resetPeerScoring() { s.r.log.Println("Reseting peer scores") + // TODO : Only reset scoring for a specific peer/peerset? for pk := range s._neglectedNodes { delete(s._neglectedNodes, pk) } diff --git a/router/state_forward.go b/router/state_forward.go index fc8c1f99..9b854873 100644 --- a/router/state_forward.go +++ b/router/state_forward.go @@ -90,12 +90,10 @@ func (s *state) _forward(p *peer, f *types.Frame) error { case types.TypeVirtualSnakeBootstrapACK: // Bootstrap ACK messages are only handled specially when they reach a dead end. // Otherwise they are forwarded normally by falling through. - if deadend { - if err := s._handleBootstrapACK(p, f); err != nil { - return fmt.Errorf("s._handleBootstrapACK (port %d): %w", p.port, err) - } - return nil + if err := s._handleBootstrapACK(p, f, nexthop, deadend); err != nil { + return fmt.Errorf("s._handleBootstrapACK (port %d): %w", p.port, err) } + return nil case types.TypeVirtualSnakeSetup: // Setup messages are handled at each node on the path. Since the _handleSetup diff --git a/router/state_snek.go b/router/state_snek.go index 470de1ca..81487698 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -51,8 +51,15 @@ type virtualSnakeEntry struct { Active bool } +type neglectedBootstrapData struct { + Acknowledged bool + Prev *peer + Next *peer +} + +type neglectedBootstrapTable map[types.VirtualSnakePathID]*neglectedBootstrapData + type neglectedSetupData struct { - PathID types.VirtualSnakePathID Acknowledged bool Prev *peer Next *peer @@ -63,10 +70,11 @@ type neglectedSetupTable map[types.VirtualSnakePathID]*neglectedSetupData type neglectedNodeTable map[types.PublicKey]*neglectedNodeEntry type neglectedNodeEntry struct { - AttemptCount []uint64 // List of attempt counts seen in the order they were seen - HopCount uint64 // The hop count from the attempt when the entry was created - LastAttempt time.Time // The time that the last attempt was seen - FailedSetups neglectedSetupTable // Map of failed setup attempts + AttemptCount []uint64 // List of attempt counts seen in the order they were seen + HopCount uint64 // The hop count from the attempt when the entry was created + LastAttempt time.Time // The time that the last attempt was seen + FailedBootstraps neglectedBootstrapTable // Map of failed bootstrap attempts + FailedSetups neglectedSetupTable // Map of failed setup attempts } // valid returns true if the update hasn't expired, or false if it has. It is @@ -358,6 +366,7 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea if !deadend { var frame *types.Frame = nil + // NOTE : Only add additional signatures if the node is struggling if bootstrap.AttemptCount >= neglectedNodeTrackingPoint { if entry, ok := s._neglectedNodes[rx.DestinationKey]; ok { if entry.AttemptCount[len(entry.AttemptCount)-1] < uint64(bootstrap.AttemptCount) { @@ -366,19 +375,44 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea entry.AttemptCount = []uint64{uint64(bootstrap.AttemptCount)} entry.HopCount = uint64(len(bootstrap.Signatures)) entry.LastAttempt = time.Now() + entry.FailedBootstraps = make(neglectedBootstrapTable) entry.FailedSetups = make(neglectedSetupTable) } } else { entry := &neglectedNodeEntry{ - AttemptCount: []uint64{uint64(bootstrap.AttemptCount)}, - HopCount: uint64(len(bootstrap.Signatures)), - LastAttempt: time.Now(), - FailedSetups: make(neglectedSetupTable), + AttemptCount: []uint64{uint64(bootstrap.AttemptCount)}, + HopCount: uint64(len(bootstrap.Signatures)), + LastAttempt: time.Now(), + FailedBootstraps: make(neglectedBootstrapTable), + FailedSetups: make(neglectedSetupTable), } s._neglectedNodes[rx.DestinationKey] = entry } - // NOTE : Only add additional signatures if the node is struggling + s._neglectedNodes[rx.DestinationKey].FailedBootstraps[bootstrap.PathID] = &neglectedBootstrapData{ + Acknowledged: false, + Prev: from, + Next: nexthop, + } + + score := nexthop.EvaluatePeerScore(s._neglectedNodes) + if score <= lowScoreThreshold { + if s.r.scorePeers { + nexthop.stop(fmt.Errorf("peer score below threshold: %d", score)) + } + } + + longestHopCount := 1 + for _, node := range s._neglectedNodes { + if node.HopCount > uint64(longestHopCount) { + longestHopCount = int(node.HopCount) + } + } + duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + // TODO : more failing nodes through me, shorter duration + s._peerScoreReset.Reset(time.Second * duration) + s.r.log.Printf("Duration: %d", duration) + if s.r.public.CompareTo(rx.DestinationKey) > 0 { switch { case len(bootstrap.Signatures) == 0: @@ -471,13 +505,52 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea // packet. This function will work out whether the remote node is a suitable // candidate to set up an outbound path to, and if so, will send path setup // packets to the network. -func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame) error { +func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame, nexthop *peer, deadend bool) error { // Unmarshal the bootstrap ACK. var bootstrapACK types.VirtualSnakeBootstrapACK _, err := bootstrapACK.UnmarshalBinary(rx.Payload) if err != nil { return fmt.Errorf("bootstrapACK.UnmarshalBinary: %w", err) } + + // TODO : sig verification before this? + if !deadend { + knownFailure := false + if node, ok := s._neglectedNodes[rx.SourceKey]; ok { + if data, ok := node.FailedSetups[bootstrapACK.PathID]; ok { + knownFailure = true + data.Prev.send(rx) + data.Acknowledged = true + score := data.Prev.EvaluatePeerScore(s._neglectedNodes) + if score <= lowScoreThreshold { + if s.r.scorePeers { + data.Prev.stop(fmt.Errorf("peer score below threshold: %d", score)) + } + } + + longestHopCount := 1 + for _, node := range s._neglectedNodes { + if node.HopCount > uint64(longestHopCount) { + longestHopCount = int(node.HopCount) + } + } + duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + s.r.log.Printf("ACK Duration: %ds", duration) + s._peerScoreReset.Reset(time.Second * duration) + } else { + // TODO : should never get here! + } + } + + if !knownFailure { + if nexthop != nil && !nexthop.send(rx) { + s.r.log.Println("Dropping forwarded packet of type", rx.Type) + } + } + + return nil + } + if s.r.secure { // Verify that the source signature hasn't been changed by the remote // side. If it has then it won't validate using our own public key. @@ -499,6 +572,7 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame) error { return nil } } + root := s._rootAnnouncement() update := false asc := s._ascending @@ -569,20 +643,20 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame) error { send.DestinationKey = rx.SourceKey send.SourceKey = s.r.public send.Payload = append(send.Payload[:0], b[:n]...) - nexthop := s.r.state._nextHopsTree(s.r.local, send) + setupNexthop := s.r.state._nextHopsTree(s.r.local, send) // Importantly, we will only create a DHT entry if it appears as though our next // hop has actually accepted the packet. Otherwise we'll create a path entry and // the setup message won't go anywhere. switch { - case nexthop == nil: + case setupNexthop == nil: fallthrough // No peer was identified, which shouldn't happen. - case nexthop.local(): + case setupNexthop.local(): fallthrough // The peer is local, which shouldn't happen. - case !nexthop.started.Load(): + case !setupNexthop.started.Load(): fallthrough // The peer has shut down or errored. - case nexthop.proto == nil: + case setupNexthop.proto == nil: fallthrough // The peer doesn't have a protocol queue for some reason. - case !nexthop.proto.push(send): + case !setupNexthop.proto.push(send): return nil // We failed to push the message into the peer queue. } index := virtualSnakeIndex{ @@ -594,7 +668,7 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame) error { Origin: rx.SourceKey, Target: rx.SourceKey, Source: s.r.local, - Destination: nexthop, + Destination: setupNexthop, LastSeen: time.Now(), Root: bootstrapACK.Root, } @@ -669,7 +743,6 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { if node, ok := s._neglectedNodes[rx.SourceKey]; ok { if nexthop != nil { node.FailedSetups[setup.PathID] = &neglectedSetupData{ - PathID: setup.PathID, Acknowledged: false, Prev: from, Next: nexthop, @@ -679,7 +752,6 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { if score <= lowScoreThreshold { if s.r.scorePeers { nexthop.stop(fmt.Errorf("peer score below threshold: %d", score)) - // TODO : reset scores } } From 3a583ce45bd5e4473b8c45521143a1b90dfdc345 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Tue, 8 Mar 2022 17:04:16 -0300 Subject: [PATCH 21/36] Rearrange bootstrap ack signature verification --- router/state_snek.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/router/state_snek.go b/router/state_snek.go index 81487698..3ae3b550 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -513,7 +513,19 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame, nexthop *peer, return fmt.Errorf("bootstrapACK.UnmarshalBinary: %w", err) } - // TODO : sig verification before this? + if s.r.secure { + // Verify that the destination signature is OK, which allows us to confirm + // that the remote node accepted our bootstrap and that the remote node is + // who they claim to be. + if !ed25519.Verify( + rx.SourceKey[:], + append(bootstrapACK.SourceSig[:], append(rx.DestinationKey[:], bootstrapACK.PathID[:]...)...), + bootstrapACK.DestinationSig[:], + ) { + return nil + } + } + if !deadend { knownFailure := false if node, ok := s._neglectedNodes[rx.SourceKey]; ok { @@ -561,16 +573,6 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame, nexthop *peer, ) { return nil } - // Verify that the destination signature is OK, which allows us to confirm - // that the remote node accepted our bootstrap and that the remote node is - // who they claim to be. - if !ed25519.Verify( - rx.SourceKey[:], - append(bootstrapACK.SourceSig[:], append(rx.DestinationKey[:], bootstrapACK.PathID[:]...)...), - bootstrapACK.DestinationSig[:], - ) { - return nil - } } root := s._rootAnnouncement() From 624c12228bcb8b4bc68e59ebad283231ea50fb0a Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Wed, 9 Mar 2022 13:46:29 -0300 Subject: [PATCH 22/36] Limit number of neglected nodes being tracked --- router/peer.go | 97 +++++++++++++++++++++++++++++-------------- router/state.go | 8 ++++ router/state_snek.go | 99 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 151 insertions(+), 53 deletions(-) diff --git a/router/peer.go b/router/peer.go index 4cb7b88f..b97facca 100644 --- a/router/peer.go +++ b/router/peer.go @@ -64,47 +64,84 @@ type peer struct { started atomic.Bool // Thread-safe toggle for marking a peer as down. proto *fifoQueue // Thread-safe queue for outbound protocol messages. traffic *lifoQueue // Thread-safe queue for outbound traffic messages. + scoreCache float64 // Tracks peer score information from replaced sources } func (p *peer) EvaluatePeerScore(bootstraps neglectedNodeTable) int { peerScore := 0.0 + // NOTE : more failing nodes through me lower peer score - no, this could result + // in a scenario where multiple attackers across the network aim to cause nodes + // near the center of the network to begin cutting off their peers. + // Distance truly is the best measure for proper attack isolation in this case. + // And the distance factor needs to grow exponentially to really ensure that + // central nodes don't inadvertently cutoff peers. for _, node := range bootstraps { - for _, setup := range node.FailedSetups { - // NOTE : more failing nodes through me lower peer score - no, this could result - // in a scenario where multiple attackers across the network aim to cause nodes - // near the center of the network to begin cutting off their peers. - // Distance truly is the best measure for proper attack isolation in this case. - // And the distance factor needs to grow exponentially to really ensure that - // central nodes don't inadvertently cutoff peers. - if setup.Acknowledged && setup.Prev == p { - // NOTE : 1 for each new infraction - peerScore -= 1 + 6/float64(node.HopCount-3) // TODO : Better equation? - } else if !setup.Acknowledged && setup.Next == p { - // NOTE : 1 for each new infraction - peerScore -= 0.5 + 0.01*math.Pow(float64(node.HopCount), 4) // TODO : Better equation? - } + peerScore += p.EvaluatePeerScoreForNode(node) + } + + peerScore += float64(p.scoreCache) + + p.router.log.Printf("PeerScore: %s --- %f", p.public.String()[:8], peerScore) + return int(peerScore) +} + +func scoreMissingAck(hopCount uint64) float64 { + // TODO : Better equation? + return -(0.5 + 0.01*math.Pow(float64(hopCount), 4)) +} + +func scoreAck(hopCount uint64) float64 { + // TODO : Better equation? + return -(1 + 6/float64(hopCount-3)) +} + +func cachePeerScoreHistory(node *neglectedNodeEntry) { + for _, bootstrap := range node.FailedBootstraps { + if bootstrap.Acknowledged { + // NOTE : 1 for each new infraction + bootstrap.Prev.scoreCache += scoreAck(node.HopCount) + } else if !bootstrap.Acknowledged { + // NOTE : 1 for each new infraction + bootstrap.Next.scoreCache += scoreMissingAck(node.HopCount) } + } - for _, bootstrap := range node.FailedBootstraps { - // NOTE : more failing nodes through me lower peer score - no, this could result - // in a scenario where multiple attackers across the network aim to cause nodes - // near the center of the network to begin cutting off their peers. - // Distance truly is the best measure for proper attack isolation in this case. - // And the distance factor needs to grow exponentially to really ensure that - // central nodes don't inadvertently cutoff peers. - if bootstrap.Acknowledged && bootstrap.Prev == p { - // NOTE : 1 for each new infraction - peerScore -= 1 + 6/float64(node.HopCount-3) // TODO : Better equation? - } else if !bootstrap.Acknowledged && bootstrap.Next == p { - // NOTE : 1 for each new infraction - peerScore -= 0.5 + 0.01*math.Pow(float64(node.HopCount), 4) // TODO : Better equation? - } + for _, setup := range node.FailedSetups { + if setup.Acknowledged { + // NOTE : 1 for each new infraction + setup.Prev.scoreCache += scoreAck(node.HopCount) + } else if !setup.Acknowledged { + // NOTE : 1 for each new infraction + setup.Next.scoreCache += scoreMissingAck(node.HopCount) } } +} - p.router.log.Printf("PeerScore: %s --- %f", p.public.String()[:8], peerScore) - return int(peerScore) +func (p *peer) EvaluatePeerScoreForNode(node *neglectedNodeEntry) float64 { + peerScore := 0.0 + + for _, bootstrap := range node.FailedBootstraps { + if bootstrap.Acknowledged && bootstrap.Prev == p { + // NOTE : 1 for each new infraction + peerScore += scoreAck(node.HopCount) + } else if !bootstrap.Acknowledged && bootstrap.Next == p { + // NOTE : 1 for each new infraction + peerScore += scoreMissingAck(node.HopCount) + } + } + + for _, setup := range node.FailedSetups { + if setup.Acknowledged && setup.Prev == p { + // NOTE : 1 for each new infraction + peerScore += scoreAck(node.HopCount) + } else if !setup.Acknowledged && setup.Next == p { + // NOTE : 1 for each new infraction + peerScore += scoreMissingAck(node.HopCount) + } + } + + return peerScore } func (p *peer) String() string { // to make sim less ugly diff --git a/router/state.go b/router/state.go index dac8f2b1..a68702a1 100644 --- a/router/state.go +++ b/router/state.go @@ -101,9 +101,17 @@ func (s *state) _start() { func (s *state) _resetPeerScoring() { s.r.log.Println("Reseting peer scores") // TODO : Only reset scoring for a specific peer/peerset? + // maybe only start reset timers on a per peer basis as well + // if you are seeing issues correlated with a specific peer, then you need to time + // issues seen from that peer, not just any peer for pk := range s._neglectedNodes { delete(s._neglectedNodes, pk) } + for _, peer := range s._peers { + if peer != nil { + peer.scoreCache = 0 + } + } } // _maintainTreeIn resets the tree maintenance timer to the specified diff --git a/router/state_snek.go b/router/state_snek.go index 3ae3b550..3663e7d4 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -30,8 +30,11 @@ import ( const virtualSnakeMaintainInterval = time.Second const virtualSnakeNeighExpiryPeriod = time.Hour -const bootstrapAttemptResetPoint = math.MaxUint32 // TODO : Pick a more meaningful value -const neglectedNodeTrackingPoint = 5 // NOTE : Start tracking a neglected node's bootstraps when their attempt count reaches this number +const bootstrapAttemptResetPoint = math.MaxUint32 // TODO : Pick a more meaningful value +const neglectedNodeTrackingPoint = 5 // NOTE : Start tracking a neglected node's bootstraps when their attempt count reaches this number +const maxNeglectedNodesToTrack = 5 // NOTE : This prevents an attacker from flooding large amounts of sybils into the network to cause a lot of inappropriate peer disconnections +const staleInformationPeriod = 3 * virtualSnakeMaintainInterval // Neglected node information older than this could be considered stale +const peerScoreResetPeriod = 2 * staleInformationPeriod // How long to wait before clearing peer scores type virtualSnakeTable map[virtualSnakeIndex]*virtualSnakeEntry @@ -368,9 +371,12 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea var frame *types.Frame = nil // NOTE : Only add additional signatures if the node is struggling if bootstrap.AttemptCount >= neglectedNodeTrackingPoint { + trackBootstrap := false if entry, ok := s._neglectedNodes[rx.DestinationKey]; ok { + trackBootstrap = true if entry.AttemptCount[len(entry.AttemptCount)-1] < uint64(bootstrap.AttemptCount) { entry.AttemptCount = append(entry.AttemptCount, uint64(bootstrap.AttemptCount)) + entry.LastAttempt = time.Now() } else { entry.AttemptCount = []uint64{uint64(bootstrap.AttemptCount)} entry.HopCount = uint64(len(bootstrap.Signatures)) @@ -379,20 +385,55 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea entry.FailedSetups = make(neglectedSetupTable) } } else { - entry := &neglectedNodeEntry{ - AttemptCount: []uint64{uint64(bootstrap.AttemptCount)}, - HopCount: uint64(len(bootstrap.Signatures)), - LastAttempt: time.Now(), - FailedBootstraps: make(neglectedBootstrapTable), - FailedSetups: make(neglectedSetupTable), + if len(s._neglectedNodes) < maxNeglectedNodesToTrack { + trackBootstrap = true + entry := &neglectedNodeEntry{ + AttemptCount: []uint64{uint64(bootstrap.AttemptCount)}, + HopCount: uint64(len(bootstrap.Signatures)), + LastAttempt: time.Now(), + FailedBootstraps: make(neglectedBootstrapTable), + FailedSetups: make(neglectedSetupTable), + } + s._neglectedNodes[rx.DestinationKey] = entry + } else { + for key, node := range s._neglectedNodes { + replaceNode := false + if len(bootstrap.Signatures) > int(node.HopCount) { + replaceNode = true + } else if time.Since(node.LastAttempt) > staleInformationPeriod { + // Problem: sybils can overwrite the longest node/s then stop sending failures + // what do? maybe if hop count is lower but time since is... something? + // maybe if time since is 3 x snake bootstrap attempt interval? + // ensures to only track nodes that are continuously failing + s.r.log.Println("Replace stale node") + replaceNode = true + } + + if replaceNode { + trackBootstrap = true + // TODO : Does this result in an issue since we no longer can route via reverse path? + cachePeerScoreHistory(node) + delete(s._neglectedNodes, key) + entry := &neglectedNodeEntry{ + AttemptCount: []uint64{uint64(bootstrap.AttemptCount)}, + HopCount: uint64(len(bootstrap.Signatures)), + LastAttempt: time.Now(), + FailedBootstraps: make(neglectedBootstrapTable), + FailedSetups: make(neglectedSetupTable), + } + s._neglectedNodes[rx.DestinationKey] = entry + break + } + } } - s._neglectedNodes[rx.DestinationKey] = entry } - s._neglectedNodes[rx.DestinationKey].FailedBootstraps[bootstrap.PathID] = &neglectedBootstrapData{ - Acknowledged: false, - Prev: from, - Next: nexthop, + if trackBootstrap { + s._neglectedNodes[rx.DestinationKey].FailedBootstraps[bootstrap.PathID] = &neglectedBootstrapData{ + Acknowledged: false, + Prev: from, + Next: nexthop, + } } score := nexthop.EvaluatePeerScore(s._neglectedNodes) @@ -408,9 +449,12 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea longestHopCount = int(node.HopCount) } } - duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + + // duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + duration := peerScoreResetPeriod // TODO : more failing nodes through me, shorter duration - s._peerScoreReset.Reset(time.Second * duration) + // s._peerScoreReset.Reset(time.Second * duration) + s._peerScoreReset.Reset(duration) s.r.log.Printf("Duration: %d", duration) if s.r.public.CompareTo(rx.DestinationKey) > 0 { @@ -546,11 +590,15 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame, nexthop *peer, longestHopCount = int(node.HopCount) } } - duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) - s.r.log.Printf("ACK Duration: %ds", duration) - s._peerScoreReset.Reset(time.Second * duration) + // duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + duration := peerScoreResetPeriod + // TODO : more failing nodes through me, shorter duration + // s._peerScoreReset.Reset(time.Second * duration) + s._peerScoreReset.Reset(duration) + s.r.log.Printf("Duration: %d", duration) } else { // TODO : should never get here! + // This means we received an ACK to a bootstrap we haven't seen but should have } } @@ -763,9 +811,11 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { longestHopCount = int(node.HopCount) } } - duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + // duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + duration := peerScoreResetPeriod // TODO : more failing nodes through me, shorter duration - s._peerScoreReset.Reset(time.Second * duration) + // s._peerScoreReset.Reset(time.Second * duration) + s._peerScoreReset.Reset(duration) s.r.log.Printf("Duration: %d", duration) } } @@ -921,9 +971,12 @@ func (s *state) _handleSetupACK(from *peer, rx *types.Frame, nexthop *peer) erro longestHopCount = int(node.HopCount) } } - duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) - s.r.log.Printf("ACK Duration: %ds", duration) - s._peerScoreReset.Reset(time.Second * duration) + // duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + duration := peerScoreResetPeriod + // TODO : more failing nodes through me, shorter duration + // s._peerScoreReset.Reset(time.Second * duration) + s._peerScoreReset.Reset(duration) + s.r.log.Printf("Duration: %d", duration) } else { // TODO : should never get here! } From be09fc33f79b0f40de935923882fa7f8ffc1f5f3 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Wed, 9 Mar 2022 14:01:23 -0300 Subject: [PATCH 23/36] Fix scoring for acknowledged bootstrap frames --- router/peer.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/router/peer.go b/router/peer.go index b97facca..dca00709 100644 --- a/router/peer.go +++ b/router/peer.go @@ -87,32 +87,31 @@ func (p *peer) EvaluatePeerScore(bootstraps neglectedNodeTable) int { } func scoreMissingAck(hopCount uint64) float64 { - // TODO : Better equation? + // TODO : Refine this + // NOTE : Gives exponential weighting the higher the hop count as that implies being closer to the problem return -(0.5 + 0.01*math.Pow(float64(hopCount), 4)) } func scoreAck(hopCount uint64) float64 { - // TODO : Better equation? + // TODO : Better equation. Haven't refined this at all return -(1 + 6/float64(hopCount-3)) } func cachePeerScoreHistory(node *neglectedNodeEntry) { for _, bootstrap := range node.FailedBootstraps { - if bootstrap.Acknowledged { - // NOTE : 1 for each new infraction - bootstrap.Prev.scoreCache += scoreAck(node.HopCount) - } else if !bootstrap.Acknowledged { - // NOTE : 1 for each new infraction + // NOTE : Cannot know which peer is suspect if an ACK was received. + // Since there isn't guaranteed to be a matching setup pair due to the forwarding + // logic being different for the setup frames, we have no way of guaranteeing + // if a setup frame was sent in response to the bootstrap ack. + if !bootstrap.Acknowledged { bootstrap.Next.scoreCache += scoreMissingAck(node.HopCount) } } for _, setup := range node.FailedSetups { if setup.Acknowledged { - // NOTE : 1 for each new infraction setup.Prev.scoreCache += scoreAck(node.HopCount) } else if !setup.Acknowledged { - // NOTE : 1 for each new infraction setup.Next.scoreCache += scoreMissingAck(node.HopCount) } } @@ -122,10 +121,11 @@ func (p *peer) EvaluatePeerScoreForNode(node *neglectedNodeEntry) float64 { peerScore := 0.0 for _, bootstrap := range node.FailedBootstraps { - if bootstrap.Acknowledged && bootstrap.Prev == p { - // NOTE : 1 for each new infraction - peerScore += scoreAck(node.HopCount) - } else if !bootstrap.Acknowledged && bootstrap.Next == p { + // NOTE : Cannot know which peer is suspect if an ACK was received. + // Since there isn't guaranteed to be a matching setup pair due to the forwarding + // logic being different for the setup frames, we have no way of guaranteeing + // if a setup frame was sent in response to the bootstrap ack. + if !bootstrap.Acknowledged && bootstrap.Next == p { // NOTE : 1 for each new infraction peerScore += scoreMissingAck(node.HopCount) } From 55dcc7d1bd2242db3768af52a036b58afe463198 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Wed, 9 Mar 2022 14:26:26 -0300 Subject: [PATCH 24/36] Remove exploit due to manipulating bootstrap attempt count --- router/state_snek.go | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/router/state_snek.go b/router/state_snek.go index 3663e7d4..91d7948f 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -73,7 +73,6 @@ type neglectedSetupTable map[types.VirtualSnakePathID]*neglectedSetupData type neglectedNodeTable map[types.PublicKey]*neglectedNodeEntry type neglectedNodeEntry struct { - AttemptCount []uint64 // List of attempt counts seen in the order they were seen HopCount uint64 // The hop count from the attempt when the entry was created LastAttempt time.Time // The time that the last attempt was seen FailedBootstraps neglectedBootstrapTable // Map of failed bootstrap attempts @@ -370,25 +369,16 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea if !deadend { var frame *types.Frame = nil // NOTE : Only add additional signatures if the node is struggling + // TODO : Change attempt count to a bool since the numeric value cannot be trusted? if bootstrap.AttemptCount >= neglectedNodeTrackingPoint { trackBootstrap := false if entry, ok := s._neglectedNodes[rx.DestinationKey]; ok { trackBootstrap = true - if entry.AttemptCount[len(entry.AttemptCount)-1] < uint64(bootstrap.AttemptCount) { - entry.AttemptCount = append(entry.AttemptCount, uint64(bootstrap.AttemptCount)) - entry.LastAttempt = time.Now() - } else { - entry.AttemptCount = []uint64{uint64(bootstrap.AttemptCount)} - entry.HopCount = uint64(len(bootstrap.Signatures)) - entry.LastAttempt = time.Now() - entry.FailedBootstraps = make(neglectedBootstrapTable) - entry.FailedSetups = make(neglectedSetupTable) - } + entry.LastAttempt = time.Now() } else { if len(s._neglectedNodes) < maxNeglectedNodesToTrack { trackBootstrap = true entry := &neglectedNodeEntry{ - AttemptCount: []uint64{uint64(bootstrap.AttemptCount)}, HopCount: uint64(len(bootstrap.Signatures)), LastAttempt: time.Now(), FailedBootstraps: make(neglectedBootstrapTable), @@ -401,10 +391,9 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea if len(bootstrap.Signatures) > int(node.HopCount) { replaceNode = true } else if time.Since(node.LastAttempt) > staleInformationPeriod { - // Problem: sybils can overwrite the longest node/s then stop sending failures - // what do? maybe if hop count is lower but time since is... something? - // maybe if time since is 3 x snake bootstrap attempt interval? - // ensures to only track nodes that are continuously failing + // NOTE : This is to prevent attackers from filling the neglected + // node list with artificially high hop counts then not continuing + // to send frames. s.r.log.Println("Replace stale node") replaceNode = true } @@ -415,7 +404,6 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea cachePeerScoreHistory(node) delete(s._neglectedNodes, key) entry := &neglectedNodeEntry{ - AttemptCount: []uint64{uint64(bootstrap.AttemptCount)}, HopCount: uint64(len(bootstrap.Signatures)), LastAttempt: time.Now(), FailedBootstraps: make(neglectedBootstrapTable), From 16648c4d3acc2252bb04c044a0d39ed7239d5a69 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Wed, 9 Mar 2022 16:23:17 -0300 Subject: [PATCH 25/36] Add ack settling time to prevent preemptive peer scoring --- router/peer.go | 58 ++++++++++++++++++++++++++------------------ router/state_snek.go | 39 +++++++++++++++++++++++------ 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/router/peer.go b/router/peer.go index dca00709..481d47f3 100644 --- a/router/peer.go +++ b/router/peer.go @@ -99,20 +99,24 @@ func scoreAck(hopCount uint64) float64 { func cachePeerScoreHistory(node *neglectedNodeEntry) { for _, bootstrap := range node.FailedBootstraps { - // NOTE : Cannot know which peer is suspect if an ACK was received. - // Since there isn't guaranteed to be a matching setup pair due to the forwarding - // logic being different for the setup frames, we have no way of guaranteeing - // if a setup frame was sent in response to the bootstrap ack. - if !bootstrap.Acknowledged { - bootstrap.Next.scoreCache += scoreMissingAck(node.HopCount) + if time.Since(bootstrap.ArrivalTime) > ackSettlingPeriod { + // NOTE : Cannot know which peer is suspect if an ACK was received. + // Since there isn't guaranteed to be a matching setup pair due to the forwarding + // logic being different for the setup frames, we have no way of guaranteeing + // if a setup frame was sent in response to the bootstrap ack. + if !bootstrap.Acknowledged { + bootstrap.Next.scoreCache += scoreMissingAck(node.HopCount) + } } } for _, setup := range node.FailedSetups { - if setup.Acknowledged { - setup.Prev.scoreCache += scoreAck(node.HopCount) - } else if !setup.Acknowledged { - setup.Next.scoreCache += scoreMissingAck(node.HopCount) + if time.Since(setup.ArrivalTime) > ackSettlingPeriod { + if setup.Acknowledged { + setup.Prev.scoreCache += scoreAck(node.HopCount) + } else if !setup.Acknowledged { + setup.Next.scoreCache += scoreMissingAck(node.HopCount) + } } } } @@ -121,23 +125,31 @@ func (p *peer) EvaluatePeerScoreForNode(node *neglectedNodeEntry) float64 { peerScore := 0.0 for _, bootstrap := range node.FailedBootstraps { - // NOTE : Cannot know which peer is suspect if an ACK was received. - // Since there isn't guaranteed to be a matching setup pair due to the forwarding - // logic being different for the setup frames, we have no way of guaranteeing - // if a setup frame was sent in response to the bootstrap ack. - if !bootstrap.Acknowledged && bootstrap.Next == p { - // NOTE : 1 for each new infraction - peerScore += scoreMissingAck(node.HopCount) + if time.Since(bootstrap.ArrivalTime) > ackSettlingPeriod { + // NOTE : Cannot know which peer is suspect if an ACK was received. + // Since there isn't guaranteed to be a matching setup pair due to the forwarding + // logic being different for the setup frames, we have no way of guaranteeing + // if a setup frame was sent in response to the bootstrap ack. + if !bootstrap.Acknowledged && bootstrap.Next == p { + // NOTE : 1 for each new infraction + peerScore += scoreMissingAck(node.HopCount) + } + } else { + // NOTE : Ignoring this datapoint for scoring purposes } } for _, setup := range node.FailedSetups { - if setup.Acknowledged && setup.Prev == p { - // NOTE : 1 for each new infraction - peerScore += scoreAck(node.HopCount) - } else if !setup.Acknowledged && setup.Next == p { - // NOTE : 1 for each new infraction - peerScore += scoreMissingAck(node.HopCount) + if time.Since(setup.ArrivalTime) > ackSettlingPeriod { + if setup.Acknowledged && setup.Prev == p { + // NOTE : 1 for each new infraction + peerScore += scoreAck(node.HopCount) + } else if !setup.Acknowledged && setup.Next == p { + // NOTE : 1 for each new infraction + peerScore += scoreMissingAck(node.HopCount) + } + } else { + // NOTE : Ignoring this datapoint for scoring purposes } } diff --git a/router/state_snek.go b/router/state_snek.go index 91d7948f..b1c7dd5f 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -32,9 +32,11 @@ const virtualSnakeMaintainInterval = time.Second const virtualSnakeNeighExpiryPeriod = time.Hour const bootstrapAttemptResetPoint = math.MaxUint32 // TODO : Pick a more meaningful value const neglectedNodeTrackingPoint = 5 // NOTE : Start tracking a neglected node's bootstraps when their attempt count reaches this number -const maxNeglectedNodesToTrack = 5 // NOTE : This prevents an attacker from flooding large amounts of sybils into the network to cause a lot of inappropriate peer disconnections +const maxNeglectedNodesToTrack = 10 // NOTE : This prevents an attacker from flooding large amounts of sybils into the network to cause a lot of inappropriate peer disconnections or memory spikes const staleInformationPeriod = 3 * virtualSnakeMaintainInterval // Neglected node information older than this could be considered stale const peerScoreResetPeriod = 2 * staleInformationPeriod // How long to wait before clearing peer scores +const ackSettlingPeriod = time.Second * 2 // TODO : Arrive at this value better +// NOTE : Must be < staleInformationPeriod to prevent exploit type virtualSnakeTable map[virtualSnakeIndex]*virtualSnakeEntry @@ -56,6 +58,7 @@ type virtualSnakeEntry struct { type neglectedBootstrapData struct { Acknowledged bool + ArrivalTime time.Time Prev *peer Next *peer } @@ -64,6 +67,7 @@ type neglectedBootstrapTable map[types.VirtualSnakePathID]*neglectedBootstrapDat type neglectedSetupData struct { Acknowledged bool + ArrivalTime time.Time Prev *peer Next *peer } @@ -74,7 +78,6 @@ type neglectedNodeTable map[types.PublicKey]*neglectedNodeEntry type neglectedNodeEntry struct { HopCount uint64 // The hop count from the attempt when the entry was created - LastAttempt time.Time // The time that the last attempt was seen FailedBootstraps neglectedBootstrapTable // Map of failed bootstrap attempts FailedSetups neglectedSetupTable // Map of failed setup attempts } @@ -372,15 +375,16 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea // TODO : Change attempt count to a bool since the numeric value cannot be trusted? if bootstrap.AttemptCount >= neglectedNodeTrackingPoint { trackBootstrap := false - if entry, ok := s._neglectedNodes[rx.DestinationKey]; ok { + if node, ok := s._neglectedNodes[rx.DestinationKey]; ok { trackBootstrap = true - entry.LastAttempt = time.Now() + if uint64(len(bootstrap.Signatures)) > node.HopCount { + node.HopCount = uint64(len(bootstrap.Signatures)) + } } else { if len(s._neglectedNodes) < maxNeglectedNodesToTrack { trackBootstrap = true entry := &neglectedNodeEntry{ HopCount: uint64(len(bootstrap.Signatures)), - LastAttempt: time.Now(), FailedBootstraps: make(neglectedBootstrapTable), FailedSetups: make(neglectedSetupTable), } @@ -388,9 +392,22 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea } else { for key, node := range s._neglectedNodes { replaceNode := false + + latestArrival := time.UnixMicro(0) + for _, info := range node.FailedBootstraps { + if info.ArrivalTime.After(latestArrival) { + latestArrival = info.ArrivalTime + } + } + for _, info := range node.FailedSetups { + if info.ArrivalTime.After(latestArrival) { + latestArrival = info.ArrivalTime + } + } + if len(bootstrap.Signatures) > int(node.HopCount) { replaceNode = true - } else if time.Since(node.LastAttempt) > staleInformationPeriod { + } else if time.Since(latestArrival) > staleInformationPeriod { // NOTE : This is to prevent attackers from filling the neglected // node list with artificially high hop counts then not continuing // to send frames. @@ -400,12 +417,12 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea if replaceNode { trackBootstrap = true - // TODO : Does this result in an issue since we no longer can route via reverse path? + // NOTE : Reverse path routing guarantees still exist in this case. + // We just don't track this node for peer scoring purposes anymore. cachePeerScoreHistory(node) delete(s._neglectedNodes, key) entry := &neglectedNodeEntry{ HopCount: uint64(len(bootstrap.Signatures)), - LastAttempt: time.Now(), FailedBootstraps: make(neglectedBootstrapTable), FailedSetups: make(neglectedSetupTable), } @@ -419,6 +436,7 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea if trackBootstrap { s._neglectedNodes[rx.DestinationKey].FailedBootstraps[bootstrap.PathID] = &neglectedBootstrapData{ Acknowledged: false, + ArrivalTime: time.Now(), Prev: from, Next: nexthop, } @@ -778,10 +796,15 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { return nil } + // NOTE : If you only see setups and not bootstraps then you can conclude that you aren't + // attached to a malicious peer. Otherwise you would have been guaranteed to also see the + // corresponding bootstrap frames from that node. + // Other than this case, there are no firm conclusions that can be drawn. if node, ok := s._neglectedNodes[rx.SourceKey]; ok { if nexthop != nil { node.FailedSetups[setup.PathID] = &neglectedSetupData{ Acknowledged: false, + ArrivalTime: time.Now(), Prev: from, Next: nexthop, } From a6578b7c40c059a1b39a5367c856733eabc14bd6 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Wed, 9 Mar 2022 16:35:34 -0300 Subject: [PATCH 26/36] Cleanup peer scoring reset call sites --- router/state_snek.go | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/router/state_snek.go b/router/state_snek.go index b1c7dd5f..d5b1556a 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -456,12 +456,10 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea } } - // duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) + // TODO : more failing nodes through me, shorter duration, ie. should being closer + // to the problem result in a short score reset period? duration := peerScoreResetPeriod - // TODO : more failing nodes through me, shorter duration - // s._peerScoreReset.Reset(time.Second * duration) s._peerScoreReset.Reset(duration) - s.r.log.Printf("Duration: %d", duration) if s.r.public.CompareTo(rx.DestinationKey) > 0 { switch { @@ -596,12 +594,8 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame, nexthop *peer, longestHopCount = int(node.HopCount) } } - // duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) duration := peerScoreResetPeriod - // TODO : more failing nodes through me, shorter duration - // s._peerScoreReset.Reset(time.Second * duration) s._peerScoreReset.Reset(duration) - s.r.log.Printf("Duration: %d", duration) } else { // TODO : should never get here! // This means we received an ACK to a bootstrap we haven't seen but should have @@ -822,12 +816,8 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { longestHopCount = int(node.HopCount) } } - // duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) duration := peerScoreResetPeriod - // TODO : more failing nodes through me, shorter duration - // s._peerScoreReset.Reset(time.Second * duration) s._peerScoreReset.Reset(duration) - s.r.log.Printf("Duration: %d", duration) } } @@ -982,12 +972,8 @@ func (s *state) _handleSetupACK(from *peer, rx *types.Frame, nexthop *peer) erro longestHopCount = int(node.HopCount) } } - // duration := time.Duration(uint64(math.Max(float64(30-0.6*math.Pow(float64(longestHopCount), 2)), 0))) duration := peerScoreResetPeriod - // TODO : more failing nodes through me, shorter duration - // s._peerScoreReset.Reset(time.Second * duration) s._peerScoreReset.Reset(duration) - s.r.log.Printf("Duration: %d", duration) } else { // TODO : should never get here! } From 03f3fc8a78e947397ca886ac500ef1cb8e62eb0c Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Tue, 22 Mar 2022 11:50:58 -0300 Subject: [PATCH 27/36] Swap attempt cout for single failing byte --- router/state_snek.go | 11 +++++++---- types/virtualsnake.go | 20 +++++++------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/router/state_snek.go b/router/state_snek.go index d5b1556a..9be3c977 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -177,9 +177,13 @@ func (s *state) _bootstrapNow() { // the same root node when processing the update. b := frameBufferPool.Get().(*[types.MaxFrameSize]byte) defer frameBufferPool.Put(b) + failing := byte(0) + if s._bootstrapAttempt > neglectedNodeTrackingPoint { + failing = 1 + } bootstrap := types.VirtualSnakeBootstrap{ - Root: ann.Root, - AttemptCount: types.Varu64(s._bootstrapAttempt + 1), + Root: ann.Root, + Failing: failing, } // Generate a random path ID. @@ -372,8 +376,7 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea if !deadend { var frame *types.Frame = nil // NOTE : Only add additional signatures if the node is struggling - // TODO : Change attempt count to a bool since the numeric value cannot be trusted? - if bootstrap.AttemptCount >= neglectedNodeTrackingPoint { + if bootstrap.Failing > 0 { trackBootstrap := false if node, ok := s._neglectedNodes[rx.DestinationKey]; ok { trackBootstrap = true diff --git a/types/virtualsnake.go b/types/virtualsnake.go index 4ed0f1bf..db189708 100644 --- a/types/virtualsnake.go +++ b/types/virtualsnake.go @@ -20,13 +20,13 @@ type VirtualSnakeBootstrap struct { PathID VirtualSnakePathID SourceSig VirtualSnakePathSig Root - AttemptCount Varu64 - Signatures []BootstrapSignature + Failing byte + Signatures []BootstrapSignature } func (v *VirtualSnakeBootstrap) MarshalBinary(buf []byte) (int, error) { // TODO : Add hop signatures size to below check - if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+ed25519.SignatureSize+v.AttemptCount.Length() { + if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+ed25519.SignatureSize+1 { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -38,11 +38,8 @@ func (v *VirtualSnakeBootstrap) MarshalBinary(buf []byte) (int, error) { } offset += n offset += copy(buf[offset:], v.SourceSig[:]) - n, err = v.AttemptCount.MarshalBinary(buf[offset:]) - if err != nil { - return 0, fmt.Errorf("v.AttemptCount.MarshalBinary: %w", err) - } - offset += n + buf[offset] = v.Failing + offset += 1 for _, sig := range v.Signatures { n, err := sig.MarshalBinary(buf[offset:]) if err != nil { @@ -67,11 +64,8 @@ func (v *VirtualSnakeBootstrap) UnmarshalBinary(buf []byte) (int, error) { } offset += l offset += copy(v.SourceSig[:], buf[offset:]) - l, err = v.AttemptCount.UnmarshalBinary(buf[offset:]) - if err != nil { - return 0, fmt.Errorf("v.AttemptCount.UnmarshalBinary: %w", err) - } - offset += l + v.Failing = buf[offset] + offset += 1 remaining := buf[offset:] for i := uint64(0); len(remaining) >= BootstrapSignatureSize; i++ { var signature BootstrapSignature From b5de1e366e7b4a40cea7a5a086c61ff0c8568355 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Wed, 23 Mar 2022 10:58:14 -0300 Subject: [PATCH 28/36] Update snake frame size check --- types/virtualsnake.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/types/virtualsnake.go b/types/virtualsnake.go index db189708..17894f75 100644 --- a/types/virtualsnake.go +++ b/types/virtualsnake.go @@ -25,8 +25,8 @@ type VirtualSnakeBootstrap struct { } func (v *VirtualSnakeBootstrap) MarshalBinary(buf []byte) (int, error) { - // TODO : Add hop signatures size to below check - if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+ed25519.SignatureSize+1 { + sizeOfSignatures := len(v.Signatures) * (BootstrapSignatureSize) + if len(buf) < VirtualSnakePathIDLength+v.Root.Length()+ed25519.SignatureSize+1+sizeOfSignatures { return 0, fmt.Errorf("buffer too small") } offset := 0 @@ -51,7 +51,6 @@ func (v *VirtualSnakeBootstrap) MarshalBinary(buf []byte) (int, error) { } func (v *VirtualSnakeBootstrap) UnmarshalBinary(buf []byte) (int, error) { - // TODO : Add hop signatures size to below check if len(buf) < VirtualSnakePathIDLength+v.Root.MinLength()+ed25519.SignatureSize+1 { return 0, fmt.Errorf("buffer too small") } From 9a8eba77a35e489957b6f67c10c8907a9e6e85ce Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Thu, 24 Mar 2022 12:00:30 -0300 Subject: [PATCH 29/36] Switch to per peer score accumulation --- router/peer.go | 35 ++++++++++++++------------- router/state.go | 57 ++++++++++++++++++++++++-------------------- router/state_snek.go | 20 +++++++--------- 3 files changed, 57 insertions(+), 55 deletions(-) diff --git a/router/peer.go b/router/peer.go index 481d47f3..1ecd3e3c 100644 --- a/router/peer.go +++ b/router/peer.go @@ -49,22 +49,23 @@ const ( // These need to be a simple int type for gobind/gomobile to export them // the peering). Having separate actors allows reads and writes to take // place concurrently. type peer struct { - reader phony.Inbox - writer phony.Inbox - router *Router - port types.SwitchPortID // Not mutated after peer setup. - context context.Context // Not mutated after peer setup. - cancel context.CancelFunc // Not mutated after peer setup. - conn net.Conn // Not mutated after peer setup. - uri ConnectionURI // Not mutated after peer setup. - zone ConnectionZone // Not mutated after peer setup. - peertype ConnectionPeerType // Not mutated after peer setup. - public types.PublicKey // Not mutated after peer setup. - keepalives bool // Not mutated after peer setup. - started atomic.Bool // Thread-safe toggle for marking a peer as down. - proto *fifoQueue // Thread-safe queue for outbound protocol messages. - traffic *lifoQueue // Thread-safe queue for outbound traffic messages. - scoreCache float64 // Tracks peer score information from replaced sources + reader phony.Inbox + writer phony.Inbox + router *Router + port types.SwitchPortID // Not mutated after peer setup. + context context.Context // Not mutated after peer setup. + cancel context.CancelFunc // Not mutated after peer setup. + conn net.Conn // Not mutated after peer setup. + uri ConnectionURI // Not mutated after peer setup. + zone ConnectionZone // Not mutated after peer setup. + peertype ConnectionPeerType // Not mutated after peer setup. + public types.PublicKey // Not mutated after peer setup. + keepalives bool // Not mutated after peer setup. + started atomic.Bool // Thread-safe toggle for marking a peer as down. + proto *fifoQueue // Thread-safe queue for outbound protocol messages. + traffic *lifoQueue // Thread-safe queue for outbound traffic messages. + scoreCache float64 // Tracks peer score information from replaced sources + peerScoreAccumulator *time.Timer // Accumulates peer merit points over time. } func (p *peer) EvaluatePeerScore(bootstraps neglectedNodeTable) int { @@ -82,7 +83,7 @@ func (p *peer) EvaluatePeerScore(bootstraps neglectedNodeTable) int { peerScore += float64(p.scoreCache) - p.router.log.Printf("PeerScore: %s --- %f", p.public.String()[:8], peerScore) + // p.router.log.Printf("PeerScore: %s --- %f", p.public.String()[:8], peerScore) return int(peerScore) } diff --git a/router/state.go b/router/state.go index a68702a1..44b1947e 100644 --- a/router/state.go +++ b/router/state.go @@ -57,7 +57,7 @@ type state struct { _snaketimer *time.Timer // Virtual snake maintenance timer _waiting bool // Is the tree waiting to reparent? _filterPacket FilterFn // Function called when forwarding packets - _peerScoreReset *time.Timer + _neglectReset *time.Timer } // _start resets the state and starts tree and virtual snake maintenance. @@ -88,9 +88,9 @@ func (s *state) _start() { }) } - if s._peerScoreReset == nil { - s._peerScoreReset = time.AfterFunc(0, func() { - s.Act(nil, s._resetPeerScoring) + if s._neglectReset == nil { + s._neglectReset = time.AfterFunc(0, func() { + s.Act(nil, s._resetNeglectedNodes) }) } @@ -98,20 +98,18 @@ func (s *state) _start() { s._maintainSnakeIn(0) } -func (s *state) _resetPeerScoring() { - s.r.log.Println("Reseting peer scores") - // TODO : Only reset scoring for a specific peer/peerset? - // maybe only start reset timers on a per peer basis as well - // if you are seeing issues correlated with a specific peer, then you need to time - // issues seen from that peer, not just any peer +func (s *state) _resetNeglectedNodes() { for pk := range s._neglectedNodes { delete(s._neglectedNodes, pk) } - for _, peer := range s._peers { - if peer != nil { - peer.scoreCache = 0 - } +} + +func (s *state) _accumulatePeerScore(p *peer) { + if p != nil && p.scoreCache < 100 { + p.scoreCache += 5 } + + p.peerScoreAccumulator.Reset(peerScoreResetPeriod) } // _maintainTreeIn resets the tree maintenance timer to the specified @@ -149,19 +147,26 @@ func (s *state) _addPeer(conn net.Conn, public types.PublicKey, uri ConnectionUR } ctx, cancel := context.WithCancel(s.r.context) new = &peer{ - router: s.r, - port: types.SwitchPortID(i), - conn: conn, - public: public, - uri: uri, - zone: zone, - peertype: peertype, - keepalives: keepalives, - context: ctx, - cancel: cancel, - proto: newFIFOQueue(), - traffic: newLIFOQueue(trafficBuffer), + router: s.r, + port: types.SwitchPortID(i), + conn: conn, + public: public, + uri: uri, + zone: zone, + peertype: peertype, + keepalives: keepalives, + context: ctx, + cancel: cancel, + proto: newFIFOQueue(), + traffic: newLIFOQueue(trafficBuffer), + peerScoreAccumulator: nil, + } + if new.peerScoreAccumulator == nil { + new.peerScoreAccumulator = time.AfterFunc(peerScoreResetPeriod, func() { + s.Act(nil, func() { s._accumulatePeerScore(new) }) + }) } + s._peers[i] = new s.r.log.Println("Connected to peer", new.public.String(), "on port", new.port) v, _ := s.r.active.LoadOrStore(hex.EncodeToString(new.public[:])+string(zone), atomic.NewUint64(0)) diff --git a/router/state_snek.go b/router/state_snek.go index 9be3c977..0cc8db95 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -459,10 +459,8 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea } } - // TODO : more failing nodes through me, shorter duration, ie. should being closer - // to the problem result in a short score reset period? - duration := peerScoreResetPeriod - s._peerScoreReset.Reset(duration) + s._neglectReset.Reset(peerScoreResetPeriod) + from.peerScoreAccumulator.Reset(peerScoreResetPeriod) if s.r.public.CompareTo(rx.DestinationKey) > 0 { switch { @@ -598,10 +596,8 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame, nexthop *peer, } } duration := peerScoreResetPeriod - s._peerScoreReset.Reset(duration) - } else { - // TODO : should never get here! - // This means we received an ACK to a bootstrap we haven't seen but should have + s._neglectReset.Reset(duration) + from.peerScoreAccumulator.Reset(peerScoreResetPeriod) } } @@ -820,7 +816,8 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { } } duration := peerScoreResetPeriod - s._peerScoreReset.Reset(duration) + s._neglectReset.Reset(duration) + from.peerScoreAccumulator.Reset(peerScoreResetPeriod) } } @@ -976,9 +973,8 @@ func (s *state) _handleSetupACK(from *peer, rx *types.Frame, nexthop *peer) erro } } duration := peerScoreResetPeriod - s._peerScoreReset.Reset(duration) - } else { - // TODO : should never get here! + s._neglectReset.Reset(duration) + from.peerScoreAccumulator.Reset(peerScoreResetPeriod) } } From 244d3999f15cc180fefe969c59d9604579aefa29 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sat, 26 Mar 2022 10:49:55 -0300 Subject: [PATCH 30/36] Silently drop duplicate frames with bootstrap failures --- router/state_snek.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/router/state_snek.go b/router/state_snek.go index 0cc8db95..e2142e82 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -579,6 +579,11 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame, nexthop *peer, knownFailure := false if node, ok := s._neglectedNodes[rx.SourceKey]; ok { if data, ok := node.FailedSetups[bootstrapACK.PathID]; ok { + if data.Acknowledged { + // NOTE : This peer is sending us duplicate frames. + return nil + } + knownFailure = true data.Prev.send(rx) data.Acknowledged = true @@ -795,6 +800,11 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { // Other than this case, there are no firm conclusions that can be drawn. if node, ok := s._neglectedNodes[rx.SourceKey]; ok { if nexthop != nil { + if _, ok := node.FailedSetups[setup.PathID]; ok { + // NOTE : This peer is sending us duplicate frames. + return nil + } + node.FailedSetups[setup.PathID] = &neglectedSetupData{ Acknowledged: false, ArrivalTime: time.Now(), @@ -958,6 +968,10 @@ func (s *state) _handleSetupACK(from *peer, rx *types.Frame, nexthop *peer) erro if v.Source.local() || v.Source.send(rx) { if node, ok := s._neglectedNodes[rx.SourceKey]; ok { if data, ok := node.FailedSetups[setup.PathID]; ok { + if data.Acknowledged { + // NOTE : This peer is sending us duplicate frames. + continue + } data.Acknowledged = true score := data.Prev.EvaluatePeerScore(s._neglectedNodes) if score <= lowScoreThreshold { From 06f1be7a539aad896b994de09d547c8e6473723c Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sat, 26 Mar 2022 10:50:45 -0300 Subject: [PATCH 31/36] Ensure peer is still running before resetting the score accumulator --- router/state_snek.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/router/state_snek.go b/router/state_snek.go index e2142e82..80ea7f26 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -460,7 +460,9 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea } s._neglectReset.Reset(peerScoreResetPeriod) - from.peerScoreAccumulator.Reset(peerScoreResetPeriod) + if nexthop.started.Load() { + nexthop.peerScoreAccumulator.Reset(peerScoreResetPeriod) + } if s.r.public.CompareTo(rx.DestinationKey) > 0 { switch { @@ -600,9 +602,10 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame, nexthop *peer, longestHopCount = int(node.HopCount) } } - duration := peerScoreResetPeriod - s._neglectReset.Reset(duration) - from.peerScoreAccumulator.Reset(peerScoreResetPeriod) + s._neglectReset.Reset(peerScoreResetPeriod) + if data.Prev.started.Load() { + data.Prev.peerScoreAccumulator.Reset(peerScoreResetPeriod) + } } } @@ -825,9 +828,10 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { longestHopCount = int(node.HopCount) } } - duration := peerScoreResetPeriod - s._neglectReset.Reset(duration) - from.peerScoreAccumulator.Reset(peerScoreResetPeriod) + s._neglectReset.Reset(peerScoreResetPeriod) + if nexthop.started.Load() { + nexthop.peerScoreAccumulator.Reset(peerScoreResetPeriod) + } } } @@ -986,9 +990,10 @@ func (s *state) _handleSetupACK(from *peer, rx *types.Frame, nexthop *peer) erro longestHopCount = int(node.HopCount) } } - duration := peerScoreResetPeriod - s._neglectReset.Reset(duration) - from.peerScoreAccumulator.Reset(peerScoreResetPeriod) + s._neglectReset.Reset(peerScoreResetPeriod) + if data.Prev.started.Load() { + data.Prev.peerScoreAccumulator.Reset(peerScoreResetPeriod) + } } } From fd97796094ab6f3d556548d30d23cd3313cb701e Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sat, 26 Mar 2022 10:51:19 -0300 Subject: [PATCH 32/36] Don't crash simulator when root node cannot be interpreted --- cmd/pineconesim/simulator/simulator.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/pineconesim/simulator/simulator.go b/cmd/pineconesim/simulator/simulator.go index 2ea36838..1b44ddda 100644 --- a/cmd/pineconesim/simulator/simulator.go +++ b/cmd/pineconesim/simulator/simulator.go @@ -218,7 +218,8 @@ func (sim *Simulator) handleTreeRootAnnUpdate(node string, root string, sequence if peerNode, err := sim.State.GetNodeName(root); err == nil { rootName = peerNode } else { - log.Fatalf("Cannot convert %s to root for %s", root, node) + log.Printf("Cannot convert %s to root for %s", root, node) + rootName = "UNKNOWN" } sim.State.Act(nil, func() { sim.State._updateTreeRootAnnouncement(node, rootName, sequence, time, coords) }) } From ea63fa2ffad07a370ac21f7b55266fd129a36d2f Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sat, 26 Mar 2022 10:51:36 -0300 Subject: [PATCH 33/36] Add back in logging when negative peer scoring is being applied --- router/peer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/peer.go b/router/peer.go index 1ecd3e3c..00159b92 100644 --- a/router/peer.go +++ b/router/peer.go @@ -83,7 +83,7 @@ func (p *peer) EvaluatePeerScore(bootstraps neglectedNodeTable) int { peerScore += float64(p.scoreCache) - // p.router.log.Printf("PeerScore: %s --- %f", p.public.String()[:8], peerScore) + p.router.log.Printf("PeerScore: %s --- %f", p.public.String()[:8], peerScore) return int(peerScore) } From b596ff745ae06f794621fdb076eff9acada69e68 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Sat, 26 Mar 2022 10:52:13 -0300 Subject: [PATCH 34/36] Add simulator sequences for testing sybil attacks --- .../attack_sybil_grid_bootstrap.json | 976 ++++++++++++++++++ .../sequences/attack_sybil_grid_setup.json | 976 ++++++++++++++++++ .../sequences/attack_sybil_hub_bootstrap.json | 893 ++++++++++++++++ .../sequences/attack_sybil_hub_setup.json | 893 ++++++++++++++++ .../attack_sybil_line_bootstrap.json | 893 ++++++++++++++++ .../sequences/attack_sybil_line_setup.json | 893 ++++++++++++++++ 6 files changed, 5524 insertions(+) create mode 100644 cmd/pineconesim/sequences/attack_sybil_grid_bootstrap.json create mode 100644 cmd/pineconesim/sequences/attack_sybil_grid_setup.json create mode 100644 cmd/pineconesim/sequences/attack_sybil_hub_bootstrap.json create mode 100644 cmd/pineconesim/sequences/attack_sybil_hub_setup.json create mode 100644 cmd/pineconesim/sequences/attack_sybil_line_bootstrap.json create mode 100644 cmd/pineconesim/sequences/attack_sybil_line_setup.json diff --git a/cmd/pineconesim/sequences/attack_sybil_grid_bootstrap.json b/cmd/pineconesim/sequences/attack_sybil_grid_bootstrap.json new file mode 100644 index 00000000..512f5f52 --- /dev/null +++ b/cmd/pineconesim/sequences/attack_sybil_grid_bootstrap.json @@ -0,0 +1,976 @@ +{ + "EventSequence": [ + { + "Command": "RemoveNode", + "Data": { + "Name": "m1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m15" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "Sybil Proxy" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h15" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h16" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m1", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m2", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m3", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m4", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m5", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m6", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m7", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m8", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m9", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m10", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m11", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m12", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m13", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m14", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m15", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "Sybil Proxy", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h1", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h2", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h3", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h4", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h5", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h6", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h7", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h8", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h9", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h10", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h11", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h12", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h13", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h14", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h15", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h16", + "NodeType": "Default" + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m1", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m2", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m3", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m4", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m5", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m6", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m7", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m8", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m9", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m10", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m11", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m12", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m13", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m14", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m15", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h2", + "Peer": "h3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h2", + "Peer": "h6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h3", + "Peer": "h4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h3", + "Peer": "h7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h4", + "Peer": "h8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h5", + "Peer": "h6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h5", + "Peer": "h9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h6", + "Peer": "h7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h6", + "Peer": "h10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h7", + "Peer": "h8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h7", + "Peer": "h11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h8", + "Peer": "h12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h9", + "Peer": "h10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h9", + "Peer": "h13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h10", + "Peer": "h11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h10", + "Peer": "h14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h11", + "Peer": "h12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h11", + "Peer": "h15" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h12", + "Peer": "h16" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h13", + "Peer": "h14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h14", + "Peer": "h15" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h15", + "Peer": "h16" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m1" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m15" + } + }, + { + "Command": "Delay", + "Data": { + "Length": 10000 + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "Sybil Proxy" + } + } + ] +} diff --git a/cmd/pineconesim/sequences/attack_sybil_grid_setup.json b/cmd/pineconesim/sequences/attack_sybil_grid_setup.json new file mode 100644 index 00000000..efb46aaa --- /dev/null +++ b/cmd/pineconesim/sequences/attack_sybil_grid_setup.json @@ -0,0 +1,976 @@ +{ + "EventSequence": [ + { + "Command": "RemoveNode", + "Data": { + "Name": "m1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m15" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "Sybil Proxy" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h15" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h16" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m1", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m2", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m3", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m4", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m5", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m6", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m7", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m8", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m9", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m10", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m11", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m12", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m13", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m14", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m15", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "Sybil Proxy", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h1", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h2", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h3", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h4", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h5", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h6", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h7", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h8", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h9", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h10", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h11", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h12", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h13", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h14", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h15", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h16", + "NodeType": "Default" + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m1", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m2", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m3", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m4", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m5", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m6", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m7", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m8", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m9", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m10", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m11", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m12", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m13", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m14", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m15", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h2", + "Peer": "h3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h2", + "Peer": "h6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h3", + "Peer": "h4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h3", + "Peer": "h7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h4", + "Peer": "h8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h5", + "Peer": "h6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h5", + "Peer": "h9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h6", + "Peer": "h7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h6", + "Peer": "h10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h7", + "Peer": "h8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h7", + "Peer": "h11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h8", + "Peer": "h12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h9", + "Peer": "h10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h9", + "Peer": "h13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h10", + "Peer": "h11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h10", + "Peer": "h14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h11", + "Peer": "h12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h11", + "Peer": "h15" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h12", + "Peer": "h16" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h13", + "Peer": "h14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h14", + "Peer": "h15" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h15", + "Peer": "h16" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m1" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m15" + } + }, + { + "Command": "Delay", + "Data": { + "Length": 10000 + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "Sybil Proxy" + } + } + ] +} diff --git a/cmd/pineconesim/sequences/attack_sybil_hub_bootstrap.json b/cmd/pineconesim/sequences/attack_sybil_hub_bootstrap.json new file mode 100644 index 00000000..d176249b --- /dev/null +++ b/cmd/pineconesim/sequences/attack_sybil_hub_bootstrap.json @@ -0,0 +1,893 @@ +{ + "EventSequence": [ + { + "Command": "RemoveNode", + "Data": { + "Name": "m1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m15" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "Sybil Proxy" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h15" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m1", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m2", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m3", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m4", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m5", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m6", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m7", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m8", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m9", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m10", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m11", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m12", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m13", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m14", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m15", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "Sybil Proxy", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h1", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h2", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h3", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h4", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h5", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h6", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h7", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h8", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h9", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h10", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h11", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h12", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h13", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h14", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h15", + "NodeType": "Default" + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m1", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m2", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m3", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m4", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m5", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m6", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m7", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m8", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m9", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m10", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m11", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m12", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m13", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m14", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m15", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h15" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m1" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m15" + } + }, + { + "Command": "Delay", + "Data": { + "Length": 10000 + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "Sybil Proxy" + } + } + ] +} diff --git a/cmd/pineconesim/sequences/attack_sybil_hub_setup.json b/cmd/pineconesim/sequences/attack_sybil_hub_setup.json new file mode 100644 index 00000000..b170cdff --- /dev/null +++ b/cmd/pineconesim/sequences/attack_sybil_hub_setup.json @@ -0,0 +1,893 @@ +{ + "EventSequence": [ + { + "Command": "RemoveNode", + "Data": { + "Name": "m1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m15" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "Sybil Proxy" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h15" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m1", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m2", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m3", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m4", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m5", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m6", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m7", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m8", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m9", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m10", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m11", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m12", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m13", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m14", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m15", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "Sybil Proxy", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h1", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h2", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h3", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h4", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h5", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h6", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h7", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h8", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h9", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h10", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h11", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h12", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h13", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h14", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h15", + "NodeType": "Default" + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m1", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m2", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m3", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m4", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m5", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m6", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m7", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m8", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m9", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m10", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m11", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m12", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m13", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m14", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m15", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h15" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m1" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m15" + } + }, + { + "Command": "Delay", + "Data": { + "Length": 10000 + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "Sybil Proxy" + } + } + ] +} diff --git a/cmd/pineconesim/sequences/attack_sybil_line_bootstrap.json b/cmd/pineconesim/sequences/attack_sybil_line_bootstrap.json new file mode 100644 index 00000000..a9a85b6b --- /dev/null +++ b/cmd/pineconesim/sequences/attack_sybil_line_bootstrap.json @@ -0,0 +1,893 @@ +{ + "EventSequence": [ + { + "Command": "RemoveNode", + "Data": { + "Name": "m1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m15" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "Sybil Proxy" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h15" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m1", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m2", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m3", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m4", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m5", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m6", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m7", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m8", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m9", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m10", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m11", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m12", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m13", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m14", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m15", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "Sybil Proxy", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h1", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h2", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h3", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h4", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h5", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h6", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h7", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h8", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h9", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h10", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h11", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h12", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h13", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h14", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h15", + "NodeType": "Default" + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m1", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m2", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m3", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m4", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m5", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m6", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m7", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m8", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m9", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m10", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m11", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m12", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m13", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m14", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m15", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "100", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "0", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h2", + "Peer": "h3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h3", + "Peer": "h4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h4", + "Peer": "h5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h5", + "Peer": "h6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h6", + "Peer": "h7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h7", + "Peer": "h8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h8", + "Peer": "h9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h9", + "Peer": "h10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h10", + "Peer": "h11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h11", + "Peer": "h12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h12", + "Peer": "h13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h13", + "Peer": "h14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h14", + "Peer": "h15" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m1" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m15" + } + }, + { + "Command": "Delay", + "Data": { + "Length": 10000 + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "Sybil Proxy" + } + } + ] +} diff --git a/cmd/pineconesim/sequences/attack_sybil_line_setup.json b/cmd/pineconesim/sequences/attack_sybil_line_setup.json new file mode 100644 index 00000000..af0b56ce --- /dev/null +++ b/cmd/pineconesim/sequences/attack_sybil_line_setup.json @@ -0,0 +1,893 @@ +{ + "EventSequence": [ + { + "Command": "RemoveNode", + "Data": { + "Name": "m1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "m15" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "Sybil Proxy" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h1" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h2" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h3" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h4" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h5" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h6" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h7" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h8" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h9" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h10" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h11" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h12" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h13" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h14" + } + }, + { + "Command": "RemoveNode", + "Data": { + "Name": "h15" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m1", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m2", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m3", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m4", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m5", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m6", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m7", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m8", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m9", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m10", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m11", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m12", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m13", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m14", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "m15", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "Sybil Proxy", + "NodeType": "GeneralAdversary" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h1", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h2", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h3", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h4", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h5", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h6", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h7", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h8", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h9", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h10", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h11", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h12", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h13", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h14", + "NodeType": "Default" + } + }, + { + "Command": "AddNode", + "Data": { + "Name": "h15", + "NodeType": "Default" + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m1", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m2", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m3", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m4", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m5", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m6", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m7", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m8", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m9", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m10", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m11", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m12", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m13", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m14", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "ConfigureAdversaryDefaults", + "Data": { + "Node": "m15", + "DropRates": { + "Overall": "0", + "Keepalive": "0", + "TreeAnnouncement": "0", + "TreeRouted": "0", + "VirtualSnakeBootstrap": "0", + "VirtualSnakeBootstrapACK": "0", + "VirtualSnakeSetup": "100", + "VirtualSnakeSetupACK": "0", + "VirtualSnakeTeardown": "0", + "VirtualSnakeRouted": "0" + } + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "h2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h2", + "Peer": "h3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h3", + "Peer": "h4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h4", + "Peer": "h5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h5", + "Peer": "h6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h6", + "Peer": "h7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h7", + "Peer": "h8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h8", + "Peer": "h9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h9", + "Peer": "h10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h10", + "Peer": "h11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h11", + "Peer": "h12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h12", + "Peer": "h13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h13", + "Peer": "h14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h14", + "Peer": "h15" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m1" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m2" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m3" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m4" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m5" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m6" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m7" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m8" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m9" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m10" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m11" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m12" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m13" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m14" + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "Sybil Proxy", + "Peer": "m15" + } + }, + { + "Command": "Delay", + "Data": { + "Length": 10000 + } + }, + { + "Command": "AddPeer", + "Data": { + "Node": "h1", + "Peer": "Sybil Proxy" + } + } + ] +} From e071b42f16477de3507987322872eec080966ada Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Tue, 26 Apr 2022 12:49:44 -0500 Subject: [PATCH 35/36] Disable peer scoring by default behind a feature flag --- .../simulator/adversary/drop_packets.go | 1 - router/router.go | 13 +- router/state.go | 25 +- router/state_snek.go | 316 +++++++++--------- 4 files changed, 196 insertions(+), 159 deletions(-) diff --git a/cmd/pineconesim/simulator/adversary/drop_packets.go b/cmd/pineconesim/simulator/adversary/drop_packets.go index 374dad7d..4e3044e7 100644 --- a/cmd/pineconesim/simulator/adversary/drop_packets.go +++ b/cmd/pineconesim/simulator/adversary/drop_packets.go @@ -123,7 +123,6 @@ func NewAdversaryRouter(log *log.Logger, sk ed25519.PrivateKey, debug bool) *Adv } rtr.InjectPacketFilter(adversary.selectivelyDrop) - rtr.DisablePeerScoring() return adversary } diff --git a/router/router.go b/router/router.go index 98e1027b..de2e6dfa 100644 --- a/router/router.go +++ b/router/router.go @@ -66,7 +66,7 @@ func NewRouter(logger *log.Logger, sk ed25519.PrivateKey, debug bool) *Router { cancel: cancel, secure: !insecure, _subscribers: make(map[chan<- events.Event]*phony.Inbox), - scorePeers: true, + scorePeers: false, } // Populate the node keys from the supplied private key. copy(r.private[:], sk) @@ -87,8 +87,15 @@ func NewRouter(logger *log.Logger, sk ed25519.PrivateKey, debug bool) *Router { return r } -func (r *Router) DisablePeerScoring() { - r.scorePeers = false +func (r *Router) EnablePeerScoring() { + r.scorePeers = true + r.state.assignResetNeglectTimer() + + for _, p := range r.state._peers { + if p != nil { + r.state.assignPeerScoreAccumulatorTimer(p) + } + } } func (r *Router) InjectPacketFilter(fn FilterFn) { diff --git a/router/state.go b/router/state.go index d5481156..65965b8d 100644 --- a/router/state.go +++ b/router/state.go @@ -88,14 +88,28 @@ func (s *state) _start() { }) } + if s.r.scorePeers { + s.assignResetNeglectTimer() + } + + s._maintainTreeIn(0) + s._maintainSnakeIn(0) +} + +func (s *state) assignResetNeglectTimer() { if s._neglectReset == nil { s._neglectReset = time.AfterFunc(0, func() { s.Act(nil, s._resetNeglectedNodes) }) } +} - s._maintainTreeIn(0) - s._maintainSnakeIn(0) +func (s *state) assignPeerScoreAccumulatorTimer(p *peer) { + if p.peerScoreAccumulator == nil { + p.peerScoreAccumulator = time.AfterFunc(peerScoreResetPeriod, func() { + s.Act(nil, func() { s._accumulatePeerScore(p) }) + }) + } } func (s *state) _resetNeglectedNodes() { @@ -161,10 +175,9 @@ func (s *state) _addPeer(conn net.Conn, public types.PublicKey, uri ConnectionUR traffic: newFairFIFOQueue(trafficBuffer), peerScoreAccumulator: nil, } - if new.peerScoreAccumulator == nil { - new.peerScoreAccumulator = time.AfterFunc(peerScoreResetPeriod, func() { - s.Act(nil, func() { s._accumulatePeerScore(new) }) - }) + + if s.r.scorePeers { + s.assignPeerScoreAccumulatorTimer(new) } s._peers[i] = new diff --git a/router/state_snek.go b/router/state_snek.go index 028909e4..15e6e811 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -178,8 +178,10 @@ func (s *state) _bootstrapNow() { b := frameBufferPool.Get().(*[types.MaxFrameSize]byte) defer frameBufferPool.Put(b) failing := byte(0) - if s._bootstrapAttempt > neglectedNodeTrackingPoint { - failing = 1 + if s.r.scorePeers { + if s._bootstrapAttempt > neglectedNodeTrackingPoint { + failing = 1 + } } bootstrap := types.VirtualSnakeBootstrap{ Root: ann.Root, @@ -217,10 +219,13 @@ func (s *state) _bootstrapNow() { // bootstrap packets. if p := s._nextHopsSNEK(send, true); p != nil && p.proto != nil { p.proto.push(send) - s._bootstrapAttempt++ - if s._bootstrapAttempt >= bootstrapAttemptResetPoint { - s._bootstrapAttempt = 0 - s.r.log.Println("Resetting bootstrap attempt count") + + if s.r.scorePeers { + s._bootstrapAttempt++ + if s._bootstrapAttempt >= bootstrapAttemptResetPoint { + s._bootstrapAttempt = 0 + s.r.log.Println("Resetting bootstrap attempt count") + } } } } @@ -379,93 +384,95 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea if !deadend { var frame *types.Frame = nil - // NOTE : Only add additional signatures if the node is struggling - if bootstrap.Failing > 0 { - trackBootstrap := false - if node, ok := s._neglectedNodes[rx.DestinationKey]; ok { - trackBootstrap = true - if uint64(len(bootstrap.Signatures)) > node.HopCount { - node.HopCount = uint64(len(bootstrap.Signatures)) - } - } else { - if len(s._neglectedNodes) < maxNeglectedNodesToTrack { + if s.r.scorePeers { + // NOTE : Only add additional signatures if the node is struggling + if bootstrap.Failing > 0 { + trackBootstrap := false + if node, ok := s._neglectedNodes[rx.DestinationKey]; ok { trackBootstrap = true - entry := &neglectedNodeEntry{ - HopCount: uint64(len(bootstrap.Signatures)), - FailedBootstraps: make(neglectedBootstrapTable), - FailedSetups: make(neglectedSetupTable), + if uint64(len(bootstrap.Signatures)) > node.HopCount { + node.HopCount = uint64(len(bootstrap.Signatures)) } - s._neglectedNodes[rx.DestinationKey] = entry } else { - for key, node := range s._neglectedNodes { - replaceNode := false - - latestArrival := time.UnixMicro(0) - for _, info := range node.FailedBootstraps { - if info.ArrivalTime.After(latestArrival) { - latestArrival = info.ArrivalTime - } + if len(s._neglectedNodes) < maxNeglectedNodesToTrack { + trackBootstrap = true + entry := &neglectedNodeEntry{ + HopCount: uint64(len(bootstrap.Signatures)), + FailedBootstraps: make(neglectedBootstrapTable), + FailedSetups: make(neglectedSetupTable), } - for _, info := range node.FailedSetups { - if info.ArrivalTime.After(latestArrival) { - latestArrival = info.ArrivalTime + s._neglectedNodes[rx.DestinationKey] = entry + } else { + for key, node := range s._neglectedNodes { + replaceNode := false + + latestArrival := time.UnixMicro(0) + for _, info := range node.FailedBootstraps { + if info.ArrivalTime.After(latestArrival) { + latestArrival = info.ArrivalTime + } + } + for _, info := range node.FailedSetups { + if info.ArrivalTime.After(latestArrival) { + latestArrival = info.ArrivalTime + } } - } - if len(bootstrap.Signatures) > int(node.HopCount) { - replaceNode = true - } else if time.Since(latestArrival) > staleInformationPeriod { - // NOTE : This is to prevent attackers from filling the neglected - // node list with artificially high hop counts then not continuing - // to send frames. - s.r.log.Println("Replace stale node") - replaceNode = true - } + if len(bootstrap.Signatures) > int(node.HopCount) { + replaceNode = true + } else if time.Since(latestArrival) > staleInformationPeriod { + // NOTE : This is to prevent attackers from filling the neglected + // node list with artificially high hop counts then not continuing + // to send frames. + s.r.log.Println("Replace stale node") + replaceNode = true + } - if replaceNode { - trackBootstrap = true - // NOTE : Reverse path routing guarantees still exist in this case. - // We just don't track this node for peer scoring purposes anymore. - cachePeerScoreHistory(node) - delete(s._neglectedNodes, key) - entry := &neglectedNodeEntry{ - HopCount: uint64(len(bootstrap.Signatures)), - FailedBootstraps: make(neglectedBootstrapTable), - FailedSetups: make(neglectedSetupTable), + if replaceNode { + trackBootstrap = true + // NOTE : Reverse path routing guarantees still exist in this case. + // We just don't track this node for peer scoring purposes anymore. + cachePeerScoreHistory(node) + delete(s._neglectedNodes, key) + entry := &neglectedNodeEntry{ + HopCount: uint64(len(bootstrap.Signatures)), + FailedBootstraps: make(neglectedBootstrapTable), + FailedSetups: make(neglectedSetupTable), + } + s._neglectedNodes[rx.DestinationKey] = entry + break } - s._neglectedNodes[rx.DestinationKey] = entry - break } } } - } - if trackBootstrap { - s._neglectedNodes[rx.DestinationKey].FailedBootstraps[bootstrap.PathID] = &neglectedBootstrapData{ - Acknowledged: false, - ArrivalTime: time.Now(), - Prev: from, - Next: nexthop, + if trackBootstrap { + s._neglectedNodes[rx.DestinationKey].FailedBootstraps[bootstrap.PathID] = &neglectedBootstrapData{ + Acknowledged: false, + ArrivalTime: time.Now(), + Prev: from, + Next: nexthop, + } } - } - score := nexthop.EvaluatePeerScore(s._neglectedNodes) - if score <= lowScoreThreshold { - if s.r.scorePeers { - nexthop.stop(fmt.Errorf("peer score below threshold: %d", score)) + score := nexthop.EvaluatePeerScore(s._neglectedNodes) + if score <= lowScoreThreshold { + if s.r.scorePeers { + nexthop.stop(fmt.Errorf("peer score below threshold: %d", score)) + } } - } - longestHopCount := 1 - for _, node := range s._neglectedNodes { - if node.HopCount > uint64(longestHopCount) { - longestHopCount = int(node.HopCount) + longestHopCount := 1 + for _, node := range s._neglectedNodes { + if node.HopCount > uint64(longestHopCount) { + longestHopCount = int(node.HopCount) + } } - } - s._neglectReset.Reset(peerScoreResetPeriod) - if nexthop.started.Load() { - nexthop.peerScoreAccumulator.Reset(peerScoreResetPeriod) + s._neglectReset.Reset(peerScoreResetPeriod) + if nexthop.started.Load() { + nexthop.peerScoreAccumulator.Reset(peerScoreResetPeriod) + } } if s.r.public.CompareTo(rx.DestinationKey) > 0 { @@ -476,8 +483,10 @@ func (s *state) _handleBootstrap(from *peer, rx *types.Frame, nexthop *peer, dea // bootstrap candidate. We are a candidate so sign the bootstrap. fallthrough case s.r.public.CompareTo(bootstrap.Signatures[len(bootstrap.Signatures)-1].PublicKey) < 0: - if err := bootstrap.Sign(s.r.private[:]); err != nil { - return fmt.Errorf("failed signing bootstrap: %w", err) + if s.r.scorePeers { + if err := bootstrap.Sign(s.r.private[:]); err != nil { + return fmt.Errorf("failed signing bootstrap: %w", err) + } } frame = getFrame() frame.Type = types.TypeVirtualSnakeBootstrap @@ -583,32 +592,34 @@ func (s *state) _handleBootstrapACK(from *peer, rx *types.Frame, nexthop *peer, if !deadend { knownFailure := false - if node, ok := s._neglectedNodes[rx.SourceKey]; ok { - if data, ok := node.FailedSetups[bootstrapACK.PathID]; ok { - if data.Acknowledged { - // NOTE : This peer is sending us duplicate frames. - return nil - } + if s.r.scorePeers { + if node, ok := s._neglectedNodes[rx.SourceKey]; ok { + if data, ok := node.FailedSetups[bootstrapACK.PathID]; ok { + if data.Acknowledged { + // NOTE : This peer is sending us duplicate frames. + return nil + } - knownFailure = true - data.Prev.send(rx) - data.Acknowledged = true - score := data.Prev.EvaluatePeerScore(s._neglectedNodes) - if score <= lowScoreThreshold { - if s.r.scorePeers { - data.Prev.stop(fmt.Errorf("peer score below threshold: %d", score)) + knownFailure = true + data.Prev.send(rx) + data.Acknowledged = true + score := data.Prev.EvaluatePeerScore(s._neglectedNodes) + if score <= lowScoreThreshold { + if s.r.scorePeers { + data.Prev.stop(fmt.Errorf("peer score below threshold: %d", score)) + } } - } - longestHopCount := 1 - for _, node := range s._neglectedNodes { - if node.HopCount > uint64(longestHopCount) { - longestHopCount = int(node.HopCount) + longestHopCount := 1 + for _, node := range s._neglectedNodes { + if node.HopCount > uint64(longestHopCount) { + longestHopCount = int(node.HopCount) + } + } + s._neglectReset.Reset(peerScoreResetPeriod) + if data.Prev.started.Load() { + data.Prev.peerScoreAccumulator.Reset(peerScoreResetPeriod) } - } - s._neglectReset.Reset(peerScoreResetPeriod) - if data.Prev.started.Load() { - data.Prev.peerScoreAccumulator.Reset(peerScoreResetPeriod) } } } @@ -801,40 +812,42 @@ func (s *state) _handleSetup(from *peer, rx *types.Frame, nexthop *peer) error { return nil } - // NOTE : If you only see setups and not bootstraps then you can conclude that you aren't - // attached to a malicious peer. Otherwise you would have been guaranteed to also see the - // corresponding bootstrap frames from that node. - // Other than this case, there are no firm conclusions that can be drawn. - if node, ok := s._neglectedNodes[rx.SourceKey]; ok { - if nexthop != nil { - if _, ok := node.FailedSetups[setup.PathID]; ok { - // NOTE : This peer is sending us duplicate frames. - return nil - } + if s.r.scorePeers { + // NOTE : If you only see setups and not bootstraps then you can conclude that you aren't + // attached to a malicious peer. Otherwise you would have been guaranteed to also see the + // corresponding bootstrap frames from that node. + // Other than this case, there are no firm conclusions that can be drawn. + if node, ok := s._neglectedNodes[rx.SourceKey]; ok { + if nexthop != nil { + if _, ok := node.FailedSetups[setup.PathID]; ok { + // NOTE : This peer is sending us duplicate frames. + return nil + } - node.FailedSetups[setup.PathID] = &neglectedSetupData{ - Acknowledged: false, - ArrivalTime: time.Now(), - Prev: from, - Next: nexthop, - } + node.FailedSetups[setup.PathID] = &neglectedSetupData{ + Acknowledged: false, + ArrivalTime: time.Now(), + Prev: from, + Next: nexthop, + } - score := nexthop.EvaluatePeerScore(s._neglectedNodes) - if score <= lowScoreThreshold { - if s.r.scorePeers { - nexthop.stop(fmt.Errorf("peer score below threshold: %d", score)) + score := nexthop.EvaluatePeerScore(s._neglectedNodes) + if score <= lowScoreThreshold { + if s.r.scorePeers { + nexthop.stop(fmt.Errorf("peer score below threshold: %d", score)) + } } - } - longestHopCount := 1 - for _, node := range s._neglectedNodes { - if node.HopCount > uint64(longestHopCount) { - longestHopCount = int(node.HopCount) + longestHopCount := 1 + for _, node := range s._neglectedNodes { + if node.HopCount > uint64(longestHopCount) { + longestHopCount = int(node.HopCount) + } + } + s._neglectReset.Reset(peerScoreResetPeriod) + if nexthop.started.Load() { + nexthop.peerScoreAccumulator.Reset(peerScoreResetPeriod) } - } - s._neglectReset.Reset(peerScoreResetPeriod) - if nexthop.started.Load() { - nexthop.peerScoreAccumulator.Reset(peerScoreResetPeriod) } } } @@ -974,29 +987,31 @@ func (s *state) _handleSetupACK(from *peer, rx *types.Frame, nexthop *peer) erro continue } if v.Source.local() || v.Source.send(rx) { - if node, ok := s._neglectedNodes[rx.SourceKey]; ok { - if data, ok := node.FailedSetups[setup.PathID]; ok { - if data.Acknowledged { - // NOTE : This peer is sending us duplicate frames. - continue - } - data.Acknowledged = true - score := data.Prev.EvaluatePeerScore(s._neglectedNodes) - if score <= lowScoreThreshold { - if s.r.scorePeers { - data.Prev.stop(fmt.Errorf("peer score below threshold: %d", score)) + if s.r.scorePeers { + if node, ok := s._neglectedNodes[rx.SourceKey]; ok { + if data, ok := node.FailedSetups[setup.PathID]; ok { + if data.Acknowledged { + // NOTE : This peer is sending us duplicate frames. + continue + } + data.Acknowledged = true + score := data.Prev.EvaluatePeerScore(s._neglectedNodes) + if score <= lowScoreThreshold { + if s.r.scorePeers { + data.Prev.stop(fmt.Errorf("peer score below threshold: %d", score)) + } } - } - longestHopCount := 1 - for _, node := range s._neglectedNodes { - if node.HopCount > uint64(longestHopCount) { - longestHopCount = int(node.HopCount) + longestHopCount := 1 + for _, node := range s._neglectedNodes { + if node.HopCount > uint64(longestHopCount) { + longestHopCount = int(node.HopCount) + } + } + s._neglectReset.Reset(peerScoreResetPeriod) + if data.Prev.started.Load() { + data.Prev.peerScoreAccumulator.Reset(peerScoreResetPeriod) } - } - s._neglectReset.Reset(peerScoreResetPeriod) - if data.Prev.started.Load() { - data.Prev.peerScoreAccumulator.Reset(peerScoreResetPeriod) } } } @@ -1005,7 +1020,10 @@ func (s *state) _handleSetupACK(from *peer, rx *types.Frame, nexthop *peer) erro if v == s._candidate { s._setAscendingNode(v) s._candidate = nil - s._bootstrapAttempt = 0 + + if s.r.scorePeers { + s._bootstrapAttempt = 0 + } } } } From f7a7f0d982466e30bcfa178d5dc53a97fdcdb9c9 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Tue, 26 Apr 2022 13:44:18 -0500 Subject: [PATCH 36/36] Cleanup unused code --- router/state_snek.go | 4 +--- router/state_tree.go | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/router/state_snek.go b/router/state_snek.go index 26254b41..70ed9864 100644 --- a/router/state_snek.go +++ b/router/state_snek.go @@ -200,9 +200,7 @@ func (s *state) _bootstrapNow() { ed25519.Sign(s.r.private[:], append(s.r.public[:], bootstrap.PathID[:]...)), ) } - // if err := bootstrap.Sign(s.r.private[:]); err != nil { - // return - // } + n, err := bootstrap.MarshalBinary(b[:]) if err != nil { return diff --git a/router/state_tree.go b/router/state_tree.go index fbe118ca..30edb39c 100644 --- a/router/state_tree.go +++ b/router/state_tree.go @@ -341,11 +341,8 @@ func (s *state) _handleTreeAnnouncement(p *peer, f *types.Frame) error { case AcceptNewParent: s._setParent(p) s._sendTreeAnnouncements() - // why not bootstrap here as well? - // s._bootstrapNow() case SelectNewParent: if s._selectNewParent() { - // why bootstrap here as well when we can wait for snek maintenance s._bootstrapNow() } case SelectNewParentWithWait: @@ -356,7 +353,6 @@ func (s *state) _handleTreeAnnouncement(p *peer, f *types.Frame) error { s.Act(nil, func() { s._waiting = false if s._selectNewParent() { - // why bootstrap here as well when we can wait for snek maintenance s._bootstrapNow() } })