Skip to content

Commit 0375a4d

Browse files
committed
feat: add support for libp2p pnet
1 parent 6f29ae7 commit 0375a4d

9 files changed

Lines changed: 220 additions & 14 deletions

File tree

lib/bitswap.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ var _ json.Marshaler = (*BsCheckOutput)(nil)
6161
func CheckBitswapCID(ctx context.Context, h host.Host, c cid.Cid, ma multiaddr.Multiaddr, getBlock bool) (*BsCheckOutput, error) {
6262
var err error
6363
if h == nil {
64-
h, err = libp2pHost()
64+
h, err = libp2pHost(ctx)
6565
}
6666

6767
if err != nil {
@@ -171,10 +171,8 @@ loop:
171171
}, nil
172172
}
173173

174-
func GetBitswapCID(root cid.Cid, ai *peer.AddrInfo) error {
175-
176-
ctx := context.Background()
177-
h, err := libp2pHost()
174+
func GetBitswapCID(ctx context.Context, root cid.Cid, ai *peer.AddrInfo) error {
175+
h, err := libp2pHost(ctx)
178176
if err != nil {
179177
return err
180178
}

lib/dht.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
)
1717

1818
func DhtProtocolMessenger(ctx context.Context, proto protocol.ID, ai *peer.AddrInfo) (*dhtpb.ProtocolMessenger, error) {
19-
h, err := libp2pHost()
19+
h, err := libp2pHost(ctx)
2020
if err != nil {
2121
return nil, err
2222
}

lib/identify.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func IdentifyRequest(ctx context.Context, maStr string, allowUnknownPeer bool) (
4545
}
4646
}
4747

48-
h, err := libp2pHost()
48+
h, err := libp2pHost(ctx)
4949
if err != nil {
5050
return nil, err
5151
}

lib/libp2p.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package vole
22

33
import (
4+
"context"
5+
46
"github.com/libp2p/go-libp2p"
57
"github.com/libp2p/go-libp2p/core/host"
68
)
79

8-
func libp2pHost() (host.Host, error) {
9-
h, err := libp2p.New(
10+
func libp2pHost(ctx context.Context) (host.Host, error) {
11+
opts := []libp2p.Option{
1012
libp2p.EnableHolePunching(),
11-
)
13+
}
14+
if psk, ok := pnetPSKFromContext(ctx); ok {
15+
opts = append(opts, libp2p.PrivateNetwork(psk))
16+
}
17+
18+
h, err := libp2p.New(opts...)
1219
if err != nil {
1320
return nil, err
1421
}

lib/only_connect.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func OnlyConnect(ctx context.Context, p *peer.AddrInfo) error {
14-
h, err := libp2pHost()
14+
h, err := libp2pHost(ctx)
1515
if err != nil {
1616
return err
1717
}

lib/ping.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func Ping(ctx context.Context, forceRelay bool, p *peer.AddrInfo) error {
2626
}
2727
}
2828

29-
h, err := libp2pHost()
29+
h, err := libp2pHost(ctx)
3030
if err != nil {
3131
return err
3232
}

lib/pnet.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package vole
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
"github.com/libp2p/go-libp2p/core/pnet"
9+
)
10+
11+
type pnetPSKContextKey struct{}
12+
13+
func WithPnetPSK(ctx context.Context, psk pnet.PSK) context.Context {
14+
if ctx == nil {
15+
ctx = context.Background()
16+
}
17+
cpy := make([]byte, len(psk))
18+
copy(cpy, psk)
19+
return context.WithValue(ctx, pnetPSKContextKey{}, pnet.PSK(cpy))
20+
}
21+
22+
func pnetPSKFromContext(ctx context.Context) (pnet.PSK, bool) {
23+
if ctx == nil {
24+
return nil, false
25+
}
26+
psk, ok := ctx.Value(pnetPSKContextKey{}).(pnet.PSK)
27+
return psk, ok
28+
}
29+
30+
func LoadPnetPSK(path string) (pnet.PSK, error) {
31+
f, err := os.Open(path)
32+
if err != nil {
33+
return nil, fmt.Errorf("read pnet swarm key %q: %w", path, err)
34+
}
35+
defer func() { _ = f.Close() }()
36+
37+
psk, err := pnet.DecodeV1PSK(f)
38+
if err != nil {
39+
return nil, fmt.Errorf("decode pnet swarm key %q: %w", path, err)
40+
}
41+
return psk, nil
42+
}

lib/pnet_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package vole
2+
3+
import (
4+
"context"
5+
"errors"
6+
"os"
7+
"strings"
8+
"testing"
9+
"time"
10+
11+
"github.com/libp2p/go-libp2p/core/peer"
12+
corepnet "github.com/libp2p/go-libp2p/core/pnet"
13+
)
14+
15+
const testSwarmKeyV1 = "/key/swarm/psk/1.0.0/\n/base16/\n0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n"
16+
const testSwarmKeyV1Alt = "/key/swarm/psk/1.0.0/\n/base16/\nfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210\n"
17+
18+
func writeTempFile(t *testing.T, contents string) string {
19+
t.Helper()
20+
f, err := os.CreateTemp(t.TempDir(), "swarmkey-*")
21+
if err != nil {
22+
t.Fatalf("CreateTemp: %v", err)
23+
}
24+
if _, err := f.WriteString(contents); err != nil {
25+
_ = f.Close()
26+
t.Fatalf("WriteString: %v", err)
27+
}
28+
if err := f.Close(); err != nil {
29+
t.Fatalf("Close: %v", err)
30+
}
31+
return f.Name()
32+
}
33+
34+
func TestLoadPnetPSK_MissingFile(t *testing.T) {
35+
_, err := LoadPnetPSK("does-not-exist")
36+
if err == nil {
37+
t.Fatalf("expected error")
38+
}
39+
}
40+
41+
func TestLoadPnetPSK_InvalidFile(t *testing.T) {
42+
path := writeTempFile(t, "not a swarm key")
43+
_, err := LoadPnetPSK(path)
44+
if err == nil {
45+
t.Fatalf("expected error")
46+
}
47+
}
48+
49+
func TestPnet_AllowsOnlySameKey(t *testing.T) {
50+
path := writeTempFile(t, testSwarmKeyV1)
51+
psk, err := LoadPnetPSK(path)
52+
if err != nil {
53+
t.Fatalf("LoadPnetPSK: %v", err)
54+
}
55+
56+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
57+
defer cancel()
58+
59+
hNo, err := libp2pHost(ctx)
60+
if err != nil {
61+
t.Fatalf("libp2pHost(no pnet): %v", err)
62+
}
63+
defer hNo.Close()
64+
65+
hYesA, err := libp2pHost(WithPnetPSK(ctx, psk))
66+
if err != nil {
67+
t.Fatalf("libp2pHost(pnet A): %v", err)
68+
}
69+
defer hYesA.Close()
70+
71+
hYesB, err := libp2pHost(WithPnetPSK(ctx, psk))
72+
if err != nil {
73+
t.Fatalf("libp2pHost(pnet B): %v", err)
74+
}
75+
defer hYesB.Close()
76+
77+
// pnet->no-pnet should fail.
78+
aiNo := peer.AddrInfo{ID: hNo.ID(), Addrs: hNo.Addrs()}
79+
dialCtx, dialCancel := context.WithTimeout(ctx, 3*time.Second)
80+
err = hYesA.Connect(dialCtx, aiNo)
81+
dialCancel()
82+
if err == nil {
83+
t.Fatalf("expected pnet dial to non-pnet host to fail")
84+
}
85+
// Depending on the transport/OS, the pnet failure may be wrapped in dial/security negotiation errors.
86+
if !corepnet.IsPNetError(err) && !strings.Contains(strings.ToLower(err.Error()), "privnet") {
87+
// Don't fail the test on classification; the key property is that the dial fails.
88+
t.Logf("dial failed with non-pnet-classified error: %T: %v", err, err)
89+
}
90+
91+
// pnet->pnet (same key) should succeed.
92+
aiYesB := peer.AddrInfo{ID: hYesB.ID(), Addrs: hYesB.Addrs()}
93+
dialCtx2, dialCancel2 := context.WithTimeout(ctx, 5*time.Second)
94+
err = hYesA.Connect(dialCtx2, aiYesB)
95+
dialCancel2()
96+
if err != nil {
97+
// If this fails, surface the most useful root cause.
98+
var pnetErr corepnet.Error
99+
if errors.As(err, &pnetErr) {
100+
t.Fatalf("unexpected pnet error connecting same-key hosts: %v", err)
101+
}
102+
t.Fatalf("expected same-key hosts to connect, got: %v", err)
103+
}
104+
}
105+
106+
func TestPnet_DifferentKeysFail(t *testing.T) {
107+
pathA := writeTempFile(t, testSwarmKeyV1)
108+
pskA, err := LoadPnetPSK(pathA)
109+
if err != nil {
110+
t.Fatalf("LoadPnetPSK(A): %v", err)
111+
}
112+
113+
pathB := writeTempFile(t, testSwarmKeyV1Alt)
114+
pskB, err := LoadPnetPSK(pathB)
115+
if err != nil {
116+
t.Fatalf("LoadPnetPSK(B): %v", err)
117+
}
118+
119+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
120+
defer cancel()
121+
122+
hA, err := libp2pHost(WithPnetPSK(ctx, pskA))
123+
if err != nil {
124+
t.Fatalf("libp2pHost(pnet A): %v", err)
125+
}
126+
defer hA.Close()
127+
128+
hB, err := libp2pHost(WithPnetPSK(ctx, pskB))
129+
if err != nil {
130+
t.Fatalf("libp2pHost(pnet B): %v", err)
131+
}
132+
defer hB.Close()
133+
134+
aiB := peer.AddrInfo{ID: hB.ID(), Addrs: hB.Addrs()}
135+
dialCtx, dialCancel := context.WithTimeout(ctx, 3*time.Second)
136+
err = hA.Connect(dialCtx, aiB)
137+
dialCancel()
138+
if err == nil {
139+
t.Fatalf("expected different-key pnet connect to fail")
140+
}
141+
}

main.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,31 @@ func main() {
3737
app := &cli.App{
3838
Name: "vole",
3939
Usage: "a collection of tools for digging around IPFS nodes",
40+
Before: func(c *cli.Context) error {
41+
pnetPath := c.Path("pnet")
42+
if pnetPath == "" {
43+
return nil
44+
}
45+
psk, err := vole.LoadPnetPSK(pnetPath)
46+
if err != nil {
47+
return err
48+
}
49+
c.Context = vole.WithPnetPSK(c.Context, psk)
50+
return nil
51+
},
4052
Authors: []*cli.Author{
4153
{
4254
Name: "Adin Schmahmann",
4355
4456
},
4557
},
58+
Flags: []cli.Flag{
59+
&cli.PathFlag{
60+
Name: "pnet",
61+
Usage: "path to a libp2p private network swarm key file to use for all commands that create a libp2p host",
62+
Value: "",
63+
},
64+
},
4665
Commands: []*cli.Command{
4766
{
4867
Name: "bitswap",
@@ -354,7 +373,6 @@ Note: may not work with some transports such as p2p-circuit (not applicable) and
354373
Name: "ping",
355374
ArgsUsage: "<multiaddr>",
356375
Flags: []cli.Flag{
357-
358376
&cli.BoolFlag{
359377
Name: "force-relay",
360378
Usage: `Ping the peer over a relay instead of a direct connection`,
@@ -446,7 +464,7 @@ var bitswapGetCmd = &cli.Command{
446464
return err
447465
}
448466

449-
return vole.GetBitswapCID(root, ai)
467+
return vole.GetBitswapCID(cctx.Context, root, ai)
450468
},
451469
}
452470
var bitswapCheckCmd = &cli.Command{

0 commit comments

Comments
 (0)