mirror of
https://github.com/bolucat/Archive.git
synced 2025-12-24 13:28:37 +08:00
Update On Fri Dec 19 19:41:39 CET 2025
This commit is contained in:
654
nodepass/.github/copilot-instructions.md
vendored
654
nodepass/.github/copilot-instructions.md
vendored
@@ -1,96 +1,70 @@
|
||||
# NodePass AI Coding Agent Instructions
|
||||
# NodePass Development Guide
|
||||
|
||||
## Project Overview
|
||||
## Architecture Overview
|
||||
|
||||
NodePass is an enterprise-grade TCP/UDP network tunneling solution with a three-tier S/C/M architecture supporting server, client, and master modes. Written in Go 1.25+, focused on performance, security, and zero-configuration deployment.
|
||||
NodePass is a Go-based TCP/UDP tunneling solution with a **tri-modal architecture** (Server/Client/Master) built on separation of control and data channels.
|
||||
|
||||
## Architecture Essentials
|
||||
### Core Components
|
||||
|
||||
### Three-Tier S/C/M Architecture
|
||||
- **`cmd/nodepass/`**: Entry point with URL-based configuration parsing
|
||||
- `main.go`: Simple entry that invokes `start()` with version injection
|
||||
- `core.go`: URL parser, logger initialization, TLS mode selection, core factory (`createCore()`)
|
||||
- **`internal/`**: Three operational modes sharing `common.go` base (~1970 lines):
|
||||
- `server.go`: Accepts tunnel connections via `tunnelHandshake()`, binds target addresses, supports bidirectional data flow
|
||||
- `client.go`: Initiates tunnel connections, supports single-end forwarding (`singleStart()`) and dual-end handshake (`commonStart()`)
|
||||
- `master.go`: RESTful API server with instance management, SSE events, gob persistence (~2165 lines)
|
||||
- `common.go`: Shared functionality - DNS caching, buffer pools, slot management, connection routing
|
||||
- **External packages** (NodePassProject org on GitHub):
|
||||
- `pool`: TCP connection pooling with auto-scaling (min/max capacity)
|
||||
- `quic`: QUIC transport with 0-RTT support
|
||||
- `npws`: WebSocket transport adapter
|
||||
- `conn`: Utilities (`DataExchange`, `StatConn` for traffic accounting, `RateLimiter` for bandwidth control)
|
||||
- `logs`: Structured logger with levels (none/debug/info/warn/error/event)
|
||||
- `cert`: TLS certificate generation and management
|
||||
|
||||
1. **Server Mode** (`internal/server.go`): Accepts tunnel connections, manages connection pools, forwards traffic bidirectionally
|
||||
2. **Client Mode** (`internal/client.go`): Connects to servers, supports single-end forwarding or dual-end handshake modes
|
||||
3. **Master Mode** (`internal/master.go`): RESTful API for dynamic instance management with persistent state in `nodepass.gob`
|
||||
### Data Flow Modes
|
||||
|
||||
### Critical Design Patterns
|
||||
1. **Server Receives Mode** (Reverse): Server binds target address locally → signals client → client connects back → data flows: External → Server → Client → Target
|
||||
2. **Server Sends Mode** (Forward): Server connects to remote target → client signals server → server creates outgoing connection → data flows: Client → Server → Remote Target
|
||||
3. **Client Single-End Forwarding**: Client binds tunnel address locally (e.g., `127.0.0.1:8080`) → direct forwarding to target without server coordination (no control channel)
|
||||
|
||||
- **Separation of Control/Data Channels**:
|
||||
- Control channel: Unencrypted TCP for signaling (`np://` scheme with fragments)
|
||||
- Data channel: Configurable TLS (modes 0/1/2) for actual traffic
|
||||
|
||||
- **Connection Pooling**: Pre-established connections via `github.com/NodePassProject/pool` library
|
||||
- Server controls `max` pool capacity, passes to client during handshake
|
||||
- Client manages `min` capacity for persistent connections
|
||||
- QUIC multiplexing available as alternative transport (`quic=1`)
|
||||
|
||||
- **Bidirectional Data Flow**: Automatic mode detection in `Common.runMode`
|
||||
- Mode 0: Auto-detect based on target address bindability
|
||||
- Mode 1: Reverse/single-end (server receives OR client listens locally)
|
||||
- Mode 2: Forward/dual-end (server sends OR client connects remotely)
|
||||
Mode selection is **automatic** via `initTargetListener()` success/failure. Server tries binding target address; if successful = mode 1 (reverse), if fails = mode 2 (forward). Client tries binding tunnel address; if successful = single-end, if fails = dual-end. Force with `mode` query parameter (`0`=auto, `1`=reverse/single, `2`=forward/dual).
|
||||
|
||||
### Key Components
|
||||
- `/cmd/nodepass/main.go`: Entry point, version variable injection
|
||||
- `/cmd/nodepass/core.go`: Mode dispatch, TLS setup, certificate hot-reload
|
||||
- `/internal/common.go`: Shared primitives (buffer pools, slot management, DNS resolution, encoding)
|
||||
- `/internal/{server,client,master}.go`: Mode-specific implementations inheriting `Common`
|
||||
## URL-Based Configuration
|
||||
|
||||
### External Dependencies (NodePassProject Ecosystem)
|
||||
All configuration through URL scheme: `<mode>://<auth>@<tunnel>/<target>?<params>`
|
||||
|
||||
All critical networking primitives are in separate libraries:
|
||||
- `github.com/NodePassProject/cert`: TLS certificate generation and management
|
||||
- `github.com/NodePassProject/conn`: Enhanced connections (`StatConn` with traffic tracking)
|
||||
- `github.com/NodePassProject/logs`: Multi-level logger (None/Debug/Info/Warn/Error/Event)
|
||||
- `github.com/NodePassProject/name`: DNS resolver with caching and background refresh
|
||||
- `github.com/NodePassProject/pool`: TCP connection pooling with auto-scaling
|
||||
- `github.com/NodePassProject/quic`: QUIC multiplexing for 0-RTT connections
|
||||
|
||||
**Never modify these libraries directly** - they're external dependencies. Use their exported APIs only.
|
||||
|
||||
## Configuration System
|
||||
|
||||
### URL-Based Configuration
|
||||
|
||||
All modes use URL-style configuration: `scheme://[password@]host:port/target?param=value`
|
||||
|
||||
```bash
|
||||
# Server: bind_addr/target_addr with pool capacity and TLS
|
||||
server://password@0.0.0.0:10101/127.0.0.1:8080?max=1024&tls=1&log=debug
|
||||
|
||||
# Client: server_addr/local_addr with min capacity and mode
|
||||
client://password@server:10101/127.0.0.1:9090?min=128&mode=0&rate=100
|
||||
|
||||
# Master: api_addr/prefix with TLS and custom certs
|
||||
master://0.0.0.0:9090/api?log=info&tls=2&crt=/path/cert.pem&key=/path/key.pem
|
||||
**URL Structure Examples:**
|
||||
```
|
||||
server://password@0.0.0.0:10101/127.0.0.1:8080?tls=1&max=512
|
||||
client://password@server.com:10101/localhost:8080?min=64&type=1
|
||||
master://0.0.0.0:9090/api?log=debug&tls=2&crt=/path/cert.pem&key=/path/key.pem
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
**Critical query parameters:**
|
||||
- `log`: Log level - `none`|`debug`|`info`(default)|`warn`|`error`|`event`
|
||||
- `tls`: Encryption mode - `0` (plain TCP/UDP), `1` (self-signed cert in memory), `2` (custom cert with `crt`/`key` files)
|
||||
- Mode 0: No encryption, fastest but insecure
|
||||
- Mode 1: Auto-generated self-signed cert, no verification, protects against passive sniffing
|
||||
- Mode 2: Custom certificate with validation, requires both `crt` and `key` parameters pointing to PEM files
|
||||
- **Note**: QUIC transport (`type=1`) requires minimum `tls=1`
|
||||
- `type`: Pool transport protocol - `0` (TCP pool, default), `1` (QUIC with 0-RTT), `2` (WebSocket)
|
||||
- `mode`: Force run mode - `0` (auto-detect via binding), `1` (server=reverse/client=single-end), `2` (server=forward/client=dual-end)
|
||||
- `dns`: DNS cache TTL duration (default `5m`, accepts Go duration syntax like `30s`, `10m`, `1h`)
|
||||
- `min`: Client minimum pool capacity (default `64`)
|
||||
- `max`: Server maximum pool capacity (default `1024`)
|
||||
- `rate`: Bandwidth limit in **Mbps * 8** (e.g., `rate=100` = 100Mbps = 12.5MB/s; internal unit is bytes/sec, computed as rate*125000)
|
||||
- `slot`: Max concurrent connections - TCP+UDP combined (default `65536`, `0`=unlimited)
|
||||
- `proxy`: PROXY protocol version - `0` (disabled), `1` (v1 text format), `2` (v2 binary format)
|
||||
- `read`: Connection read timeout (default `0` = infinite, accepts Go duration like `30s`, `5m`)
|
||||
- `dial`: Local bind IP for outgoing connections (default `auto` = system routing, or specific IP like `192.168.1.100`)
|
||||
- Automatic fallback to system routing if specified IP fails (logged as "fallback to system auto")
|
||||
- `notcp`: Disable TCP forwarding - `0` (enabled), `1` (disabled)
|
||||
- `noudp`: Disable UDP forwarding - `0` (enabled), `1` (disabled)
|
||||
|
||||
- `log`: none|debug|info|warn|error|event (default: info)
|
||||
- `tls`: 0=plain, 1=self-signed, 2=custom cert (server/master only, client inherits from server)
|
||||
- `min`/`max`: Connection pool capacity (client sets min, server sets max)
|
||||
- `mode`: 0=auto, 1=reverse/single-end, 2=forward/dual-end
|
||||
- `quic`: 0=TCP pool, 1=QUIC multiplexing (requires tls≥1)
|
||||
- `dns`: Custom DNS servers (comma-separated, default: 1.1.1.1,8.8.8.8)
|
||||
- `read`: Timeout duration (e.g., 1h, 30m, 15s, default: 0=no timeout)
|
||||
- `rate`: Mbps bandwidth limit (0=unlimited)
|
||||
- `slot`: Max concurrent connections (default: 65536)
|
||||
- `proxy`: PROXY protocol v1 support (0=off, 1=on)
|
||||
- `dial`: Local bind IP for outbound connections (default: auto)
|
||||
- `notcp`/`noudp`: Disable TCP/UDP (0=enabled, 1=disabled)
|
||||
**Password field usage:** The `@` password portion in URLs (e.g., `mykey@server:10101`) becomes `tunnelKey` for authentication - it's NOT a system password, just a shared secret for tunnel validation. Server compares incoming `tunnelKey` via XOR+base64 encoding in handshake.
|
||||
|
||||
### Environment Variables for Tuning
|
||||
|
||||
Runtime behavior tunable without recompilation (see `internal/common.go`):
|
||||
```go
|
||||
NP_TCP_DATA_BUF_SIZE=16384 // TCP buffer size
|
||||
NP_UDP_DATA_BUF_SIZE=16384 // UDP buffer size
|
||||
NP_HANDSHAKE_TIMEOUT=5s // Handshake timeout
|
||||
NP_POOL_GET_TIMEOUT=5s // Pool connection acquisition timeout
|
||||
NP_REPORT_INTERVAL=5s // Health check reporting interval
|
||||
NP_RELOAD_INTERVAL=1h // TLS cert hot-reload interval (mode 2)
|
||||
NP_SEMAPHORE_LIMIT=65536 // Signal channel buffer size
|
||||
NP_DNS_CACHING_TTL=5m // DNS cache TTL
|
||||
```
|
||||
Examples in `docs/en/examples.md`, full configuration reference in `docs/en/configuration.md`.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
@@ -98,199 +72,439 @@ NP_DNS_CACHING_TTL=5m // DNS cache TTL
|
||||
|
||||
```bash
|
||||
# Development build
|
||||
go build -o nodepass ./cmd/nodepass
|
||||
cd cmd/nodepass
|
||||
go build -ldflags "-X main.version=dev"
|
||||
|
||||
# Release build with version injection (mimics .goreleaser.yml)
|
||||
go build -trimpath -ldflags="-s -w -X main.version=1.0.0" -o nodepass ./cmd/nodepass
|
||||
```
|
||||
# Release build (via goreleaser)
|
||||
goreleaser build --snapshot --clean
|
||||
|
||||
### Testing Manually
|
||||
|
||||
No automated test suite exists currently. Test via real-world scenarios:
|
||||
|
||||
```bash
|
||||
# Terminal 1: Server with debug logging and self-signed TLS
|
||||
./nodepass "server://0.0.0.0:10101/127.0.0.1:8080?log=debug&tls=1&max=256"
|
||||
|
||||
# Terminal 2: Client connecting to server
|
||||
./nodepass "client://localhost:10101/127.0.0.1:9090?log=debug&min=64"
|
||||
|
||||
# Terminal 3: Master API mode
|
||||
./nodepass "master://0.0.0.0:9090/api?log=debug&tls=0"
|
||||
```
|
||||
|
||||
**Test checklist** (from CONTRIBUTING.md):
|
||||
1. Test each mode (server, client, master) with `log=debug`
|
||||
2. Verify TCP and UDP forwarding separately
|
||||
3. Test all TLS modes (0, 1, 2) with certificate validation
|
||||
4. Test QUIC mode (`quic=1`) with TLS≥1
|
||||
5. Verify graceful shutdown with SIGTERM/SIGINT
|
||||
6. Stress test with high concurrency and connection pool scaling
|
||||
|
||||
### Docker Build
|
||||
|
||||
```bash
|
||||
# Docker build (multi-stage, scratch-based final image)
|
||||
docker build --build-arg VERSION=dev -t nodepass:dev .
|
||||
```
|
||||
|
||||
### Release Process
|
||||
Build produces single static binary with no external dependencies. The `-ldflags "-X main.version=..."` injects version into `main.version` variable displayed in `exit()` banner.
|
||||
|
||||
Uses GoReleaser on tag push (`v*.*.*`). See `.goreleaser.yml` for build matrix (Linux, Windows, macOS, FreeBSD across multiple architectures).
|
||||
### Testing Patterns
|
||||
|
||||
## Code Patterns and Conventions
|
||||
**No test suite exists** - all testing is manual via URL invocations. Common test scenarios:
|
||||
|
||||
### Error Handling
|
||||
```bash
|
||||
# Server mode (binds :10101 for tunnel, forwards to local 8080)
|
||||
nodepass "server://:10101/127.0.0.1:8080?log=debug&tls=1"
|
||||
|
||||
Always wrap errors with context using `fmt.Errorf("function: action failed: %w", err)`. See pattern in `start()`, `createCore()`, `NewServer()`, etc.
|
||||
# Client mode (connects to server:10101, creates local listener on :8080)
|
||||
nodepass "client://server:10101/127.0.0.1:8080?min=128&log=debug"
|
||||
|
||||
# Master API mode (launches API server on :10101 with /api prefix)
|
||||
nodepass "master://:10101/api?log=debug&tls=1"
|
||||
|
||||
# Test QUIC transport with bandwidth limiting
|
||||
nodepass "server://:10101/127.0.0.1:8080?type=1&tls=1&rate=100"
|
||||
|
||||
# Test multi-target load balancing (comma-separated targets)
|
||||
nodepass "client://server:10101/target1.com:80,target2.com:80,target3.com:80?mode=2"
|
||||
```
|
||||
|
||||
**Debugging tips:**
|
||||
- Use `log=debug` to see connection lifecycle events, pool operations, handshake details
|
||||
- Check `DataExchange` log messages for connection completion status and byte counts
|
||||
- Monitor pool capacity with `Active()` and `Capacity()` calls logged periodically
|
||||
- TLS handshake failures appear as "access denied" warnings - verify `tunnelKey` matches
|
||||
- DNS resolution issues trigger fallback to cached addresses with warning logs
|
||||
|
||||
### Environment Tuning
|
||||
|
||||
Performance constants in `common.go` (lines 93-105) are environment-configurable via `NP_*` prefix:
|
||||
|
||||
```bash
|
||||
# Increase semaphore limit for high concurrency (default 65536)
|
||||
export NP_SEMAPHORE_LIMIT=131072
|
||||
|
||||
# Larger TCP buffer for high-bandwidth links (default 16384)
|
||||
export NP_TCP_DATA_BUF_SIZE=32768
|
||||
|
||||
# Extend handshake timeout for slow networks (default 5s)
|
||||
export NP_HANDSHAKE_TIMEOUT=10s
|
||||
|
||||
# Pool connection acquisition timeout (default 5s)
|
||||
export NP_POOL_GET_TIMEOUT=10s
|
||||
|
||||
# Pool scaling intervals (defaults: min=100ms, max=1s)
|
||||
export NP_MIN_POOL_INTERVAL=50ms
|
||||
export NP_MAX_POOL_INTERVAL=2s
|
||||
|
||||
# Health check report frequency (default 5s)
|
||||
export NP_REPORT_INTERVAL=10s
|
||||
|
||||
# Service restart cooldown (default 3s)
|
||||
export NP_SERVICE_COOLDOWN=5s
|
||||
|
||||
# Graceful shutdown timeout (default 5s)
|
||||
export NP_SHUTDOWN_TIMEOUT=10s
|
||||
|
||||
# TLS certificate reload interval for mode 2 (default 1h)
|
||||
export NP_RELOAD_INTERVAL=30m
|
||||
```
|
||||
|
||||
All duration values accept Go duration syntax (`s`, `m`, `h`). Changes require restart to take effect.
|
||||
|
||||
## Code Conventions
|
||||
|
||||
### Logging
|
||||
|
||||
Use the injected `logger` instance with appropriate levels:
|
||||
Use structured logging with `logger` from `logs.Logger`. Six levels: none/debug/info/warn/error/event. Format strings with `%v` placeholders:
|
||||
|
||||
```go
|
||||
logger.Debug("Detailed info: %v", detail) // Verbose debugging
|
||||
logger.Info("Operation: %v", status) // Normal operations
|
||||
logger.Warn("Non-critical issue: %v", err) // Recoverable problems
|
||||
logger.Error("Critical error: %v", err) // Functionality affected
|
||||
logger.Event("Traffic stats: %v", stats) // Important events
|
||||
logger.Debug("TLS cert reloaded: %v", crtFile)
|
||||
logger.Info("Server started: server://%v@%v/%v", key, tunnel, target)
|
||||
logger.Warn("tunnelHandshake: access denied: %v", remoteAddr)
|
||||
logger.Error("Certificate load failed: %v", err)
|
||||
logger.Event("Traffic stats: TCP RX=%d TX=%d", tcpRX, tcpTX)
|
||||
```
|
||||
|
||||
### Goroutine Management
|
||||
**Never use `fmt.Printf`** except in `exit()` help banner. All user-facing output goes through logger.
|
||||
|
||||
All long-running goroutines must:
|
||||
1. Check `ctx.Err()` regularly for cancellation
|
||||
2. Use proper cleanup with `defer` statements
|
||||
3. Handle panics in critical sections
|
||||
4. Release resources (slots, buffers, connections) on exit
|
||||
### Error Handling
|
||||
|
||||
### Buffer Management
|
||||
Wrap errors with context using `fmt.Errorf` with `%w` verb for error chain preservation:
|
||||
|
||||
Use sync.Pool for TCP/UDP buffers to reduce GC pressure:
|
||||
```go
|
||||
buf := c.getTCPBuffer() // Gets []byte from tcpBufferPool
|
||||
defer c.putTCPBuffer(buf)
|
||||
return fmt.Errorf("start: initTunnelListener failed: %w", err)
|
||||
return fmt.Errorf("tunnelHandshake: decode failed: %w", err)
|
||||
```
|
||||
|
||||
Functions return `error` as last return value. Restart logic uses `err != nil && err != io.EOF` pattern - `io.EOF` signals graceful shutdown, other errors trigger restart after `serviceCooldown`.
|
||||
|
||||
### Connection Pool Interface
|
||||
|
||||
All transport types (`pool.ServerPool`, `quic.ServerPool`, `npws.ServerPool`) implement unified `TransportPool` interface (defined in `common.go` line 92):
|
||||
|
||||
```go
|
||||
type TransportPool interface {
|
||||
// IncomingGet retrieves connection from server pool by ID with timeout
|
||||
IncomingGet(timeout time.Duration) (string, net.Conn, error)
|
||||
|
||||
// OutgoingGet retrieves connection from client pool for given ID with timeout
|
||||
OutgoingGet(id string, timeout time.Duration) (net.Conn, error)
|
||||
|
||||
// Flush signals pool to drop all connections and reset state
|
||||
Flush()
|
||||
|
||||
// Close terminates pool and all managed connections
|
||||
Close()
|
||||
|
||||
// Ready reports if pool has reached minimum capacity
|
||||
Ready() bool
|
||||
|
||||
// Active returns current active connection count
|
||||
Active() int
|
||||
|
||||
// Capacity returns maximum pool capacity
|
||||
Capacity() int
|
||||
|
||||
// Interval returns current auto-scaling interval
|
||||
Interval() time.Duration
|
||||
|
||||
// AddError increments error counter for health monitoring
|
||||
AddError()
|
||||
|
||||
// ErrorCount returns cumulative error count
|
||||
ErrorCount() int
|
||||
|
||||
// ResetError clears error counter
|
||||
ResetError()
|
||||
}
|
||||
```
|
||||
|
||||
Connection IDs are generated via FNV hash: `hash := fnv.New64a(); hash.Write([]byte); id := hex.EncodeToString(hash.Sum(nil))`. Server generates IDs for incoming connections, client receives IDs via control channel.
|
||||
|
||||
### Buffer Pool Management
|
||||
|
||||
**Critical**: Always return buffers to prevent memory leaks. Pools are initialized in constructor with `sync.Pool`:
|
||||
|
||||
```go
|
||||
tcpBufferPool: &sync.Pool{
|
||||
New: func() any {
|
||||
buf := make([]byte, tcpDataBufSize)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Usage pattern:
|
||||
```go
|
||||
buffer := c.getTCPBuffer() // Acquire from pool
|
||||
defer c.putTCPBuffer(buffer) // ALWAYS return via defer
|
||||
// Use buffer for I/O operations...
|
||||
```
|
||||
|
||||
UDP buffers follow identical pattern with `getUDPBuffer()`/`putUDPBuffer()`. Buffer sizes configurable via `NP_TCP_DATA_BUF_SIZE` (default 16384) and `NP_UDP_DATA_BUF_SIZE` (default 16384).
|
||||
|
||||
### Slot Management
|
||||
|
||||
Connection slots prevent resource exhaustion:
|
||||
Connection slots limit concurrent connections via atomic counters. Check before accepting connections:
|
||||
|
||||
```go
|
||||
if !c.tryAcquireSlot(isUDP) {
|
||||
return fmt.Errorf("slot limit reached")
|
||||
logger.Warn("Slot limit reached: %d", c.slotLimit)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
defer c.releaseSlot(isUDP)
|
||||
```
|
||||
|
||||
### Configuration via Environment Variables
|
||||
Slots are combined TCP+UDP count. `slotLimit=0` disables limit. Slot tracking uses `atomic.AddInt32()` for thread-safe counters.
|
||||
|
||||
### Context Management
|
||||
|
||||
Each mode initializes context in `start()` method:
|
||||
|
||||
Runtime behavior tunable without recompilation:
|
||||
```go
|
||||
var tcpDataBufSize = getEnvAsInt("NP_TCP_DATA_BUF_SIZE", 16384)
|
||||
```
|
||||
See `common.go` for full list: `NP_SEMAPHORE_LIMIT`, `NP_HANDSHAKE_TIMEOUT`, `NP_POOL_GET_TIMEOUT`, etc.
|
||||
|
||||
### TLS Certificate Hot-Reload
|
||||
|
||||
Mode 2 (custom certs) reloads certificates hourly without restart using `GetCertificate` callback in `core.go`.
|
||||
|
||||
### Comments Style
|
||||
|
||||
Maintain bilingual (Chinese/English) comments for public APIs and exported functions:
|
||||
```go
|
||||
// NewServer 创建新的服务端实例
|
||||
// NewServer creates a new server instance
|
||||
func NewServer(parsedURL *url.URL, ...) (*Server, error) { ... }
|
||||
func (c *Common) initContext() {
|
||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||
}
|
||||
```
|
||||
|
||||
## External Dependencies
|
||||
Graceful shutdown via `shutdown(ctx, stopFunc)` helper:
|
||||
1. Calls `stopFunc()` to close listeners/pools
|
||||
2. Waits for `ctx.Done()` or `shutdownTimeout` (default 5s)
|
||||
3. Logs completion/timeout status
|
||||
|
||||
All from `github.com/NodePassProject/*` ecosystem:
|
||||
- **cert**: TLS certificate generation and management
|
||||
- **conn**: Enhanced network connections with statistics tracking (`StatConn`)
|
||||
- **logs**: Multi-level logger (None/Debug/Info/Warn/Error/Event)
|
||||
- **name**: DNS resolver with caching and background refresh
|
||||
- **pool**: TCP connection pooling with auto-scaling
|
||||
- **quic**: QUIC multiplexing for 0-RTT connections
|
||||
Restart loop pattern in `Run()` methods:
|
||||
```go
|
||||
for ctx.Err() == nil {
|
||||
if err := c.start(); err != nil && err != io.EOF {
|
||||
c.logger.Error("Client error: %v", err)
|
||||
c.stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(serviceCooldown): // 3s default
|
||||
}
|
||||
logInfo("Client restart")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `contextCheckInterval` (50ms) in tight loops: `select { case <-ctx.Done(): return; case <-time.After(contextCheckInterval): }`
|
||||
|
||||
### Traffic Accounting
|
||||
|
||||
All connections wrapped in `conn.StatConn` for automatic byte counting and rate limiting:
|
||||
|
||||
```go
|
||||
targetConn = &conn.StatConn{
|
||||
Conn: targetConn,
|
||||
RX: &c.tcpRX, // Points to Common's atomic uint64 counter
|
||||
TX: &c.tcpTX, // Points to Common's atomic uint64 counter
|
||||
Rate: c.rateLimiter, // Optional rate limiter (nil if rate=0)
|
||||
}
|
||||
```
|
||||
|
||||
Counters updated atomically on every Read/Write. Master mode reads counters to compute traffic deltas. `DataExchange()` from `conn` package handles bidirectional copy with automatic accounting:
|
||||
|
||||
```go
|
||||
conn.DataExchange(connA, connB, readTimeout, buffer1, buffer2)
|
||||
```
|
||||
|
||||
Rate limiting initialized via `initRateLimiter()` if `rateLimit > 0` (rate in bytes/sec = query param * 125000).
|
||||
|
||||
## Master Mode Specifics
|
||||
|
||||
### API Patterns
|
||||
|
||||
- Authentication via `X-API-Key` header (auto-generated, stored in `nodepass.gob`)
|
||||
- SSE events at `/events` endpoint for real-time updates
|
||||
- State persistence with `encoding/gob` for instance recovery
|
||||
- OpenAPI spec at `/openapi.json`, Swagger UI at `/docs`
|
||||
|
||||
RESTful endpoints at `/{prefix}/*` (default `/api/*`):
|
||||
- Instance CRUD: POST/GET/PATCH/PUT/DELETE on `/instances` and `/instances/{id}`
|
||||
- Real-time events: SSE stream at `/events` (types: initial, create, update, delete, shutdown, log)
|
||||
- Service info: GET/POST on `/info` for master details and alias updates
|
||||
- TCPing utility: GET on `/tcping` for connection testing
|
||||
|
||||
### Instance Management
|
||||
|
||||
Each instance runs as a separate `exec.Cmd` process. Master tracks via `instances sync.Map` with status fields: `running`, `stopped`, `error`. Auto-restart enabled via `Restart` boolean field.
|
||||
Instances stored in `sync.Map` (concurrent-safe), persisted to `gob/nodepass.gob` using `gob` encoding. State file layout:
|
||||
- API key (auto-generated 32-byte hex on first start)
|
||||
- Instance map serialization with all fields except those tagged `gob:"-"`
|
||||
|
||||
### State Persistence
|
||||
Key `Instance` struct fields:
|
||||
```go
|
||||
type Instance struct {
|
||||
ID string // 8-char hex identifier
|
||||
Alias string // User-friendly name
|
||||
Type string // "server" or "client"
|
||||
Status string // "running", "stopped", "error"
|
||||
URL string // Original user-provided URL
|
||||
Config string // Computed URL with all defaults filled
|
||||
Restart bool // Auto-restart policy
|
||||
Meta Meta // Metadata with peer info and tags
|
||||
cmd *exec.Cmd // Running subprocess (not serialized)
|
||||
stopped chan struct{} // Shutdown coordination (not serialized)
|
||||
// Traffic baseline tracking (not serialized)
|
||||
TCPRXBase/TCPTXBase/UDPRXBase/UDPTXBase uint64
|
||||
}
|
||||
```
|
||||
|
||||
All instances stored in `nodepass.gob` using Go's `encoding/gob`:
|
||||
- Auto-saved on instance changes via `saveMasterState()`
|
||||
- Restored on startup via `restoreMasterState()`
|
||||
- Mutex-protected writes with `stateMu`
|
||||
Instance lifecycle:
|
||||
1. **Create**: `POST /instances` with URL → generates ID → spawns subprocess → stores in `sync.Map` → persists to gob
|
||||
2. **Monitor**: Periodic goroutine reads `/proc/<pid>/status` for traffic stats, computes deltas from baseline
|
||||
3. **Update**: `PATCH /instances/{id}` with actions: `start`, `stop`, `restart`, `reset-traffic`, `toggle-restart`
|
||||
4. **Delete**: `DELETE /instances/{id}` → stops subprocess → removes from map → re-persists gob
|
||||
|
||||
Subprocess management uses `exec.CommandContext()` with instance-specific context. Logs captured via custom `InstanceLogWriter` that parses structured logs and emits SSE events.
|
||||
|
||||
### SSE Events
|
||||
|
||||
Real-time updates via `/events` endpoint (Server-Sent Events). Event types and payloads:
|
||||
|
||||
- `initial`: Full instance list on connection (sent once per subscriber)
|
||||
- `create`: New instance created (includes full Instance object)
|
||||
- `update`: Instance state changed (includes full Instance object with updated fields)
|
||||
- `delete`: Instance removed (includes ID only)
|
||||
- `shutdown`: Master shutting down (no payload)
|
||||
- `log`: Instance log line (includes `instance.id` and `logs` fields)
|
||||
|
||||
Subscribers stored in `sync.Map` with unique IDs. Event broadcasting via `notifyChannel` (buffered channel). Connection management pattern:
|
||||
|
||||
```go
|
||||
subscriber := &Subscriber{id: generateID(), channel: make(chan *InstanceEvent, 100)}
|
||||
m.subscribers.Store(subscriber.id, subscriber)
|
||||
defer m.subscribers.Delete(subscriber.id)
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-subscriber.channel:
|
||||
fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Type, jsonData)
|
||||
flusher.Flush()
|
||||
case <-r.Context().Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Authentication
|
||||
|
||||
API Key in `X-API-Key` header. Special instance ID `********` for key regeneration via PATCH action `restart`.
|
||||
Auto-generated API key on first start. Special instance ID `********` (8 asterisks) reserved for key operations:
|
||||
- `GET /instances/********`: Retrieve current API key
|
||||
- `PATCH /instances/********` with `{"action": "restart"}`: Regenerate API key
|
||||
|
||||
## Testing and Validation
|
||||
Protected endpoints check `X-API-Key` header. Public endpoints: `/openapi.json`, `/docs` (Swagger UI).
|
||||
|
||||
No automated test suite currently. Manual testing workflow (from CONTRIBUTING.md):
|
||||
1. Test each mode (server, client, master) with `log=debug`
|
||||
2. Verify TCP and UDP forwarding separately
|
||||
3. Test TLS modes 0, 1, 2 with certificate validation
|
||||
4. Stress test with high concurrency and connection pool scaling
|
||||
Key validation pattern:
|
||||
```go
|
||||
if apiKey := r.Header.Get("X-API-Key"); apiKey != m.apiKey {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### TCPing Functionality
|
||||
|
||||
Built-in connectivity testing via `GET /tcping?target=host:port`. Concurrent limit enforced via buffered semaphore (`tcpingSem chan struct{}` with capacity 10). Returns JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"target": "example.com:443",
|
||||
"connected": true,
|
||||
"latency": 42,
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
Timeout handling: 1s for semaphore acquisition, 5s for TCP dial. Latency measured in milliseconds.
|
||||
|
||||
## Integration Points
|
||||
|
||||
### External Package Boundaries
|
||||
|
||||
- **`github.com/NodePassProject/pool`**: TCP connection pooling with dynamic scaling
|
||||
- **`github.com/NodePassProject/quic`**: QUIC-based transport (0-RTT support)
|
||||
- **`github.com/NodePassProject/npws`**: WebSocket transport wrapper
|
||||
- **`github.com/NodePassProject/conn`**: Connection helpers (`DataExchange`, `StatConn`, `RateLimiter`)
|
||||
- **`github.com/NodePassProject/cert`**: TLS certificate generation/management
|
||||
|
||||
When modifying transport behavior, coordinate with corresponding package version in `go.mod`.
|
||||
|
||||
### DNS Caching
|
||||
|
||||
Custom DNS resolution via `dnsCacheEntry` stored in `sync.Map` with TTL. Functions: `getTunnelTCPAddr()`, `getTargetTCPAddr()`.
|
||||
|
||||
### Handshake Protocol
|
||||
|
||||
**Server-side handshake** (`server.go` lines 208-279):
|
||||
1. Creates HTTP server with `HandlerFunc` on `tunnelListener`
|
||||
2. Validates incoming HTTP GET request to path `/`
|
||||
3. Extracts `Authorization` header and verifies Bearer token using HMAC-SHA256:
|
||||
- Client sends: `Authorization: Bearer <HMAC-SHA256(tunnelKey)>`
|
||||
- Server verifies via `hmac.Equal()` constant-time comparison
|
||||
4. Extracts client IP from `RemoteAddr()` (strips port if present)
|
||||
5. Responds with JSON config containing:
|
||||
```json
|
||||
{
|
||||
"flow": "<dataFlow>", // Direction: "+" or "-"
|
||||
"max": <maxPoolCapacity>, // Server pool capacity
|
||||
"tls": "<tlsCode>", // TLS mode: "0", "1", or "2"
|
||||
"type": "<poolType>" // Transport: "0" (TCP), "1" (QUIC), "2" (WS)
|
||||
}
|
||||
```
|
||||
6. Closes HTTP server after successful handshake
|
||||
7. Recreates `tunnelListener` for subsequent pool connections
|
||||
|
||||
**Client-side handshake** (`client.go` lines 231-273):
|
||||
1. Constructs HTTP GET request to `http://<tunnelAddr>/`
|
||||
2. Sets `Host` header to `tunnelName` for DNS-based routing
|
||||
3. Generates HMAC-SHA256 token: `hex.EncodeToString(hmac.New(sha256.New, []byte(tunnelKey)).Sum(nil))`
|
||||
4. Sends `Authorization: Bearer <token>` header
|
||||
5. Receives JSON response and decodes config
|
||||
6. Updates local configuration:
|
||||
- `dataFlow`: Controls connection direction
|
||||
- `maxPoolCapacity`: Adopts server's pool size
|
||||
- `tlsCode`: Applies server's TLS settings to data connections
|
||||
- `poolType`: Switches transport type if needed
|
||||
7. Logs loaded configuration for debugging
|
||||
|
||||
**Authentication mechanism**: HMAC-SHA256 provides cryptographic authentication without transmitting the raw `tunnelKey`. Token generation in `common.go` lines 248-256 uses standard library `crypto/hmac` and `crypto/sha256`.
|
||||
|
||||
### Load Balancing & Failover
|
||||
|
||||
Multi-target support via comma-separated addresses in URL path. `dialWithRotation()` (`common.go` lines 385-450) implements:
|
||||
- Round-robin distribution using atomic counter
|
||||
- Automatic failover on connection errors
|
||||
- Single-target fast path optimization
|
||||
- Dynamic DNS resolution per attempt
|
||||
|
||||
Example: `client://server:10101/target1:80,target2:80,target3:80` rotates across three backends.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **URL parsing**: Always include scheme (`server://`, `client://`, `master://`) or startup fails
|
||||
- **TLS mismatch**: Client inherits TLS mode from server during handshake—don't configure client TLS manually
|
||||
- **Pool capacity**: Server sets `max`, client sets `min`—mismatch causes connection issues
|
||||
- **Local address detection**: Single-end mode triggers automatically for localhost/127.0.0.1 tunnel addresses
|
||||
- **QUIC requirement**: QUIC mode (`quic=1`) forces TLS mode 1 minimum—cannot use mode 0
|
||||
- **Don't modify NodePassProject libraries**: These are external dependencies, not internal packages
|
||||
- **Always decode before using tunnel URLs**: Use `Common.decode()` for base64+XOR encoded data
|
||||
- **UDP session cleanup**: Sessions in `targetUDPSession` require explicit cleanup with timeouts
|
||||
- **Certificate hot-reload**: Only applies to `tls=2` mode with periodic checks every `ReloadInterval`
|
||||
- **Graceful shutdown**: Use context cancellation propagation, don't abruptly close connections
|
||||
1. **TLS Mode vs Pool Type**: `tls` parameter applies to data channel, `type` parameter selects transport (QUIC requires `tls=1` minimum)
|
||||
2. **URL Password Field**: Used as `tunnelKey` for authentication - not actual password
|
||||
3. **Buffer Pool Management**: Always return buffers via `putTCPBuffer()`/`putUDPBuffer()` to prevent leaks
|
||||
4. **Signal Channel Buffering**: `signalChan` has `semaphoreLimit` capacity - blocks if full
|
||||
5. **Instance Config vs URL**: Master stores both user-provided URL and computed config string with all defaults
|
||||
|
||||
## Key Files Reference
|
||||
## Key File References
|
||||
|
||||
- `cmd/nodepass/main.go`: Entry point, version variable injection
|
||||
- `cmd/nodepass/core.go`: Mode dispatch, TLS setup, CLI help formatting
|
||||
- `internal/common.go`: Shared primitives (buffer pools, slot management, encoding, config init)
|
||||
- `internal/server.go`: Server lifecycle, tunnel handshake, forward/reverse modes
|
||||
- `internal/client.go`: Client lifecycle, single-end/dual-end modes, tunnel connection
|
||||
- `internal/master.go`: HTTP API, SSE events, instance subprocess management, state persistence
|
||||
- **`internal/common.go`** (1970 lines): Core shared functionality
|
||||
- Lines 29-85: `Common` struct definition with all shared fields
|
||||
- Lines 93-122: Environment-configurable performance constants
|
||||
- Lines 140-165: Buffer pool management (`getTCPBuffer`, `putTCPBuffer`, `getUDPBuffer`, `putUDPBuffer`)
|
||||
- Lines 168-200: Slot management (`tryAcquireSlot`, `releaseSlot`)
|
||||
- Lines 250-270: Handshake encoding/decoding (`xor`, `encode`, `decode`)
|
||||
- Lines 385-450: Load balancing with failover (`dialWithRotation`)
|
||||
- Lines 722-726: Rate limiter initialization
|
||||
- Lines 1229, 1568: `DataExchange` calls for bidirectional traffic
|
||||
|
||||
## Documentation Requirements
|
||||
- **`internal/server.go`** (320 lines): Server mode implementation
|
||||
- Lines 32-62: Server constructor with pool initialization
|
||||
- Lines 65-106: Run loop with restart logic
|
||||
- Lines 109-183: Start sequence and mode detection
|
||||
- Lines 194-320: Tunnel handshake with concurrent connection acceptance
|
||||
|
||||
When adding features:
|
||||
1. Update relevant `docs/en/*.md` and `docs/zh/*.md` files
|
||||
2. Add examples to `docs/en/examples.md`
|
||||
3. Document new query parameters in `docs/en/configuration.md`
|
||||
4. Update API endpoints in `docs/en/api.md` if touching master mode
|
||||
5. Keep README.md feature list current
|
||||
- **`internal/client.go`** (273 lines): Client mode implementation
|
||||
- Lines 33-61: Client constructor
|
||||
- Lines 111-132: Mode detection logic (single-end vs dual-end)
|
||||
- Lines 135-210: Pool initialization per transport type
|
||||
- Lines 218-273: Tunnel handshake with config reception
|
||||
|
||||
## Additional Notes
|
||||
- **`internal/master.go`** (2165 lines): Master API server
|
||||
- Lines 67-90: Master struct definition
|
||||
- Lines 91-124: Instance struct with traffic tracking
|
||||
- Lines 138-145: InstanceEvent for SSE
|
||||
- Lines 330+: RESTful handlers and instance management
|
||||
|
||||
- Project uses Go 1.25+ features, maintain compatibility
|
||||
- Single binary with no external runtime dependencies (except TLS cert files for mode 2)
|
||||
- Focus on zero-configuration deployment - defaults should work for most use cases
|
||||
- Performance-critical paths: buffer allocation, connection pooling, data transfer loops
|
||||
- Security considerations: TLS mode selection, API key protection, input validation on master API
|
||||
|
||||
## Documentation References
|
||||
|
||||
- `/docs/en/how-it-works.md`: Deep dive into control/data channel separation and data flow modes
|
||||
- `/docs/en/configuration.md`: Complete parameter reference with examples
|
||||
- `/docs/en/api.md`: Master mode API specification with authentication and SSE events
|
||||
- `CONTRIBUTING.md`: Development setup, architecture overview, contribution guidelines
|
||||
- **`cmd/nodepass/core.go`** (165 lines): Entry point and configuration
|
||||
- Lines 17-35: URL parsing and core creation
|
||||
- Lines 38-59: Logger initialization
|
||||
- Lines 62-75: Core factory (`createCore`)
|
||||
- Lines 78-143: TLS configuration with three modes
|
||||
|
||||
Reference in New Issue
Block a user