An open API service indexing awesome lists of open source software.

https://github.com/mevdschee/p2pquic-go


https://github.com/mevdschee/p2pquic-go

Last synced: 3 months ago
JSON representation

Awesome Lists containing this project

README

          

# p2pquic-go

p2pquic-go is a peer-to-peer QUIC library in Go that enables direct connections between peers behind NAT using UDP hole-punching and QUIC transport. It provides both a reusable library and command-line tools for testing.

## Features

- **NAT Traversal**: UDP hole-punching with STUN support
- **QUIC Transport**: Reliable, encrypted peer-to-peer connections
- **Simple API**: Easy-to-use library for building P2P applications
- **Signaling Server**: Coordinate peer discovery and connection setup
- **Testing Tools**: Command-line utilities for testing peer connections

## Installation

```bash
go get github.com/mevdschee/p2pquic-go
```

## Project Structure

```
p2pquic-go/
├── pkg/
│ ├── p2pquic/ # Core P2P QUIC library
│ │ ├── p2pquic.go # Main peer implementation
│ │ ├── signaling.go # Signaling client
│ │ └── types.go # Data structures
│ └── signaling/ # Decoupled signaling server (transport-agnostic)
│ └── server.go # Peer registry logic
├── cmd/
│ ├── p2pquic-test/ # Peer testing tool
│ └── p2pquic-signal/ # HTTP signaling server
└── examples/
└── simple/ # Basic usage example
```

**Key Packages:**
- **`pkg/p2pquic`** - Reusable library for P2P QUIC connections with NAT traversal
- **`pkg/signaling`** - Transport-agnostic signaling server (not coupled to HTTP)
- **`cmd/p2pquic-test`** - Command-line tool for testing peer connections
- **`cmd/p2pquic-signal`** - HTTP wrapper around the signaling package

## Library Usage

### Basic Example

```go
package main

import (
"context"
"log"

"github.com/mevdschee/p2pquic-go/pkg/p2pquic"
)

func main() {
// Create a peer
config := p2pquic.Config{
PeerID: "my-peer",
LocalPort: 9000,
SignalingURL: "http://signaling-server:8080",
EnableSTUN: true,
}

peer, err := p2pquic.NewPeer(config)
if err != nil {
log.Fatal(err)
}
defer peer.Close()

// Discover NAT candidates
candidates, err := peer.DiscoverCandidates()
if err != nil {
log.Fatal(err)
}

// Register with signaling server
if err := peer.Register(); err != nil {
log.Fatal(err)
}

// Server mode: listen for connections
if err := peer.Listen(); err != nil {
log.Fatal(err)
}

conn, err := peer.Accept(context.Background())
if err != nil {
log.Fatal(err)
}

// Use the QUIC connection...
}
```

### Client Mode

```go
// Connect to a remote peer (candidates fetched from signaling server)
conn, err := peer.Connect("remote-peer-id")
if err != nil {
log.Fatal(err)
}
defer conn.CloseWithError(0, "done")

// Or provide candidates directly:
conn, err := peer.Connect("remote-peer-id",
p2pquic.WithCandidates(
p2pquic.Candidate{IP: "192.168.1.100", Port: 9000},
p2pquic.Candidate{IP: "1.2.3.4", Port: 9000},
),
)

// Open a stream and communicate
stream, err := conn.OpenStreamSync(context.Background())
if err != nil {
log.Fatal(err)
}
defer stream.Close()

// Write and read data
stream.Write([]byte("Hello!"))
buf := make([]byte, 1024)
n, _ := stream.Read(buf)
log.Printf("Received: %s", buf[:n])
```

## Command-Line Tools

### Signaling Server

The signaling server is available both as a standalone HTTP server and as a reusable package.

#### As a Command-Line Tool

Start the HTTP signaling server on a publicly accessible machine:

```bash
# Build
go build ./cmd/p2pquic-signal

# Run
./p2pquic-signal -port 8080
```

#### As a Library

The `pkg/signaling` package is **transport-agnostic** and can be used with any transport layer (HTTP, gRPC, WebSocket, etc.):

```go
import "github.com/mevdschee/p2pquic-go/pkg/signaling"

// Create signaling server
server := signaling.NewServer()

// Register a peer
server.Register("peer-id", []signaling.Candidate{
{IP: "192.168.1.100", Port: 9000},
})

// Get peer info
peer, exists := server.GetPeer("peer-id")

// List all peers
peers := server.GetAllPeers()
```

The HTTP server in `cmd/p2pquic-signal` is just a thin wrapper around this package.

### Peer Testing Tool

Test peer-to-peer connections:

**Server Mode:**
```bash
# Build
go build ./cmd/p2pquic-test

# Run server peer (defaults to ID "server")
./p2pquic-test -mode server -port 9000 -signaling http://your-server:8080

# Or with custom ID
./p2pquic-test -mode server -id myserver -port 9000 -signaling http://your-server:8080
```

**Client Mode:**
```bash
# Run client peer connecting to "server" (defaults to ID "client")
./p2pquic-test -mode client -remote server -port 9001 -signaling http://your-server:8080

# Or with custom ID connecting to "myserver"
./p2pquic-test -mode client -id myclient -remote myserver -port 9001 -signaling http://your-server:8080
```

**Flags:**
- `-mode`: Operation mode (`server` or `client`)
- `-id`: Unique peer identifier
- `-remote`: Remote peer ID to connect to (client mode only)
- `-port`: Local UDP port to bind to
- `-signaling`: Signaling server URL
- `-stun`: Enable STUN for public IP discovery (default: true)

> **Why does the client need a port?**
>
> Both peers need a specific port for UDP hole-punching to work correctly:
>
> 1. **STUN Discovery**: The peer discovers its public IP:port mapping using this port
> 2. **UDP Hole-Punching**: Sends packets from this port to create NAT mappings
> 3. **QUIC Connection**: Receives the actual connection on this same port
>
> All three steps must use the **same port**, otherwise NAT mappings won't match and hole-punching will fail.
>
> The different ports in examples (`9000` vs `9001`) are only needed for **local testing on the same machine**. In production across different networks, both peers can use the same port number (e.g., both use `9000`).

## How It Works

1. **Candidate Discovery**: Each peer discovers its network candidates using STUN (public IP) and local network interfaces
2. **Signaling**: Peers register their candidates with a central signaling server
3. **UDP Hole-Punching**: Client sends UDP packets to all server candidates to "punch holes" in NATs
4. **QUIC Connection**: After hole-punching, a QUIC connection is established directly between peers

## Architecture

```
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Peer A │ │ Signaling Server │ │ Peer B │
│ (Behind NAT)│ │ (Public Server) │ │ (Behind NAT)│
└──────┬──────┘ └────────┬─────────┘ └──────┬──────┘
│ │ │
│ 1. Register candidates │ │
├────────────────────────►│ │
│ │ 2. Register candidates │
│ │◄─────────────────────────┤
│ │ │
│ 3. Get peer B info │ │
├────────────────────────►│ │
│ │ │
│ 4. UDP hole-punch packets │
├───────────────────────────────────────────────────►│
│◄───────────────────────────────────────────────────┤
│ │ │
│ 5. QUIC connection established │
├═══════════════════════════════════════════════════►│
```

## API Reference

### `Config`

Configuration for creating a peer:

```go
type Config struct {
PeerID string // Unique peer identifier
LocalPort int // UDP port to bind to
SignalingURL string // Signaling server URL
EnableSTUN bool // Enable STUN discovery
}
```

### `Peer`

Main peer interface:

- `NewPeer(config Config) (*Peer, error)` - Create a new peer
- `DiscoverCandidates() ([]Candidate, error)` - Discover NAT candidates
- `Register() error` - Register with signaling server
- `Listen() error` - Start listening for incoming connections
- `Accept(ctx context.Context) (*quic.Conn, error)` - Accept incoming connection
- `Connect(remotePeerID string, opts ...ConnectOption) (*quic.Conn, error)` - Connect to remote peer
- `ContinuousHolePunch(ctx context.Context)` - Continuously punch holes to discovered peers
- `Close() error` - Close peer and release resources

### `ConnectOption`

Functional options for customizing connection behavior:

- `WithCandidates(candidates ...Candidate)` - Provide candidates directly instead of fetching from signaling server

### `signaling.Server`

Transport-agnostic signaling server (in `pkg/signaling`):

- `NewServer() *Server` - Create a new signaling server
- `Register(peerID string, candidates []Candidate) error` - Register a peer
- `GetPeer(peerID string) (*PeerInfo, bool)` - Get peer information
- `GetAllPeers() []*PeerInfo` - List all registered peers
- `RemovePeer(peerID string)` - Remove a peer from registry
- `PeerCount() int` - Get number of registered peers

## Testing NAT Traversal

To test actual NAT traversal:

1. Deploy signaling server on a **public server** (VPS, cloud instance)
2. Run server peer on **Machine A** (behind NAT)
3. Run client peer on **Machine B** (behind different NAT)
4. Observe direct P2P connection establishment

## Limitations

- **Symmetric NAT**: May fail if both peers have strict symmetric NAT
- **Firewall Rules**: Some firewalls block all unsolicited UDP traffic
- **Port Randomization**: Some NATs use cryptographic port randomization

## Dependencies

- `github.com/quic-go/quic-go` - QUIC implementation in Go

## License

See LICENSE file for details.