HUProxy or How to Bypass Internet censorship

Some networks only have restricted access to the internet or run MITM on the encryption to scan the traffic. In the most cases it is possible to setup a secure proxy connection to bypass this restrictions.

This guide shows how to use HUProxy to connect to a remote SSH server and setup a socks5 proxy to bypass any censorship and MITM attacks.

Requirements

The following things are required:

  • reachable from public internet
  • linux server with docker

To make the usage easier a domain or subdomain should be used instead of the IP address. This guide uses huproxy-test.boehmke.net as domain.

Important: Because HUProxy does not support any authentication or encryption it is required to setup HUProxy behind a reverse proxy that also handles the authentication via basic auth.

This guide uses Traefik as reverse proxy with automatic TLS certificate request from Let’s Encrypt.

Build HUProxy image

Currently HUProxy is not available as prebuild docker image. So the first step is to build the docker image.

First clone the HUProxy repo

git clone https://github.com/google/huproxy.git

then enter the cloned directory

cd huproxy

and as last step build the image

docker build -t local/huproxy .

Setup Server

The following docker-compose.yml shows a simple setup including SSL and basic auth:

version: "3"

volumes:
  traefik_data:

services:
  traefik:
    image: traefik:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    command:
      - --entryPoints.web.address=:80
      - --entryPoints.websecure.address=:443

      - --providers.docker
      - --providers.docker.exposedByDefault=false

      - --certificatesresolvers.letsencrypt.acme.httpChallenge.entryPoint=web
      - --certificatesresolvers.letsencrypt.acme.email=admin@example.com
      - --certificatesresolvers.letsencrypt.acme.storage=/data/acme.json

    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - traefik_data:/data/

  huproxy:
    image: local/huproxy
    restart: unless-stopped
    command: "/huproxy --listen :8086"

    labels:
      traefik.enable: "true"
      traefik.http.routers.huproxy.rule: "Host(`huproxy-test.boehmke.net`)"
      traefik.http.routers.huproxy.entrypoints: "websecure"
      traefik.http.routers.huproxy.middlewares: "huproxy-auth@docker"
      traefik.http.routers.huproxy.tls.certresolver: "letsencrypt"
      traefik.http.routers.huproxy.tls.options: "default"
      traefik.http.services.huproxy.loadbalancer.server.port: "8086"
      traefik.http.middlewares.huproxy-auth.basicauth.users: "proxy:$apr1$4rgk5ce9$LR8iGRL7hq8By5vc6/tLu1"

In this example a user proxy with the password proxy is used. To run the proxy simply start the applications with docker-compose up -d.

Setup Client

Note: this requires a working go compiler

To connect to the client the huproxyclient is needed.

To build the client clone the HUProxy repo (or reuse the one from Build HUProxy image)

git clone https://github.com/google/huproxy.git

then enter the cloned directory

cd huproxy/huproxyclient

Then build the client

# build for native system
go build -v .

# build for windows
GOOS=windows GOARCH=amd64 go build -v .

The directory now contains the build application huproxyclient (or huproxyclient.exe for windows). This application can now be used on a system with a blocked internet connection.

To connect to an SSH server at ssh.example.com run

ssh -o "ProxyCommand=./huproxyclient -auth=proxy:proxy wss://huproxy-test.boehmke.net/proxy/%h/%p" ssh.example.com

this way the connection to ssh.example.com is created via the huproxy server at huproxy-test.boehmke.net.

For an easier reuse it is also possible to add the ProxyCommand for the ~/.ssh/config

IPv6 NAT & Docker

In the past the only way to get a working IPv6 NAT setup for docker was the great docker-ipv6nat companion container initiated by Robbert Klarenbeek.

Since some month now there is also support for IPv6 NAT in the docker daemon that can be used like the IPv4 NAT. Currently this feature is still marked as experimental and there are still some open issue (see below).

This post should give a basic example how to use the IPv6 NAT of the docker daemon and also document a part of the development. It also list issues and fixes that in the progress to get this feature ready for productive.

Enable IPv6 NAT

By default the creation of ip6tables rules is disable to keep backward compatibility. Additional it is also required to enable IPv6 and set a private IPv6 network for the fixed-cidr-v6 in the daemon or in a user-defined network. The second step is exactly the same as for the docker-ipv6nat image.

To enable IPv6 NAT on the default docker network add the following to the /etc/docker/daemon.json (adapt the fixed-cidr-v6 as needed):

{
    "experimental": true,
    "ip6tables": true,
    "fixed-cidr-v6": "fd00:dead:beef::/48",
    "ipv6": true
}

To use IPv6 NAT for user defined networks simply create a subnet with enabled IPv6:

docker network create --ipv6 --subnet fd00:dead:beef::/48 mynetwork

In this case you only have to add the following to the /etc/docker/daemon.json:

{
    "experimental": true,
    "ip6tables": true
}

Thats all. Simply start a random container and expose a port and it should be reachable via IPv6.

History

There are a lot of request for IPv6 NAT in the docker daemon. One of the most popular one is moby#25407. Because this feature was not implemented for a long time the docker-ipv6nat project was created.

In 2017 wrridgwa created a Merge Request libnetwork#2023 for libnetwork that implements the creation of ip6tables rules. Sadly this Merge Request was never merged and get outdated after some time.

In July 2020 I reworked this Merge Request to get it working with the latest version of libnetwork and published the changes as libnetwork#2572. After some month and multiple requested changes the Merge Request gets merged end of october. (Vendor MR: moby#41604)

Finally the new --ip6tables config was added to the daemon in moby#41622 as an experimental option. Also cli#2846 updated the documentation for new option.

Sadly the modifications of the docker daemon caused some new issues:

With release 20.10.6 (2021-04-12) all of this issues should be resolved.