Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial peer scoring implementation to detect problematic nodes #36

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ac952ea
Add basic integration test using the simulator
devonh Jan 28, 2022
7b02958
Add basic integration test for snake neighbours
devonh Jan 28, 2022
3a055b0
Fix event api race condition when first subscribing to a node
devonh Jan 29, 2022
51e0672
Move integration tests to live alongside router code
devonh Jan 29, 2022
1d8ddb6
Refactor integration tests to be able to reuse validation functions
devonh Jan 29, 2022
f40c3d1
Add simple adversary integration tests
devonh Jan 30, 2022
1eabba5
Annotate integration test functions with param names
devonh Jan 30, 2022
54033d7
Add null events to sim command interface
devonh Jan 31, 2022
f153699
Add ping/pong packet types to drop packets filter
devonh Jan 31, 2022
61d48ce
Update router filter logging for more details
devonh Jan 31, 2022
4f9086f
Don't update sim graph if not being used
devonh Jan 31, 2022
67e9c1f
Add logging on successful sim snek ping/pong
devonh Jan 31, 2022
58555a2
Add adversarial integration tests
devonh Jan 31, 2022
a7133f7
Allow for running specific test cases since using run isn't working
devonh Feb 10, 2022
d0b3cd8
Rename adversary test to better align with new test parameters
devonh Feb 10, 2022
02a66ca
Fix bug where bootstrap ack was handled incorrectly (#31)
devonh Feb 23, 2022
32cd2d9
Fix buffer size checks for un/marshal snake bootstrap (#32)
devonh Feb 24, 2022
70b73bb
Initial prototype to assist nodes failing to bootstrap
devonh Feb 26, 2022
b01af6d
Test out peer scoring on bootstrap setup frames
devonh Mar 8, 2022
23b411b
Add peer scoring to failing bootstrap frames
devonh Mar 8, 2022
3a583ce
Rearrange bootstrap ack signature verification
devonh Mar 8, 2022
624c122
Limit number of neglected nodes being tracked
devonh Mar 9, 2022
be09fc3
Fix scoring for acknowledged bootstrap frames
devonh Mar 9, 2022
55dcc7d
Remove exploit due to manipulating bootstrap attempt count
devonh Mar 9, 2022
16648c4
Add ack settling time to prevent preemptive peer scoring
devonh Mar 9, 2022
a6578b7
Cleanup peer scoring reset call sites
devonh Mar 9, 2022
03f3fc8
Swap attempt cout for single failing byte
devonh Mar 22, 2022
b5de1e3
Update snake frame size check
devonh Mar 23, 2022
9a8eba7
Switch to per peer score accumulation
devonh Mar 24, 2022
244d399
Silently drop duplicate frames with bootstrap failures
devonh Mar 26, 2022
06f1be7
Ensure peer is still running before resetting the score accumulator
devonh Mar 26, 2022
fd97796
Don't crash simulator when root node cannot be interpreted
devonh Mar 26, 2022
ea63fa2
Add back in logging when negative peer scoring is being applied
devonh Mar 26, 2022
b596ff7
Add simulator sequences for testing sybil attacks
devonh Mar 26, 2022
8a41db8
Merge main into resilient-snek
devonh Mar 26, 2022
a4fbd6c
Merge branch 'main' into resilient-snek
neilalexander Mar 29, 2022
e071b42
Disable peer scoring by default behind a feature flag
devonh Apr 26, 2022
8225a1b
Merge remote-tracking branch 'origin/main' into resilient-snek
devonh Apr 26, 2022
f7a7f0d
Cleanup unused code
devonh Apr 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
976 changes: 976 additions & 0 deletions cmd/pineconesim/sequences/attack_sybil_grid_bootstrap.json

Large diffs are not rendered by default.

976 changes: 976 additions & 0 deletions cmd/pineconesim/sequences/attack_sybil_grid_setup.json

Large diffs are not rendered by default.

893 changes: 893 additions & 0 deletions cmd/pineconesim/sequences/attack_sybil_hub_bootstrap.json

Large diffs are not rendered by default.

893 changes: 893 additions & 0 deletions cmd/pineconesim/sequences/attack_sybil_hub_setup.json

Large diffs are not rendered by default.

893 changes: 893 additions & 0 deletions cmd/pineconesim/sequences/attack_sybil_line_bootstrap.json

Large diffs are not rendered by default.

893 changes: 893 additions & 0 deletions cmd/pineconesim/sequences/attack_sybil_line_setup.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions cmd/pineconesim/simulator/adversary/drop_packets.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,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,
Expand Down Expand Up @@ -123,8 +127,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 {
Expand Down
30 changes: 25 additions & 5 deletions cmd/pineconesim/simulator/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package simulator

import (
"crypto/ed25519"
"fmt"
"log"
"strconv"
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions cmd/pineconesim/simulator/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions cmd/pineconesim/simulator/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
21 changes: 13 additions & 8 deletions cmd/pineconesim/simulator/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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,
}
Expand Down Expand Up @@ -118,14 +123,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) {
Expand Down
1 change: 1 addition & 0 deletions cmd/pineconesim/simulator/pathfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
6 changes: 3 additions & 3 deletions cmd/pineconesim/simulator/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,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)
Expand All @@ -41,8 +41,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 {
Expand Down
3 changes: 3 additions & 0 deletions cmd/pineconesim/simulator/simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ func (sim *Simulator) handleTreeRootAnnUpdate(node string, root string, sequence
rootName := ""
if peerNode, err := sim.State.GetNodeName(root); err == nil {
rootName = peerNode
} else {
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) })
}
Expand Down
57 changes: 49 additions & 8 deletions cmd/pineconesim/simulator/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"reflect"

"github.com/Arceliar/phony"
"github.com/matrix-org/pinecone/router"
)

type RootAnnouncement struct {
Expand Down Expand Up @@ -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
}

Expand All @@ -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)})
}

Expand Down
55 changes: 54 additions & 1 deletion router/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading