github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/p2p/peers.go (about)

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/qri-io/qri/config"
     8  	"github.com/qri-io/qri/profile"
     9  
    10  	peer "github.com/libp2p/go-libp2p-core/peer"
    11  	swarm "github.com/libp2p/go-libp2p-swarm"
    12  	ma "github.com/multiformats/go-multiaddr"
    13  )
    14  
    15  // ConnectedQriProfiles lists all connected peers that support the qri protocol
    16  func (n *QriNode) ConnectedQriProfiles(ctx context.Context) map[profile.ID]*config.ProfilePod {
    17  	peers := map[profile.ID]*config.ProfilePod{}
    18  	if n.host == nil {
    19  		return peers
    20  	}
    21  	// TODO (ramfox): refactor to rely on `ConnectedQriPeerIDs` & add GetNetworkAddrs
    22  	// convenience func
    23  	for _, conn := range n.host.Network().Conns() {
    24  		if p, err := n.Repo.Profiles().PeerProfile(ctx, conn.RemotePeer()); err == nil {
    25  			if pe, err := p.Encode(); err == nil {
    26  				pe.Online = true
    27  				// Build host multiaddress,
    28  				// TODO - this should be a convenience func
    29  				hostAddr, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s", conn.RemotePeer().Pretty()))
    30  				if err != nil {
    31  					log.Debug(err.Error())
    32  					return nil
    33  				}
    34  
    35  				pe.NetworkAddrs = []string{
    36  					conn.RemoteMultiaddr().Encapsulate(hostAddr).String(),
    37  				}
    38  				peers[p.ID] = pe
    39  			}
    40  		}
    41  	}
    42  	return peers
    43  }
    44  
    45  // ConnectedQriPeerIDs returns a slice of peer.IDs this peer is currently connected to
    46  func (n *QriNode) ConnectedQriPeerIDs() []peer.ID {
    47  	return n.qis.ConnectedQriPeers()
    48  }
    49  
    50  // ClosestConnectedQriPeers checks if a peer is connected, and if so adds it to the top
    51  // of a slice cap(max) of peers to try to connect to
    52  // TODO - In the future we'll use a few tricks to improve on just iterating the list
    53  // at a bare minimum we should grab a randomized set of peers
    54  func (n *QriNode) ClosestConnectedQriPeers(ctx context.Context, profileID profile.ID, max int) (pid []peer.ID) {
    55  	added := 0
    56  	if !n.Online {
    57  		return []peer.ID{}
    58  	}
    59  
    60  	if peerIDs, err := n.Repo.Profiles().PeerIDs(ctx, profileID); err == nil {
    61  		for _, peerID := range peerIDs {
    62  			if len(n.host.Network().ConnsToPeer(peerID)) > 0 {
    63  				added++
    64  				pid = append(pid, peerID)
    65  			}
    66  		}
    67  	}
    68  
    69  	if len(pid) == 0 {
    70  		for _, conn := range n.host.Network().Conns() {
    71  			peerID := conn.RemotePeer()
    72  			protocols, err := n.host.Peerstore().SupportsProtocols(peerID, string(depQriProtocolID))
    73  			if err != nil {
    74  				continue
    75  			}
    76  			if len(protocols) != 0 {
    77  				pid = append(pid, peerID)
    78  				added++
    79  				if added >= max {
    80  					break
    81  				}
    82  			}
    83  		}
    84  	}
    85  
    86  	return
    87  }
    88  
    89  // peerDifference returns a slice of peer IDs that are present in a but not b
    90  func peerDifference(a, b []peer.ID) (diff []peer.ID) {
    91  	m := make(map[peer.ID]bool)
    92  	for _, bid := range b {
    93  		m[bid] = true
    94  	}
    95  
    96  	for _, aid := range a {
    97  		if _, ok := m[aid]; !ok {
    98  			diff = append(diff, aid)
    99  		}
   100  	}
   101  	return
   102  }
   103  
   104  // PeerInfo returns peer peer ID & network multiaddrs from the Host Peerstore
   105  func (n *QriNode) PeerInfo(pid peer.ID) peer.AddrInfo {
   106  	if !n.Online {
   107  		return peer.AddrInfo{}
   108  	}
   109  
   110  	return n.host.Peerstore().PeerInfo(pid)
   111  }
   112  
   113  // Peers returns a list of currently connected peer IDs
   114  func (n *QriNode) Peers() []peer.ID {
   115  	if n.host == nil {
   116  		return []peer.ID{}
   117  	}
   118  	conns := n.host.Network().Conns()
   119  	seen := make(map[peer.ID]struct{})
   120  	peers := make([]peer.ID, 0, len(conns))
   121  
   122  	for _, c := range conns {
   123  		p := c.LocalPeer()
   124  		if _, found := seen[p]; found {
   125  			continue
   126  		}
   127  
   128  		seen[p] = struct{}{}
   129  		peers = append(peers, p)
   130  	}
   131  
   132  	return peers
   133  }
   134  
   135  // ConnectedPeers lists all IPFS connected peers
   136  func (n *QriNode) ConnectedPeers() []string {
   137  	if n.host == nil {
   138  		return []string{}
   139  	}
   140  	conns := n.host.Network().Conns()
   141  	peers := make([]string, len(conns))
   142  	for i, c := range conns {
   143  		peers[i] = c.RemotePeer().Pretty()
   144  		if ti := n.host.ConnManager().GetTagInfo(c.RemotePeer()); ti != nil {
   145  			peers[i] = fmt.Sprintf("%s, %d, %v", c.RemotePeer().Pretty(), ti.Value, ti.Tags)
   146  		}
   147  	}
   148  
   149  	return peers
   150  }
   151  
   152  // PeerConnectionParams defines parameters for the ConnectToPeer command
   153  type PeerConnectionParams struct {
   154  	Peername  string
   155  	ProfileID profile.ID
   156  	PeerID    peer.ID
   157  	Multiaddr ma.Multiaddr
   158  }
   159  
   160  // ConnectToPeer takes a raw peer ID & tries to work out a route to that
   161  // peer, explicitly connecting to them.
   162  func (n *QriNode) ConnectToPeer(ctx context.Context, p PeerConnectionParams) (*profile.Profile, error) {
   163  	log.Debugf("connect to peer: %v", p)
   164  	pinfo, err := n.peerConnectionParamsToPeerInfo(ctx, p)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	if swarm, ok := n.host.Network().(*swarm.Swarm); ok {
   170  		// clear backoff b/c we're explicitly dialing this peer
   171  		swarm.Backoff().Clear(pinfo.ID)
   172  	}
   173  
   174  	if err := n.host.Connect(ctx, pinfo); err != nil {
   175  		return nil, fmt.Errorf("host connect %s failure: %s", pinfo.ID.Pretty(), err)
   176  	}
   177  
   178  	// do an explicit connection upgrade
   179  	// this will block until the peer's profile has been
   180  	if err := n.qis.QriProfileRequest(ctx, pinfo.ID); err != nil {
   181  		return nil, fmt.Errorf("error establishing qri connections: %w", err)
   182  	}
   183  
   184  	// ConnectedPeerProfile will return nil if the profile is not found
   185  	pro := n.qis.ConnectedPeerProfile(pinfo.ID)
   186  	if err == nil {
   187  		return nil, fmt.Errorf("unable to get profile from peer %q", pinfo.ID)
   188  	}
   189  
   190  	return pro, nil
   191  }
   192  
   193  // DisconnectFromPeer explicitly closes a connection to a peer
   194  func (n *QriNode) DisconnectFromPeer(ctx context.Context, p PeerConnectionParams) error {
   195  	pinfo, err := n.peerConnectionParamsToPeerInfo(ctx, p)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	conns := n.host.Network().ConnsToPeer(pinfo.ID)
   201  	for _, conn := range conns {
   202  		if err := conn.Close(); err != nil {
   203  			return err
   204  		}
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  // peerConnectionParamsToPeerInfo turns connection parameters into something p2p can dial
   211  func (n *QriNode) peerConnectionParamsToPeerInfo(ctx context.Context, p PeerConnectionParams) (pi peer.AddrInfo, err error) {
   212  	if p.Multiaddr != nil {
   213  		return toPeerInfos([]ma.Multiaddr{p.Multiaddr})[0], nil
   214  	} else if len(p.PeerID) > 0 {
   215  		return n.getPeerInfo(p.PeerID)
   216  	}
   217  
   218  	proID := p.ProfileID
   219  	if len(proID) == 0 && p.Peername != "" {
   220  		// TODO - there's lot's of possibile ambiguity around resolving peernames
   221  		// this naive implementation for now just checks the profile store for a
   222  		// matching peername
   223  		proID, err = n.Repo.Profiles().PeernameID(ctx, p.Peername)
   224  		if err != nil {
   225  			return
   226  		}
   227  	}
   228  
   229  	ids, err := n.Repo.Profiles().PeerIDs(ctx, proID)
   230  	if err != nil {
   231  		return
   232  	}
   233  	if len(ids) == 0 {
   234  		return peer.AddrInfo{}, fmt.Errorf("no network info for %s", proID)
   235  	}
   236  
   237  	// TODO - there's ambiguity here that we should address, for now
   238  	// we'll just by default connect to the first peer
   239  	return n.getPeerInfo(ids[0])
   240  }
   241  
   242  // getPeerInfo first looks for local peer info, then tries to fall back to using IPFS
   243  // to do routing lookups
   244  func (n *QriNode) getPeerInfo(pid peer.ID) (peer.AddrInfo, error) {
   245  	// first check for local peer info
   246  	if pinfo := n.host.Peerstore().PeerInfo(pid); len(pinfo.ID) > 0 {
   247  		// _, err := n.RequestProfile(pinfo.ID)
   248  		return pinfo, nil
   249  	}
   250  
   251  	// attempt to use ipfs routing table to discover peer
   252  	ipfsnode, err := n.IPFS()
   253  	if err != nil {
   254  		log.Debug(err.Error())
   255  		return peer.AddrInfo{}, err
   256  	}
   257  
   258  	return ipfsnode.Routing.FindPeer(context.Background(), pid)
   259  }