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.yaml
The 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.yaml
The ./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 authentication
The 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: 2222
PortMe 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: 8080
Usage 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.