- Renamed bad name in PR docker build - Removed escaped \n in greetings - Fixed up misspell action
Private Internet Access Client
Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access or Mullvad VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and Tinyproxy
ANNOUNCEMENT: Support for Mullvad
Click to show base components
- Alpine 3.11 for a tiny image (37MB of packages, 6.7MB of Go binary and 5.6MB for Alpine)
- OpenVPN 2.4.8 to tunnel to PIA servers
- IPtables 1.8.3 enforces the container to communicate only through the VPN or with other containers in its virtual network (acts as a killswitch)
- Unbound 1.9.6 configured with Cloudflare's 1.1.1.1 DNS over TLS (configurable with 5 different providers)
- Files and blocking lists built periodically used with Unbound (see
BLOCK_MALICIOUS,BLOCK_SURVEILLANCEandBLOCK_ADSenvironment variables) - TinyProxy 1.10.0
- Shadowsocks 3.3.4
Features
- Based on Alpine 3.11 for a small Docker image below 50MB
- Supports Private Internet Access and Mullvad servers
- DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses
- Choose the vpn network protocol,
udportcp - Built in firewall kill switch to allow traffic only with needed PIA servers and LAN devices
- Built in SOCKS5 proxy (Shadowsocks, tunnels TCP+UDP)
- Built in HTTP proxy (Tinyproxy, tunnels TCP)
- Connect other containers to it
- Connect LAN devices to it
- Compatible with amd64, i686 (32 bit), ARM 64 bit, ARM 32 bit v6 and v7, ppc64le and even that s390x 🎆
Private Internet Access
- Pick the region
- Pick the level of encryption
- Enable port forwarding
Mullvad
- Pick the country, city and ISP
- Pick the port to use (i.e.
53(udp) or80(tcp))
Extra niche features
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Subprograms all drop root privileges once launched
- Subprograms output streams are all merged together
- Can work as a Kubernetes sidecar container, thanks @rorph
Setup
-
Requirements
-
Docker 1.13, in order to have Docker API 1.25 which supports
init(and, if you use docker-compose, docker-compose version 1.22.0) -
A Private Internet Access username and password (sign up) or Mullvad user ID (sign up)
-
External firewall requirements, if you have one
- At start only
- Allow outbound TCP 443 to github.com
- If
DOT=on, allow outbound TCP 853 to allow Unbound to resolve github.com and the PIA subdomain name if you use PIA. - If
DOT=offandVPNSP=pia, allow outbound UDP 53 to your DNS provider to resolve the PIA subdomain name.
- If
VPNSP=pia,PIA_ENCRYPTION=strongandPROTOCOL=udp: allow outbound UDP 1197 to the corresponding VPN server IPs - If
VPNSP=pia,PIA_ENCRYPTION=normalandPROTOCOL=udp: allow outbound UDP 1198 to the corresponding VPN server IPs - If
VPNSP=pia,PIA_ENCRYPTION=strongandPROTOCOL=tcp: allow outbound TCP 501 to the corresponding VPN server IPs - If
VPNSP=pia,PIA_ENCRYPTION=normalandPROTOCOL=tcp: allow outbound TCP 502 to the corresponding VPN server IPs - If
VPNSP=mullvadandPORT=, please refer to the mapping of Mullvad servers in these source code lines to find the corresponding UDP port number and IP address(es) of your choice - If
VPNSP=mullvadandPORT=53, allow outbound UDP 53 to the corresponding VPN server IPs, which you can fine in the mapping of Mullvad servers - If
VPNSP=mullvadandPORT=80, allow outbound TCP 80 to the corresponding VPN server IPs, which you can fine in the mapping of Mullvad servers - If
VPNSP=mullvadandPORT=443, allow outbound TCP 443 to the corresponding VPN server IPs, which you can fine in the mapping of Mullvad servers - If
SHADOWSOCKS=on, allow inbound TCP 8388 and UDP 8388 from your LAN - If
TINYPROXY=on, allow inbound TCP 8888 from your LAN
- At start only
-
-
Launch the container with:
docker run -d --init --name=pia --cap-add=NET_ADMIN \ -e REGION="CA Montreal" -e USER=js89ds7 -e PASSWORD=8fd9s239G \ qmcgaw/private-internet-accessor use docker-compose.yml with:
docker-compose up -dNote that you can:
- Change the many environment variables available
- Use
-p 8888:8888/tcpto access the HTTP web proxy (and put your LAN inEXTRA_SUBNETSenvironment variable) - Use
-p 8388:8388/tcp -p 8388:8388/udpto access the SOCKS5 proxy (and put your LAN inEXTRA_SUBNETSenvironment variable) - Pass additional arguments to openvpn using Docker's command function (commands after the image name)
-
You can update the image with
docker pull qmcgaw/private-internet-access:latest. There are also docker tags for older versions available:qmcgaw/private-internet-access:v2linked to the v2 release (Golang based, only PIA)qmcgaw/private-internet-access:v1linked to the v1 release (shell scripting based, no support, only PIA)qmcgaw/private-internet-access:oldtag, which is the latest shell scripting version (shell scripting based, no support, only PIA)
Testing
Check the PIA IP address matches your expectations
docker run --rm --network=container:pia alpine:3.11 wget -qO- https://ipinfo.io
Environment variables
| Environment variable | Default | Description |
|---|---|---|
VPNSP |
pia |
VPN Service Provider, one of pia, mullvad |
REGION |
CA Montreal |
(PIA only) one of the PIA regions |
COUNTRY |
Sweden |
(Mullvad only) one of the Mullvad countries |
CITY |
(Mullvad only, optional) one of the Mullvad cities | |
ISP |
(Mullvad only, optional) one of the Mullvad ISP | |
PORT |
(Mullvad only, optional) For TCP, 80 or 443, or 53 for UDP. Leave blank for default Mullvad server port |
|
PROTOCOL |
udp |
tcp or udp |
PIA_ENCRYPTION |
strong |
(PIA only) normal or strong or custom |
USER |
PIA username or Mullvad user ID | |
PASSWORD |
Your PIA password | |
DOT |
on |
on or off, to activate DNS over TLS to 1.1.1.1 |
DOT_PROVIDERS |
cloudflare |
Comma delimited list of DNS over TLS providers from cloudflare, google, quad9, quadrant, cleanbrowsing, securedns, libredns |
DOT_CACHING |
on |
Unbound caching feature, on or off |
DOT_IPV6 |
on |
Unbound will resolve domain names using IPv6 as well as IPv4 |
DOT_PRIVATE_ADDRESS |
All IPv4 and IPv6 CIDRs private ranges | Comma separated list of CIDRs or single IP addresses. Note that the default setting prevents DNS rebinding |
DOT_VERBOSITY |
1 |
Unbound verbosity level from 0 to 5 (full debug) |
DOT_VERBOSITY_DETAILS |
0 |
Unbound details verbosity level from 0 to 4 |
DOT_VALIDATION_LOGLEVEL |
0 |
Unbound validation log level from 0 to 2 |
BLOCK_MALICIOUS |
on |
on or off, blocks malicious hostnames and IPs |
BLOCK_SURVEILLANCE |
off |
on or off, blocks surveillance hostnames and IPs |
BLOCK_ADS |
off |
on or off, blocks ads hostnames and IPs |
UNBLOCK |
comma separated string (i.e. web.com,web2.ca) to unblock hostnames |
|
EXTRA_SUBNETS |
comma separated subnets allowed in the container firewall (i.e. 192.168.1.0/24,192.168.10.121,10.0.0.5/28) |
|
PORT_FORWARDING |
off |
(PIA only) Set to on to forward a port on PIA server |
PORT_FORWARDING_STATUS_FILE |
/forwarded_port |
(PIA only) File path to store the forwarded port number |
TINYPROXY |
off |
on or off, to enable the internal HTTP proxy tinyproxy |
TINYPROXY_LOG |
Info |
Info, Connect, Notice, Warning, Error or Critical |
TINYPROXY_PORT |
8888 |
1024 to 65535 internal port for HTTP proxy |
TINYPROXY_USER |
Username to use to connect to the HTTP proxy | |
TINYPROXY_PASSWORD |
Passsword to use to connect to the HTTP proxy | |
SHADOWSOCKS |
off |
on or off, to enable the internal SOCKS5 proxy Shadowsocks |
SHADOWSOCKS_LOG |
off |
on or off to enable logging for Shadowsocks |
SHADOWSOCKS_PORT |
8388 |
1024 to 65535 internal port for SOCKS5 proxy |
SHADOWSOCKS_PASSWORD |
Passsword to use to connect to the SOCKS5 proxy | |
TZ |
Specify a timezone to use i.e. Europe/London |
|
OPENVPN_VERBOSITY |
1 |
Openvpn verbosity level from 0 to 6 |
OPENVPN_ROOT |
no |
Run OpenVPN as root, yes or no |
OPENVPN_TARGET_IP |
Specify a target VPN server IP address to use, valid for Mullvad and Private Internet Access | |
OPENVPN_CIPHER |
Specify a custom cipher to use, use at your own risk. It will also set ncp-disable if using AES GCM for PIA |
Connect to it
There are various ways to achieve this, depending on your use case.
-
Connect containers in the same docker-compose.yml as PIA
Add
network_mode: "service:pia"to your docker-compose.yml (no need fordepends_on) -
Connect other containers to PIA
Add
--network=container:piawhen launching the container, provided PIA is already running -
Connect containers from another docker-compose.yml
Add
network_mode: "container:pia"to your docker-compose.yml, provided PIA is already running -
Connect LAN devices through the built-in HTTP proxy *Tinyproxy* (i.e. with Chrome, Kodi, etc.)
You might want to use Shadowsocks instead which tunnels UDP as well as TCP, whereas Tinyproxy only tunnels TCP.
- Setup a HTTP proxy client, such as SwitchyOmega for Chrome
- Ensure the PIA container is launched with:
- port
8888published-p 8888:8888/tcp - your LAN subnet, i.e.
192.168.1.0/24, set as-e EXTRA_SUBNETS=192.168.1.0/24
- port
- With your HTTP proxy client, connect to the Docker host (i.e.
192.168.1.10) on port8888. You need to enter your credentials if you set them withTINYPROXY_USERandTINYPROXY_PASSWORD. - If you set
TINYPROXY_LOGtoInfo, more information will be logged in the Docker logs
-
Connect LAN devices through the built-in SOCKS5 proxy *Shadowsocks* (per app, system wide, etc.)
- Setup a SOCKS5 proxy client, there is a list of ShadowSocks clients for all platforms
- note some clients do not tunnel UDP so your DNS queries will be done locally and not through PIA and its built in DNS over TLS
- Clients that support such UDP tunneling are, as far as I know:
- iOS: Potatso Lite
- OSX: ShadowsocksX
- Android: Shadowsocks by Max Lv
- Ensure the PIA container is launched with:
- port
8388published-p 8388:8388/tcp -p 8388:8388/udp - your LAN subnet, i.e.
192.168.1.0/24, set as-e EXTRA_SUBNETS=192.168.1.0/24
- port
- With your SOCKS5 proxy client
- Enter the Docker host (i.e.
192.168.1.10) as the server IP - Enter port TCP (and UDP, if available)
8388as the server port - Use the password you have set with
SHADOWSOCKS_PASSWORD - Choose the encryption method/algorithm
chacha20-ietf-poly1305
- Enter the Docker host (i.e.
- If you set
SHADOWSOCKS_LOGtoon, (a lot) more information will be logged in the Docker logs
- Setup a SOCKS5 proxy client, there is a list of ShadowSocks clients for all platforms
-
Access ports of containers connected to PIA
In example, to access port
8000of containerxyzand9000of containerabcconnected to PIA, publish ports8000and9000for the PIA container and access them as you would with any other container -
Access ports of containers connected to PIA, all in the same docker-compose.yml
In example, to access port
8000of containerxyzand9000of containerabcconnected to PIA, publish port8000and9000for the PIA container. The docker-compose.yml file would look like:version: '3.7' services: pia: image: qmcgaw/private-internet-access container_name: pia init: true cap_add: - NET_ADMIN environment: - USER=js89ds7 - PASSWORD=8fd9s239G ports: - 8000:8000/tcp - 9000:9000/tcp abc: image: abc container_name: abc network_mode: "service:pia" xyz: image: xyz container_name: xyz network_mode: "service:pia"
Private Internet Access port forwarding
Note that not all regions support port forwarding.
When PORT_FORWARDING=on, a port will be forwarded on the PIA server side and written to the file specified by PORT_FORWARDING_STATUS_FILE=/forwarded_port.
It can be useful to mount this file as a volume to read it from other containers, for example to configure a torrenting client.
FAQ
Openvpn disconnects because of a ping timeout
It happens especially on some PIA servers where they change their configuration or the server goes offline.
You will obtain an error similar to:
openvpn: Wed Mar 18 22:13:00 2020 [3a51ae90324bcb0719cb399b650c64d4] Inactivity timeout (--ping-restart), restarting,
openvpn: Wed Mar 18 22:13:00 2020 SIGUSR1[soft,ping-restart] received, process restarting,
...
openvpn: Wed Mar 18 22:13:17 2020 Preserving previous TUN/TAP instance: tun0,
openvpn: Wed Mar 18 22:13:17 2020 NOTE: Pulled options changed on restart, will need to close and reopen TUN/TAP device.,
openvpn: Wed Mar 18 22:13:17 2020 ERROR: Linux route delete command failed: external program exited with error status: 2,
openvpn: Wed Mar 18 22:13:17 2020 ERROR: Linux route delete command failed: external program exited with error status: 2,
openvpn: Wed Mar 18 22:13:17 2020 ERROR: Linux route delete command failed: external program exited with error status: 2,
openvpn: Wed Mar 18 22:13:17 2020 ERROR: Linux route delete command failed: external program exited with error status: 2,
openvpn: Wed Mar 18 22:13:17 2020 /sbin/ip addr del dev tun0 local 10.6.11.6 peer 10.6.11.5,
openvpn: Wed Mar 18 22:13:17 2020 Linux ip addr del failed: external program exited with error status: 2,
openvpn: Wed Mar 18 22:13:18 2020 ERROR: Cannot ioctl TUNSETIFF tun: Operation not permitted (errno=1),
openvpn: Wed Mar 18 22:13:18 2020 Exiting due to fatal error,
exit status 1
To fix it, you would have to run openvpn with root, by setting the environment variable OPENVPN_ROOT=yes.
Private Internet Access: Why do I see openvpn warnings at start?
You might see some warnings similar to:
openvpn: Sat Feb 22 15:55:02 2020 WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
openvpn: Sat Feb 22 15:55:02 2020 WARNING: 'link-mtu' is used inconsistently, local='link-mtu 1569', remote='link-mtu 1542'
openvpn: Sat Feb 22 15:55:02 2020 WARNING: 'cipher' is used inconsistently, local='cipher AES-256-CBC', remote='cipher BF-CBC'
openvpn: Sat Feb 22 15:55:02 2020 WARNING: 'auth' is used inconsistently, local='auth SHA256', remote='auth SHA1'
openvpn: Sat Feb 22 15:55:02 2020 WARNING: 'keysize' is used inconsistently, local='keysize 256', remote='keysize 128'
openvpn: Sat Feb 22 15:55:02 2020 WARNING: 'comp-lzo' is present in remote config but missing in local config, remote='comp-lzo'
openvpn: Sat Feb 22 15:55:02 2020 [a121ce520d670b71bfd3aa475485539b] Peer Connection Initiated with [AF_INET]xx.xx.xx.xx:1197
It is mainly because the option disable-occ was removed for transparency with you.
Private Internet Access explains here why the warnings show up.
What files does it download at start before tunneling?
At start, the Go entrypoint only downloads, depending on your settings:
- If
DOT=on: DNS over TLS named root for Unbound - If
DOT=on: DNS over TLS root key for Unbound - If
BLOCK_MALICIOUS=on: Malicious hostnames and IP addresses block lists for Unbound - If
BLOCK_SURVEILLANCE=on: Surveillance hostnames and IP addresses block lists for Unbound - If
BLOCK_ADS=on: Ads hostnames and IP addresses block lists for Unbound
How to build Docker images of older or alternate versions
First, install Git.
The following will build the Docker image locally and replace the previous one you built or pulled.
-
Build the latest image
docker build -t qmcgaw/private-internet-access https://github.com/qdm12/private-internet-access-docker.git -
Find a commit you want to build for, in example
095623925a9cc0e5cf89d5b9b510714792267d9b, then:docker build -t qmcgaw/private-internet-access https://github.com/qdm12/private-internet-access-docker.git#095623925a9cc0e5cf89d5b9b510714792267d9b -
Find a branch you want to build for, in example
mullvad, then:docker build -t qmcgaw/private-internet-access https://github.com/qdm12/private-internet-access-docker.git#mullvad
Mullvad does not work with IPv6?
By default, the Mullvad server tunnels both ipv4 and ipv6, hence openvpn will try to create an
ipv6 route. To allow the container to create such route, you have to specify net.ipv6.conf.all.disable_ipv6=0
at runtime, using either:
-
For a Docker run command, the flag:
--sysctl net.ipv6.conf.all.disable_ipv6=0 -
In a docker-compose file:
sysctls: - net.ipv6.conf.all.disable_ipv6=0
What's all this Go code?
The Go code is a big rewrite of the previous shell entrypoint, it allows for:
- better testing
- better maintainability
- ease of implementing new features
- faster boot
- asynchronous/parallel operations
It is mostly made of the internal directory and the entry Go file cmd/main.go.
How to test DNS over TLS?
- You can test DNSSEC using internet.nl/connection
- Check DNS leak tests with https://www.dnsleaktest.com
- Some other DNS leaks tests might not work because of this (TLDR: Unbound DNS server is a local caching intermediary)
How to fix OpenVPN failing to start?
You can try:
- Installing the tun kernel module on your host with
insmod /lib/modules/tun.koormodprobe tun - Adding
--device=/dev/net/tunto your docker run command (equivalent for docker-compose, kubernetes, etc.)
Development
-
Setup your environment
Using VSCode and Docker
- Install Docker
- On Windows, share a drive with Docker Desktop and have the project on that partition
- On OSX, share your project directory with Docker Desktop
- With Visual Studio Code, install the remote containers extension
- In Visual Studio Code, press on
F1and selectRemote-Containers: Open Folder in Container... - Your dev environment is ready to go!... and it's running in a container 👍
Locally
Install Go, Docker and Git; then:
go mod download go get github.com/golang/mock/gomock go get github.com/golang/mock/mockgenAnd finally install golangci-lint
- Install Docker
-
Commands available:
# Build the entrypoint binary go build cmd/main.go # Test the entrypoint code go test ./... # Lint the code golangci-lint run # Build the Docker image docker build -t qmcgaw/private-internet-access . -
The Go code is in the Go file cmd/main.go and the internal directory, you might want to start reading the main.go file.
-
See Contributing for more information on how to contribute to this repository.
Contributors
Thanks for all the contributions, whether small or not so small!
- @JeordyR for testing the Mullvad version and opening a PR with a few fixes 👍
- @rorph for a PR to pick a random region for PIA and a PR to make the container work with kubernetes
- @JesterEE for a PR to fix silly line endings in block lists back then 📎
- @elmerfdz for a PR to add timezone information to have correct log timestampts 🕙
- @Juggels for a PR to write the PIA forwarded port to a file
- @gdlx for a PR to fix and improve PIA port forwarding script
- @janaz for keeping an eye on updating things in the Dockerfile
TODOs
Expand me
- Support Windscribe
- Gotify support for notificactions
- Periodic update of malicious block lists with Unbound restart
- Improve healthcheck
- Check IP address belongs to selected region
- Check for DNS provider somehow if this is even possible
- Support for other VPN protocols
- Wireguard (wireguard-go)
- Show new versions/commits available at start
- Colors & emojis
- Setup
- Logging streams
- More unit tests
- Write in Go
- DNS over TLS to replace Unbound
- HTTP proxy to replace tinyproxy
- use go-Shadowsocks2
- DNS over HTTPS, maybe use github.com/likexian/doh-go
- use iptables-go to replace iptables
- wireguard-go
- Openvpn to replace openvpn
License
This repository is under an MIT license