Skip to content

Commit

Permalink
feat: libp2p identify command can discover peer ids even if they are …
Browse files Browse the repository at this point in the history
…not passed in the multiaddr
  • Loading branch information
aschmahmann committed Aug 1, 2023
1 parent b05def4 commit 973a280
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 7 deletions.
71 changes: 68 additions & 3 deletions lib/identify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,47 @@ package vole

import (
"context"
"fmt"
"regexp"
"sort"

"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-libp2p/p2p/net/swarm"
"github.com/multiformats/go-multiaddr"
)

type IdentifyInfo struct {
PeerId peer.ID
ProtocolVersion string
AgentVersion string
Addresses []multiaddr.Multiaddr
Protocols []protocol.ID
}

func IdentifyRequest(ctx context.Context, maStr string) (*IdentifyInfo, error) {
func IdentifyRequest(ctx context.Context, maStr string, allowUnknownPeer bool) (*IdentifyInfo, error) {
usingBogusPeerID := false

ai, err := peer.AddrInfoFromString(maStr)
if err != nil {
return nil, err
if !allowUnknownPeer {
return nil, err
}
ma, err := multiaddr.NewMultiaddr(maStr)
if err != nil {
return nil, err
}

bogusPeerId, err := peer.Decode("QmadAdJ3f63JyNs65X7HHzqDwV53ynvCcKtNFvdNaz3nhk")
if err != nil {
panic("the hard coded bogus peerID is invalid")
}
usingBogusPeerID = true
ai = &peer.AddrInfo{
ID: bogusPeerId,
Addrs: []multiaddr.Multiaddr{ma},
}
}

h, err := libp2pHost()
Expand All @@ -29,12 +51,54 @@ func IdentifyRequest(ctx context.Context, maStr string) (*IdentifyInfo, error) {
}

if err := h.Connect(ctx, *ai); err != nil {
return nil, err
if !usingBogusPeerID {
return nil, err
}
newPeerId, err := extractPeerIDFromError(err)
if err != nil {
return nil, err
}
ai.ID = newPeerId
if err := h.Connect(ctx, *ai); err != nil {
return nil, err
}
}

return extractIdentifyInfo(h.Peerstore(), ai.ID)
}

func extractPeerIDFromError(inputErr error) (peer.ID, error) {
dialErr, ok := inputErr.(*swarm.DialError)
if !ok {
return "", inputErr
}
errText := dialErr.DialErrors[0].Cause.Error()

noiseRe := regexp.MustCompile(`expected\s(.+?),\sbut\sremote\skey\smatches\s(.+?)$`)
match := noiseRe.FindStringSubmatch(errText)
if len(match) == 3 {
remotePeerIDStr := match[2]
p, err := peer.Decode(remotePeerIDStr)
if err != nil {
return "", fmt.Errorf("there was a peerID mismatch but the returned peerID could not be parsed, %w", err)
}
return p, nil
}

tlsRe := regexp.MustCompile(`expected\s(.+?),\sgot\s(.+?)$`)
match = tlsRe.FindStringSubmatch(errText)
if len(match) == 3 {
remotePeerIDStr := match[2]
p, err := peer.Decode(remotePeerIDStr)
if err != nil {
return "", fmt.Errorf("there was a peerID mismatch but the returned peerID could not be parsed, %w", err)
}
return p, nil
}

return "", inputErr
}

func extractIdentifyInfo(ps peerstore.Peerstore, p peer.ID) (*IdentifyInfo, error) {
info := &IdentifyInfo{}

Expand All @@ -44,6 +108,7 @@ func extractIdentifyInfo(ps peerstore.Peerstore, p peer.ID) (*IdentifyInfo, erro
return nil, err
}

info.PeerId = addrInfo.ID
info.Addresses = addrs
sort.Slice(info.Addresses, func(i, j int) bool { return info.Addresses[i].String() < info.Addresses[j].String() })

Expand Down
77 changes: 76 additions & 1 deletion lib/identify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import (
"testing"

"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-libp2p/p2p/security/noise"
tls "github.com/libp2p/go-libp2p/p2p/security/tls"
quic "github.com/libp2p/go-libp2p/p2p/transport/quic"
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
"github.com/multiformats/go-multiaddr"
)

Expand Down Expand Up @@ -44,7 +50,7 @@ func TestIdentifyRequest(t *testing.T) {
t.Fatal(err)
}

resp, err := IdentifyRequest(ctx, hostAddrs[0].String())
resp, err := IdentifyRequest(ctx, hostAddrs[0].String(), false)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -82,3 +88,72 @@ func TestIdentifyRequest(t *testing.T) {
}
}
}

func TestDiscoverPeerID(t *testing.T) {
runTest := func(t *testing.T, h host.Host) {
t.Helper()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

resp, err := IdentifyRequest(ctx, h.Addrs()[0].String(), true)
if err != nil {
t.Fatal(err)
}
if resp.PeerId != h.ID() {
t.Fatalf("peerID mismatch: expected %s, got %s", h.ID(), resp.PeerId)
}
}

t.Run("tcp+tls", func(t *testing.T) {
h, err := libp2p.New(
libp2p.Transport(tcp.NewTCPTransport),
libp2p.Security(tls.ID, tls.New),
libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"),
)
if err != nil {
t.Fatal(err)
}
runTest(t, h)
})
t.Run("tcp+noise", func(t *testing.T) {
h, err := libp2p.New(
libp2p.Transport(tcp.NewTCPTransport),
libp2p.Security(noise.ID, noise.New),
libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"),
)
if err != nil {
t.Fatal(err)
}
runTest(t, h)
})
t.Run("quic", func(t *testing.T) {
h, err := libp2p.New(
libp2p.Transport(quic.NewTransport),
libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic"),
)
if err != nil {
t.Fatal(err)
}
runTest(t, h)
})
t.Run("quic-v1", func(t *testing.T) {
h, err := libp2p.New(
libp2p.Transport(quic.NewTransport),
libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1"),
)
if err != nil {
t.Fatal(err)
}
runTest(t, h)
})
t.Run("ws", func(t *testing.T) {
h, err := libp2p.New(
libp2p.Transport(ws.New),
libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0/ws"),
)
if err != nil {
t.Fatal(err)
}
runTest(t, h)
})
}
19 changes: 16 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,19 +278,32 @@ func main() {
Usage: "tools for working with libp2p",
Subcommands: []*cli.Command{
{
Name: "identify",
ArgsUsage: "<multiaddr>",
Name: "identify",
ArgsUsage: "<multiaddr>",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "allow-unknown-peer",
Usage: `if the multiaddr does not end with /p2p/PeerID allow trying to determine the peerID at the destination.
Note: connecting to a peer without knowing its peerID is generally insecure, however it is situationally useful.
Note: may not work with some transports such as p2p-circuit (not applicable) and webtransport (requires certificate hashes).
`,
DefaultText: "false",
Value: false,
},
},
Usage: "learn about the peer with the given multiaddr",
Description: "connects to the target address and runs identify against the peer",
Action: func(c *cli.Context) error {
if c.NArg() != 1 {
return fmt.Errorf("invalid number of arguments")
}
resp, err := vole.IdentifyRequest(c.Context, c.Args().First())
allowUnknownPeer := c.Bool("allow-unknown-peer")
resp, err := vole.IdentifyRequest(c.Context, c.Args().First(), allowUnknownPeer)
if err != nil {
return err
}

fmt.Printf("PeerID: %q\n", resp.PeerId)
fmt.Printf("Protocol version: %q\n", resp.ProtocolVersion)
fmt.Printf("Agent version: %q\n", resp.AgentVersion)

Expand Down

0 comments on commit 973a280

Please sign in to comment.