Skip to main content

aio-network-forward

💡 Overview

aio-network-forward is a lightweight and user-friendly library for creating asynchronous TCP and UDP port forwards in Python. Built on top of asyncio and aio-sockets, it exposes a very small API for starting and stopping local forwarding endpoints without dealing with the low-level socket lifecycle yourself.

This library is useful when you want to proxy a local port to another TCP or UDP service, build small tunneling utilities, or embed simple forwarding behavior directly into your own async applications.

✨ Features

Simple Port Forwarding API: Start or stop TCP and UDP forwarding with just a few coroutine calls.

Automatic Port Fallback: If the requested local port is already in use, the library can fall back to a random available port.

Bidirectional Relay: TCP traffic is proxied in both directions, and UDP replies are relayed back to the original sender automatically.

IPv4 and IPv6 Support: Address family is selected automatically from the target address.

Embeddable Design: The library is easy to integrate into existing asyncio applications and lets you customize logging and listening addresses.

Multiple Backends and Load Balancing: Register several remote targets on the same local port; new connections are distributed across backends with round-robin, and failed backends are skipped automatically.

Backend Failover: If a TCP backend cannot be reached (connection refused, timeout, etc.), the library tries the next backend in the list before closing the client connection.

📦 Installation

Install from PyPI:

pip install aio-network-forward aio-sockets

Minimum supported version: Python 3.8

Usage

Basic Forward Example

The example below shows both TCP and UDP usage. Replace the startup line depending on which protocol you want to forward.

import asyncio
import aio_sockets as aio
import aio_network_forward as aionf


async def main():
    forward_to = ("127.0.0.1", 8686)

    # TCP
    local_port = await aionf.start_forward_tcp(forward_to, 8080)

    # UDP
    # local_port = await aionf.start_forward_udp(forward_to, 8080)

    print(f"forwarding started on 0.0.0.0:{local_port} -> {forward_to[0]}:{forward_to[1]}")
    await aio.wait_all_tasks_done()


asyncio.run(main())

Bind to a Random Local Port

If the preferred local port is unavailable, the library will automatically retry with a random available port. You can also pass 0 directly if you always want the OS to choose one.

import asyncio
import aio_sockets as aio
import aio_network_forward as aionf


async def main():
    forward_to = ("127.0.0.1", 8686)
    local_port = await aionf.start_forward_tcp(forward_to, 0)
    print(f"allocated local port: {local_port}")
    await aio.wait_all_tasks_done()


asyncio.run(main())

Multiple Backends on One Local Port

If you call start_forward_tcp() or start_forward_udp() again with the same local_port, the new forward_to_addr is appended to the existing backend list instead of creating a second listener. Incoming connections (TCP) or new UDP clients are assigned to backends in round-robin order.

Use the port returned by the first call as local_port for subsequent calls. Passing 0 again creates a separate listener rather than appending. If the first call fell back to a random port because the requested one was busy, later calls must use that returned port, not the originally requested value.

import asyncio
import aio_sockets as aio
import aio_network_forward as aionf


async def main():
    local_port = await aionf.start_forward_tcp(("127.0.0.1", 9001), 8080)
    await aionf.start_forward_tcp(("127.0.0.1", 9002), local_port)
    await aionf.start_forward_tcp(("127.0.0.1", 9003), local_port)
    print(f"port {local_port} forwards to three backends")
    await aio.wait_all_tasks_done()


asyncio.run(main())

To remove one backend without stopping the whole listener, call stop_forward_tcp(forward_to_addr) or stop_forward_udp(forward_to_addr). The local port stays open while other backends remain; the listener is closed only when the last backend is removed.

Limit Listening Address to Localhost

By default, the module listens on 0.0.0.0 for IPv4 and :: for IPv6. You can change that behavior before starting the forward.

import asyncio
import aio_sockets as aio
import aio_network_forward as aionf


async def main():
    aionf.aio_network_forward._listen_ipv4 = "127.0.0.1"
    aionf.aio_network_forward._listen_ipv6 = "::1"

    forward_to = ("127.0.0.1", 8686)
    local_port = await aionf.start_forward_tcp(forward_to, 8585)
    print(f"listening on localhost:{local_port}")
    await aio.wait_all_tasks_done()


asyncio.run(main())

HTTP Forward Demo

This repository includes network_forward_demo.py, which starts a local HTTP server on 127.0.0.1:8686, then exposes it through a forwarding port.

python network_forward_demo.py

After startup, you can open the forwarded address shown in the logs, for example:

http://127.0.0.1:8585

API

start_forward_tcp(forward_to_addr, local_port) -> int

Start a local TCP forward to forward_to_addr and return the actual listening port. If local_port is already forwarded by this module, append the target to the backend list instead of creating a new listener.

stop_forward_tcp(forward_to_addr) -> None

Stop the TCP forward associated with forward_to_addr. If other backends share the same local port, only that backend is removed and related forwarded sockets are closed.

start_forward_udp(forward_to_addr, local_port) -> int

Start a local UDP forward to forward_to_addr and return the actual listening port. If local_port is already forwarded by this module, append the target to the backend list instead of creating a new listener.

stop_forward_udp(forward_to_addr) -> None

Stop the UDP forward associated with forward_to_addr. If other backends share the same local port, only that backend is removed.

Module Configuration

These module-level variables can be adjusted before starting forwards:

Variable Default Description
_listen_ipv4 "0.0.0.0" IPv4 bind address for local listeners
_listen_ipv6 "::" IPv6 bind address for local listeners
_tcp_connect_timeout 10 Seconds to wait when connecting to a TCP backend; on timeout, the next backend is tried
_tcp_read_size 8192 TCP relay buffer size in bytes
logger StdoutLogger() Logger used for forwarding events

Example:

import aio_network_forward as aionf

aionf.aio_network_forward._tcp_connect_timeout = 5
aionf.aio_network_forward.logger = my_logger

Notes

This project depends on aio-sockets for its socket wrapper utilities.

start_forward_tcp() and start_forward_udp() create background tasks only. In a standalone script, you should keep the main coroutine alive with await aio.wait_all_tasks_done(), otherwise the program will exit immediately after startup.

UDP forwarding keeps a temporary per-client forwarding socket and closes it automatically after a period of inactivity.

Metadata

Release files for aio-network-forward 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aio-network-forward 0.1.3
File Size Uploaded
aio_network_forward-0.1.3.tar.gz 11.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aio-network-forward 0.1.3
File Interpreter ABI Platform
aio_network_forward-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 23.3 kB

Release files / aio_network_forward-0.1.3.tar.gz

Download URL aio_network_forward-0.1.3.tar.gz
Size 11.8 kB
Tags Source
SHA-256 checksum
How to use checksums
02b277be60ae98bcb8cbaaefa1bc90468698c3c54d4c09c3c8ea80f84d6b2c2e
BLAKE2b-256 checksum
How to use checksums
8e05b007aaad5b5f9186bd9de984b1c98f1debc14584bf5d1480d98670e8ad46
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release files / aio_network_forward-0.1.3-py3-none-any.whl

Download URL aio_network_forward-0.1.3-py3-none-any.whl
Size 11.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5a7f530e3ea999289b32a9bc040b70e538461198c81ebe19db4b508d4177e469
BLAKE2b-256 checksum
How to use checksums
7b6f43f8156daed04db6b5de0bd3af2d5700b0fa9af01bee4c7c7043e3ea3699
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page