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)
| File | Size | Uploaded | |
|---|---|---|---|
| aio_network_forward-0.1.3.tar.gz | 11.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|