A high-performance TCP port forwarding tool written in Go that allows multiple port forwarding connections to be multiplexed over a single TCP connection between a client and server.
PortMe enables you to:
- Forward multiple local ports to remote destinations through a single server connection
- Reduce the number of open connections and simplify firewall configurations
- Manage port forwarding rules through YAML configuration files
# Start the server
./bin/server --config configs/server.yaml
# Start the client
./bin/client --config configs/client.yamlThe test script supports two profiles for running the full test stack:
# Run tests with no authentication (default)
./scripts/run-test.sh
# Or explicitly specify no_auth profile
./scripts/run-test.sh no_auth
# Run tests with token authentication
./scripts/run-test.sh token_auth
# Show help and available options
./scripts/run-test.sh --help# Build the project
make build./bin/server [flags]
Flags:
--config string Path to server configuration file (required)
Examples:
./bin/server --config configs/server.yaml./bin/client [flags]
Flags:
--config string Path to client configuration file (required)
Examples:
./bin/client --config configs/client.yamlThe ./scripts/run-test.sh script supports two profiles for testing different configurations:
- TCP Dummy Server: Port 4321
- PortMe Server: Port 6060 using
configs/server.yaml - PortMe Client: Forwards TCP port 6432 using
configs/client.yaml - Dummy Client: Connects to
127.0.0.1:6432 - Authentication: Disabled
- Environment Variables:
SERVER_AUTH_TOKEN="secret_token",CLIENT_AUTH_TOKEN="secret_token" - TCP Dummy Server: Port 4321 (same as above)
- PortMe Server: Port 6060 using
configs/server_auth.yaml - PortMe Client: Forwards TCP port 7432 using
configs/client_auth.yaml - Dummy Client: Connects to
127.0.0.1:6432 - Authentication: Enabled with token-based authentication
PortMe supports token-based authentication for secure connections. When authentication is enabled:
# server_auth.yaml
listen_address: "127.0.0.1"
listen_port: 6060
max_connections: 100
enable_auth_rules: true
# Authentication rules
auth_rules:
- name: "secure_tunnel"
remote_address: "127.0.0.1:4321"
auth_token: "env_SERVER_AUTH_TOKEN" # Reads from environment variable
log_level: "info"# client_auth.yaml
server_address: "127.0.0.1:6060"
log_level: "info"
forwarding_rules:
- name: "secure_tunnel"
remote_address: "127.0.0.1:4321"
local_port: 6432
auth_token: "env_CLIENT_AUTH_TOKEN" # Reads from environment variable
keepalive_interval: "15s"
activity_deadline: "2m"For the token_auth profile, the script automatically sets these environment variables:
export SERVER_AUTH_TOKEN="secret_token"
export CLIENT_AUTH_TOKEN="secret_token"The server configuration defines the listening parameters and connection limits.
# Server configuration file (server.yaml)
listen_address: "127.0.0.1" # IP address to bind the server to
listen_port: 6060 # Port to listen on (can be int or string)
log_level: "info" # Logging level: debug, info, warn, error
max_connections: 100 # Maximum concurrent client connections
enable_auth_rules: false # Enable/disable authenticationThe client configuration defines forwarding rules and connection parameters.
# Client configuration file (client.yaml)
server_address: "127.0.0.1:6060" # Server endpoint to connect to
log_level: "info" # Logging level: debug, info, warn, error
# Connection timing parameters
keepalive_interval: "15s" # Interval between keepalive messages
activity_deadline: "60s" # Maximum idle time before connection timeout
# Port forwarding rules
forwarding_rules:
- name: "web-server" # Human-readable rule identifier
remote_address: "192.168.1.100:80" # Destination server:port
local_port: 8080 # Local port to bind and forward
# auth_token: "optional_token" # Optional: authentication token for secure connections
- name: "database"
remote_address: "10.0.0.50:5432"
local_port: 5432
- name: "ssh-access"
remote_address: "remote.example.com:22"
local_port: 2222PortMe uses a custom multiplexing protocol that allows multiple independent TCP streams to be transmitted over a single TCP connection between the client and server.
┌─────────────────┐ TCP Connection ┌─────────────────┐
│ Client Side │ ◄────────────────────► │ Server Side │
│ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ Local Port │ │ │ │ Connection │ │
│ │ 8080 │ │ ◄─ Stream 1 ─────────► │ │ Handler │ │
│ └─────────────┘ │ │ └─────────────┘ │
│ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ Local Port │ │ ◄─ Stream 2 ─────────► │ │ Connection │ │
│ │ 5432 │ │ │ │ Handler │ │
│ └─────────────┘ │ │ └─────────────┘ │
│ │ │ │
└─────────────────┘ └─────────────────┘
All data is transmitted using a 12-byte header followed by payload data:
┌─────────────────────────────────────────────────────────┐
│ Frame Header (12 bytes) │
├─────────────┬─────────────┬─────────────────────────────┤
│ Stream ID │ Payload Len │ Control Messages │
│ (4 bytes) │ (4 bytes) │ (4 bytes) │
├─────────────┼─────────────┼─────────────────────────────┤
│ Payload Data (variable) │
│ (Max: 16MB per frame) │
└─────────────────────────────────────────────────────────┘
- Stream ID (4 bytes): Unique identifier for each multiplexed stream
- Payload Length (4 bytes): Length of the data payload in bytes
- Control Messages (4 bytes): Stream control flags and metadata
Forward local port 8080 to a remote web server through the multiplexer.
# client.yaml
forwarding_rules:
- name: "internal-web"
remote_address: "internal.company.com:80"
local_port: 8080Usage Flow:
- Start the server:
./bin/server --config configs/server.yaml - Start the client:
./bin/client --config configs/client.yaml - Access the remote web server locally:
http://localhost:8080
Client Server
| |
| TCP Connection |
|----------------------->|
| |
| Client Hello |
|----------------------->|
| |
| Server Hello |
|<-----------------------|
| |
Client Server Remote Target
| | |
| STREAM_OPEN | |
| (rule: "web") | |
|----------------------->| |
| | |
| STREAM_OPEN_ACK | |
|<-----------------------| |
| | |
| | TCP Connect |
| |------------------------>|
| | |
| | Connection Established |
| |<------------------------|
| | |
Local App Client Server Remote Service
| | | |
| HTTP Request | | |
|------------------> | | |
| | STREAM_DATA | |
| | (stream_id: 1) | |
| |------------------> | |
| | | Forwarded Request |
| | |-------------------> |
| | | |
| | | HTTP Response |
| | |<------------------- |
| | STREAM_DATA | |
| | (stream_id: 1) | |
| |<------------------ | |
| HTTP Response | | |
|<------------------ | | |
| | | |
Client Server
| |
| STREAM_CLOSE |
| (stream_id: 1) |
|----------------------->|
| |
| STREAM_CLOSE_ACK |
| (stream_id: 1) |
|<-----------------------|
| |
| Repeat for all |
| active streams |
| |
| CONNECTION_CLOSE |
|----------------------->|
| |
| CONNECTION_CLOSE_ACK |
|<-----------------------|
| |
| TCP Connection |
| Closed |
- debug: Detailed protocol frames and connection events
- info: Connection establishment, stream creation/termination
- warn: Connection errors and retry attempts
- error: Critical failures and connection drops
# Server logs
INFO Server started successfully listen_address="127.0.0.1:6060" max_connections=100
INFO Client connected client_id="127.0.0.1:52342"
DEBUG Received data from client client_id="127.0.0.1:52342" bytes=512
INFO Client connection closed client_id="127.0.0.1:52342"
# Client logs
INFO Connected to server server_addr="127.0.0.1:6060"
INFO Started port forwarding listener listen_addr="127.0.0.1:8080" remote_addr="web.example.com:80" rule="web-server"
DEBUG Forwarded data direction="local->remote" bytes=1024
INFO Established forwarding connection local_addr="127.0.0.1:52344" remote_addr="web.example.com:80"- Max Payload Size: 16MB per frame
- Default Buffer Size: 4KB
- Max Connections: 100 concurrent client connections
- Keepalive Interval: 15 seconds (configurable)
- Go 1.17 or higher
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.