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 }