Config
In the library, configuration is a ForwarderConfig dataclass per
forwarder. ProtoPokeAPI takes a list of them and wires up the shared
session registry, event bus, tamper controller, and rules engine.
ForwarderConfig¶
from protopoke.config import ForwarderConfig
fwd = ForwarderConfig(
name="Default",
listen_host="127.0.0.1",
listen_port=8080,
upstream_host="10.0.0.1",
upstream_port=9090,
)
| Field | Type | Default | Description |
|---|---|---|---|
name |
str | "Forwarder" |
Human-readable label |
enabled |
bool | True |
Include in "start all" |
forwarder_type |
str / ForwarderType |
"tcp" |
"tcp", "udp", or "socks5" |
listen_host |
str | "127.0.0.1" |
Bind address |
listen_port |
int | 8080 |
Listen port |
upstream_host |
str | "127.0.0.1" |
Target host (ignored by SOCKS5) |
upstream_port |
int | 9090 |
Target port (ignored by SOCKS5) |
connect_timeout |
float | 10.0 |
Upstream connection timeout |
read_buffer_size |
int | 4096 |
Read buffer size |
socks_auth_username |
str | None |
SOCKS5 username (None = no-auth) |
socks_auth_password |
str | None |
SOCKS5 password |
max_sessions |
int | 0 |
Max concurrent sessions (0 = unlimited) |
keep_upstream_on_client_disconnect |
bool | True |
Keep upstream open after the client disconnects (→ ONLY_SERVER). TCP/SOCKS5 only |
keep_client_on_server_disconnect |
bool | True |
Keep the client writable after the server disconnects (→ ONLY_CLIENT). TCP/SOCKS5 only |
half_open_idle_timeout |
float | 0.0 |
Seconds a half-open session may sit idle before its surviving connection is reaped. 0 disables reaping. TCP/SOCKS5 only |
tamper_enabled |
bool | False |
Enable intercept on startup |
framer_name |
str | "raw" |
raw, delimiter, length_prefix, line (UDP is always raw) |
framer_kwargs |
dict | {} |
Framer-specific parameters |
custom_framer_path |
str | None |
Path to a custom framer script |
protocol_definition_path |
str | None |
Protocol definition file path |
log_level |
str | "INFO" |
Logging level for this forwarder |
tls_listen |
bool | False |
TLS MITM on the client side (rejected for UDP/SOCKS5) |
tls_upstream |
bool | False |
Connect to the upstream over TLS |
ca_cert_path / ca_key_path |
str | None |
Custom CA (auto-generated at ~/.protopoke/ca.* when unset) |
tls_cert_path / tls_key_path |
str | None |
Fixed leaf cert/key (skips the auto-CA) |
Transport types¶
# Plain TCP proxy (default)
tcp = ForwarderConfig(name="tcp", listen_port=8080,
upstream_host="10.0.0.1", upstream_port=9090)
# UDP proxy — one session per (client_host, client_port) flow.
# Always uses the raw framer; cannot use tls_listen.
udp = ForwarderConfig(name="dns", forwarder_type="udp", listen_port=5353,
upstream_host="1.1.1.1", upstream_port=53)
# SOCKS5 proxy — the upstream target comes from each client's CONNECT
# request, so upstream_host / upstream_port are ignored.
socks = ForwarderConfig(name="socks", forwarder_type="socks5",
listen_port=1080,
socks_auth_username="user", socks_auth_password="pass")
TLS / MITM¶
fwd = ForwarderConfig(
name="https",
listen_port=8443,
upstream_host="api.example.com",
upstream_port=443,
tls_listen=True, # terminate TLS from the client (MITM)
tls_upstream=True, # re-encrypt to the upstream
)
On first use a root CA is generated at ~/.protopoke/ca.crt / ca.key and
reused across restarts. The client must trust that CA. Export it once TLS
listening is active:
# api.ca is the active CertificateAuthority (set when tls_listen is on
# in auto-CA mode); cert_pem is the PEM-encoded CA certificate.
with open("protopoke-ca.crt", "wb") as f:
f.write(api.ca.cert_pem)
Upstream certificate verification is intentionally disabled — ProtoPoke is a reverse-engineering tool, not a production proxy.
Half-open sessions (TCP / SOCKS5)¶
When one peer disconnects, ProtoPoke does not propagate the EOF to the
other side. The session transitions to ONLY_SERVER or ONLY_CLIENT and the
surviving connection stays fully open so the Forge tab can keep driving it.
The session reaches CLOSED only once both sides are gone. This is controlled
by keep_upstream_on_client_disconnect / keep_client_on_server_disconnect
(both default True); set either to False to restore the legacy TCP
half-close.
fwd = ForwarderConfig(
name="svc",
listen_port=8080,
upstream_host="10.0.0.1",
upstream_port=9090,
half_open_idle_timeout=120.0, # reap an idle half-open session after 120s
)
Because the surviving peer never learns the other side is gone, a keep-alive
server that never closes would leave the half-open session — and its sockets —
alive forever, eventually exhausting file descriptors / ephemeral ports.
half_open_idle_timeout guards against this: once a session is half-open, its
surviving connection is reaped if it sees no traffic for that many seconds.
Active connections and operator-driven Forge traffic reset the timer. The
default is 0.0, which disables reaping — set a positive value for
long-running, unattended forwarders.
Multiple forwarders¶
api = ProtoPokeAPI([
ForwarderConfig(name="Service A", listen_port=8080,
upstream_host="10.0.0.1", upstream_port=9090),
ForwarderConfig(name="Service B", listen_port=8081,
upstream_host="10.0.0.2", upstream_port=9091),
])
await api.start()
# Control individual forwarders
await api.start_forwarder("Service A")
await api.stop_forwarder("Service B")
print(api.is_running("Service A"), api.list_running())
Serialising a config¶
Projects¶
There is no database — sessions and frames live in memory for the life of
the process. To persist a whole working set, use ProjectManager, which
bundles forwarders, rules, playbooks, captured traffic, display filters, and
MCP settings into a single .pp ZIP archive.
from protopoke.project.manager import ProjectManager
pm = ProjectManager()
pm.new("My Capture")
pm.forwarders[0].listen_port = 9000
pm.save_as("/path/to/project.pp")
pm.save() # re-save to the same path
pm2 = ProjectManager()
state = pm2.open("/path/to/project.pp")
# state.forwarders, state.rules_engine, state.intercept_filter,
# state.playbooks, state.captured_sessions, state.mcp_settings, ...
A .pp archive contains project.json, forwarders.json, rules.json,
forge.json, logs.json, filters.json, and mcp.json. Loading is bounded
for safety (max 32 members, 100 MB per member).
Next¶
- Traffic — sessions, events, and decoding frames
- User Interface — Config — the same, in the TUI