@@ -42,7 +42,7 @@ ENV VPNSP=pia \
|
|||||||
UID=1000 \
|
UID=1000 \
|
||||||
GID=1000 \
|
GID=1000 \
|
||||||
IP_STATUS_FILE="/ip" \
|
IP_STATUS_FILE="/ip" \
|
||||||
# PIA, Windscribe, Surfshark and Cyberghost only
|
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN only
|
||||||
USER= \
|
USER= \
|
||||||
PASSWORD= \
|
PASSWORD= \
|
||||||
REGION="Austria" \
|
REGION="Austria" \
|
||||||
@@ -58,6 +58,8 @@ ENV VPNSP=pia \
|
|||||||
PORT= \
|
PORT= \
|
||||||
# Cyberghost only
|
# Cyberghost only
|
||||||
CYBERGHOST_GROUP="Premium UDP Europe" \
|
CYBERGHOST_GROUP="Premium UDP Europe" \
|
||||||
|
# NordVPN only
|
||||||
|
SERVER_NUMBER= \
|
||||||
# Openvpn
|
# Openvpn
|
||||||
OPENVPN_CIPHER= \
|
OPENVPN_CIPHER= \
|
||||||
OPENVPN_AUTH= \
|
OPENVPN_AUTH= \
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -1,6 +1,8 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access, Mullvad, Windscribe, Surfshark and Cyberghost VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
|
||||||
|
Mullvad, Windscribe, Surfshark Cyberghost and NordVPN VPN servers, using Go, OpenVPN,
|
||||||
|
iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
||||||
|
|
||||||
**ANNOUNCEMENT**: *[Video of the Git history of Gluetun](https://youtu.be/khipOYJtGJ0)*
|
**ANNOUNCEMENT**: *[Video of the Git history of Gluetun](https://youtu.be/khipOYJtGJ0)*
|
||||||
|
|
||||||
@@ -33,7 +35,8 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.12 for a small Docker image of 52MB
|
- Based on Alpine 3.12 for a small Docker image of 52MB
|
||||||
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark** and **Cyberghost** servers
|
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**,
|
||||||
|
**Surfshark**, **Cyberghost** and **NordVPN** servers
|
||||||
- DNS over TLS baked in with service provider(s) of your choice
|
- DNS over TLS baked in with service provider(s) of your choice
|
||||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||||
- Choose the vpn network protocol, `udp` or `tcp`
|
- Choose the vpn network protocol, `udp` or `tcp`
|
||||||
@@ -90,9 +93,9 @@
|
|||||||
[](https://windscribe.com/?affid=mh7nyafu)
|
[](https://windscribe.com/?affid=mh7nyafu)
|
||||||
|
|
||||||
- Surfshark: **username** and **password** ([sign up](https://order.surfshark.com/))
|
- Surfshark: **username** and **password** ([sign up](https://order.surfshark.com/))
|
||||||
- Cyberghost: **username**, **password** and **device client key file**
|
- Cyberghost: **username**, **password** and **device client key file** ([sign up](https://www.cyberghostvpn.com/en_US/buy/cyberghost-vpn-4))
|
||||||
([sign up](https://www.cyberghostvpn.com/en_US/buy/cyberghost-vpn-4))
|
|
||||||
- Vyprvpn: **username** and **password**
|
- Vyprvpn: **username** and **password**
|
||||||
|
- NordVPN: **username** and **password**
|
||||||
- If you have a host or router firewall, please refer [to the firewall documentation](https://github.com/qdm12/private-internet-access-docker/blob/master/doc/firewall.md)
|
- If you have a host or router firewall, please refer [to the firewall documentation](https://github.com/qdm12/private-internet-access-docker/blob/master/doc/firewall.md)
|
||||||
|
|
||||||
1. On some devices you may need to setup your tunnel kernel module on your host with `insmod /lib/modules/tun.ko` or `modprobe tun`
|
1. On some devices you may need to setup your tunnel kernel module on your host with `insmod /lib/modules/tun.ko` or `modprobe tun`
|
||||||
@@ -118,7 +121,6 @@
|
|||||||
- Use `-p 8888:8888/tcp` to access the HTTP web proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
|
- Use `-p 8888:8888/tcp` to access the HTTP web proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
|
||||||
- Use `-p 8388:8388/tcp -p 8388:8388/udp` to access the SOCKS5 proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
|
- Use `-p 8388:8388/tcp -p 8388:8388/udp` to access the SOCKS5 proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
|
||||||
- Use `-p 8000:8000/tcp` to access the [HTTP control server](#HTTP-control-server) built-in
|
- Use `-p 8000:8000/tcp` to access the [HTTP control server](#HTTP-control-server) built-in
|
||||||
- Pass additional arguments to *openvpn* using Docker's command function (commands after the image name)
|
|
||||||
|
|
||||||
**If you encounter an issue with the tun device not being available, see [the FAQ](https://github.com/qdm12/private-internet-access-docker/blob/master/doc/faq.md#how-to-fix-openvpn-failing-to-start)**
|
**If you encounter an issue with the tun device not being available, see [the FAQ](https://github.com/qdm12/private-internet-access-docker/blob/master/doc/faq.md#how-to-fix-openvpn-failing-to-start)**
|
||||||
|
|
||||||
@@ -142,7 +144,7 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
|
|||||||
|
|
||||||
| Variable | Default | Choices | Description |
|
| Variable | Default | Choices | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn` | VPN Service Provider |
|
| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn`, `nordvpn` | VPN Service Provider |
|
||||||
| `IP_STATUS_FILE` | `/ip` | Any filepath | Filepath to store the public IP address assigned |
|
| `IP_STATUS_FILE` | `/ip` | Any filepath | Filepath to store the public IP address assigned |
|
||||||
| `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use |
|
| `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use |
|
||||||
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
|
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
|
||||||
@@ -210,13 +212,22 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
|
|||||||
|
|
||||||
And use the line produced as the value for the environment variable `CLIENT_KEY`.
|
And use the line produced as the value for the environment variable `CLIENT_KEY`.
|
||||||
|
|
||||||
- VyprVPN
|
- NordVPN
|
||||||
|
|
||||||
| Variable | Default | Choices | Description |
|
| Variable | Default | Choices | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 🏁 `USER` | | | Your username |
|
| 🏁 `USER` | | | Your username |
|
||||||
| 🏁 `PASSWORD` | | | Your password |
|
| 🏁 `PASSWORD` | | | Your password |
|
||||||
| `REGION` | `Austria` | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region |
|
| `REGION` | `Austria` | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region |
|
||||||
|
| `SERVER_NUMBER` | | Server integer number | Optional server number. For example `251` for `Italy #251` |
|
||||||
|
|
||||||
|
- NordVPN
|
||||||
|
|
||||||
|
| Variable | Default | Choices | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 🏁 `USER` | | | Your username |
|
||||||
|
| 🏁 `PASSWORD` | | | Your password |
|
||||||
|
| 🏁 `REGION` | `Austria` (wrong) | One of the NordVPN server name, i.e. `Cyprus #12` | VPN server name |
|
||||||
|
|
||||||
### DNS over TLS
|
### DNS over TLS
|
||||||
|
|
||||||
|
|||||||
106
cmd/mapper/main.go
Normal file
106
cmd/mapper/main.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/network"
|
||||||
|
"github.com/qdm12/private-internet-access-docker/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
os.Exit(_main())
|
||||||
|
}
|
||||||
|
|
||||||
|
func _main() int {
|
||||||
|
provider := flag.String("provider", "nordvpn", "VPN provider to map region to IP addresses using their API, can be 'nordvpn'")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
client := network.NewClient(30 * time.Second) // big file so 30 seconds
|
||||||
|
switch *provider {
|
||||||
|
case "nordvpn":
|
||||||
|
servers, ignoredServers, err := nordvpn(client)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
for _, server := range servers {
|
||||||
|
fmt.Printf(
|
||||||
|
"{Region: %q, Number: %d, TCP: %t, UDP: %t, IP: net.IP{%s}},\n",
|
||||||
|
server.Region, server.Number, server.TCP, server.UDP, strings.ReplaceAll(server.IP.String(), ".", ", "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fmt.Print("\n\n")
|
||||||
|
for _, serverName := range ignoredServers {
|
||||||
|
fmt.Printf("ignored server %q because it does not support both UDP and TCP\n", serverName)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Printf("Provider %q is not supported\n", *provider)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func nordvpn(client network.Client) (servers []models.NordvpnServer, ignoredServers []string, err error) {
|
||||||
|
content, status, err := client.GetContent("https://nordvpn.com/api/server")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else if status != http.StatusOK {
|
||||||
|
return nil, nil, fmt.Errorf("HTTP status %d from NordVPN API", status)
|
||||||
|
}
|
||||||
|
response := []struct {
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Features struct {
|
||||||
|
UDP bool `json:"openvpn_udp"`
|
||||||
|
TCP bool `json:"openvpn_tcp"`
|
||||||
|
} `json:"features"`
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(content, &response); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, element := range response {
|
||||||
|
if !element.Features.TCP && !element.Features.UDP {
|
||||||
|
ignoredServers = append(ignoredServers, element.Name)
|
||||||
|
}
|
||||||
|
ip := net.ParseIP(element.IPAddress)
|
||||||
|
if ip == nil {
|
||||||
|
return nil, nil, fmt.Errorf("IP address %q is not valid for server %q", element.IPAddress, element.Name)
|
||||||
|
}
|
||||||
|
i := strings.IndexRune(element.Name, '#')
|
||||||
|
if i < 0 {
|
||||||
|
return nil, nil, fmt.Errorf("No ID in server name %q", element.Name)
|
||||||
|
}
|
||||||
|
idString := element.Name[i+1:]
|
||||||
|
idUint64, err := strconv.ParseUint(idString, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("Bad ID in server name %q", element.Name)
|
||||||
|
}
|
||||||
|
id := uint16(idUint64)
|
||||||
|
server := models.NordvpnServer{
|
||||||
|
Region: element.Country,
|
||||||
|
Number: id,
|
||||||
|
IP: ip,
|
||||||
|
TCP: element.Features.TCP,
|
||||||
|
UDP: element.Features.UDP,
|
||||||
|
}
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
sort.Slice(servers, func(i, j int) bool {
|
||||||
|
if servers[i].Region == servers[j].Region {
|
||||||
|
return servers[i].Number < servers[j].Number
|
||||||
|
}
|
||||||
|
return servers[i].Region < servers[j].Region
|
||||||
|
})
|
||||||
|
return servers, ignoredServers, nil
|
||||||
|
}
|
||||||
@@ -15,50 +15,25 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
# More variables are available, see the readme table
|
# More variables are available, see the readme table
|
||||||
- VPNSP=private internet access
|
- VPNSP=private internet access
|
||||||
- PROTOCOL=udp
|
|
||||||
- OPENVPN_VERBOSITY=1
|
# Timezone for accurate logs times
|
||||||
- OPENVPN_ROOT=no
|
|
||||||
- OPENVPN_TARGET_IP=
|
|
||||||
- TZ=
|
- TZ=
|
||||||
|
|
||||||
# PIA, Windscribe, Surfshark and Cyberghost only
|
# All VPN providers
|
||||||
- REGION=Austria
|
|
||||||
- USER=js89ds7
|
- USER=js89ds7
|
||||||
|
|
||||||
|
# All VPN providers but Mullvad
|
||||||
- PASSWORD=8fd9s239G
|
- PASSWORD=8fd9s239G
|
||||||
|
|
||||||
# PIA only
|
# Cyberghost only
|
||||||
- PIA_ENCRYPTION=strong
|
- CLIENT_KEY=
|
||||||
- PORT_FORWARDING=off
|
|
||||||
|
# All VPN providers but Mullvad
|
||||||
|
- REGION=Austria
|
||||||
|
|
||||||
# Mullvad only
|
# Mullvad only
|
||||||
- COUNTRY=Sweden
|
- COUNTRY=Sweden
|
||||||
- CITY=
|
|
||||||
- ISP=
|
|
||||||
|
|
||||||
# Mullvad and Windscribe only
|
# Allow for example your LAN, set to: 192.168.1.0/24
|
||||||
- PORT=
|
|
||||||
|
|
||||||
# Cyberghost only
|
|
||||||
- CYBERGHOST_GROUP=Premium UDP Europe
|
|
||||||
- CLIENT_KEY=
|
|
||||||
|
|
||||||
# DNS over TLS
|
|
||||||
- DOT=on
|
|
||||||
- DOT_PROVIDERS=cloudflare
|
|
||||||
- DOT_IPV6=off
|
|
||||||
- DOT_VERBOSITY=1
|
|
||||||
- BLOCK_MALICIOUS=on
|
|
||||||
- BLOCK_SURVEILLANCE=off
|
|
||||||
- BLOCK_ADS=off
|
|
||||||
- UNBLOCK=
|
|
||||||
- DNS_UPDATE_PERIOD=24h
|
|
||||||
# Firewall
|
|
||||||
- EXTRA_SUBNETS=
|
- EXTRA_SUBNETS=
|
||||||
# Shadowsocks
|
|
||||||
- SHADOWSOCKS=off
|
|
||||||
- SHADOWSOCKS_PASSWORD=
|
|
||||||
# Tinyproxy
|
|
||||||
- TINYPROXY=off
|
|
||||||
- TINYPROXY_USER=
|
|
||||||
- TINYPROXY_PASSWORD=
|
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
5184
internal/constants/nordvpn.go
Normal file
5184
internal/constants/nordvpn.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,8 @@ const (
|
|||||||
Cyberghost models.VPNProvider = "cyberghost"
|
Cyberghost models.VPNProvider = "cyberghost"
|
||||||
// Vyprvpn is a VPN provider
|
// Vyprvpn is a VPN provider
|
||||||
Vyprvpn models.VPNProvider = "vyprvpn"
|
Vyprvpn models.VPNProvider = "vyprvpn"
|
||||||
|
// NordVPN is a VPN provider
|
||||||
|
Nordvpn models.VPNProvider = "nordvpn"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ type ProviderSettings struct {
|
|||||||
PortForwarding PortForwarding
|
PortForwarding PortForwarding
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerSelection struct {
|
type ServerSelection struct { //nolint:maligned
|
||||||
// Common
|
// Common
|
||||||
Protocol NetworkProtocol
|
Protocol NetworkProtocol
|
||||||
TargetIP net.IP
|
TargetIP net.IP
|
||||||
|
|
||||||
// Cyberghost, PIA, Surfshark, Windscribe
|
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
|
||||||
Region string
|
Region string
|
||||||
|
|
||||||
// Cyberghost
|
// Cyberghost
|
||||||
@@ -36,6 +36,9 @@ type ServerSelection struct {
|
|||||||
|
|
||||||
// PIA
|
// PIA
|
||||||
EncryptionPreset string
|
EncryptionPreset string
|
||||||
|
|
||||||
|
// NordVPN
|
||||||
|
Number uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtraConfigOptions struct {
|
type ExtraConfigOptions struct {
|
||||||
@@ -61,6 +64,14 @@ func (p *ProviderSettings) String() string {
|
|||||||
fmt.Sprintf("%s settings:", strings.Title(string(p.Name))),
|
fmt.Sprintf("%s settings:", strings.Title(string(p.Name))),
|
||||||
"Network protocol: " + string(p.ServerSelection.Protocol),
|
"Network protocol: " + string(p.ServerSelection.Protocol),
|
||||||
}
|
}
|
||||||
|
customPort := ""
|
||||||
|
if p.ServerSelection.CustomPort > 0 {
|
||||||
|
customPort = fmt.Sprintf("%d", p.ServerSelection.CustomPort)
|
||||||
|
}
|
||||||
|
number := ""
|
||||||
|
if p.ServerSelection.Number > 0 {
|
||||||
|
number = fmt.Sprintf("%d", p.ServerSelection.Number)
|
||||||
|
}
|
||||||
switch strings.ToLower(string(p.Name)) {
|
switch strings.ToLower(string(p.Name)) {
|
||||||
case "private internet access":
|
case "private internet access":
|
||||||
settingsList = append(settingsList,
|
settingsList = append(settingsList,
|
||||||
@@ -73,12 +84,12 @@ func (p *ProviderSettings) String() string {
|
|||||||
"Country: "+p.ServerSelection.Country,
|
"Country: "+p.ServerSelection.Country,
|
||||||
"City: "+p.ServerSelection.City,
|
"City: "+p.ServerSelection.City,
|
||||||
"ISP: "+p.ServerSelection.ISP,
|
"ISP: "+p.ServerSelection.ISP,
|
||||||
"Custom port: "+string(p.ServerSelection.CustomPort),
|
"Custom port: "+customPort,
|
||||||
)
|
)
|
||||||
case "windscribe":
|
case "windscribe":
|
||||||
settingsList = append(settingsList,
|
settingsList = append(settingsList,
|
||||||
"Region: "+p.ServerSelection.Region,
|
"Region: "+p.ServerSelection.Region,
|
||||||
"Custom port: "+string(p.ServerSelection.CustomPort),
|
"Custom port: "+customPort,
|
||||||
)
|
)
|
||||||
case "surfshark":
|
case "surfshark":
|
||||||
settingsList = append(settingsList,
|
settingsList = append(settingsList,
|
||||||
@@ -94,6 +105,15 @@ func (p *ProviderSettings) String() string {
|
|||||||
settingsList = append(settingsList,
|
settingsList = append(settingsList,
|
||||||
"Region: "+p.ServerSelection.Region,
|
"Region: "+p.ServerSelection.Region,
|
||||||
)
|
)
|
||||||
|
case "nordvpn":
|
||||||
|
settingsList = append(settingsList,
|
||||||
|
"Region: "+p.ServerSelection.Region,
|
||||||
|
"Number: "+number,
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
settingsList = append(settingsList,
|
||||||
|
"<Missing String method, please implement me!>",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if p.ServerSelection.TargetIP != nil {
|
if p.ServerSelection.TargetIP != nil {
|
||||||
settingsList = append(settingsList,
|
settingsList = append(settingsList,
|
||||||
|
|||||||
@@ -36,3 +36,11 @@ type VyprvpnServer struct {
|
|||||||
Region string
|
Region string
|
||||||
IPs []net.IP
|
IPs []net.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NordvpnServer struct { //nolint:maligned
|
||||||
|
Region string
|
||||||
|
Number uint16
|
||||||
|
IP net.IP
|
||||||
|
TCP bool
|
||||||
|
UDP bool
|
||||||
|
}
|
||||||
|
|||||||
22
internal/params/nordvpn.go
Normal file
22
internal/params/nordvpn.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package params
|
||||||
|
|
||||||
|
import (
|
||||||
|
libparams "github.com/qdm12/golibs/params"
|
||||||
|
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetNordvpnRegion obtains the region (country) for the NordVPN server from the
|
||||||
|
// environment variable REGION
|
||||||
|
func (r *reader) GetNordvpnRegion() (region string, err error) {
|
||||||
|
return r.envParams.GetValueIfInside("REGION", constants.NordvpnRegionChoices())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNordvpnRegion obtains the server number (optional) for the NordVPN server from the
|
||||||
|
// environment variable SERVER_NUMBER
|
||||||
|
func (r *reader) GetNordvpnNumber() (number uint16, err error) {
|
||||||
|
n, err := r.envParams.GetEnvIntRange("SERVER_NUMBER", 0, 65535, libparams.Default("0"))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint16(n), nil
|
||||||
|
}
|
||||||
@@ -81,6 +81,10 @@ type Reader interface {
|
|||||||
// Vyprvpn getters
|
// Vyprvpn getters
|
||||||
GetVyprvpnRegion() (region string, err error)
|
GetVyprvpnRegion() (region string, err error)
|
||||||
|
|
||||||
|
// NordVPN getters
|
||||||
|
GetNordvpnRegion() (region string, err error)
|
||||||
|
GetNordvpnNumber() (number uint16, err error)
|
||||||
|
|
||||||
// Shadowsocks getters
|
// Shadowsocks getters
|
||||||
GetShadowSocks() (activated bool, err error)
|
GetShadowSocks() (activated bool, err error)
|
||||||
GetShadowSocksLog() (activated bool, err error)
|
GetShadowSocksLog() (activated bool, err error)
|
||||||
@@ -123,7 +127,7 @@ func NewReader(logger logging.Logger, fileManager files.FileManager) Reader {
|
|||||||
|
|
||||||
// GetVPNSP obtains the VPN service provider to use from the environment variable VPNSP
|
// GetVPNSP obtains the VPN service provider to use from the environment variable VPNSP
|
||||||
func (r *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) {
|
func (r *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) {
|
||||||
s, err := r.envParams.GetValueIfInside("VPNSP", []string{"pia", "private internet access", "mullvad", "windscribe", "surfshark", "cyberghost", "vyprvpn"})
|
s, err := r.envParams.GetValueIfInside("VPNSP", []string{"pia", "private internet access", "mullvad", "windscribe", "surfshark", "cyberghost", "vyprvpn", "nordvpn"})
|
||||||
if s == "pia" {
|
if s == "pia" {
|
||||||
s = "private internet access"
|
s = "private internet access"
|
||||||
}
|
}
|
||||||
|
|||||||
153
internal/provider/nordvpn.go
Normal file
153
internal/provider/nordvpn.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/network"
|
||||||
|
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||||
|
"github.com/qdm12/private-internet-access-docker/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nordvpn struct{}
|
||||||
|
|
||||||
|
func newNordvpn() *nordvpn {
|
||||||
|
return &nordvpn{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findServers(selection models.ServerSelection) (servers []models.NordvpnServer) {
|
||||||
|
for _, server := range constants.NordvpnServers() {
|
||||||
|
if strings.EqualFold(server.Region, selection.Region) {
|
||||||
|
if (selection.Protocol == constants.TCP && !server.TCP) || (selection.Protocol == constants.UDP && !server.UDP) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if selection.Number > 0 && server.Number == selection.Number {
|
||||||
|
return []models.NordvpnServer{server}
|
||||||
|
}
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return servers
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractIPsFromServers(servers []models.NordvpnServer) (ips []net.IP) {
|
||||||
|
ips = make([]net.IP, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
ips[i] = servers[i].IP
|
||||||
|
}
|
||||||
|
return ips
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetIPInIps(targetIP net.IP, ips []net.IP) error {
|
||||||
|
for i := range ips {
|
||||||
|
if targetIP.Equal(ips[i]) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipsString := make([]string, len(ips))
|
||||||
|
for i := range ips {
|
||||||
|
ipsString[i] = ips[i].String()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("target IP address %s not found in IP addresses %s", targetIP, strings.Join(ipsString, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nordvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
|
||||||
|
servers := findServers(selection)
|
||||||
|
ips := extractIPsFromServers(servers)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
if selection.Number > 0 {
|
||||||
|
return nil, fmt.Errorf("no IP found for region %q, protocol %s and number %d", selection.Region, selection.Protocol, selection.Number)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no IP found for region %q, protocol %s", selection.Region, selection.Protocol)
|
||||||
|
}
|
||||||
|
var IP net.IP
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
if err := targetIPInIps(selection.TargetIP, ips); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
IP = selection.TargetIP
|
||||||
|
} else {
|
||||||
|
IP = ips[0]
|
||||||
|
}
|
||||||
|
var port uint16
|
||||||
|
switch {
|
||||||
|
case selection.Protocol == constants.UDP:
|
||||||
|
port = 1194
|
||||||
|
case selection.Protocol == constants.TCP:
|
||||||
|
port = 443
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
||||||
|
}
|
||||||
|
return []models.OpenVPNConnection{{IP: IP, Port: port, Protocol: selection.Protocol}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
|
||||||
|
if len(cipher) == 0 {
|
||||||
|
cipher = aes256cbc
|
||||||
|
}
|
||||||
|
if len(auth) == 0 {
|
||||||
|
auth = "sha512"
|
||||||
|
}
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
|
||||||
|
// Nordvpn specific
|
||||||
|
"resolv-retry infinite",
|
||||||
|
"tun-mtu 1500",
|
||||||
|
"tun-mtu-extra 32",
|
||||||
|
"mssfix 1450",
|
||||||
|
"ping 15",
|
||||||
|
"ping-restart 0",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"reneg-sec 0",
|
||||||
|
"comp-lzo no",
|
||||||
|
"fast-io",
|
||||||
|
"key-direction 1",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"remote-random",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
|
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||||
|
fmt.Sprintf("proto %s", string(connections[0].Protocol)),
|
||||||
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
|
fmt.Sprintf("auth %s", auth),
|
||||||
|
}
|
||||||
|
if !root {
|
||||||
|
lines = append(lines, "user nonrootuser")
|
||||||
|
}
|
||||||
|
for _, connection := range connections {
|
||||||
|
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port))
|
||||||
|
}
|
||||||
|
lines = append(lines, []string{
|
||||||
|
"<ca>",
|
||||||
|
"-----BEGIN CERTIFICATE-----",
|
||||||
|
constants.NordvpnCertificate,
|
||||||
|
"-----END CERTIFICATE-----",
|
||||||
|
"</ca>",
|
||||||
|
}...)
|
||||||
|
lines = append(lines, []string{
|
||||||
|
"<tls-auth>",
|
||||||
|
"-----BEGIN OpenVPN Static key V1-----",
|
||||||
|
constants.NordvpnOpenvpnStaticKeyV1,
|
||||||
|
"-----END OpenVPN Static key V1-----",
|
||||||
|
"</tls-auth>",
|
||||||
|
"",
|
||||||
|
}...)
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nordvpn) GetPortForward(client network.Client) (port uint16, err error) {
|
||||||
|
panic("port forwarding is not supported for nordvpn")
|
||||||
|
}
|
||||||
@@ -27,6 +27,8 @@ func New(provider models.VPNProvider) Provider {
|
|||||||
return newCyberghost()
|
return newCyberghost()
|
||||||
case constants.Vyprvpn:
|
case constants.Vyprvpn:
|
||||||
return newVyprvpn()
|
return newVyprvpn()
|
||||||
|
case constants.Nordvpn:
|
||||||
|
return newNordvpn()
|
||||||
default:
|
default:
|
||||||
return nil // should never occur
|
return nil // should never occur
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func newSurfshark() *surfshark {
|
|||||||
return &surfshark{}
|
return &surfshark{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
|
func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
|
||||||
var IPs []net.IP
|
var IPs []net.IP
|
||||||
for _, server := range constants.SurfsharkServers() {
|
for _, server := range constants.SurfsharkServers() {
|
||||||
if strings.EqualFold(server.Region, selection.Region) {
|
if strings.EqualFold(server.Region, selection.Region) {
|
||||||
@@ -54,7 +54,7 @@ func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (con
|
|||||||
return connections, nil
|
return connections, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ func GetOpenVPNSettings(paramsReader params.Reader, vpnProvider models.VPNProvid
|
|||||||
settings.Provider, err = GetCyberghostSettings(paramsReader)
|
settings.Provider, err = GetCyberghostSettings(paramsReader)
|
||||||
case constants.Vyprvpn:
|
case constants.Vyprvpn:
|
||||||
settings.Provider, err = GetVyprvpnSettings(paramsReader)
|
settings.Provider, err = GetVyprvpnSettings(paramsReader)
|
||||||
|
case constants.Nordvpn:
|
||||||
|
settings.Provider, err = GetNordvpnSettings(paramsReader)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("VPN service provider %q is not valid", vpnProvider)
|
err = fmt.Errorf("VPN service provider %q is not valid", vpnProvider)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,3 +153,25 @@ func GetVyprvpnSettings(paramsReader params.Reader) (settings models.ProviderSet
|
|||||||
}
|
}
|
||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNordvpnSettings obtains NordVPN settings from environment variables using the params package.
|
||||||
|
func GetNordvpnSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) {
|
||||||
|
settings.Name = constants.Nordvpn
|
||||||
|
settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.ServerSelection.Region, err = paramsReader.GetNordvpnRegion()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.ServerSelection.Number, err = paramsReader.GetNordvpnNumber()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user