Why?
Rotating IP is expensive:
- Residential Pool: $0.9~$5/GB
- Random Data Center: smallest package starting from $3/100IP/100GB/mth
But IPv6 is dirt cheap: Most servers comes with a minimal /64
, and you can easily get /48
s 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 asIPv6SUBNET
. - 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.