Skip to content

igorrius/tcp-sproxy

Repository files navigation

tcp-sproxy (NATS TCP Stream Proxy)

A minimal TCP stream proxy that tunnels arbitrary TCP connections over NATS. It consists of:

  • A server that accepts proxy requests via NATS and connects to the target TCP service.
  • A client that listens on a local TCP port and forwards each incoming connection through NATS to the server, which then talks to the target.

This is useful when direct TCP connectivity to a service is not possible, but NATS connectivity is available.

Status

Experimental. Suitable for demos and experiments. Security, auth, and advanced features are out of scope for now.

How it works (high level)

  • Control plane:
    • Client requests a new proxy connection by sending a NATS request to subject proxy.request with metadata: remote_host and remote_port.
    • Server replies with a generated connection ID.
  • Data plane:
    • Client -> Server bytes are published to p.data.to_server.{connectionID}.
    • Server -> Client bytes are published to p.data.to_client.{connectionID}.
  • The server maintains a TCP connection to the remote service and relays bytes between it and the client over NATS.

Internally, messages are serialized as JSON using a simple transport.Message structure.

Repository layout

  • cmd/server: NATS proxy server
  • cmd/client: NATS proxy client
  • internal/...: domain, use cases, NATS transport, and in-memory repository
  • integration_tests: docker-compose setup and a Redis-based end‑to‑end test

Requirements

  • Go 1.24+
  • A running NATS server (e.g., nats:2.9-alpine)
  • Docker and Docker Compose (for integration tests or containerized runs)

Build

Build both binaries locally:

go build -o bin/nats-proxy-server ./cmd/server
go build -o bin/nats-proxy-client ./cmd/client

Or run directly:

go run ./cmd/server --help
go run ./cmd/client --help

Quick start locally (Redis example)

  1. Start NATS:
docker run --rm -p 4222:4222 --name nats nats:2.9-alpine
  1. Start Redis locally for the demo:
docker run --rm -p 6379:6379 --name redis redis:7-alpine
  1. Start the proxy server (in another terminal):
bin/nats-proxy-server --nats-url nats://127.0.0.1:4222 --log-level info
  1. Start the proxy client to expose a local port that forwards to Redis via NATS:
bin/nats-proxy-client \
  --nats-url nats://127.0.0.1:4222 \
  --listen-addr 127.0.0.1:6380 \
  --remote-addr 127.0.0.1:6379 \
  --proxy-addr localhost:8081  # currently informational/reserved
  1. Test with redis-cli:
redis-cli -h 127.0.0.1 -p 6380 PING
redis-cli -h 127.0.0.1 -p 6380 SET key value
redis-cli -h 127.0.0.1 -p 6380 GET key

If everything is wired, you should see PONG and value replies proxied over NATS.

Configuration

Both binaries use Cobra + Viper. You can configure via flags, env vars, or config files.

Server (nats-proxy-server)

  • Flags:
    • --nats-url (default nats://localhost:4222)
    • --listen-addr (default :8080) — currently not used by the server
    • --log-level (debug|info|warn|error; default info)
    • --config (path to config file; default search: ./ then $HOME, file name .nats-proxy-server.*)
  • Environment:
    • NATS_URL maps to nats.url
    • LOG_LEVEL maps to log.level
  • Example YAML (e.g., .nats-proxy-server.yaml):
nats:
  url: nats://localhost:4222
log:
  level: info

Client (nats-proxy-client)

  • Flags:
    • --nats-url (default nats://localhost:4222)
    • --listen-addr (default 0.0.0.0:8082) — local TCP listen address
    • --remote-addr (default redis:6379) — target service address the server will dial
    • --proxy-addr (default proxy-server:8081) — currently informational/reserved in NATS transport
    • --log-level (debug|info|warn|error; default info)
    • --config (path to config file; default search: ./ then $HOME, file name .nats-proxy-client.*)
  • Environment:
    • NATS_URLnats.url
    • LISTEN_ADDRclient.listen_addr
    • REMOTE_ADDRclient.remote_addr
    • PROXY_ADDRclient.proxy_addr
    • LOG_LEVELlog.level
  • Example YAML (e.g., .nats-proxy-client.yaml):
nats:
  url: nats://localhost:4222
client:
  listen_addr: 0.0.0.0:8082
  remote_addr: redis:6379
  proxy_addr: proxy-server:8081
log:
  level: info

Docker

  • Server-only image (root Dockerfile):
docker build -t tcp-sproxy-server -f Dockerfile .
# Then run with a NATS_URL env variable
# NOTE: The Dockerfile exposes 8080 and defines a healthcheck path; the server does not expose an HTTP endpoint.
docker run --rm --network host -e NATS_URL=nats://127.0.0.1:4222 tcp-sproxy-server
  • Dev/integration image (contains both server and client):
docker build -t tcp-sproxy-bundle -f integration_tests/build/Dockerfile .

Tests

  • Unit tests:
go test ./...
  • Integration test (Redis through the proxy using Docker Compose):
# Using Makefile
make test-integration

# Or manually
docker-compose -f integration_tests/docker-compose.yml up -d --build
# the test-runner service will execute integration tests
# when finished
docker-compose -f integration_tests/docker-compose.yml down

Note: Integration tests require the following ports to be available on the host:

  • 4222 - NATS messaging
  • 6379 - Redis database
  • 8080, 8081 - Proxy server
  • 8082 - Proxy client

If these ports are already in use, you'll need to either stop the conflicting services or modify the port mappings in integration_tests/docker-compose.yml.

Continuous Integration

This project uses GitHub Actions for automated testing:

Unit Tests

  • Workflow: .github/workflows/test.yml
  • Triggers: Pull requests and pushes to main/master branches
  • Features:
    • Runs on Go 1.24
    • Executes go test with race detection
    • Generates code coverage reports
    • Uploads coverage to Codecov
    • Caches Go modules for faster builds

Integration Tests

  • Workflow: .github/workflows/integration-test.yml
  • Triggers: Manual dispatch, daily schedule (2 AM UTC), pushes to main/master
  • Features:
    • Runs Docker Compose-based integration tests via make test-integration
    • Automatically spins up Redis, NATS, proxy server, and proxy client containers
    • Executes end-to-end Redis proxy tests
    • No manual service setup required - all dependencies managed by Docker Compose

Both workflows ensure code quality and functionality across different scenarios.

Logging

Both components use logrus. Levels: debug, info, warn, error (set via --log-level or LOG_LEVEL).

Notes & Limitations

  • No authentication, rate limiting, or encryption provided by this project. Use NATS security features and network controls as appropriate.
  • The --proxy-addr on the client is currently reserved/informational in the NATS transport implementation.
  • The server does not currently expose an HTTP endpoint (despite the Dockerfile healthcheck example).

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

TCP client-server based on streaming transport

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •