Nordvpn support (#189), fix #178

This commit is contained in:
Quentin McGaw
2020-07-15 18:14:45 -04:00
committed by GitHub
parent 616ba0c538
commit 1281026850
15 changed files with 5564 additions and 51 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,8 @@ const (
Cyberghost models.VPNProvider = "cyberghost"
// Vyprvpn is a VPN provider
Vyprvpn models.VPNProvider = "vyprvpn"
// NordVPN is a VPN provider
Nordvpn models.VPNProvider = "nordvpn"
)
const (

View File

@@ -14,12 +14,12 @@ type ProviderSettings struct {
PortForwarding PortForwarding
}
type ServerSelection struct {
type ServerSelection struct { //nolint:maligned
// Common
Protocol NetworkProtocol
TargetIP net.IP
// Cyberghost, PIA, Surfshark, Windscribe
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
Region string
// Cyberghost
@@ -36,6 +36,9 @@ type ServerSelection struct {
// PIA
EncryptionPreset string
// NordVPN
Number uint16
}
type ExtraConfigOptions struct {
@@ -61,6 +64,14 @@ func (p *ProviderSettings) String() string {
fmt.Sprintf("%s settings:", strings.Title(string(p.Name))),
"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)) {
case "private internet access":
settingsList = append(settingsList,
@@ -73,12 +84,12 @@ func (p *ProviderSettings) String() string {
"Country: "+p.ServerSelection.Country,
"City: "+p.ServerSelection.City,
"ISP: "+p.ServerSelection.ISP,
"Custom port: "+string(p.ServerSelection.CustomPort),
"Custom port: "+customPort,
)
case "windscribe":
settingsList = append(settingsList,
"Region: "+p.ServerSelection.Region,
"Custom port: "+string(p.ServerSelection.CustomPort),
"Custom port: "+customPort,
)
case "surfshark":
settingsList = append(settingsList,
@@ -94,6 +105,15 @@ func (p *ProviderSettings) String() string {
settingsList = append(settingsList,
"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 {
settingsList = append(settingsList,

View File

@@ -36,3 +36,11 @@ type VyprvpnServer struct {
Region string
IPs []net.IP
}
type NordvpnServer struct { //nolint:maligned
Region string
Number uint16
IP net.IP
TCP bool
UDP bool
}

View 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
}

View File

@@ -81,6 +81,10 @@ type Reader interface {
// Vyprvpn getters
GetVyprvpnRegion() (region string, err error)
// NordVPN getters
GetNordvpnRegion() (region string, err error)
GetNordvpnNumber() (number uint16, err error)
// Shadowsocks getters
GetShadowSocks() (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
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" {
s = "private internet access"
}

View 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")
}

View File

@@ -27,6 +27,8 @@ func New(provider models.VPNProvider) Provider {
return newCyberghost()
case constants.Vyprvpn:
return newVyprvpn()
case constants.Nordvpn:
return newNordvpn()
default:
return nil // should never occur
}

View File

@@ -16,7 +16,7 @@ func newSurfshark() *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
for _, server := range constants.SurfsharkServers() {
if strings.EqualFold(server.Region, selection.Region) {
@@ -54,7 +54,7 @@ func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (con
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 {
cipher = aes256cbc
}

View File

@@ -64,6 +64,8 @@ func GetOpenVPNSettings(paramsReader params.Reader, vpnProvider models.VPNProvid
settings.Provider, err = GetCyberghostSettings(paramsReader)
case constants.Vyprvpn:
settings.Provider, err = GetVyprvpnSettings(paramsReader)
case constants.Nordvpn:
settings.Provider, err = GetNordvpnSettings(paramsReader)
default:
err = fmt.Errorf("VPN service provider %q is not valid", vpnProvider)
}

View File

@@ -153,3 +153,25 @@ func GetVyprvpnSettings(paramsReader params.Reader) (settings models.ProviderSet
}
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
}