diff --git a/allowlist.go b/allowlist.go new file mode 100644 index 0000000..fd72af0 --- /dev/null +++ b/allowlist.go @@ -0,0 +1,215 @@ +package rcmgr + +import ( + "bytes" + "errors" + "fmt" + "net" + "sync" + + "github.com/libp2p/go-libp2p-core/peer" + "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" +) + +type Allowlist struct { + mu sync.RWMutex + // a simple structure of lists of networks. There is probably a faster way + // to check if an IP address is in this network than iterating over this + // list, but this is good enough for small numbers of networks (<1_000). + // Analyze the benchmark before trying to optimize this. + + // Any peer with these IPs are allowed + allowedNetworks []*net.IPNet + + // Only the specified peers can use these IPs + allowedPeerByNetwork map[peer.ID][]*net.IPNet +} + +// WithAllowlistedMultiaddrs sets the multiaddrs to be in the allowlist +func WithAllowlistedMultiaddrs(mas []multiaddr.Multiaddr) Option { + return func(rm *resourceManager) error { + for _, ma := range mas { + err := rm.allowlist.Add(ma) + if err != nil { + return err + } + } + return nil + } +} + +func newAllowlist() Allowlist { + return Allowlist{ + allowedPeerByNetwork: make(map[peer.ID][]*net.IPNet), + } +} + +func toIPNet(ma multiaddr.Multiaddr) (*net.IPNet, peer.ID, error) { + var ipString string + var mask string + var allowedPeerStr string + var allowedPeer peer.ID + var isIPV4 bool + + multiaddr.ForEach(ma, func(c multiaddr.Component) bool { + if c.Protocol().Code == multiaddr.P_IP4 || c.Protocol().Code == multiaddr.P_IP6 { + isIPV4 = c.Protocol().Code == multiaddr.P_IP4 + ipString = c.Value() + } + if c.Protocol().Code == multiaddr.P_IPCIDR { + mask = c.Value() + } + if c.Protocol().Code == multiaddr.P_P2P { + allowedPeerStr = c.Value() + } + return ipString == "" || mask == "" || allowedPeerStr == "" + }) + + if ipString == "" { + return nil, allowedPeer, errors.New("missing ip address") + } + + if allowedPeerStr != "" { + var err error + allowedPeer, err = peer.Decode(allowedPeerStr) + if err != nil { + return nil, allowedPeer, fmt.Errorf("failed to decode allowed peer: %w", err) + } + } + + if mask == "" { + ip := net.ParseIP(ipString) + if ip == nil { + return nil, allowedPeer, errors.New("invalid ip address") + } + var mask net.IPMask + if isIPV4 { + mask = net.CIDRMask(32, 32) + } else { + mask = net.CIDRMask(128, 128) + } + + net := &net.IPNet{IP: ip, Mask: mask} + return net, allowedPeer, nil + } + + _, ipnet, err := net.ParseCIDR(ipString + "/" + mask) + return ipnet, allowedPeer, err + +} + +// Add takes a multiaddr and adds it to the allowlist. The multiaddr should be +// an ip address of the peer with or without a `/p2p` protocol. +// e.g. /ip4/1.2.3.4/p2p/QmFoo, /ip4/1.2.3.4, and /ip4/1.2.3.0/ipcidr/24 are valid. +// /p2p/QmFoo is not valid. +func (al *Allowlist) Add(ma multiaddr.Multiaddr) error { + ipnet, allowedPeer, err := toIPNet(ma) + if err != nil { + return err + } + al.mu.Lock() + defer al.mu.Unlock() + + if allowedPeer != peer.ID("") { + // We have a peerID constraint + if al.allowedPeerByNetwork == nil { + al.allowedPeerByNetwork = make(map[peer.ID][]*net.IPNet) + } + al.allowedPeerByNetwork[allowedPeer] = append(al.allowedPeerByNetwork[allowedPeer], ipnet) + } else { + al.allowedNetworks = append(al.allowedNetworks, ipnet) + } + return nil +} + +func (al *Allowlist) Remove(ma multiaddr.Multiaddr) error { + ipnet, allowedPeer, err := toIPNet(ma) + if err != nil { + return err + } + al.mu.Lock() + defer al.mu.Unlock() + + ipNetList := al.allowedNetworks + + if allowedPeer != "" { + // We have a peerID constraint + ipNetList = al.allowedPeerByNetwork[allowedPeer] + } + + if ipNetList == nil { + return nil + } + + i := len(ipNetList) + for i > 0 { + i-- + if ipNetList[i].IP.Equal(ipnet.IP) && bytes.Equal(ipNetList[i].Mask, ipnet.Mask) { + // swap remove + ipNetList[i] = ipNetList[len(ipNetList)-1] + ipNetList = ipNetList[:len(ipNetList)-1] + // We only remove one thing + break + } + } + + if allowedPeer != "" { + al.allowedPeerByNetwork[allowedPeer] = ipNetList + } else { + al.allowedNetworks = ipNetList + } + + return nil +} + +func (al *Allowlist) Allowed(ma multiaddr.Multiaddr) bool { + ip, err := manet.ToIP(ma) + if err != nil { + return false + } + al.mu.RLock() + defer al.mu.RUnlock() + + for _, network := range al.allowedNetworks { + if network.Contains(ip) { + return true + } + } + + for _, allowedNetworks := range al.allowedPeerByNetwork { + for _, network := range allowedNetworks { + if network.Contains(ip) { + return true + } + } + } + + return false +} + +func (al *Allowlist) AllowedPeerAndMultiaddr(peerID peer.ID, ma multiaddr.Multiaddr) bool { + ip, err := manet.ToIP(ma) + if err != nil { + return false + } + al.mu.RLock() + defer al.mu.RUnlock() + + for _, network := range al.allowedNetworks { + if network.Contains(ip) { + // We found a match that isn't constrained by a peerID + return true + } + } + + if expectedNetworks, ok := al.allowedPeerByNetwork[peerID]; ok { + for _, expectedNetwork := range expectedNetworks { + if expectedNetwork.Contains(ip) { + return true + } + } + } + + return false +} diff --git a/allowlist_test.go b/allowlist_test.go new file mode 100644 index 0000000..7f5267e --- /dev/null +++ b/allowlist_test.go @@ -0,0 +1,264 @@ +package rcmgr + +import ( + "crypto/rand" + "fmt" + "net" + "testing" + + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/test" + "github.com/multiformats/go-multiaddr" +) + +func TestAllowedSimple(t *testing.T) { + allowlist := newAllowlist() + ma := multiaddr.StringCast("/ip4/1.2.3.4/tcp/1234") + err := allowlist.Add(ma) + if err != nil { + t.Fatalf("failed to add ip4: %s", err) + } + + if !allowlist.Allowed(ma) { + t.Fatalf("addr should be allowed") + } +} + +func TestAllowedWithPeer(t *testing.T) { + type testcase struct { + name string + allowlist []string + endpoint multiaddr.Multiaddr + peer peer.ID + // Is this endpoint allowed? (We don't have peer info yet) + isConnAllowed bool + // Is this peer + endpoint allowed? + isAllowedWithPeer bool + } + + peerA := test.RandPeerIDFatal(t) + peerB := test.RandPeerIDFatal(t) + multiaddrA := multiaddr.StringCast("/ip4/1.2.3.4/tcp/1234") + multiaddrB := multiaddr.StringCast("/ip4/2.2.3.4/tcp/1234") + + testcases := []testcase{ + { + name: "Blocked", + isConnAllowed: false, + isAllowedWithPeer: false, + allowlist: []string{"/ip4/1.2.3.1"}, + endpoint: multiaddrA, + peer: peerA, + }, + { + name: "Blocked wrong peer", + isConnAllowed: true, + isAllowedWithPeer: false, + allowlist: []string{"/ip4/1.2.3.4" + "/p2p/" + peer.Encode(peerB)}, + endpoint: multiaddrA, + peer: peerA, + }, + { + name: "allowed on network", + isConnAllowed: true, + isAllowedWithPeer: true, + allowlist: []string{"/ip4/1.2.3.0/ipcidr/24"}, + endpoint: multiaddrA, + peer: peerA, + }, + { + name: "Blocked peer not on network", + isConnAllowed: true, + isAllowedWithPeer: true, + allowlist: []string{"/ip4/1.2.3.0/ipcidr/24"}, + endpoint: multiaddrA, + peer: peerA, + }, { + name: "allowed. right network, right peer", + isConnAllowed: true, + isAllowedWithPeer: true, + allowlist: []string{"/ip4/1.2.3.0/ipcidr/24" + "/p2p/" + peer.Encode(peerA)}, + endpoint: multiaddrA, + peer: peerA, + }, { + name: "allowed. right network, no peer", + isConnAllowed: true, + isAllowedWithPeer: true, + allowlist: []string{"/ip4/1.2.3.0/ipcidr/24"}, + endpoint: multiaddrA, + peer: peerA, + }, + { + name: "Blocked. right network, wrong peer", + isConnAllowed: true, + isAllowedWithPeer: false, + allowlist: []string{"/ip4/1.2.3.0/ipcidr/24" + "/p2p/" + peer.Encode(peerB)}, + endpoint: multiaddrA, + peer: peerA, + }, + { + name: "allowed peer any ip", + isConnAllowed: true, + isAllowedWithPeer: true, + allowlist: []string{"/ip4/0.0.0.0/ipcidr/0"}, + endpoint: multiaddrA, + peer: peerA, + }, + { + name: "allowed peer multiple ips in allowlist", + isConnAllowed: true, + isAllowedWithPeer: true, + allowlist: []string{"/ip4/1.2.3.4/p2p/" + peer.Encode(peerA), "/ip4/2.2.3.4/p2p/" + peer.Encode(peerA)}, + endpoint: multiaddrA, + peer: peerA, + }, + { + name: "allowed peer multiple ips in allowlist", + isConnAllowed: true, + isAllowedWithPeer: true, + allowlist: []string{"/ip4/1.2.3.4/p2p/" + peer.Encode(peerA), "/ip4/1.2.3.4/p2p/" + peer.Encode(peerA)}, + endpoint: multiaddrA, + peer: peerA, + }, + { + name: "allowed peer multiple ips in allowlist", + isConnAllowed: true, + isAllowedWithPeer: true, + allowlist: []string{"/ip4/1.2.3.4/p2p/" + peer.Encode(peerA), "/ip4/2.2.3.4/p2p/" + peer.Encode(peerA)}, + endpoint: multiaddrB, + peer: peerA, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + allowlist := newAllowlist() + for _, maStr := range tc.allowlist { + ma, err := multiaddr.NewMultiaddr(maStr) + if err != nil { + fmt.Printf("failed to parse multiaddr: %s", err) + } + allowlist.Add(ma) + } + + if allowlist.Allowed(tc.endpoint) != tc.isConnAllowed { + t.Fatalf("%v: expected %v", !tc.isConnAllowed, tc.isConnAllowed) + } + + if allowlist.AllowedPeerAndMultiaddr(tc.peer, tc.endpoint) != tc.isAllowedWithPeer { + t.Fatalf("%v: expected %v", !tc.isAllowedWithPeer, tc.isAllowedWithPeer) + } + }) + } + +} + +func TestRemoved(t *testing.T) { + type testCase struct { + name string + allowedMA string + } + peerA := test.RandPeerIDFatal(t) + maA := multiaddr.StringCast("/ip4/1.2.3.4") + + testCases := []testCase{ + {name: "ip4", allowedMA: "/ip4/1.2.3.4"}, + {name: "ip4 with peer", allowedMA: "/ip4/1.2.3.4/p2p/" + peer.Encode(peerA)}, + {name: "ip4 network", allowedMA: "/ip4/0.0.0.0/ipcidr/0"}, + {name: "ip4 network with peer", allowedMA: "/ip4/0.0.0.0/ipcidr/0/p2p/" + peer.Encode(peerA)}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + allowlist := newAllowlist() + ma := multiaddr.StringCast(tc.allowedMA) + + err := allowlist.Add(ma) + if err != nil { + t.Fatalf("failed to add ip4: %s", err) + } + + if !allowlist.AllowedPeerAndMultiaddr(peerA, maA) { + t.Fatalf("addr should be allowed") + } + + allowlist.Remove((ma)) + + if allowlist.AllowedPeerAndMultiaddr(peerA, maA) { + t.Fatalf("addr should not be allowed") + } + }) + } +} + +// BenchmarkAllowlistCheck benchmarks the allowlist with plausible conditions. +func BenchmarkAllowlistCheck(b *testing.B) { + allowlist := newAllowlist() + + // How often do we expect a peer to be specified? 1 in N + ratioOfSpecifiedPeers := 10 + + // How often do we expect an allowlist hit? 1 in N + ratioOfAllowlistHit := 100 + + // How many multiaddrs in our allowlist? + howManyMultiaddrsInAllowList := 1_000 + + // How often is the IP addr an IPV6? 1 in N + ratioOfIPV6 := 20 + + countOfTotalPeersForTest := 100_000 + + mas := make([]multiaddr.Multiaddr, countOfTotalPeersForTest) + for i := 0; i < countOfTotalPeersForTest; i++ { + + ip := make([]byte, 16) + n, err := rand.Reader.Read(ip) + if err != nil || n != 16 { + b.Fatalf("Failed to generate IP address") + } + + var ipString string + + if i%ratioOfIPV6 == 0 { + // IPv6 + ip6 := net.IP(ip) + ipString = "/ip6/" + ip6.String() + } else { + // IPv4 + ip4 := net.IPv4(ip[0], ip[1], ip[2], ip[3]) + ipString = "/ip4/" + ip4.String() + } + + var ma multiaddr.Multiaddr + if i%ratioOfSpecifiedPeers == 0 { + ma = multiaddr.StringCast(ipString + "/p2p/" + peer.Encode(test.RandPeerIDFatal(b))) + } else { + ma = multiaddr.StringCast(ipString) + } + if err != nil { + b.Fatalf("Failed to generate multiaddr: %v", ipString) + } + + mas[i] = ma + } + + for _, ma := range mas[:howManyMultiaddrsInAllowList] { + err := allowlist.Add(ma) + if err != nil { + b.Fatalf("Failed to add multiaddr") + } + } + + masInAllowList := mas[:howManyMultiaddrsInAllowList] + masNotInAllowList := mas[howManyMultiaddrsInAllowList:] + + b.ResetTimer() + for n := 0; n < b.N; n++ { + if n%ratioOfAllowlistHit == 0 { + allowlist.Allowed(masInAllowList[n%len(masInAllowList)]) + } else { + allowlist.Allowed(masNotInAllowList[n%len(masNotInAllowList)]) + } + } +} diff --git a/docs/allowlist.md b/docs/allowlist.md new file mode 100644 index 0000000..4ada09c --- /dev/null +++ b/docs/allowlist.md @@ -0,0 +1,54 @@ +# Allowlist + +Imagine you have a node that is getting overloaded by possibly malicious +incoming connections. This node won't be able to accept incoming connections +from peers it _knows_ to be good. This node would effectively be _eclipsed_ from +the network since no other nodes will be able to connect to it. + +This is the problem that the Allowlist is designed to solve. + +## Design Goals + +- We should not fail to allocate a resource for an allowlisted peer because the + normal transient and system scopes are at their limits. This is the minimum + bar to avoid eclipse attacks. +- Minimal changes to resource manager and existing code (e.g. go-libp2p). +- The allowlist scope itself is limited to avoid giving an allowlisted peer the + ability to DoS a node. +- PeerIDs can optionally be fed into the allowlist. This will give an extra + step of verification before continuing to allow the peer to open streams. + - A peer may be able to open a connection, but after the handshake, if it's + not an expected peer id we move it to the normal system scope. +- We can have multiple PeerIDs for a given IP addr. +- No extra cost for the happy path when we are still below system and transient + limits. + +## Proposed change + +Add a change to `ResourceManager.OpenConnection` so that it accepts a multiaddr +parameter of the endpoint the connection is for. + +Add a change to `ResourceManager` to initialize it with a set of allowlisted +multiaddrs. This set can be modified at runtime as well for dynamic updating. + +For example, an allowlist set could look like: +``` +/ip4/1.1.1.1 +/ip6/2345:0425:2CA1::0567:5673:23b5 +/ip4/192.168.1.1/p2p/qmFoo +/ip4/192.168.1.1/p2p/qmBar +/ip4/1.2.3.0/ipcidr/24 +``` + +When a new connection is opened, the resource manager tries to allocate with the +normal system and transient resource scopes. If that fails, it checks if the +multiaddr matches an item in the set of allowlisted multiaddrs. If so, it +creates the connection resource scope using the allowlisted specific system and +transient resource scopes. If it wasn't an allowlisted multiaddr it fails as +before. + +When an allowlisted connection is tied to a peer id and transfered with +`ConnManagementScope.SetPeer`, we check if that peer id matches the expected +value in the allowlist (if it exists). If it does not match, we attempt to +transfer this resource to the normal system and peer scope. If that transfer +fails we close the connection. diff --git a/go.mod b/go.mod index 39e437a..3c2ea2d 100644 --- a/go.mod +++ b/go.mod @@ -3,36 +3,40 @@ module github.com/libp2p/go-libp2p-resource-manager go 1.17 require ( - github.com/ipfs/go-log/v2 v2.5.0 - github.com/libp2p/go-libp2p-core v0.14.0 + github.com/ipfs/go-log/v2 v2.5.1 + github.com/libp2p/go-libp2p-core v0.19.0 + github.com/multiformats/go-multiaddr v0.6.0 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/stretchr/testify v1.7.0 ) require ( - github.com/btcsuite/btcd v0.20.1-beta // indirect + github.com/btcsuite/btcd v0.22.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/gogo/protobuf v1.3.1 // indirect - github.com/ipfs/go-cid v0.0.7 // indirect + github.com/ipfs/go-cid v0.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.0.4 // indirect github.com/libp2p/go-buffer-pool v0.0.2 // indirect github.com/libp2p/go-openssl v0.0.7 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect - github.com/minio/sha256-simd v0.1.1 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.0.3 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect - github.com/multiformats/go-multiaddr v0.4.1 // indirect github.com/multiformats/go-multibase v0.0.3 // indirect - github.com/multiformats/go-multihash v0.0.14 // indirect + github.com/multiformats/go-multicodec v0.4.1 // indirect + github.com/multiformats/go-multihash v0.0.15 // indirect github.com/multiformats/go-varint v0.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect - github.com/spaolacci/murmur3 v1.1.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1 // indirect - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect + golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index b78292d..f4ec102 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,22 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= +github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -15,20 +24,29 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= -github.com/ipfs/go-log/v2 v2.5.0 h1:+MhAooFd9XZNvR0i9FriKW6HB0ql7HNXUuflWtc0dd4= -github.com/ipfs/go-log/v2 v2.5.0/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/ipfs/go-cid v0.2.0 h1:01JTiihFq9en9Vz0lc0VDWvZe/uBonGpzo4THP0vcQ0= +github.com/ipfs/go-cid v0.2.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= +github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= +github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -37,8 +55,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= -github.com/libp2p/go-libp2p-core v0.14.0 h1:0kYSgiK/D7Eo28GTuRXo5YHsWwAisVpFCqCVPUd/vJs= -github.com/libp2p/go-libp2p-core v0.14.0/go.mod h1:tLasfcVdTXnixsLB0QYaT1syJOhsbrhG7q6pGrHtBg8= +github.com/libp2p/go-libp2p-core v0.19.0 h1:KDw7hanmh0EuVdZqsHCAzmkdiYMk5uR5h0UGSCVTxSU= +github.com/libp2p/go-libp2p-core v0.19.0/go.mod h1:AkA+FUKQfYt1FLNef5fOPlo/naAWjKy/RCjkcPjqzYg= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= github.com/libp2p/go-openssl v0.0.7 h1:eCAzdLejcNVBzP/iZM9vqHnQm+XyCEbSSIheIPRGNsw= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= @@ -47,8 +65,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= -github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -57,18 +75,23 @@ github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= -github.com/multiformats/go-multiaddr v0.4.1 h1:Pq37uLx3hsyNlTDir7FZyU8+cFCTqd5y1KiM2IzOutI= github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= +github.com/multiformats/go-multiaddr v0.6.0 h1:qMnoOPj2s8xxPU5kZ57Cqdr0hHhARz7mFsPMIiYNqzg= +github.com/multiformats/go-multiaddr v0.6.0/go.mod h1:F4IpaKZuPP360tOMn2Tpyu0At8w23aRyVqeK0DbFeGM= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multicodec v0.4.1 h1:BSJbf+zpghcZMZrwTYBGwy0CPcVZGWiC72Cp8bBd4R4= +github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= -github.com/multiformats/go-multihash v0.0.14 h1:QoBceQYQQtNUuf6s7wHxnE2c8bhbMqhfGzNI032se/I= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.0.15 h1:hWOPdrNqDjwHDx82vsYGSDZNyktOJJ2dzZJzFkOV1jM= +github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= @@ -78,7 +101,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -96,14 +118,20 @@ go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -112,11 +140,14 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/rcmgr.go b/rcmgr.go index ba62969..65f330e 100644 --- a/rcmgr.go +++ b/rcmgr.go @@ -9,6 +9,7 @@ import ( "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" + "github.com/multiformats/go-multiaddr" logging "github.com/ipfs/go-log/v2" ) @@ -21,9 +22,14 @@ type resourceManager struct { trace *trace metrics *metrics + allowlist *Allowlist + system *systemScope transient *transientScope + allowlistedSystem *systemScope + allowlistedTransient *transientScope + cancelCtx context.Context cancel func() wg sync.WaitGroup @@ -89,10 +95,12 @@ var _ network.PeerScope = (*peerScope)(nil) type connectionScope struct { *resourceScope - dir network.Direction - usefd bool - rcmgr *resourceManager - peer *peerScope + dir network.Direction + usefd bool + isAllowlisted bool + rcmgr *resourceManager + peer *peerScope + endpoint multiaddr.Multiaddr } var _ network.ConnScope = (*connectionScope)(nil) @@ -117,11 +125,13 @@ var _ network.StreamManagementScope = (*streamScope)(nil) type Option func(*resourceManager) error func NewResourceManager(limits Limiter, opts ...Option) (network.ResourceManager, error) { + allowlist := newAllowlist() r := &resourceManager{ - limits: limits, - svc: make(map[string]*serviceScope), - proto: make(map[protocol.ID]*protocolScope), - peer: make(map[peer.ID]*peerScope), + limits: limits, + allowlist: &allowlist, + svc: make(map[string]*serviceScope), + proto: make(map[protocol.ID]*protocolScope), + peer: make(map[peer.ID]*peerScope), } for _, opt := range opts { @@ -134,11 +144,16 @@ func NewResourceManager(limits Limiter, opts ...Option) (network.ResourceManager return nil, err } - r.system = newSystemScope(limits.GetSystemLimits(), r) + r.system = newSystemScope(limits.GetSystemLimits(), r, "system") r.system.IncRef() - r.transient = newTransientScope(limits.GetTransientLimits(), r) + r.transient = newTransientScope(limits.GetTransientLimits(), r, "transient", r.system.resourceScope) r.transient.IncRef() + r.allowlistedSystem = newSystemScope(limits.GetSystemLimits(), r, "allowlistedSystem") + r.allowlistedSystem.IncRef() + r.allowlistedTransient = newTransientScope(limits.GetTransientLimits(), r, "allowlistedTransient", r.allowlistedSystem.resourceScope) + r.allowlistedTransient.IncRef() + r.cancelCtx, r.cancel = context.WithCancel(context.Background()) r.wg.Add(1) @@ -147,6 +162,10 @@ func NewResourceManager(limits Limiter, opts ...Option) (network.ResourceManager return r, nil } +func (r *resourceManager) GetAllowlist() *Allowlist { + return r.allowlist +} + func (r *resourceManager) ViewSystem(f func(network.ResourceScope) error) error { return f(r.system) } @@ -255,10 +274,23 @@ func (r *resourceManager) nextStreamId() int64 { return r.streamId } -func (r *resourceManager) OpenConnection(dir network.Direction, usefd bool) (network.ConnManagementScope, error) { - conn := newConnectionScope(dir, usefd, r.limits.GetConnLimits(), r) +func (r *resourceManager) OpenConnection(dir network.Direction, usefd bool, endpoint multiaddr.Multiaddr) (network.ConnManagementScope, error) { + var conn *connectionScope + conn = newConnectionScope(dir, usefd, r.limits.GetConnLimits(), r, endpoint) - if err := conn.AddConn(dir, usefd); err != nil { + err := conn.AddConn(dir, usefd) + if err != nil { + // Try again if this is an allowlisted connection + // Failed to open connection, let's see if this was allowlisted and try again + allowed := r.allowlist.Allowed(endpoint) + if allowed { + conn.Done() + conn = newAllowListedConnectionScope(dir, usefd, r.limits.GetConnLimits(), r, endpoint) + err = conn.AddConn(dir, usefd) + } + } + + if err != nil { conn.Done() r.metrics.BlockConn(dir, usefd) return nil, err @@ -363,17 +395,17 @@ func (r *resourceManager) gc() { } } -func newSystemScope(limit Limit, rcmgr *resourceManager) *systemScope { +func newSystemScope(limit Limit, rcmgr *resourceManager, name string) *systemScope { return &systemScope{ - resourceScope: newResourceScope(limit, nil, "system", rcmgr.trace, rcmgr.metrics), + resourceScope: newResourceScope(limit, nil, name, rcmgr.trace, rcmgr.metrics), } } -func newTransientScope(limit Limit, rcmgr *resourceManager) *transientScope { +func newTransientScope(limit Limit, rcmgr *resourceManager, name string, systemScope *resourceScope) *transientScope { return &transientScope{ resourceScope: newResourceScope(limit, - []*resourceScope{rcmgr.system.resourceScope}, - "transient", rcmgr.trace, rcmgr.metrics), + []*resourceScope{systemScope}, + name, rcmgr.trace, rcmgr.metrics), system: rcmgr.system, } } @@ -408,14 +440,28 @@ func newPeerScope(p peer.ID, limit Limit, rcmgr *resourceManager) *peerScope { } } -func newConnectionScope(dir network.Direction, usefd bool, limit Limit, rcmgr *resourceManager) *connectionScope { +func newConnectionScope(dir network.Direction, usefd bool, limit Limit, rcmgr *resourceManager, endpoint multiaddr.Multiaddr) *connectionScope { return &connectionScope{ resourceScope: newResourceScope(limit, []*resourceScope{rcmgr.transient.resourceScope, rcmgr.system.resourceScope}, fmt.Sprintf("conn-%d", rcmgr.nextConnId()), rcmgr.trace, rcmgr.metrics), - dir: dir, - usefd: usefd, - rcmgr: rcmgr, + dir: dir, + usefd: usefd, + rcmgr: rcmgr, + endpoint: endpoint, + } +} + +func newAllowListedConnectionScope(dir network.Direction, usefd bool, limit Limit, rcmgr *resourceManager, endpoint multiaddr.Multiaddr) *connectionScope { + return &connectionScope{ + resourceScope: newResourceScope(limit, + []*resourceScope{rcmgr.allowlistedTransient.resourceScope, rcmgr.allowlistedSystem.resourceScope}, + fmt.Sprintf("conn-%d", rcmgr.nextConnId()), rcmgr.trace, rcmgr.metrics), + dir: dir, + usefd: usefd, + rcmgr: rcmgr, + endpoint: endpoint, + isAllowlisted: true, } } @@ -500,6 +546,49 @@ func (s *connectionScope) PeerScope() network.PeerScope { return s.peer } +// transferAllowedToStandard transfers this connection scope from being part of +// the allowlist set of scopes to being part of the standard set of scopes. +// Happens when we first allowlisted this connection due to its IP, but later +// discovered that the peer id not what we expected. +func (s *connectionScope) transferAllowedToStandard() (err error) { + + systemScope := s.rcmgr.system.resourceScope + transientScope := s.rcmgr.transient.resourceScope + + stat := s.resourceScope.rc.stat() + + for _, scope := range s.edges { + scope.ReleaseForChild(stat) + scope.DecRef() // removed from edges + } + s.edges = nil + + if err := systemScope.ReserveForChild(stat); err != nil { + return err + } + systemScope.IncRef() + + // Undo this if we fail later + defer func() { + if err != nil { + systemScope.ReleaseForChild(stat) + systemScope.DecRef() + } + }() + + if err := transientScope.ReserveForChild(stat); err != nil { + return err + } + transientScope.IncRef() + + // Update edges + s.edges = []*resourceScope{ + systemScope, + transientScope, + } + return nil +} + func (s *connectionScope) SetPeer(p peer.ID) error { s.Lock() defer s.Unlock() @@ -507,6 +596,34 @@ func (s *connectionScope) SetPeer(p peer.ID) error { if s.peer != nil { return fmt.Errorf("connection scope already attached to a peer") } + + system := s.rcmgr.system + transient := s.rcmgr.transient + + if s.isAllowlisted { + system = s.rcmgr.allowlistedSystem + transient = s.rcmgr.allowlistedTransient + + if !s.rcmgr.allowlist.AllowedPeerAndMultiaddr(p, s.endpoint) { + s.isAllowlisted = false + + // This is not an allowed peer + multiaddr combination. We need to + // transfer this connection to the general scope. We'll do this first by + // transferring the connection to the system and transient scopes, then + // continue on with this function. The idea is that a connection + // shouldn't get the benefit of evading the transient scope because it + // was _almost_ an allowlisted connection. + if err := s.transferAllowedToStandard(); err != nil { + // Failed to transfer this connection to the standard scopes + return err + } + + // set the system and transient scopes to the non-allowlisted ones + system = s.rcmgr.system + transient = s.rcmgr.transient + } + } + s.peer = s.rcmgr.getPeerScope(p) // juggle resources from transient scope to peer scope @@ -518,13 +635,13 @@ func (s *connectionScope) SetPeer(p peer.ID) error { return err } - s.rcmgr.transient.ReleaseForChild(stat) - s.rcmgr.transient.DecRef() // removed from edges + transient.ReleaseForChild(stat) + transient.DecRef() // removed from edges // update edges edges := []*resourceScope{ s.peer.resourceScope, - s.rcmgr.system.resourceScope, + system.resourceScope, } s.resourceScope.edges = edges diff --git a/rcmgr_test.go b/rcmgr_test.go index a18815c..fdf8dbf 100644 --- a/rcmgr_test.go +++ b/rcmgr_test.go @@ -6,8 +6,12 @@ import ( "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p-core/test" + "github.com/multiformats/go-multiaddr" ) +var dummyMA = multiaddr.StringCast("/ip4/1.2.3.4/tcp/1234") + func TestResourceManager(t *testing.T) { peerA := peer.ID("A") peerB := peer.ID("B") @@ -242,7 +246,7 @@ func TestResourceManager(t *testing.T) { }) // open an inbound connection, using an fd - conn, err := mgr.OpenConnection(network.DirInbound, true) + conn, err := mgr.OpenConnection(network.DirInbound, true, dummyMA) if err != nil { t.Fatal(err) } @@ -257,10 +261,10 @@ func TestResourceManager(t *testing.T) { }) // the connection is transient, we shouldn't be able to open a second one - if _, err := mgr.OpenConnection(network.DirInbound, true); err == nil { + if _, err := mgr.OpenConnection(network.DirInbound, true, dummyMA); err == nil { t.Fatal("expected OpenConnection to fail") } - if _, err := mgr.OpenConnection(network.DirInbound, false); err == nil { + if _, err := mgr.OpenConnection(network.DirInbound, false, dummyMA); err == nil { t.Fatal("expected OpenConnection to fail") } @@ -277,7 +281,7 @@ func TestResourceManager(t *testing.T) { }) // open another inbound connection, using an fd - conn1, err := mgr.OpenConnection(network.DirInbound, true) + conn1, err := mgr.OpenConnection(network.DirInbound, true, dummyMA) if err != nil { t.Fatal(err) } @@ -315,7 +319,7 @@ func TestResourceManager(t *testing.T) { }) // we should be able to open a second transient connection now - conn2, err := mgr.OpenConnection(network.DirInbound, true) + conn2, err := mgr.OpenConnection(network.DirInbound, true, dummyMA) if err != nil { t.Fatal(err) } @@ -350,7 +354,7 @@ func TestResourceManager(t *testing.T) { // close it and reopen without using an FD -- we should be able to attach now conn2.Done() - conn2, err = mgr.OpenConnection(network.DirInbound, false) + conn2, err = mgr.OpenConnection(network.DirInbound, false, dummyMA) if err != nil { t.Fatal(err) } @@ -998,3 +1002,67 @@ func TestResourceManager(t *testing.T) { } } + +func TestResourceManagerWithAllowlist(t *testing.T) { + peerA := test.RandPeerIDFatal(t) + + limits := NewDefaultLimiter() + limits.SystemLimits = limits.SystemLimits.WithConnLimit(0, 0, 0) + limits.TransientLimits = limits.SystemLimits.WithConnLimit(0, 0, 0) + rcmgr, err := NewResourceManager(limits, WithAllowlistedMultiaddrs([]multiaddr.Multiaddr{ + multiaddr.StringCast("/ip4/1.2.3.4"), + multiaddr.StringCast("/ip4/4.3.2.1/p2p/" + peerA.String()), + })) + if err != nil { + t.Fatal(err) + } + + { + // Setup allowlist. TODO, replace this with a config once config changes are in + r := rcmgr.(*resourceManager) + + r.allowlistedSystem = newSystemScope(limits.GetSystemLimits().WithConnLimit(2, 1, 2), r, "allowlistedSystem") + r.allowlistedSystem.IncRef() + r.allowlistedTransient = newTransientScope(limits.GetTransientLimits().WithConnLimit(1, 1, 1), r, "allowlistedTransient", r.allowlistedSystem.resourceScope) + r.allowlistedTransient.IncRef() + } + + // A connection comes in from a non-allowlisted ip address + _, err = rcmgr.OpenConnection(network.DirInbound, true, multiaddr.StringCast("/ip4/1.2.3.5")) + if err == nil { + t.Fatalf("Expected this to fail. err=%v", err) + } + + // A connection comes in from an allowlisted ip address + connScope, err := rcmgr.OpenConnection(network.DirInbound, true, multiaddr.StringCast("/ip4/1.2.3.4")) + if err != nil { + t.Fatal(err) + } + + err = connScope.SetPeer(test.RandPeerIDFatal(t)) + if err != nil { + t.Fatal(err) + } + + // A connection comes in that looks like it should be allowlisted, but then has the wrong peer id. + connScope, err = rcmgr.OpenConnection(network.DirInbound, true, multiaddr.StringCast("/ip4/4.3.2.1")) + if err != nil { + t.Fatal(err) + } + + err = connScope.SetPeer(test.RandPeerIDFatal(t)) + if err == nil { + t.Fatalf("Expected this to fail. err=%v", err) + } + + // A connection comes in that looks like it should be allowlisted, and it has the allowlisted peer id + connScope, err = rcmgr.OpenConnection(network.DirInbound, true, multiaddr.StringCast("/ip4/4.3.2.1")) + if err != nil { + t.Fatal(err) + } + + err = connScope.SetPeer(peerA) + if err != nil { + t.Fatal(err) + } +}