Bypassing censorship using web hosting infrastructure
  • PHP 64.3%
  • Python 35.7%
Find a file
2026-05-31 22:41:44 +03:00
client Add ssl_context function to encapsulate SSL configuration 2026-05-31 22:26:14 +03:00
server Add auto-reload functionality for config.php changes in tubed.php 2026-05-31 22:41:44 +03:00
tools Init 2026-05-31 19:38:33 +03:00
.gitignore Add ssl_context function to encapsulate SSL configuration 2026-05-31 22:26:14 +03:00
LICENSE Update LICENSE 2026-05-31 16:02:34 +00:00
README.md Init 2026-05-31 19:38:33 +03:00

Tube

Turn a cheap PHP + nginx shared host (FTP-only deploy, PHP running as FastCGI) into an internet egress point. You run a local SOCKS5 proxy; its traffic is tunneled over plain HTTPS to a PHP endpoint on the host, which holds the real TCP sockets and — optionally — forwards everything through an upstream SOCKS5 / HTTP proxy.

It works within the harsh limits of shared hosting:

  • nginx fully buffers request bodies → no full-duplex in one request, so up/down traffic is split.
  • Very few PHP-FPM workers (often ~5) → all tunneled connections are multiplexed over one streaming response + one batched uplink, so a browser opening hundreds of sockets still only keeps 23 workers busy.
  • A persistent daemon survives FPM requests (spawned via setsid) and owns the sockets in a non-blocking event loop.
  • The public domain renders a decoy video-hosting site; the proxy endpoint returns a normal-looking 404 to anyone without the secret.

Run this only on infrastructure you own or are authorized to use. Forwarding third-party traffic through a host may violate its ToS. Authentication (a strong TUBE_SECRET) is mandatory — without it you expose an open proxy. The wire is protected by the host's HTTPS; there is no extra end-to-end crypto layer.

Layout

server/                 # deploy these to the host docroot (via FTP)
  index.php             # decoy cover site (public landing page)
  api.php               # proxy control endpoint (disguised, auth-gated)
  tubed.php             # persistent daemon (CLI only; holds sockets, multiplexes)
  upstream.php          # optional SOCKS5 / HTTP-CONNECT upstream dialer
  config.example.php    # copy to config.php, set your secret
client/
  client.py             # local SOCKS5 proxy (stdlib only)
tools/
  hostcheck.py          # verify a host + measure its FPM worker pool
  _hostcheck.php        # probe uploaded by hostcheck.py (auto-deleted)
  deploy.py             # upload server/ over FTP

1. Check your host

Confirm the host supports everything (sockets, proc_open, CLI php, egress, detached-process survival) and see how many FPM workers it gives you:

python3 tools/hostcheck.py --url https://YOUR-DOMAIN \
    --ftp-host H --ftp-user U --ftp-pass P

It prints a capability report, the worker-pool estimate, and a PASS/FAIL verdict.

2. Configure & deploy

cp server/config.example.php server/config.php
php -r "echo bin2hex(random_bytes(24)).PHP_EOL;"   # generate a secret
# edit server/config.php: set TUBE_SECRET (and TUBE_UPSTREAM if you want chaining)

python3 tools/deploy.py --ftp-host H --ftp-user U --ftp-pass P

config.php is gitignored — it holds your secret. Verify the cover site loads in a browser (https://YOUR-DOMAIN/) and that api.php returns a 404 page without the secret.

3. Run the client

python3 client/client.py --url https://YOUR-DOMAIN/api.php --secret <TUBE_SECRET>
# then point apps/OS at  socks5h://127.0.0.1:1080

Test it:

curl -x socks5h://127.0.0.1:1080 https://api.ipify.org   # shows the host's (or upstream's) IP

Upstream chaining

Forward all egress through an external proxy by setting TUBE_UPSTREAM in config.php (URL-encode special characters in the password):

const TUBE_UPSTREAM = 'socks5://user:pass@1.2.3.4:1080';   // or http://user:pass@host:port

Protocol (for the curious)

api.php relays line-framed JSON control ops to the daemon over a Unix socket:

  • open{host,port}{id} — open a target connection.
  • push{ups:[[id,b64]...], closes:[id...]}{ok} — batched client→target data + closes.
  • sub → a streamed response of length-prefixed frames <u32 len><json {id,d:b64}|{id,eof:1}> carrying target→client data for all connections; reopened every ~20s.
  • ping{pong, conns}.

License

BSD3-Clear — see LICENSE.