A peer-to-peer networking tool that enables secure communication between devices behind NAT/firewalls using the Syncthing relay network.
Note: This project is for demonstration purposes only.
- NAT Traversal: Connect 2 machines behind different NAT/firewalls and proxy between them to reach internal networks without requiring a central server or exposing IP addresses
- Censorship Resistance: In restrictive countries, double-hop VPN connections through relays in friendlier countries while appearing as standard TLS traffic
- Reverse Proxy: Serve content to the outside world from within a NAT, similar to ngrok
Uses the Syncthing relay network infrastructure but for arbitrary data instead of file synchronization. The current implementation (v2) provides:
- Relay Management: Automated discovery and connection to Syncthing relays
- Hybrid Dialer: HTTP client support with automatic routing for
.syncthing
domains - SOCKS Support: SOCKS5 proxy functionality for browser integration
- SNI Routing: Multiple subdomain support for reverse proxy scenarios
v2/
directory.
Generate a certificate and key for use with other tools:
go run v2/cmd/generate-cert/main.go --output mykeys.gob --expiry 365
--output
is required, specifies the file to write the keypair--expiry
sets the number of days until expiry (default: 1095)
Start a local SOCKS5 proxy that routes .syncthing
domains through Syncthing relays:
go run v2/cmd/socks-browser/main.go --keys mykeys.gob
- The proxy listens on
127.0.0.1:1080
- Use with browsers or tools that support SOCKS5
- Connections to
*.syncthing
domains are routed via Syncthing relays
Start a SOCKS client or server for peer-to-peer connections:
# SOCKS Server
go run v2/cmd/socks-server/main.go --keys mykeys.gob --trusted trusted_ids.txt
# SOCKS Client
go run v2/cmd/socks-client/main.go --keys mykeys.gob --server <device-id>
Start a reverse proxy that exposes a local or remote HTTP service via Syncthing relay:
go run v2/cmd/reverse-proxy/main.go --target http://localhost:8080 --keys mykeys.gob --trusted trusted_ids.txt --country DE
--target
(required): The backend URL to proxy to--keys
: Path to gob-encoded keypair (use withgenerate-cert
)--trusted
: File with trusted device IDs (one per line, optional)--country
: Relay country code (optional, auto-detects if omitted)
Basic HTTP server example for testing:
go run v2/cmd/http-hello/main.go --keys mykeys.gob --trusted trusted_ids.txt
Create an HTTP client that automatically routes .syncthing
domains through Syncthing relays:
package main
import (
"context"
"crypto/tls"
"io"
"log"
"net/http"
"time"
"github.com/acheong08/syndicate/v2/lib"
"github.com/acheong08/syndicate/v2/lib/crypto"
)
func main() {
cert, err := crypto.NewCertificate("syncthing", 1)
if err != nil {
log.Fatalf("failed to create certificate: %v", err)
}
dialer := lib.NewHybridDialer(cert)
client := &http.Client{
Transport: &http.Transport{
DialContext: dialer.Dial,
},
Timeout: 15 * time.Second,
}
ctx := context.Background()
req, err := http.NewRequestWithContext(ctx, "GET", "http://<deviceid>.syncthing/api/status", nil)
if err != nil {
log.Fatalf("failed to create request: %v", err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
log.Printf("Status: %s\nBody: %s", resp.Status, string(body))
}
Serve HTTP content over Syncthing relays with multiple subdomain support:
package main
import (
"context"
"log"
"net"
"net/http"
"sync"
"github.com/acheong08/syndicate/v2/lib"
"github.com/acheong08/syndicate/v2/lib/crypto"
"github.com/syncthing/syncthing/lib/protocol"
)
func main() {
cert, _ := crypto.NewCertificate("syncthing", 1)
log.Printf("Server Device ID: %s", protocol.NewDeviceID(cert.Certificate[0]))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create multiple HTTP handlers
handler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello from service 1"))
})
handler2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello from service 2"))
})
// Start relay manager
relayChan := lib.StartRelayManager(ctx, cert, []protocol.DeviceID{}, "DE")
// Create channels for different services
service1Chan := make(chan net.Conn, 5)
service2Chan := make(chan net.Conn, 5)
// Route connections based on SNI
go func() {
for {
select {
case <-ctx.Done():
return
case relayOut := <-relayChan:
switch relayOut.Sni {
case "service1":
service1Chan <- relayOut.Conn
case "service2":
service2Chan <- relayOut.Conn
default:
relayOut.Conn.Close()
}
}
}
}()
// Serve multiple services concurrently
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
log.Fatal(lib.ServeHTTP(ctx, handler1, service1Chan))
}()
go func() {
defer wg.Done()
log.Fatal(lib.ServeHTTP(ctx, handler2, service2Chan))
}()
wg.Wait()
}