Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.history/
.idea/
p2p_poc
peer_cache.json
84 changes: 76 additions & 8 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io"
"log"
"net"
"os"
"sync"
"time"
Expand Down Expand Up @@ -70,8 +71,8 @@ func NewClient(config Config) (P2PClient, error) {
hostOpts = append(hostOpts, libp2p.Identity(config.PrivateKey))

// Configure announce addresses if provided (useful for K8s)
var announceAddrs []multiaddr.Multiaddr
if len(config.AnnounceAddrs) > 0 {
Comment on lines +74 to 75
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The announceAddrs variable is declared but the existing address factory logic using config.AnnounceAddrs is removed without preserving its functionality. This will break the original announce address feature when both custom announce addresses and the new address factory are used together.

Copilot uses AI. Check for mistakes.
var announceAddrs []multiaddr.Multiaddr
for _, addrStr := range config.AnnounceAddrs {
maddr, err := multiaddr.NewMultiaddr(addrStr)
if err != nil {
Expand All @@ -87,17 +88,37 @@ func NewClient(config Config) (P2PClient, error) {
logger.Infof("Using custom announce addresses: %v", config.AnnounceAddrs)
}

// Create libp2p host
// Simple address factory to only use custom announce addresses if provided
// Otherwise let libp2p/AutoNAT handle address detection automatically
var addressFactory func([]multiaddr.Multiaddr) []multiaddr.Multiaddr
if len(announceAddrs) > 0 {
addressFactory = func([]multiaddr.Multiaddr) []multiaddr.Multiaddr {
return announceAddrs
}
logger.Infof("Using only custom announce addresses for advertising")
}

// Get bootstrap peers from DHT library - these can act as relays
bootstrapPeers := dht.GetDefaultBootstrapPeerAddrInfos()

// Create libp2p host with NAT traversal options
hostOpts = append(hostOpts,
libp2p.ListenAddrStrings(
"/ip4/0.0.0.0/tcp/0",
"/ip6/::/tcp/0",
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", config.Port), // Listen on all interfaces
fmt.Sprintf("/ip6/::/tcp/%d", config.Port),
),
libp2p.EnableNATService(),
libp2p.EnableHolePunching(),
libp2p.EnableRelay(),
libp2p.NATPortMap(), // Try UPnP/NAT-PMP for automatic port forwarding
libp2p.EnableNATService(), // AutoNAT to detect if we're reachable
libp2p.EnableHolePunching(), // DCUtR protocol for NAT hole punching
libp2p.EnableRelay(), // Act as relay for others
libp2p.EnableAutoRelayWithStaticRelays(bootstrapPeers), // Use bootstrap nodes as potential relays
)

// Only apply address factory if custom announce addresses are provided
if addressFactory != nil {
hostOpts = append(hostOpts, libp2p.AddrsFactory(addressFactory))
}

h, err := libp2p.New(hostOpts...)
if err != nil {
cancel()
Expand All @@ -108,7 +129,6 @@ func NewClient(config Config) (P2PClient, error) {
logger.Infof("Listening on: %v", h.Addrs())

// Set up DHT with bootstrap peers
bootstrapPeers := dht.GetDefaultBootstrapPeerAddrInfos()
kadDHT, err := dht.New(ctx, h, dht.Mode(dht.ModeServer), dht.BootstrapPeers(bootstrapPeers...))
if err != nil {
h.Close()
Expand Down Expand Up @@ -634,3 +654,51 @@ func PrivateKeyFromHex(keyHex string) (crypto.PrivKey, error) {

return priv, nil
}

// Function to check if an IP address is private
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function comment is incomplete. It should document the return value, parameters, and provide examples of what constitutes a private IP address.

Suggested change
// Function to check if an IP address is private
// isPrivateIP checks if the given multiaddr represents a private IP address.
//
// Parameters:
// addr: multiaddr.Multiaddr - The multiaddr to check. It should contain an IPv4 or IPv6 address.
//
// Returns:
// bool - Returns true if the IP address is private (e.g., RFC1918 for IPv4, unique local or link-local for IPv6, or loopback addresses), false otherwise.
//
// Examples of private IP addresses:
// IPv4: "10.0.0.1", "172.16.5.4", "192.168.1.100", "127.0.0.1"
// IPv6: "::1" (loopback), "fc00::1" (unique local), "fe80::1" (link-local)

Copilot uses AI. Check for mistakes.
func isPrivateIP(addr multiaddr.Multiaddr) bool {
ipStr, err := extractIPFromMultiaddr(addr)
if err != nil {
return false
}
// Check for IPv6 loopback
if ipStr == "::1" {
return true
}

ip := net.ParseIP(ipStr)
if ip == nil || ip.To4() == nil {
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition ip.To4() == nil will incorrectly return false for all IPv6 addresses, even though the function handles IPv6 private ranges. This should check if the IP is neither IPv4 nor IPv6, or handle IPv6 addresses properly.

Suggested change
if ip == nil || ip.To4() == nil {
if ip == nil {

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic incorrectly rejects IPv6 addresses. The ip.To4() == nil check means IPv6 addresses will always return false, but IPv6 private ranges are defined below. Remove the ip.To4() == nil condition.

Suggested change
if ip == nil || ip.To4() == nil {
if ip == nil {

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IPv6 addresses will be incorrectly classified as non-private. The condition ip.To4() == nil returns false for all IPv6 addresses, causing the function to return false even for private IPv6 ranges that are defined later in the function.

Suggested change
if ip == nil || ip.To4() == nil {
if ip == nil {

Copilot uses AI. Check for mistakes.
return false
}
Comment on lines +670 to +672
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function only handles IPv4 addresses due to the ip.To4() check, but the extractIPFromMultiaddr function only extracts IPv4 addresses. IPv6 private addresses (like link-local fe80::/10) will not be detected as private. Consider adding IPv6 support or documenting this limitation.

Copilot uses AI. Check for mistakes.

// Define private IPv4 and IPv6 ranges
privateRanges := []*net.IPNet{
// IPv4
{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(8, 32)},
{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(12, 32)},
{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)},
{IP: net.ParseIP("127.0.0.0"), Mask: net.CIDRMask(8, 32)},
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The private IP ranges are duplicated between the connection gater logic (lines 139-146) and the isPrivateIP function (lines 728-737). This duplication could lead to inconsistencies if one is updated without the other.

Copilot uses AI. Check for mistakes.
// IPv6
{IP: net.ParseIP("fc00::"), Mask: net.CIDRMask(7, 128)}, // Unique local address
{IP: net.ParseIP("fe80::"), Mask: net.CIDRMask(10, 128)}, // Link-local unicast
}

// Check if the IP falls into any of the private ranges or is loopback (::1)
for _, r := range privateRanges {
if r.Contains(ip) {
return true
}
}

return false
}

// Function to extract IP information from a Multiaddr (supports IPv4 and IPv6)
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function comment is incomplete. It should document the return values, error conditions, and provide examples of expected input/output formats.

Suggested change
// Function to extract IP information from a Multiaddr (supports IPv4 and IPv6)
// extractIPFromMultiaddr extracts the IP address (IPv4 or IPv6) from a multiaddr.Multiaddr.
//
// Returns:
// - string: The extracted IP address as a string if found.
// - error: An error if the IP address cannot be extracted or the multiaddr does not contain an IP4/IP6 protocol.
//
// Error conditions:
// - If the multiaddr does not contain an IP4 or IP6 protocol, an error is returned.
// - If the protocol value cannot be extracted, an error is returned.
//
// Example:
// addr, _ := multiaddr.NewMultiaddr("/ip4/127.0.0.1/tcp/4001")
// ip, err := extractIPFromMultiaddr(addr)
// // ip == "127.0.0.1", err == nil
//
// addr, _ := multiaddr.NewMultiaddr("/ip6/::1/tcp/4001")
// ip, err := extractIPFromMultiaddr(addr)
// // ip == "::1", err == nil
//
// addr, _ := multiaddr.NewMultiaddr("/tcp/4001")
// ip, err := extractIPFromMultiaddr(addr)
// // ip == "", err != nil

Copilot uses AI. Check for mistakes.
func extractIPFromMultiaddr(addr multiaddr.Multiaddr) (string, error) {
ip, err := addr.ValueForProtocol(multiaddr.P_IP4)
if err == nil && ip != "" {
return ip, nil
}
return addr.ValueForProtocol(multiaddr.P_IP6)
}

6 changes: 6 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@ type Config struct {
// Example: []string{"/ip4/203.0.113.1/tcp/4001"}
// If not provided, libp2p will automatically detect and announce local addresses.
AnnounceAddrs []string

// AllowPrivateIPs indicates whether to allow connections to peers with private IP addresses.
AllowPrivateIPs bool

// Port is the network port to listen on for incoming connections. If zero, a random port will be chosen.
Port int
}
4 changes: 2 additions & 2 deletions example/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ module github.com/ordishs/p2p_poc/example
go 1.25.1

require (
github.com/ordishs/gocore v1.0.81
github.com/bsv-blockchain/go-p2p-message-bus v0.0.0
github.com/libp2p/go-libp2p v0.43.0
github.com/ordishs/gocore v1.0.81
)

require (
Expand Down Expand Up @@ -39,7 +40,6 @@ require (
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.3.0 // indirect
github.com/libp2p/go-libp2p v0.43.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
github.com/libp2p/go-libp2p-kad-dht v0.34.0 // indirect
github.com/libp2p/go-libp2p-kbucket v0.7.0 // indirect
Expand Down
13 changes: 8 additions & 5 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import (
"syscall"
"time"

p2p "github.com/bsv-blockchain/go-p2p-message-bus"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/ordishs/gocore"
p2p "github.com/bsv-blockchain/go-p2p-message-bus"
)

func main() {
name := flag.String("name", "", "Your node name")
privateKey := flag.String("key", "", "Private key hex (will generate if not provided)")
topics := flag.String("topics", "broadcast_p2p_poc", "Comma-separated list of topics to subscribe to")
port := flag.Int("port", 0, "port to listen on (0 for random)")
noBroadcast := flag.Bool("no-broadcast", false, "Disable message broadcasting")

flag.Parse()
Expand Down Expand Up @@ -62,10 +63,12 @@ func main() {

// Create P2P client
client, err := p2p.NewClient(p2p.Config{
Name: *name,
Logger: logger,
PrivateKey: privKey,
PeerCacheFile: "peer_cache.json", // Enable peer persistence
Name: *name,
Logger: logger,
PrivateKey: privKey,
Port: *port,
AllowPrivateIPs: false,
PeerCacheFile: "peer_cache.json", // Enable peer persistence
})
if err != nil {
logger.Fatalf("Failed to create P2P client: %v", err)
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require (
github.com/ipld/go-ipld-prime v0.21.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/koron/go-ssdp v0.0.6 // indirect
Expand All @@ -50,7 +51,9 @@ require (
github.com/libp2p/go-yamux/v5 v5.0.1 // indirect
github.com/libp2p/zeroconf/v2 v2.2.0 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/miekg/dns v1.1.68 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
Expand All @@ -66,6 +69,7 @@ require (
github.com/multiformats/go-multistream v0.6.1 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ordishs/gocore v1.0.81 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v2 v2.2.12 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+
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/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
Expand Down Expand Up @@ -164,9 +166,14 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
Expand Down Expand Up @@ -213,6 +220,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/ordishs/gocore v1.0.81 h1:5egJfLOVrdnK6es2ED1SbXVfEazGSMkpvo1bvvKO8Nc=
github.com/ordishs/gocore v1.0.81/go.mod h1:8/PDn0aIq7/AQcrGBXHE1rkw8bkd33bwgCo2SXDq09s=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
Expand Down Expand Up @@ -437,6 +446,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
Loading