please enable javascript

IP Changing Made Easy

Alexander Comerford
January 21st, 2022 · 2 min read

The normal way to change IPs

It's very handy as a programmer to change your public facing IP. Whether you want to pretend your computer is in a different country or just want to get around an IP ban, being able to masquerade your IP has many uses.

The workflow I've always used to achieve this is:

  1. running openvpn as a background process
  2. do what I want to do
  3. kill the openvpn process

When I was testing some IP banning tooling I was using this workflow and quickly came across the problem that switching locations/IPs multiple times gets annoying as I have to kill and start openvpn multiple times. So I wanted to build a utility to make it easier by collapsing this workflow into just one step.

The ideal experience I wanted was to run a command isolated from the rest of my local network, yet be connected to a VPN whose location I can change with a single argument.

Coding it up

Since I wanted isolation for this tool, I chose podman as it's my goto container engine for my one off isolation needs. The simplest place I started was with a quick script to build an ovpn alpine image like so:

1NAME=ovpn
2cat << EOF > /tmp/Dockerfile.${NAME}
3FROM alpine:3.12
4RUN apk add --no-cache openvpn bash
5EOF
6EMPTY=$(mktemp -d)
7podman build -q -t ${NAME} -f /tmp/Dockerfile.${NAME} $EMPTY 2>&1 > /dev/null

The immediate problem I saw with this approach was that the container image doesn't include any of my local tools. It didn't seem practical to keep adding dependencies as needed to this image. This would cause image bloat and I would have to rebuild the image any time I want to add something.

I wanted a way to run this image as a conduit for my other tools.

The solution I eventually reached was to simply run the isolated openvpn container, then run a command I wanted in the containers network namespace. That way I get the network isolation I want, with the ability to isolate the entire execution of a command behind a VPN.

To do this I ran the container with privileges to create a tun interface like so:

1podman run --rm --cap-add NET_ADMIN,NET_RAW --device /dev/net/tun \
2 --name ${CONTAINER_NAME} \
3 -v /tmp/ovpn/:/tmp/ovpn/ \
4 -it -d ${NAME} \
5 bash -c "
6 openvpn \
7 --config $FILE \
8 --auth-nocache \
9 --auth-retry nointeract \
10 --dev $tun \
11 --dev-type tun \
12 --errors-to-stderr \
13 --auth-user-pass <(echo -e '$USERNAME\n$PASSWORD')
14 " 1>&2

Then I used nsenter to run a command in the context of the containers network namespace:

1...
2PID=$(podman inspect ${CONTAINER_NAME} | jq -r '.[0] | .State.Pid')
3nsenter -U -n -t $PID $@

This worked just as expected, and when the command finishes, the container can be stopped and removed for seamless cleanup.

Hooray containers and namespaces! 📦🎊

Wrapping this all together in a script run_behind_vpn, I can now run a command masquerading as different IPs like so:

1#!/usr/bin/env bash
2export USERNAME=$USERNAME
3export PASSWORD=$PASSWORD
4export US=$(find /tmp/ovpn/*us* -print -quit) # find a US ovpn file
5export JAPAN=$(find /tmp/ovpn/*jp* -print -quit) # find a JP ovpn file
6run_behind_vpn $US -- curl -s ipinfo.io | jq -r '.country'
7run_behind_vpn $JAPAN -- curl -s ipinfo.io | jq -r '.country'
1US
2JP

Now if I ever need to pretend that I'm running my computer from a different country or need a new IP, I have a method to do so.

Try the script out for yourself here and thanks for reading!

More posts from The Art of Abstraction

Committing to lunch

Solving common problems with commitment schemes

July 11th, 2021 · 10 min read

Why start a blog?

What is this blog, and why does it exist?

November 2nd, 2020 · 7 min read