Make your own IPv6 Backconnect proxy server in 1 script!

Why?

Rotating IP is expensive:

  • Residential Pool: $0.9~$5/GB
  • Random Data Center: smallest package starting from $3/100IP/100GB/mth
    • Webshare(aff) is a good start but you do want to get some upgrades which will push it to around $6/mth

But IPv6 is dirt cheap: Most servers comes with a minimal /64, and you can easily get /48s with Tunnelbroker or via your own ASN.

Getting IPv6

If you are using non-major cloud your server should comes with some IPv6: the whole IPv6 subnet should be routed to your machine directly. This means that major cloud providers(AWS, Azure, GCP, Oracle) will NOT WORK since you can only route 1 /128 to a VM at a time(and got nothing to rotate to).

You can find some dirt cheap VPS with IPv6 at

Note that this note has NOT been tested on OpenVZ - but LXC and KVM should work.

These servers are severely under powered: This note is based on Alpine Linux to save as much RAM as possible.

Or, if you don't care about IP quality, https://tunnelbroker.net/ has been offering free /48 for many years: refer to https://gist.github.com/pklaus/962408/26a55e22e1d11f5a52d12d9478bba3153544fdcb for instructions.

Find your IPv6 Subnet and Device

Run ip a. Your subnet should look like "2aaa:bb:cc:dd::/64".

For example, if you see something like

496: eth0@if497: <BROADCAST,MULTICAST,UP,LOWER_UP200,M-DOWN> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 00:aa:aa:aa:aa:aa brd ff:ff:ff:ff:ff:ff
    inet 10.0.12.11/28 brd 10.0.12.11 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2aaa:bb:cc:dd:blah:blah:blah:blah/64 scope global dynamic flags 100 
       valid_lft forever preferred_lft forever
    inet6 fe80::216:3eff:fe1f:157d/64 scope link 
       valid_lft forever preferred_lft forever

you can assume that

  • subnet is "2aaa:bb:cc:dd::/64"; We will refer to this subnet as IPv6SUBNET.
  • device is eth0.

Update Route

Replace device ID(eth0 in example) and subnet then run this block:

echo "######################## CONFIG SYSCTL #########################"
# Create sysctl config file for IPv6 settings
cat <<EOF > /etc/sysctl.d/99-ipv6.conf
net.ipv6.conf.all.accept_ra = 2        # Accept Router Advertisements on all interfaces, even if forwarding is enabled
net.ipv6.conf.eth0.accept_ra = 2       # Accept Router Advertisements specifically on eth0 interface
net.ipv6.conf.default.forwarding = 1   # Enable IPv6 forwarding for new interfaces
net.ipv6.conf.default.proxy_ndp = 1    # Enable Proxy NDP for new interfaces
net.ipv6.conf.all.forwarding = 1       # Enable IPv6 forwarding on all interfaces
net.ipv6.conf.all.proxy_ndp = 1        # Enable Proxy NDP on all interfaces
net.ipv6.conf.eth0.proxy_ndp = 1       # Enable Proxy NDP specifically on eth0
net.ipv6.ip_nonlocal_bind = 1          # Allow binding to non-local addresses
net.ipv4.ip_forward = 1  # for IPv4 proxy setup later
net.ipv4.conf.all.forwarding = 1
net.ipv4.conf.default.forwarding = 1
EOF

# Apply sysctl settings
sysctl -p /etc/sysctl.d/99-ipv6.conf

echo "############################# ROUTE ############################"
# Create persistent route config
cat <<EOF > /etc/network/if-up.d/ipv6-routes
#!/bin/sh
ip -6 route del local "$IPv6SUBNET" dev lo 2>/dev/null || true
ip -6 route del local "$IPv6SUBNET" dev eth0 2>/dev/null || true
ip -6 route add local "$IPv6SUBNET" dev lo
EOF

# Make the route script executable
chmod +x /etc/network/if-up.d/ipv6-routes

# Apply routes now
/etc/network/if-up.d/ipv6-routes

Install NDPPD

ndppd, or NDP Proxy Daemon, is a daemon that proxies neighbor discovery messages - think ARP in IPv4.

Note that there's an issue in the codebase that requires patching.

echo "######################## INSTALLING NDPPD ########################";
# install requirements
apk --no-cache add --virtual .build-dependencies make g++ linux-headers patch wget ca-certificates curl
# compile NDP Proxy Daemon
wget https://github.com/DanielAdolfsson/ndppd/archive/refs/heads/master.zip -O ndppd.zip
unzip ndppd.zip
cd ndppd-master/

# apply patch: fix a difference with POSIX issue with logger.cc
# create patch file:
cat <<EOF > logger.patch
--- logger.cc   2025-02-07 18:27:44
+++ logger-working.cc   2025-02-07 18:28:17
@@ -85,11 +85,12 @@
     char buf[2048];

 #if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE
-    if (strerror_r(errno, buf, sizeof(buf))
+    if (strerror_r(errno, buf, sizeof(buf)) != 0)
         return "Unknown error";
     return buf;
 #else
-    return strerror_r(errno, buf, sizeof(buf));
+    strerror_r(errno, buf, sizeof(buf));
+    return buf;
 #endif
 }

\ No newline at end of file
EOF

patch src/logger.cc < logger.patch
make all && make install
fi

Now setup ndppd and register auto launch:

echo "######################### CONFIG NDPPD #########################";
# config
cat <<EOF > /etc/ndppd.conf
route-ttl 30000
address-ttl 30000
proxy eth0 {
router yes
timeout 500
autowire no
keepalive yes
retries 3
promiscuous no
ttl 30000
rule $IPv6SUBNET {
    static
    autovia no
    }
}
EOF

echo "####################### CREATE SERVICE #########################";
cat <<EOF > /etc/init.d/ndppd
#!/sbin/openrc-run

# Provides: ndppd
# Required-Start: net
# Should-Start: radvd
# Default-Start:
# Default-Stop:

depend() {
  need net
  after radvd # If you want radvd to start before ndppd
}

start() {
  ebegin "Starting NDP Proxy Daemon"
  start-stop-daemon --start --quiet --pidfile "/run/ndppd.pid" --exec "/usr/local/sbin/ndppd" -- -d
  eend $?
}

stop() {
  ebegin "Stopping NDP Proxy Daemon"
  start-stop-daemon --stop --quiet --pidfile "/run/ndppd.pid"
  eend $?
}
EOF

chmod +x /etc/init.d/ndppd

echo "################### ENABLE AND START NDPPD ####################";
rc-update add ndppd default
rc-service ndppd start

Build and Install Rotating Proxy

Install Rust to build the proxy server.

Edit the PORT and IPv6SUBNET.

Using my fork of http-proxy-ipv6-pool to provide authentication.

echo "######################### INSTALL RUST #########################";

apk add curl git wget musl-dev gcc

curl -sSf https://sh.rustup.rs | sh

echo PATH="$HOME/.cargo/bin:$PATH" >> ~/.bashrc

source $HOME/.cargo/env

echo "######################### INSTALL HTTP-PROXY-IPV6-POOL #########################";
wget https://github.com/cnbeining/http-proxy-ipv6-pool/archive/refs/heads/master.zip -O http-proxy-ipv6-pool.zip
unzip http-proxy-ipv6-pool.zip
cd http-proxy-ipv6-pool-master

cargo build --release

cp target/release/http-proxy-ipv6-pool /usr/local/bin/

echo "####################### CREATE SERVICE #########################";
cat <<EOF > /etc/init.d/http-proxy-ipv6-pool
#!/sbin/openrc-run

# Provides: http-proxy-ipv6-pool
# Required-Start: net
# Should-Start: radvd
# Default-Start:
# Default-Stop:

depend() {
  need net
  after ndppd # If you want ndppd to start before proxy
}

start() {
  ebegin "Starting http-proxy-ipv6-pool Daemon"
  start-stop-daemon --start --quiet --background --make-pidfile --pidfile "/run/http-proxy-ipv6-pool.pid" --exec "/usr/local/bin/http-proxy-ipv6-pool" -- -b 0.0.0.0:"$PORT" -i "$IPv6SUBNET" -a username:password
  eend $?
}

stop() {
  ebegin "Stopping http-proxy-ipv6-pool Daemon"
  start-stop-daemon --stop --quiet --pidfile "/run/http-proxy-ipv6-pool.pid"
  eend $?
}
EOF

chmod +x /etc/init.d/http-proxy-ipv6-pool

echo "################### ENABLE AND START PROXY ####################";
rc-update add http-proxy-ipv6-pool default
rc-service http-proxy-ipv6-pool start

Test out

Running curl -x http://localhost:<PORT> ip.sb -vv should now give you different IP per request.

Leave a Reply

Your email address will not be published. Required fields are marked *