From 34b2b471db54573c9f1baa303419d4ddc7b96fc8 Mon Sep 17 00:00:00 2001 From: Aarsh Shah Date: Fri, 15 May 2020 18:56:21 +0530 Subject: [PATCH] implement connection gating support: intercept peer, address dials, upgraded conns (#201) --- addrs.go | 4 +- go.mod | 12 ++-- go.sum | 19 ++++-- swarm.go | 87 ++++++++++++++++----------- swarm_dial.go | 17 +++++- swarm_listen.go | 1 + swarm_test.go | 146 +++++++++++++++++++++++++++++---------------- testing/testing.go | 87 ++++++++++++++++++++++++--- 8 files changed, 263 insertions(+), 110 deletions(-) diff --git a/addrs.go b/addrs.go index ed510f2..17c65ae 100644 --- a/addrs.go +++ b/addrs.go @@ -1,12 +1,12 @@ package swarm import ( - mafilter "github.com/libp2p/go-maddr-filter" + ma "github.com/multiformats/go-multiaddr" mamask "github.com/whyrusleeping/multiaddr-filter" ) // http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml -var lowTimeoutFilters = mafilter.NewFilters() +var lowTimeoutFilters = ma.NewFilters() func init() { for _, p := range []string{ diff --git a/go.mod b/go.mod index 22084db..5616a63 100644 --- a/go.mod +++ b/go.mod @@ -5,19 +5,19 @@ require ( github.com/jbenet/goprocess v0.1.4 github.com/libp2p/go-addr-util v0.0.2 github.com/libp2p/go-conn-security-multistream v0.2.0 - github.com/libp2p/go-libp2p-core v0.5.2 + github.com/libp2p/go-libp2p-core v0.5.5 github.com/libp2p/go-libp2p-loggables v0.1.0 - github.com/libp2p/go-libp2p-peerstore v0.2.3 + github.com/libp2p/go-libp2p-peerstore v0.2.4 github.com/libp2p/go-libp2p-secio v0.2.2 github.com/libp2p/go-libp2p-testing v0.1.1 - github.com/libp2p/go-libp2p-transport-upgrader v0.2.0 + github.com/libp2p/go-libp2p-transport-upgrader v0.3.0 github.com/libp2p/go-libp2p-yamux v0.2.7 - github.com/libp2p/go-maddr-filter v0.0.5 github.com/libp2p/go-stream-muxer-multistream v0.3.0 github.com/libp2p/go-tcp-transport v0.2.0 - github.com/multiformats/go-multiaddr v0.2.1 + github.com/multiformats/go-multiaddr v0.2.2 github.com/multiformats/go-multiaddr-fmt v0.1.0 - github.com/multiformats/go-multiaddr-net v0.1.4 + github.com/multiformats/go-multiaddr-net v0.1.5 + github.com/stretchr/testify v1.4.0 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect ) diff --git a/go.sum b/go.sum index e0c1f5f..6734d2c 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,8 @@ github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscw github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2 h1:vhC1OXXiT9R2pczegwz6moDvuRpggaroAXhPIseh57A= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= +github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= +github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= github.com/jbenet/goprocess v0.1.3 h1:YKyIEECS/XvcfHtBzxtjBBbWK+MbvA6dG8ASiqwvr10= github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= @@ -137,16 +139,17 @@ github.com/libp2p/go-libp2p-core v0.5.0 h1:FBQ1fpq2Fo/ClyjojVJ5AKXlKhvNc/B6U0O+7 github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.1 h1:6Cu7WljPQtGY2krBlMoD8L/zH3tMUsCbqNFH7cZwCoI= github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= -github.com/libp2p/go-libp2p-core v0.5.2 h1:hevsCcdLiazurKBoeNn64aPYTVOPdY4phaEGeLtHOAs= -github.com/libp2p/go-libp2p-core v0.5.2/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.5 h1:/yiFUZDoBWqvpWeHHJ1iA8SOs5obT1/+UdNfckwD57M= +github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-mplex v0.2.1 h1:E1xaJBQnbSiTHGI1gaBKmKhu1TUKkErKJnE8iGvirYI= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= github.com/libp2p/go-libp2p-mplex v0.2.3 h1:2zijwaJvpdesST2MXpI5w9wWFRgYtMcpRX7rrw0jmOo= github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= -github.com/libp2p/go-libp2p-peerstore v0.2.3 h1:MofRq2l3c15vQpEygTetV+zRRrncz+ktiXW7H2EKoEQ= -github.com/libp2p/go-libp2p-peerstore v0.2.3/go.mod h1:K8ljLdFn590GMttg/luh4caB/3g0vKuY01psze0upRw= +github.com/libp2p/go-libp2p-peerstore v0.2.4 h1:jU9S4jYN30kdzTpDAR7SlHUD+meDUjTODh4waLWF1ws= +github.com/libp2p/go-libp2p-peerstore v0.2.4/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-secio v0.2.2 h1:rLLPvShPQAcY6eNurKNZq3eZjPWfU9kXF2eI9jIYdrg= @@ -156,6 +159,8 @@ github.com/libp2p/go-libp2p-testing v0.1.1 h1:U03z3HnGI7Ni8Xx6ONVZvUFOAzWYmolWf5 github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0 h1:5EhPgQhXZNyfL22ERZTUoVp9UVVbNowWNVtELQaKCHk= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= +github.com/libp2p/go-libp2p-transport-upgrader v0.3.0 h1:q3ULhsknEQ34eVDhv4YwKS8iet69ffs9+Fir6a7weN4= +github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= github.com/libp2p/go-libp2p-yamux v0.2.7 h1:vzKu0NVtxvEIDGCv6mjKRcK0gipSgaXmJZ6jFv0d/dk= github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= github.com/libp2p/go-maddr-filter v0.0.5 h1:CW3AgbMO6vUvT4kf87y4N+0P8KUl2aqLYhrGyDUbLSg= @@ -170,6 +175,8 @@ github.com/libp2p/go-netroute v0.1.2 h1:UHhB35chwgvcRI392znJA3RCBtZ3MpE3ahNCN5MR github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-openssl v0.0.4 h1:d27YZvLoTyMhIN4njrkr8zMDOM4lfpHIp6A+TK9fovg= github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.5 h1:pQkejVhF0xp08D4CQUcw8t+BFJeXowja6RVcb5p++EA= +github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport-transport v0.0.3 h1:zzOeXnTooCkRvoH+bSXEfXhn76+LAiwoneM0gnXjF2M= @@ -222,6 +229,8 @@ github.com/multiformats/go-multiaddr v0.2.0 h1:lR52sFwcTCuQb6bTfnXF6zA2XfyYvyd+5 github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= github.com/multiformats/go-multiaddr v0.2.1 h1:SgG/cw5vqyB5QQe5FPe2TqggU9WtrA9X4nZw7LlVqOI= github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= +github.com/multiformats/go-multiaddr v0.2.2 h1:XZLDTszBIJe6m0zF6ITBrEcZR73OPUhCBBS9rYAuUzI= +github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multiaddr-net v0.1.2 h1:P7zcBH9FRETdPkDrylcXVjQLQ2t1JQtNItZULWNWgeg= @@ -230,6 +239,8 @@ github.com/multiformats/go-multiaddr-net v0.1.3 h1:q/IYAvoPKuRzGeERn3uacWgm0LIWk github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.4 h1:g6gwydsfADqFvrHoMkS0n9Ok9CG6F7ytOH/bJDkhIOY= github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.1.5 h1:QoRKvu0xHN1FCFJcMQLbG/yQE2z441L5urvG3+qyz7g= +github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= diff --git a/swarm.go b/swarm.go index 198d88a..f5c0209 100644 --- a/swarm.go +++ b/swarm.go @@ -9,6 +9,7 @@ import ( "sync/atomic" "time" + "github.com/libp2p/go-libp2p-core/connmgr" "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" @@ -19,9 +20,7 @@ import ( "github.com/jbenet/goprocess" goprocessctx "github.com/jbenet/goprocess/context" - filter "github.com/libp2p/go-maddr-filter" ma "github.com/multiformats/go-multiaddr" - mafilter "github.com/whyrusleeping/multiaddr-filter" ) // DialTimeoutLocal is the maximum duration a Dial to local network address @@ -87,22 +86,24 @@ type Swarm struct { dsync *DialSync backf DialBackoff limiter *dialLimiter - - // filters for addresses that shouldnt be dialed (or accepted) - Filters *filter.Filters + gater connmgr.ConnectionGater proc goprocess.Process ctx context.Context bwc metrics.Reporter } -// NewSwarm constructs a Swarm -func NewSwarm(ctx context.Context, local peer.ID, peers peerstore.Peerstore, bwc metrics.Reporter) *Swarm { +// NewSwarm constructs a Swarm. +// +// NOTE: go-libp2p will be moving to dependency injection soon. The variadic +// `extra` interface{} parameter facilitates the future migration. Supported +// elements are: +// - connmgr.ConnectionGater +func NewSwarm(ctx context.Context, local peer.ID, peers peerstore.Peerstore, bwc metrics.Reporter, extra ...interface{}) *Swarm { s := &Swarm{ - local: local, - peers: peers, - bwc: bwc, - Filters: filter.NewFilters(), + local: local, + peers: peers, + bwc: bwc, } s.conns.m = make(map[peer.ID][]*Conn) @@ -110,6 +111,13 @@ func NewSwarm(ctx context.Context, local peer.ID, peers peerstore.Peerstore, bwc s.transports.m = make(map[int]transport.Transport) s.notifs.m = make(map[network.Notifiee]struct{}) + for _, i := range extra { + switch v := i.(type) { + case connmgr.ConnectionGater: + s.gater = v + } + } + s.dsync = NewDialSync(s.doDial) s.limiter = newDialLimiter(s.dialAddr) s.proc = goprocessctx.WithContext(ctx) @@ -168,33 +176,46 @@ func (s *Swarm) teardown() error { return nil } -// AddAddrFilter adds a multiaddr filter to the set of filters the swarm will use to determine which -// addresses not to dial to. -func (s *Swarm) AddAddrFilter(f string) error { - m, err := mafilter.NewMask(f) - if err != nil { - return err - } - - s.Filters.AddDialFilter(m) - return nil -} - // Process returns the Process of the swarm func (s *Swarm) Process() goprocess.Process { return s.proc } func (s *Swarm) addConn(tc transport.CapableConn, dir network.Direction) (*Conn, error) { - // The underlying transport (or the dialer) *should* filter it's own - // connections but we should double check anyways. - raddr := tc.RemoteMultiaddr() - if s.Filters.AddrBlocked(raddr) { - tc.Close() - return nil, ErrAddrFiltered + var ( + p = tc.RemotePeer() + addr = tc.RemoteMultiaddr() + ) + + if s.gater != nil { + if allow := s.gater.InterceptAddrDial(p, addr); !allow { + err := tc.Close() + if err != nil { + log.Warnf("failed to close connection with peer %s and addr %s; err: %s", p.Pretty(), addr, err) + } + return nil, ErrAddrFiltered + } } - p := tc.RemotePeer() + stat := network.Stat{Direction: dir} + c := &Conn{ + conn: tc, + swarm: s, + stat: stat, + } + + // we ONLY check upgraded connections here so we can send them a Disconnect message. + // If we do this in the Upgrader, we will not be able to do this. + if s.gater != nil { + if allow, _ := s.gater.InterceptUpgraded(c); !allow { + // TODO Send disconnect with reason here + err := tc.Close() + if err != nil { + log.Warnf("failed to close connection with peer %s and addr %s; err: %s", p.Pretty(), addr, err) + } + return nil, ErrGaterDisallowedConnection + } + } // Add the public key. if pk := tc.RemotePublicKey(); pk != nil { @@ -214,12 +235,6 @@ func (s *Swarm) addConn(tc transport.CapableConn, dir network.Direction) (*Conn, } // Wrap and register the connection. - stat := network.Stat{Direction: dir} - c := &Conn{ - conn: tc, - swarm: s, - stat: stat, - } c.streams.m = make(map[*Stream]struct{}) s.conns.m[p] = append(s.conns.m[p], c) diff --git a/swarm_dial.go b/swarm_dial.go index cae4110..f35f2b6 100644 --- a/swarm_dial.go +++ b/swarm_dial.go @@ -50,6 +50,10 @@ var ( // ErrNoGoodAddresses is returned when we find addresses for a peer but // can't use any of them. ErrNoGoodAddresses = errors.New("no good addresses") + + // ErrGaterDisallowedConnection is returned when the gater prevents us from + // forming a connection with a peer. + ErrGaterDisallowedConnection = errors.New("gater disallows connection to peer") ) // DialAttempts governs how many times a goroutine will try to dial a given peer. @@ -218,6 +222,11 @@ func (db *DialBackoff) cleanup() { // This allows us to use various transport protocols, do NAT traversal/relay, // etc. to achieve connection. func (s *Swarm) DialPeer(ctx context.Context, p peer.ID) (network.Conn, error) { + if s.gater != nil && !s.gater.InterceptPeerDial(p) { + log.Debugf("gater disallowed outbound connection to peer %s", p.Pretty()) + return nil, &DialError{Peer: p, Cause: ErrGaterDisallowedConnection} + } + return s.dialPeer(ctx, p) } @@ -339,7 +348,7 @@ func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) { if len(peerAddrs) == 0 { return nil, &DialError{Peer: p, Cause: ErrNoAddresses} } - goodAddrs := s.filterKnownUndialables(peerAddrs) + goodAddrs := s.filterKnownUndialables(p, peerAddrs) if len(goodAddrs) == 0 { return nil, &DialError{Peer: p, Cause: ErrNoGoodAddresses} } @@ -393,7 +402,7 @@ func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) { // IPv6 link-local addresses, addresses without a dial-capable transport, // and addresses that we know to be our own. // This is an optimization to avoid wasting time on dials that we know are going to fail. -func (s *Swarm) filterKnownUndialables(addrs []ma.Multiaddr) []ma.Multiaddr { +func (s *Swarm) filterKnownUndialables(p peer.ID, addrs []ma.Multiaddr) []ma.Multiaddr { lisAddrs, _ := s.InterfaceListenAddresses() var ourAddrs []ma.Multiaddr for _, addr := range lisAddrs { @@ -409,7 +418,9 @@ func (s *Swarm) filterKnownUndialables(addrs []ma.Multiaddr) []ma.Multiaddr { s.canDial, // TODO: Consider allowing link-local addresses addrutil.AddrOverNonLocalIP, - addrutil.FilterNeg(s.Filters.AddrBlocked), + func(addr ma.Multiaddr) bool { + return s.gater == nil || s.gater.InterceptAddrDial(p, addr) + }, ) } diff --git a/swarm_listen.go b/swarm_listen.go index 09d411d..ab5c42a 100644 --- a/swarm_listen.go +++ b/swarm_listen.go @@ -89,6 +89,7 @@ func (s *Swarm) AddListenAddr(a ma.Multiaddr) error { } return } + log.Debugf("swarm listener accepted connection: %s", c) s.refs.Add(1) go func() { diff --git a/swarm_test.go b/swarm_test.go index b155373..4750b6a 100644 --- a/swarm_test.go +++ b/swarm_test.go @@ -5,20 +5,21 @@ import ( "context" "fmt" "io" - "net" "sync" "testing" "time" - logging "github.com/ipfs/go-log" + "github.com/libp2p/go-libp2p-core/control" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peerstore" - ma "github.com/multiformats/go-multiaddr" - . "github.com/libp2p/go-libp2p-swarm" . "github.com/libp2p/go-libp2p-swarm/testing" + + logging "github.com/ipfs/go-log" + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" ) var log = logging.Logger("swarm_test") @@ -280,60 +281,105 @@ func TestConnHandler(t *testing.T) { } } -func TestAddrBlocking(t *testing.T) { +func TestConnectionGating(t *testing.T) { ctx := context.Background() - swarms := makeSwarms(ctx, t, 2) - - swarms[0].SetConnHandler(func(conn network.Conn) { - t.Errorf("no connections should happen! -- %s", conn) - }) - - _, block, err := net.ParseCIDR("127.0.0.1/8") - if err != nil { - t.Fatal(err) - } - - swarms[1].Filters.AddDialFilter(block) - - swarms[1].Peerstore().AddAddr(swarms[0].LocalPeer(), swarms[0].ListenAddresses()[0], peerstore.PermanentAddrTTL) - _, err = swarms[1].DialPeer(ctx, swarms[0].LocalPeer()) - if err == nil { - t.Fatal("dial should have failed") - } - - swarms[0].Peerstore().AddAddr(swarms[1].LocalPeer(), swarms[1].ListenAddresses()[0], peerstore.PermanentAddrTTL) - _, err = swarms[0].DialPeer(ctx, swarms[1].LocalPeer()) - if err == nil { - t.Fatal("dial should have failed") + tcs := map[string]struct { + p1Gater func(gater *MockConnectionGater) *MockConnectionGater + p2Gater func(gater *MockConnectionGater) *MockConnectionGater + + p1ConnectednessToP2 network.Connectedness + p2ConnectednessToP1 network.Connectedness + isP1OutboundErr bool + }{ + "no gating": { + p1ConnectednessToP2: network.Connected, + p2ConnectednessToP1: network.Connected, + isP1OutboundErr: false, + }, + "p1 gates outbound peer dial": { + p1Gater: func(c *MockConnectionGater) *MockConnectionGater { + c.PeerDial = func(p peer.ID) bool { return false } + return c + }, + p1ConnectednessToP2: network.NotConnected, + p2ConnectednessToP1: network.NotConnected, + isP1OutboundErr: true, + }, + "p1 gates outbound addr dialing": { + p1Gater: func(c *MockConnectionGater) *MockConnectionGater { + c.Dial = func(p peer.ID, addr ma.Multiaddr) bool { return false } + return c + }, + p1ConnectednessToP2: network.NotConnected, + p2ConnectednessToP1: network.NotConnected, + isP1OutboundErr: true, + }, + "p2 gates inbound peer dial before securing": { + p2Gater: func(c *MockConnectionGater) *MockConnectionGater { + c.Accept = func(c network.ConnMultiaddrs) bool { return false } + return c + }, + p1ConnectednessToP2: network.NotConnected, + p2ConnectednessToP1: network.NotConnected, + isP1OutboundErr: true, + }, + "p2 gates inbound peer dial before multiplexing": { + p1Gater: func(c *MockConnectionGater) *MockConnectionGater { + c.Secured = func(network.Direction, peer.ID, network.ConnMultiaddrs) bool { return false } + return c + }, + p1ConnectednessToP2: network.NotConnected, + p2ConnectednessToP1: network.NotConnected, + isP1OutboundErr: true, + }, + "p2 gates inbound peer dial after upgrading": { + p1Gater: func(c *MockConnectionGater) *MockConnectionGater { + c.Upgraded = func(c network.Conn) (bool, control.DisconnectReason) { return false, 0 } + return c + }, + p1ConnectednessToP2: network.NotConnected, + p2ConnectednessToP1: network.NotConnected, + isP1OutboundErr: true, + }, + "p2 gates outbound dials": { + p2Gater: func(c *MockConnectionGater) *MockConnectionGater { + c.PeerDial = func(p peer.ID) bool { return false } + return c + }, + p1ConnectednessToP2: network.Connected, + p2ConnectednessToP1: network.Connected, + isP1OutboundErr: false, + }, } -} -func TestFilterBounds(t *testing.T) { - ctx := context.Background() - swarms := makeSwarms(ctx, t, 2) + for n, tc := range tcs { + t.Run(n, func(t *testing.T) { + p1Gater := DefaultMockConnectionGater() + p2Gater := DefaultMockConnectionGater() + if tc.p1Gater != nil { + p1Gater = tc.p1Gater(p1Gater) + } + if tc.p2Gater != nil { + p2Gater = tc.p2Gater(p2Gater) + } - conns := make(chan struct{}, 8) - swarms[0].SetConnHandler(func(conn network.Conn) { - conns <- struct{}{} - }) + sw1 := GenSwarm(t, ctx, OptConnGater(p1Gater)) + sw2 := GenSwarm(t, ctx, OptConnGater(p2Gater)) - // Address that we wont be dialing from - _, block, err := net.ParseCIDR("192.0.0.1/8") - if err != nil { - t.Fatal(err) - } + p1 := sw1.LocalPeer() + p2 := sw2.LocalPeer() + sw1.Peerstore().AddAddr(p2, sw2.ListenAddresses()[0], peerstore.PermanentAddrTTL) + // 1 -> 2 + _, err := sw1.DialPeer(ctx, p2) - // set filter on both sides, shouldnt matter - swarms[1].Filters.AddDialFilter(block) - swarms[0].Filters.AddDialFilter(block) + require.Equal(t, tc.isP1OutboundErr, err != nil, n) + require.Equal(t, tc.p1ConnectednessToP2, sw1.Connectedness(p2), n) - connectSwarms(t, ctx, swarms) + require.Eventually(t, func() bool { + return tc.p2ConnectednessToP1 == sw2.Connectedness(p1) + }, 2*time.Second, 100*time.Millisecond, n) + }) - select { - case <-time.After(time.Second): - t.Fatal("should have gotten connection") - case <-conns: - t.Log("got connect") } } diff --git a/testing/testing.go b/testing/testing.go index 10de2ac..0d252c3 100644 --- a/testing/testing.go +++ b/testing/testing.go @@ -4,26 +4,31 @@ import ( "context" "testing" + "github.com/libp2p/go-libp2p-core/connmgr" + "github.com/libp2p/go-libp2p-core/control" "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peerstore" - "github.com/libp2p/go-libp2p-testing/net" - "github.com/libp2p/go-tcp-transport" - goprocess "github.com/jbenet/goprocess" csms "github.com/libp2p/go-conn-security-multistream" pstoremem "github.com/libp2p/go-libp2p-peerstore/pstoremem" secio "github.com/libp2p/go-libp2p-secio" + swarm "github.com/libp2p/go-libp2p-swarm" + "github.com/libp2p/go-libp2p-testing/net" tptu "github.com/libp2p/go-libp2p-transport-upgrader" yamux "github.com/libp2p/go-libp2p-yamux" msmux "github.com/libp2p/go-stream-muxer-multistream" + "github.com/libp2p/go-tcp-transport" - swarm "github.com/libp2p/go-libp2p-swarm" + goprocess "github.com/jbenet/goprocess" + ma "github.com/multiformats/go-multiaddr" ) type config struct { disableReuseport bool dialOnly bool + connectionGater connmgr.ConnectionGater } // Option is an option that can be passed when constructing a test swarm. @@ -39,6 +44,13 @@ var OptDialOnly Option = func(_ *testing.T, c *config) { c.dialOnly = true } +// OptConnGater configures the given connection gater on the test +func OptConnGater(cg connmgr.ConnectionGater) Option { + return func(_ *testing.T, c *config) { + c.connectionGater = cg + } +} + // GenUpgrader creates a new connection upgrader for use with this swarm. func GenUpgrader(n *swarm.Swarm) *tptu.Upgrader { id := n.LocalPeer() @@ -53,9 +65,8 @@ func GenUpgrader(n *swarm.Swarm) *tptu.Upgrader { stMuxer.AddTransport("/yamux/1.0.0", yamux.DefaultTransport) return &tptu.Upgrader{ - Secure: secMuxer, - Muxer: stMuxer, - Filters: n.Filters, + Secure: secMuxer, + Muxer: stMuxer, } } @@ -72,12 +83,16 @@ func GenSwarm(t *testing.T, ctx context.Context, opts ...Option) *swarm.Swarm { ps := pstoremem.NewPeerstore() ps.AddPubKey(p.ID, p.PubKey) ps.AddPrivKey(p.ID, p.PrivKey) - s := swarm.NewSwarm(ctx, p.ID, ps, metrics.NewBandwidthCounter()) + + s := swarm.NewSwarm(ctx, p.ID, ps, metrics.NewBandwidthCounter(), cfg.connectionGater) + // Call AddChildNoWait because we can't call AddChild after the process // may have been closed (e.g., if the context was canceled). s.Process().AddChildNoWait(goprocess.WithTeardown(ps.Close)) - tcpTransport := tcp.NewTCPTransport(GenUpgrader(s)) + upgrader := GenUpgrader(s) + upgrader.ConnGater = cfg.connectionGater + tcpTransport := tcp.NewTCPTransport(upgrader) tcpTransport.DisableReuseport = cfg.disableReuseport if err := s.AddTransport(tcpTransport); err != nil { @@ -101,3 +116,57 @@ func DivulgeAddresses(a, b network.Network) { addrs := a.Peerstore().Addrs(id) b.Peerstore().AddAddrs(id, addrs, peerstore.PermanentAddrTTL) } + +// MockConnectionGater is a mock connection gater to be used by the tests. +type MockConnectionGater struct { + Dial func(p peer.ID, addr ma.Multiaddr) bool + PeerDial func(p peer.ID) bool + Accept func(c network.ConnMultiaddrs) bool + Secured func(network.Direction, peer.ID, network.ConnMultiaddrs) bool + Upgraded func(c network.Conn) (bool, control.DisconnectReason) +} + +func DefaultMockConnectionGater() *MockConnectionGater { + m := &MockConnectionGater{} + m.Dial = func(p peer.ID, addr ma.Multiaddr) bool { + return true + } + + m.PeerDial = func(p peer.ID) bool { + return true + } + + m.Accept = func(c network.ConnMultiaddrs) bool { + return true + } + + m.Secured = func(network.Direction, peer.ID, network.ConnMultiaddrs) bool { + return true + } + + m.Upgraded = func(c network.Conn) (bool, control.DisconnectReason) { + return true, 0 + } + + return m +} + +func (m *MockConnectionGater) InterceptAddrDial(p peer.ID, addr ma.Multiaddr) (allow bool) { + return m.Dial(p, addr) +} + +func (m *MockConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) { + return m.PeerDial(p) +} + +func (m *MockConnectionGater) InterceptAccept(c network.ConnMultiaddrs) (allow bool) { + return m.Accept(c) +} + +func (m *MockConnectionGater) InterceptSecured(d network.Direction, p peer.ID, c network.ConnMultiaddrs) (allow bool) { + return m.Secured(d, p, c) +} + +func (m *MockConnectionGater) InterceptUpgraded(tc network.Conn) (allow bool, reason control.DisconnectReason) { + return m.Upgraded(tc) +} -- GitLab